|
@@ -30,6 +30,8 @@
|
|
|
#include <asm/io.h>
|
|
|
#include <asm/irq.h>
|
|
|
#include <linux/usb.h>
|
|
|
+
|
|
|
+#include "usb.h"
|
|
|
#include "hcd.h"
|
|
|
|
|
|
|
|
@@ -197,6 +199,26 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
|
|
|
|
|
|
hcd = pci_get_drvdata(dev);
|
|
|
|
|
|
+ /* Root hub suspend should have stopped all downstream traffic,
|
|
|
+ * and all bus master traffic. And done so for both the interface
|
|
|
+ * and the stub usb_device (which we check here). But maybe it
|
|
|
+ * didn't; writing sysfs power/state files ignores such rules...
|
|
|
+ *
|
|
|
+ * We must ignore the FREEZE vs SUSPEND distinction here, because
|
|
|
+ * otherwise the swsusp will save (and restore) garbage state.
|
|
|
+ */
|
|
|
+ if (hcd->self.root_hub->dev.power.power_state.event == PM_EVENT_ON)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ if (hcd->driver->suspend) {
|
|
|
+ retval = hcd->driver->suspend(hcd, message);
|
|
|
+ if (retval) {
|
|
|
+ dev_dbg (&dev->dev, "PCI pre-suspend fail, %d\n",
|
|
|
+ retval);
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/* FIXME until the generic PM interfaces change a lot more, this
|
|
|
* can't use PCI D1 and D2 states. For example, the confusion
|
|
|
* between messages and states will need to vanish, and messages
|
|
@@ -215,31 +237,13 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
|
|
|
*/
|
|
|
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
|
|
|
|
|
|
- switch (hcd->state) {
|
|
|
-
|
|
|
- /* entry if root hub wasn't yet suspended ... from sysfs,
|
|
|
- * without autosuspend, or if USB_SUSPEND isn't configured.
|
|
|
+ /* Downstream ports from this root hub should already be quiesced, so
|
|
|
+ * there will be no DMA activity. Now we can shut down the upstream
|
|
|
+ * link (except maybe for PME# resume signaling) and enter some PCI
|
|
|
+ * low power state, if the hardware allows.
|
|
|
*/
|
|
|
- case HC_STATE_RUNNING:
|
|
|
- hcd->state = HC_STATE_QUIESCING;
|
|
|
- retval = hcd->driver->suspend (hcd, message);
|
|
|
- if (retval) {
|
|
|
- dev_dbg (hcd->self.controller,
|
|
|
- "suspend fail, retval %d\n",
|
|
|
- retval);
|
|
|
- break;
|
|
|
- }
|
|
|
- hcd->state = HC_STATE_SUSPENDED;
|
|
|
- /* FALLTHROUGH */
|
|
|
+ if (hcd->state == HC_STATE_SUSPENDED) {
|
|
|
|
|
|
- /* entry with CONFIG_USB_SUSPEND, or hcds that autosuspend: the
|
|
|
- * controller and/or root hub will already have been suspended,
|
|
|
- * but it won't be ready for a PCI resume call.
|
|
|
- *
|
|
|
- * FIXME only CONFIG_USB_SUSPEND guarantees hub_suspend() will
|
|
|
- * have been called, otherwise root hub timers still run ...
|
|
|
- */
|
|
|
- case HC_STATE_SUSPENDED:
|
|
|
/* no DMA or IRQs except when HC is active */
|
|
|
if (dev->current_state == PCI_D0) {
|
|
|
pci_save_state (dev);
|
|
@@ -248,7 +252,7 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
|
|
|
|
|
|
if (!has_pci_pm) {
|
|
|
dev_dbg (hcd->self.controller, "--> PCI D0/legacy\n");
|
|
|
- break;
|
|
|
+ goto done;
|
|
|
}
|
|
|
|
|
|
/* NOTE: dev->current_state becomes nonzero only here, and
|
|
@@ -259,28 +263,29 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
|
|
|
retval = pci_set_power_state (dev, PCI_D3hot);
|
|
|
if (retval == 0) {
|
|
|
dev_dbg (hcd->self.controller, "--> PCI D3\n");
|
|
|
- retval = pci_enable_wake (dev, PCI_D3hot, hcd->remote_wakeup);
|
|
|
- if (retval)
|
|
|
- break;
|
|
|
- retval = pci_enable_wake (dev, PCI_D3cold, hcd->remote_wakeup);
|
|
|
- } else if (retval < 0) {
|
|
|
+
|
|
|
+ /* Ignore these return values. We rely on pci code to
|
|
|
+ * reject requests the hardware can't implement, rather
|
|
|
+ * than coding the same thing.
|
|
|
+ */
|
|
|
+ (void) pci_enable_wake (dev, PCI_D3hot, hcd->remote_wakeup);
|
|
|
+ (void) pci_enable_wake (dev, PCI_D3cold, hcd->remote_wakeup);
|
|
|
+ } else {
|
|
|
dev_dbg (&dev->dev, "PCI D3 suspend fail, %d\n",
|
|
|
retval);
|
|
|
(void) usb_hcd_pci_resume (dev);
|
|
|
- break;
|
|
|
}
|
|
|
- break;
|
|
|
- default:
|
|
|
+
|
|
|
+ } else {
|
|
|
dev_dbg (hcd->self.controller, "hcd state %d; not suspended\n",
|
|
|
hcd->state);
|
|
|
WARN_ON(1);
|
|
|
retval = -EINVAL;
|
|
|
- break;
|
|
|
}
|
|
|
|
|
|
- /* update power_state **ONLY** to make sysfs happier */
|
|
|
+done:
|
|
|
if (retval == 0)
|
|
|
- dev->dev.power.power_state = message;
|
|
|
+ dev->dev.power.power_state = PMSG_SUSPEND;
|
|
|
return retval;
|
|
|
}
|
|
|
EXPORT_SYMBOL (usb_hcd_pci_suspend);
|
|
@@ -336,20 +341,9 @@ int usb_hcd_pci_resume (struct pci_dev *dev)
|
|
|
dev->current_state);
|
|
|
}
|
|
|
#endif
|
|
|
- retval = pci_enable_wake (dev, dev->current_state, 0);
|
|
|
- if (retval) {
|
|
|
- dev_err(hcd->self.controller,
|
|
|
- "can't enable_wake to %d, %d!\n",
|
|
|
- dev->current_state, retval);
|
|
|
- return retval;
|
|
|
- }
|
|
|
- retval = pci_enable_wake (dev, PCI_D3cold, 0);
|
|
|
- if (retval) {
|
|
|
- dev_err(hcd->self.controller,
|
|
|
- "can't enable_wake to %d, %d!\n",
|
|
|
- PCI_D3cold, retval);
|
|
|
- return retval;
|
|
|
- }
|
|
|
+ /* yes, ignore these results too... */
|
|
|
+ (void) pci_enable_wake (dev, dev->current_state, 0);
|
|
|
+ (void) pci_enable_wake (dev, PCI_D3cold, 0);
|
|
|
} else {
|
|
|
/* Same basic cases: clean (powered/not), dirty */
|
|
|
dev_dbg(hcd->self.controller, "PCI legacy resume\n");
|
|
@@ -371,17 +365,17 @@ int usb_hcd_pci_resume (struct pci_dev *dev)
|
|
|
|
|
|
dev->dev.power.power_state = PMSG_ON;
|
|
|
|
|
|
- hcd->state = HC_STATE_RESUMING;
|
|
|
hcd->saw_irq = 0;
|
|
|
|
|
|
- retval = hcd->driver->resume (hcd);
|
|
|
- if (!HC_IS_RUNNING (hcd->state)) {
|
|
|
- dev_dbg (hcd->self.controller,
|
|
|
- "resume fail, retval %d\n", retval);
|
|
|
- usb_hc_died (hcd);
|
|
|
+ if (hcd->driver->resume) {
|
|
|
+ retval = hcd->driver->resume(hcd);
|
|
|
+ if (retval) {
|
|
|
+ dev_err (hcd->self.controller,
|
|
|
+ "PCI post-resume error %d!\n", retval);
|
|
|
+ usb_hc_died (hcd);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- retval = pci_enable_device(dev);
|
|
|
return retval;
|
|
|
}
|
|
|
EXPORT_SYMBOL (usb_hcd_pci_resume);
|