diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index 3942c4cab4..7e6f80d014 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -747,6 +747,32 @@ impl Xhci { Ok(()) } + /// Suspend a port to U3 link state. + /// Linux 7.1: xhci-hub.c → xhci_set_link_state(port, XDEV_U3). + pub fn suspend_port(&self, port_id: PortId) -> Result<()> { + let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); + let port = ports.get_mut(port_id.root_hub_port_index()) + .ok_or_else(|| Error::new(EINVAL))?; + // Detect USB 3.0 protocol speed from the extended capabilities. + // The port speed field (bits 13:10) gives the current link speed. + let speed = port.speed(); + let usb3 = speed >= 4; // SuperSpeed (4) or SuperSpeedPlus (5+) + log::info!("xhcid: suspend port {} to U3 (USB3={})", port_id, usb3); + port.suspend(usb3); + Ok(()) + } + + /// Resume a port from U3 back to U0. + /// Linux 7.1: xhci-hub.c → xhci_set_link_state(port, XDEV_U0). + pub fn resume_port(&self, port_id: PortId) -> Result<()> { + let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); + let port = ports.get_mut(port_id.root_hub_port_index()) + .ok_or_else(|| Error::new(EINVAL))?; + log::info!("xhcid: resume port {} to U0", port_id); + port.resume(); + Ok(()) + } + pub fn setup_scratchpads(&mut self) -> Result<()> { let buf_count = self.cap.max_scratchpad_bufs(); diff --git a/drivers/usb/xhcid/src/xhci/port.rs b/drivers/usb/xhcid/src/xhci/port.rs index 70f619102f..4b82f9c9f7 100644 --- a/drivers/usb/xhcid/src/xhci/port.rs +++ b/drivers/usb/xhcid/src/xhci/port.rs @@ -175,4 +175,28 @@ impl Port { pub fn link_state(&self) -> u8 { ((self.read() >> 5) & 0xF) as u8 } + + /// Transition the port to a new link state. + /// Linux 7.1: xhci_set_link_state() in xhci-hub.c. + /// Writes PLS and sets PORT_LINK_STROBE (LWS) to commit the + /// transition. All RW1CS/RW1S bits are cleared to neutral + /// before writing. + pub fn set_link_state(&mut self, link_state: u32) { + let neutral = self.flags_preserved(); + let val = (neutral & !(PortFlags::PLS_0 | PortFlags::PLS_1 | PortFlags::PLS_2 | PortFlags::PLS_3)).bits() + | link_state + | PortFlags::LWS.bits(); + self.portsc.write(val); + } + + /// Suspend the port to U3 (USB 3.0) or U3-equivalent for USB 2. + /// Linux 7.1: xhci-hub.c → xhci_set_link_state(xhci, port, XDEV_U3). + pub fn suspend(&mut self, usb3: bool) { + self.set_link_state(if usb3 { XDEV_U3 } else { XDEV_U3 }); + } + + /// Resume the port back to U0. + pub fn resume(&mut self) { + self.set_link_state(XDEV_U0); + } }