In the past, we used USB2.0 request to suspend and resume a USB3.0 device. Actually, USB3.0 hub does not support Set/Clear PORT_SUSPEND request, instead, it uses Set PORT_LINK_STATE request. This patch makes USB3.0 device suspend/resume comply with USB3.0 specification. This patch fixes the issue that USB3.0 device can not be suspended when connected to a USB3.0 external hub. Signed-off-by: Andiry Xu <andiry.xu@xxxxxxx> --- drivers/usb/core/hub.c | 29 ++++++++++++++++++----- drivers/usb/host/xhci-hub.c | 53 ++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b1a07d6..7ab2aae 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2289,7 +2289,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) } /* see 7.1.7.6 */ - status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); + if (hub_is_superspeed(hub->hdev)) + status = set_port_feature(hub->hdev, + port1 | (USB_SS_PORT_LS_U3 << 3), + USB_PORT_FEAT_LINK_STATE); + else + status = set_port_feature(hub->hdev, port1, + USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n", port1, status); @@ -2447,8 +2453,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) set_bit(port1, hub->busy_bits); /* see 7.1.7.7; affects power usage, but not budgeting */ - status = clear_port_feature(hub->hdev, - port1, USB_PORT_FEAT_SUSPEND); + if (hub_is_superspeed(hub->hdev)) + status = set_port_feature(hub->hdev, + port1 | (USB_SS_PORT_LS_U0 << 3), + USB_PORT_FEAT_LINK_STATE); + else + status = clear_port_feature(hub->hdev, + port1, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", port1, status); @@ -2470,9 +2481,15 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) SuspendCleared: if (status == 0) { - if (portchange & USB_PORT_STAT_C_SUSPEND) - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_SUSPEND); + if (hub_is_superspeed(hub->hdev)) { + if (portchange & USB_PORT_STAT_C_LINK_STATE) + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } else { + if (portchange & USB_PORT_STAT_C_SUSPEND) + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_SUSPEND); + } } clear_bit(port1, hub->busy_bits); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 7a918de..106497c 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -484,7 +484,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, && (temp & PORT_POWER) && (bus_state->suspended_ports & (1 << wIndex))) { bus_state->suspended_ports &= ~(1 << wIndex); - bus_state->port_c_suspend |= 1 << wIndex; + if (hcd->speed != HCD_USB3) + bus_state->port_c_suspend |= 1 << wIndex; } if (temp & PORT_CONNECT) { status |= USB_PORT_STAT_CONNECTION; @@ -655,35 +656,27 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (temp & XDEV_U3) { if ((temp & PORT_PE) == 0) goto error; - if (DEV_SUPERSPEED(temp)) { - temp = xhci_port_state_to_neutral(temp); - temp &= ~PORT_PLS_MASK; - temp |= PORT_LINK_STROBE | XDEV_U0; - xhci_writel(xhci, temp, - port_array[wIndex]); - xhci_readl(xhci, port_array[wIndex]); - } else { - temp = xhci_port_state_to_neutral(temp); - temp &= ~PORT_PLS_MASK; - temp |= PORT_LINK_STROBE | XDEV_RESUME; - xhci_writel(xhci, temp, - port_array[wIndex]); + + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_RESUME; + xhci_writel(xhci, temp, + port_array[wIndex]); - spin_unlock_irqrestore(&xhci->lock, - flags); - msleep(20); - spin_lock_irqsave(&xhci->lock, flags); + spin_unlock_irqrestore(&xhci->lock, + flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); - temp = xhci_readl(xhci, - port_array[wIndex]); - temp = xhci_port_state_to_neutral(temp); - temp &= ~PORT_PLS_MASK; - temp |= PORT_LINK_STROBE | XDEV_U0; - xhci_writel(xhci, temp, - port_array[wIndex]); - } - bus_state->port_c_suspend |= 1 << wIndex; + temp = xhci_readl(xhci, + port_array[wIndex]); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, + port_array[wIndex]); } + bus_state->port_c_suspend |= 1 << wIndex; slot_id = xhci_find_slot_id_by_port(hcd, xhci, wIndex + 1); @@ -754,7 +747,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) memset(buf, 0, retval); status = 0; - mask = PORT_CSC | PORT_PEC | PORT_OCC; + mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC; spin_lock_irqsave(&xhci->lock, flags); /* For each port, did anything change? If so, set that bit in buf. */ -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html