summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/driver.c50
-rw-r--r--drivers/usb/host/xhci-plat.c19
-rw-r--r--drivers/usb/host/xhci-plat.h1
3 files changed, 64 insertions, 6 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 25358cf3e324..6418cfb0fb37 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1420,11 +1420,28 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
udev->state == USB_STATE_SUSPENDED)
goto done;
+ if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) {
+ dev_dbg(&udev->dev, "device offloaded, skip suspend.\n");
+ udev->offload_at_suspend = 1;
+ }
+
/* Suspend all the interfaces and then udev itself */
if (udev->actconfig) {
n = udev->actconfig->desc.bNumInterfaces;
for (i = n - 1; i >= 0; --i) {
intf = udev->actconfig->interface[i];
+ /*
+ * Don't suspend interfaces with remote wakeup while
+ * the controller is active. This preserves pending
+ * interrupt urbs, allowing interrupt events to be
+ * handled during system suspend.
+ */
+ if (udev->offload_at_suspend &&
+ intf->needs_remote_wakeup) {
+ dev_dbg(&intf->dev,
+ "device offloaded, skip suspend.\n");
+ continue;
+ }
status = usb_suspend_interface(udev, intf, msg);
/* Ignore errors during system sleep transitions */
@@ -1435,7 +1452,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
}
if (status == 0) {
- status = usb_suspend_device(udev, msg);
+ if (!udev->offload_at_suspend)
+ status = usb_suspend_device(udev, msg);
/*
* Ignore errors from non-root-hub devices during
@@ -1480,9 +1498,11 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
*/
} else {
udev->can_submit = 0;
- for (i = 0; i < 16; ++i) {
- usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
- usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
+ if (!udev->offload_at_suspend) {
+ for (i = 0; i < 16; ++i) {
+ usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
+ usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
+ }
}
}
@@ -1524,17 +1544,35 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
udev->can_submit = 1;
/* Resume the device */
- if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume)
- status = usb_resume_device(udev, msg);
+ if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) {
+ if (!udev->offload_at_suspend)
+ status = usb_resume_device(udev, msg);
+ else
+ dev_dbg(&udev->dev,
+ "device offloaded, skip resume.\n");
+ }
/* Resume the interfaces */
if (status == 0 && udev->actconfig) {
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
intf = udev->actconfig->interface[i];
+ /*
+ * Interfaces with remote wakeup aren't suspended
+ * while the controller is active. This preserves
+ * pending interrupt urbs, allowing interrupt events
+ * to be handled during system suspend.
+ */
+ if (udev->offload_at_suspend &&
+ intf->needs_remote_wakeup) {
+ dev_dbg(&intf->dev,
+ "device offloaded, skip resume.\n");
+ continue;
+ }
usb_resume_interface(udev, intf, msg,
udev->reset_resume);
}
}
+ udev->offload_at_suspend = 0;
usb_mark_last_busy(udev);
done:
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index df2e942ad5f7..3a56d8f94519 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -20,6 +20,7 @@
#include <linux/acpi.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
+#include <linux/usb/xhci-sideband.h>
#include "xhci.h"
#include "xhci-plat.h"
@@ -484,6 +485,15 @@ static int xhci_plat_suspend_common(struct device *dev)
static int xhci_plat_suspend(struct device *dev)
{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+ if (xhci_sideband_check(hcd)) {
+ priv->sideband_at_suspend = 1;
+ dev_dbg(dev, "sideband instance active, skip suspend.\n");
+ return 0;
+ }
+
return xhci_plat_suspend_common(dev);
}
@@ -536,6 +546,15 @@ disable_clks:
static int xhci_plat_resume(struct device *dev)
{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+ if (priv->sideband_at_suspend) {
+ priv->sideband_at_suspend = 0;
+ dev_dbg(dev, "sideband instance active, skip resume.\n");
+ return 0;
+ }
+
return xhci_plat_resume_common(dev, false);
}
diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h
index fe4f95e690fa..2b32a93d2b76 100644
--- a/drivers/usb/host/xhci-plat.h
+++ b/drivers/usb/host/xhci-plat.h
@@ -16,6 +16,7 @@ struct xhci_plat_priv {
const char *firmware_name;
unsigned long long quirks;
bool power_lost;
+ unsigned sideband_at_suspend:1;
void (*plat_start)(struct usb_hcd *);
int (*init_quirk)(struct usb_hcd *);
int (*suspend_quirk)(struct usb_hcd *);