diff options
author | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-01-06 10:34:31 -0800 |
---|---|---|
committer | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-02-14 12:12:24 -0800 |
commit | 4296c70a5ec316903ef037ed15f154dd3d354ad7 (patch) | |
tree | 9e56518a0470c2884420864dda4717cf4bd2d0cb /drivers/usb/host/xhci-hub.c | |
parent | 3b9b6acd4798aafe9b9f64ccb7e8eb76f27f904a (diff) |
USB/xHCI: Enable USB 3.0 hub remote wakeup.
USB 3.0 hubs have a different remote wakeup policy than USB 2.0 hubs.
USB 2.0 hubs, once they have remote wakeup enabled, will always send
remote wakes when anything changes on a port.
However, USB 3.0 hubs have a per-port remote wake up policy that is off
by default. The Set Feature remote wake mask can be changed for any
port, enabling remote wakeup for a connect, disconnect, or overcurrent
event, much like EHCI and xHCI host controller "wake on" port status
bits. The bits are cleared to zero on the initial hub power on, or
after the hub has been reset.
Without this patch, when a USB 3.0 hub gets suspended, it will not send
a remote wakeup on device connect or disconnect. This would show up to
the user as "dead ports" unless they ran lsusb -v (since newer versions
of lsusb use the sysfs files, rather than sending control transfers).
Change the hub driver's suspend method to enable remote wake up for
disconnect, connect, and overcurrent for all ports on the hub. Modify
the xHCI driver's roothub code to handle that request, and set the "wake
on" bits in the port status registers accordingly.
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 557b6f32db86..673ad120c43e 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -422,6 +422,32 @@ void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, xhci_writel(xhci, temp, port_array[port_id]); } +void xhci_set_remote_wake_mask(struct xhci_hcd *xhci, + __le32 __iomem **port_array, int port_id, u16 wake_mask) +{ + u32 temp; + + temp = xhci_readl(xhci, port_array[port_id]); + temp = xhci_port_state_to_neutral(temp); + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_CONNECT) + temp |= PORT_WKCONN_E; + else + temp &= ~PORT_WKCONN_E; + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT) + temp |= PORT_WKDISC_E; + else + temp &= ~PORT_WKDISC_E; + + if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT) + temp |= PORT_WKOC_E; + else + temp &= ~PORT_WKOC_E; + + xhci_writel(xhci, temp, port_array[port_id]); +} + /* Test and clear port RWC bit */ void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, int port_id, u32 port_bit) @@ -448,6 +474,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, int slot_id; struct xhci_bus_state *bus_state; u16 link_state = 0; + u16 wake_mask = 0; max_ports = xhci_get_ports(hcd, &port_array); bus_state = &xhci->bus_state[hcd_index(hcd)]; @@ -593,6 +620,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case SetPortFeature: if (wValue == USB_PORT_FEAT_LINK_STATE) link_state = (wIndex & 0xff00) >> 3; + if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) + wake_mask = wIndex & 0xff00; wIndex &= 0xff; if (!wIndex || wIndex > max_ports) goto error; @@ -703,6 +732,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_readl(xhci, port_array[wIndex]); xhci_dbg(xhci, "set port reset, actual port %d status = 0x%x\n", wIndex, temp); break; + case USB_PORT_FEAT_REMOTE_WAKE_MASK: + xhci_set_remote_wake_mask(xhci, port_array, + wIndex, wake_mask); + temp = xhci_readl(xhci, port_array[wIndex]); + xhci_dbg(xhci, "set port remote wake mask, " + "actual port %d status = 0x%x\n", + wIndex, temp); + break; case USB_PORT_FEAT_BH_PORT_RESET: temp |= PORT_WR; xhci_writel(xhci, temp, port_array[wIndex]); @@ -883,6 +920,10 @@ int xhci_bus_suspend(struct usb_hcd *hcd) t2 |= PORT_LINK_STROBE | XDEV_U3; set_bit(port_index, &bus_state->bus_suspended); } + /* USB core sets remote wake mask for USB 3.0 hubs, + * including the USB 3.0 roothub, but only if CONFIG_USB_SUSPEND + * is enabled, so also enable remote wake here. + */ if (hcd->self.root_hub->do_remote_wakeup) { if (t1 & PORT_CONNECT) { t2 |= PORT_WKOC_E | PORT_WKDISC_E; |