summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorAndiry Xu <andiry.xu@amd.com>2011-04-27 18:07:50 +0800
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2011-05-02 16:42:53 -0700
commita7114230f6bd925f1c734d8ca1c32c93bf956aed (patch)
treec7e08619e6b5815fe5634120c3954ecbdb8c50e2 /drivers/usb/core
parent0ed9a57e052a3d20df052a2ff12a3b42380867aa (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>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/hub.c33
1 files changed, 20 insertions, 13 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index dcd78c15f8c..93035d862c6 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)
}
/* see 7.1.7.6 */
- /* Clear PORT_POWER if it's a USB3.0 device connected to USB 3.0
- * external hub.
- * FIXME: this is a temporary workaround to make the system able
- * to suspend/resume.
- */
- if ((hub->hdev->parent != NULL) && hub_is_superspeed(hub->hdev))
- status = clear_port_feature(hub->hdev, port1,
- USB_PORT_FEAT_POWER);
+ 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);
@@ -2469,8 +2465,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);
@@ -2492,9 +2493,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);