summaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/xhci-hub.c
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2012-11-14 16:42:32 -0800
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2013-01-03 14:10:23 -0800
commit41e7e056cdc662f704fa9262e5c6e213b4ab45dd (patch)
tree10a8d18c4bc50e829721875cc18d6040b97b6246 /drivers/usb/host/xhci-hub.c
parent8b8132bc3d1cc3d4c0687e4d638a482fa920d98a (diff)
downloadlinux-41e7e056cdc662f704fa9262e5c6e213b4ab45dd.tar.gz
linux-41e7e056cdc662f704fa9262e5c6e213b4ab45dd.tar.bz2
linux-41e7e056cdc662f704fa9262e5c6e213b4ab45dd.zip
USB: Allow USB 3.0 ports to be disabled.
If hot and warm reset fails, or a port remains in the Compliance Mode, the USB core needs to be able to disable a USB 3.0 port. Unlike USB 2.0 ports, once the port is placed into the Disabled link state, it will not report any new device connects. To get device connect notifications, we need to put the link into the Disabled state, and then the RxDetect state. The xHCI driver needs to atomically clear all change bits on USB 3.0 port disable, so that we get Port Status Change Events for future port changes. We could technically do this in the USB core instead of in the xHCI roothub code, since the port state machine can't advance out of the disabled state until we set the link state to RxDetect. However, external USB 3.0 hubs don't need this code. They are level-triggered, not edge-triggered like xHCI, so they will continue to send interrupt events when any change bit is set. Therefore it doesn't make sense to put this code in the USB core. This patch is part of a series to fix several reports of infinite loops on device enumeration failure. This includes John, when he boots with a USB 3.0 device (Roseweil eusb3 enclosure) attached to his NEC 0.96 host controller. The fix requires warm reset support, so it does not make sense to backport this patch to stable kernels without warm reset support. This patch should be backported to kernels as old as 3.2, contain the commit ID 75d7cf72ab9fa01dc70877aa5c68e8ef477229dc "usbcore: refine warm reset logic" Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Reported-by: John Covici <covici@ccs.covici.com> Cc: stable@vger.kernel.org
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r--drivers/usb/host/xhci-hub.c31
1 files changed, 29 insertions, 2 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index a686cf4905bb..a7dd4f311e14 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -761,12 +761,39 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
break;
case USB_PORT_FEAT_LINK_STATE:
temp = xhci_readl(xhci, port_array[wIndex]);
+
+ /* Disable port */
+ if (link_state == USB_SS_PORT_LS_SS_DISABLED) {
+ xhci_dbg(xhci, "Disable port %d\n", wIndex);
+ temp = xhci_port_state_to_neutral(temp);
+ /*
+ * Clear all change bits, so that we get a new
+ * connection event.
+ */
+ temp |= PORT_CSC | PORT_PEC | PORT_WRC |
+ PORT_OCC | PORT_RC | PORT_PLC |
+ PORT_CEC;
+ xhci_writel(xhci, temp | PORT_PE,
+ port_array[wIndex]);
+ temp = xhci_readl(xhci, port_array[wIndex]);
+ break;
+ }
+
+ /* Put link in RxDetect (enable port) */
+ if (link_state == USB_SS_PORT_LS_RX_DETECT) {
+ xhci_dbg(xhci, "Enable port %d\n", wIndex);
+ xhci_set_link_state(xhci, port_array, wIndex,
+ link_state);
+ temp = xhci_readl(xhci, port_array[wIndex]);
+ break;
+ }
+
/* Software should not attempt to set
- * port link state above '5' (Rx.Detect) and the port
+ * port link state above '3' (U3) and the port
* must be enabled.
*/
if ((temp & PORT_PE) == 0 ||
- (link_state > USB_SS_PORT_LS_RX_DETECT)) {
+ (link_state > USB_SS_PORT_LS_U3)) {
xhci_warn(xhci, "Cannot set link state.\n");
goto error;
}