diff options
author | Andiry Xu <andiry.xu@amd.com> | 2011-04-27 06:07:50 -0400 |
---|---|---|
committer | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2011-05-02 19:42:53 -0400 |
commit | a7114230f6bd925f1c734d8ca1c32c93bf956aed (patch) | |
tree | c7e08619e6b5815fe5634120c3954ecbdb8c50e2 | |
parent | 0ed9a57e052a3d20df052a2ff12a3b42380867aa (diff) |
usbcore: Refine USB3.0 device suspend and resume
In the past, we use 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@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
-rw-r--r-- | drivers/usb/core/hub.c | 33 | ||||
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 49 |
2 files changed, 41 insertions, 41 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index dcd78c15f8cd..93035d862c63 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -2307,14 +2307,10 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) | |||
2307 | } | 2307 | } |
2308 | 2308 | ||
2309 | /* see 7.1.7.6 */ | 2309 | /* see 7.1.7.6 */ |
2310 | /* Clear PORT_POWER if it's a USB3.0 device connected to USB 3.0 | 2310 | if (hub_is_superspeed(hub->hdev)) |
2311 | * external hub. | 2311 | status = set_port_feature(hub->hdev, |
2312 | * FIXME: this is a temporary workaround to make the system able | 2312 | port1 | (USB_SS_PORT_LS_U3 << 3), |
2313 | * to suspend/resume. | 2313 | USB_PORT_FEAT_LINK_STATE); |
2314 | */ | ||
2315 | if ((hub->hdev->parent != NULL) && hub_is_superspeed(hub->hdev)) | ||
2316 | status = clear_port_feature(hub->hdev, port1, | ||
2317 | USB_PORT_FEAT_POWER); | ||
2318 | else | 2314 | else |
2319 | status = set_port_feature(hub->hdev, port1, | 2315 | status = set_port_feature(hub->hdev, port1, |
2320 | USB_PORT_FEAT_SUSPEND); | 2316 | USB_PORT_FEAT_SUSPEND); |
@@ -2469,8 +2465,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) | |||
2469 | set_bit(port1, hub->busy_bits); | 2465 | set_bit(port1, hub->busy_bits); |
2470 | 2466 | ||
2471 | /* see 7.1.7.7; affects power usage, but not budgeting */ | 2467 | /* see 7.1.7.7; affects power usage, but not budgeting */ |
2472 | status = clear_port_feature(hub->hdev, | 2468 | if (hub_is_superspeed(hub->hdev)) |
2473 | port1, USB_PORT_FEAT_SUSPEND); | 2469 | status = set_port_feature(hub->hdev, |
2470 | port1 | (USB_SS_PORT_LS_U0 << 3), | ||
2471 | USB_PORT_FEAT_LINK_STATE); | ||
2472 | else | ||
2473 | status = clear_port_feature(hub->hdev, | ||
2474 | port1, USB_PORT_FEAT_SUSPEND); | ||
2474 | if (status) { | 2475 | if (status) { |
2475 | dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", | 2476 | dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", |
2476 | port1, status); | 2477 | port1, status); |
@@ -2492,9 +2493,15 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) | |||
2492 | 2493 | ||
2493 | SuspendCleared: | 2494 | SuspendCleared: |
2494 | if (status == 0) { | 2495 | if (status == 0) { |
2495 | if (portchange & USB_PORT_STAT_C_SUSPEND) | 2496 | if (hub_is_superspeed(hub->hdev)) { |
2496 | clear_port_feature(hub->hdev, port1, | 2497 | if (portchange & USB_PORT_STAT_C_LINK_STATE) |
2497 | USB_PORT_FEAT_C_SUSPEND); | 2498 | clear_port_feature(hub->hdev, port1, |
2499 | USB_PORT_FEAT_C_PORT_LINK_STATE); | ||
2500 | } else { | ||
2501 | if (portchange & USB_PORT_STAT_C_SUSPEND) | ||
2502 | clear_port_feature(hub->hdev, port1, | ||
2503 | USB_PORT_FEAT_C_SUSPEND); | ||
2504 | } | ||
2498 | } | 2505 | } |
2499 | 2506 | ||
2500 | clear_bit(port1, hub->busy_bits); | 2507 | clear_bit(port1, hub->busy_bits); |
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 4a3ca99fc64e..e3ddc6a95afe 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c | |||
@@ -483,7 +483,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
483 | && (temp & PORT_POWER) | 483 | && (temp & PORT_POWER) |
484 | && (bus_state->suspended_ports & (1 << wIndex))) { | 484 | && (bus_state->suspended_ports & (1 << wIndex))) { |
485 | bus_state->suspended_ports &= ~(1 << wIndex); | 485 | bus_state->suspended_ports &= ~(1 << wIndex); |
486 | bus_state->port_c_suspend |= 1 << wIndex; | 486 | if (hcd->speed != HCD_USB3) |
487 | bus_state->port_c_suspend |= 1 << wIndex; | ||
487 | } | 488 | } |
488 | if (temp & PORT_CONNECT) { | 489 | if (temp & PORT_CONNECT) { |
489 | status |= USB_PORT_STAT_CONNECTION; | 490 | status |= USB_PORT_STAT_CONNECTION; |
@@ -656,35 +657,27 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
656 | if (temp & XDEV_U3) { | 657 | if (temp & XDEV_U3) { |
657 | if ((temp & PORT_PE) == 0) | 658 | if ((temp & PORT_PE) == 0) |
658 | goto error; | 659 | goto error; |
659 | if (DEV_SUPERSPEED(temp)) { | ||
660 | temp = xhci_port_state_to_neutral(temp); | ||
661 | temp &= ~PORT_PLS_MASK; | ||
662 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
663 | xhci_writel(xhci, temp, | ||
664 | port_array[wIndex]); | ||
665 | xhci_readl(xhci, port_array[wIndex]); | ||
666 | } else { | ||
667 | temp = xhci_port_state_to_neutral(temp); | ||
668 | temp &= ~PORT_PLS_MASK; | ||
669 | temp |= PORT_LINK_STROBE | XDEV_RESUME; | ||
670 | xhci_writel(xhci, temp, | ||
671 | port_array[wIndex]); | ||
672 | 660 | ||
673 | spin_unlock_irqrestore(&xhci->lock, | 661 | temp = xhci_port_state_to_neutral(temp); |
674 | flags); | 662 | temp &= ~PORT_PLS_MASK; |
675 | msleep(20); | 663 | temp |= PORT_LINK_STROBE | XDEV_RESUME; |
676 | spin_lock_irqsave(&xhci->lock, flags); | 664 | xhci_writel(xhci, temp, |
665 | port_array[wIndex]); | ||
677 | 666 | ||
678 | temp = xhci_readl(xhci, | 667 | spin_unlock_irqrestore(&xhci->lock, |
679 | port_array[wIndex]); | 668 | flags); |
680 | temp = xhci_port_state_to_neutral(temp); | 669 | msleep(20); |
681 | temp &= ~PORT_PLS_MASK; | 670 | spin_lock_irqsave(&xhci->lock, flags); |
682 | temp |= PORT_LINK_STROBE | XDEV_U0; | 671 | |
683 | xhci_writel(xhci, temp, | 672 | temp = xhci_readl(xhci, |
684 | port_array[wIndex]); | 673 | port_array[wIndex]); |
685 | } | 674 | temp = xhci_port_state_to_neutral(temp); |
686 | bus_state->port_c_suspend |= 1 << wIndex; | 675 | temp &= ~PORT_PLS_MASK; |
676 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
677 | xhci_writel(xhci, temp, | ||
678 | port_array[wIndex]); | ||
687 | } | 679 | } |
680 | bus_state->port_c_suspend |= 1 << wIndex; | ||
688 | 681 | ||
689 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, | 682 | slot_id = xhci_find_slot_id_by_port(hcd, xhci, |
690 | wIndex + 1); | 683 | wIndex + 1); |
@@ -755,7 +748,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) | |||
755 | memset(buf, 0, retval); | 748 | memset(buf, 0, retval); |
756 | status = 0; | 749 | status = 0; |
757 | 750 | ||
758 | mask = PORT_CSC | PORT_PEC | PORT_OCC; | 751 | mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC; |
759 | 752 | ||
760 | spin_lock_irqsave(&xhci->lock, flags); | 753 | spin_lock_irqsave(&xhci->lock, flags); |
761 | /* For each port, did anything change? If so, set that bit in buf. */ | 754 | /* For each port, did anything change? If so, set that bit in buf. */ |