|
@@ -191,17 +191,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
|
|
/**
|
|
/**
|
|
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
|
|
* usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
|
|
* @dev: USB Host Controller being suspended
|
|
* @dev: USB Host Controller being suspended
|
|
- * @message: semantics in flux
|
|
|
|
|
|
+ * @message: Power Management message describing this state transition
|
|
*
|
|
*
|
|
- * Store this function in the HCD's struct pci_driver as suspend().
|
|
|
|
|
|
+ * Store this function in the HCD's struct pci_driver as .suspend.
|
|
*/
|
|
*/
|
|
int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
|
|
int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
|
|
{
|
|
{
|
|
- struct usb_hcd *hcd;
|
|
|
|
|
|
+ struct usb_hcd *hcd = pci_get_drvdata(dev);
|
|
int retval = 0;
|
|
int retval = 0;
|
|
- int has_pci_pm;
|
|
|
|
-
|
|
|
|
- hcd = pci_get_drvdata(dev);
|
|
|
|
|
|
+ int wake, w;
|
|
|
|
|
|
/* Root hub suspend should have stopped all downstream traffic,
|
|
/* Root hub suspend should have stopped all downstream traffic,
|
|
* and all bus master traffic. And done so for both the interface
|
|
* and all bus master traffic. And done so for both the interface
|
|
@@ -212,8 +210,15 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
|
|
* otherwise the swsusp will save (and restore) garbage state.
|
|
* otherwise the swsusp will save (and restore) garbage state.
|
|
*/
|
|
*/
|
|
if (!(hcd->state == HC_STATE_SUSPENDED ||
|
|
if (!(hcd->state == HC_STATE_SUSPENDED ||
|
|
- hcd->state == HC_STATE_HALT))
|
|
|
|
- return -EBUSY;
|
|
|
|
|
|
+ hcd->state == HC_STATE_HALT)) {
|
|
|
|
+ dev_warn(&dev->dev, "Root hub is not suspended\n");
|
|
|
|
+ retval = -EBUSY;
|
|
|
|
+ goto done;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We might already be suspended (runtime PM -- not yet written) */
|
|
|
|
+ if (dev->current_state != PCI_D0)
|
|
|
|
+ goto done;
|
|
|
|
|
|
if (hcd->driver->pci_suspend) {
|
|
if (hcd->driver->pci_suspend) {
|
|
retval = hcd->driver->pci_suspend(hcd, message);
|
|
retval = hcd->driver->pci_suspend(hcd, message);
|
|
@@ -221,49 +226,60 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
|
|
if (retval)
|
|
if (retval)
|
|
goto done;
|
|
goto done;
|
|
}
|
|
}
|
|
- synchronize_irq(dev->irq);
|
|
|
|
|
|
|
|
- /* 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
|
|
|
|
- * will need to provide a target system state again.
|
|
|
|
- *
|
|
|
|
- * It'll be important to learn characteristics of the target state,
|
|
|
|
- * especially on embedded hardware where the HCD will often be in
|
|
|
|
- * charge of an external VBUS power supply and one or more clocks.
|
|
|
|
- * Some target system states will leave them active; others won't.
|
|
|
|
- * (With PCI, that's often handled by platform BIOS code.)
|
|
|
|
- */
|
|
|
|
|
|
+ synchronize_irq(dev->irq);
|
|
|
|
|
|
- /* even when the PCI layer rejects some of the PCI calls
|
|
|
|
- * below, HCs can try global suspend and reduce DMA traffic.
|
|
|
|
- * PM-sensitive HCDs may already have done this.
|
|
|
|
|
|
+ /* Don't fail on error to enable wakeup. We rely on pci code
|
|
|
|
+ * to reject requests the hardware can't implement, rather
|
|
|
|
+ * than coding the same thing.
|
|
*/
|
|
*/
|
|
- has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
|
|
|
|
|
|
+ wake = (hcd->state == HC_STATE_SUSPENDED &&
|
|
|
|
+ device_may_wakeup(&dev->dev));
|
|
|
|
+ w = pci_wake_from_d3(dev, wake);
|
|
|
|
+ if (w < 0)
|
|
|
|
+ wake = w;
|
|
|
|
+ dev_dbg(&dev->dev, "wakeup: %d\n", wake);
|
|
|
|
|
|
/* Downstream ports from this root hub should already be quiesced, so
|
|
/* Downstream ports from this root hub should already be quiesced, so
|
|
* there will be no DMA activity. Now we can shut down the upstream
|
|
* there will be no DMA activity. Now we can shut down the upstream
|
|
* link (except maybe for PME# resume signaling) and enter some PCI
|
|
* link (except maybe for PME# resume signaling) and enter some PCI
|
|
* low power state, if the hardware allows.
|
|
* low power state, if the hardware allows.
|
|
*/
|
|
*/
|
|
- if (hcd->state == HC_STATE_SUSPENDED) {
|
|
|
|
|
|
+ pci_disable_device(dev);
|
|
|
|
+ done:
|
|
|
|
+ return retval;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
|
|
|
|
|
|
- /* no DMA or IRQs except when HC is active */
|
|
|
|
- if (dev->current_state == PCI_D0) {
|
|
|
|
- pci_save_state(dev);
|
|
|
|
- pci_disable_device(dev);
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled
|
|
|
|
+ * @dev: USB Host Controller being suspended
|
|
|
|
+ * @message: Power Management message describing this state transition
|
|
|
|
+ *
|
|
|
|
+ * Store this function in the HCD's struct pci_driver as .suspend_late.
|
|
|
|
+ */
|
|
|
|
+int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message)
|
|
|
|
+{
|
|
|
|
+ int retval = 0;
|
|
|
|
+ int has_pci_pm;
|
|
|
|
|
|
- if (message.event == PM_EVENT_FREEZE ||
|
|
|
|
- message.event == PM_EVENT_PRETHAW) {
|
|
|
|
- dev_dbg(hcd->self.controller, "--> no state change\n");
|
|
|
|
- goto done;
|
|
|
|
- }
|
|
|
|
|
|
+ /* We might already be suspended (runtime PM -- not yet written) */
|
|
|
|
+ if (dev->current_state != PCI_D0)
|
|
|
|
+ goto done;
|
|
|
|
|
|
- if (!has_pci_pm) {
|
|
|
|
- dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n");
|
|
|
|
- goto done;
|
|
|
|
- }
|
|
|
|
|
|
+ pci_save_state(dev);
|
|
|
|
+
|
|
|
|
+ /* Don't change state if we don't need to */
|
|
|
|
+ if (message.event == PM_EVENT_FREEZE ||
|
|
|
|
+ message.event == PM_EVENT_PRETHAW) {
|
|
|
|
+ dev_dbg(&dev->dev, "--> no state change\n");
|
|
|
|
+ goto done;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
|
|
|
|
+ if (!has_pci_pm) {
|
|
|
|
+ dev_dbg(&dev->dev, "--> PCI D0 legacy\n");
|
|
|
|
+ } else {
|
|
|
|
|
|
/* NOTE: dev->current_state becomes nonzero only here, and
|
|
/* NOTE: dev->current_state becomes nonzero only here, and
|
|
* only for devices that support PCI PM. Also, exiting
|
|
* only for devices that support PCI PM. Also, exiting
|
|
@@ -273,35 +289,16 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
|
|
retval = pci_set_power_state(dev, PCI_D3hot);
|
|
retval = pci_set_power_state(dev, PCI_D3hot);
|
|
suspend_report_result(pci_set_power_state, retval);
|
|
suspend_report_result(pci_set_power_state, retval);
|
|
if (retval == 0) {
|
|
if (retval == 0) {
|
|
- int wake = device_can_wakeup(&hcd->self.root_hub->dev);
|
|
|
|
-
|
|
|
|
- wake = wake && device_may_wakeup(hcd->self.controller);
|
|
|
|
-
|
|
|
|
- dev_dbg(hcd->self.controller, "--> PCI D3%s\n",
|
|
|
|
- wake ? "/wakeup" : "");
|
|
|
|
-
|
|
|
|
- /* 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, wake);
|
|
|
|
- (void) pci_enable_wake(dev, PCI_D3cold, wake);
|
|
|
|
|
|
+ dev_dbg(&dev->dev, "--> PCI D3\n");
|
|
} else {
|
|
} else {
|
|
dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
|
|
dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
|
|
retval);
|
|
retval);
|
|
- (void) usb_hcd_pci_resume(dev);
|
|
|
|
|
|
+ pci_restore_state(dev);
|
|
}
|
|
}
|
|
-
|
|
|
|
- } else if (hcd->state != HC_STATE_HALT) {
|
|
|
|
- dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n",
|
|
|
|
- hcd->state);
|
|
|
|
- WARN_ON(1);
|
|
|
|
- retval = -EINVAL;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-done:
|
|
|
|
- if (retval == 0) {
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
#ifdef CONFIG_PPC_PMAC
|
|
|
|
+ if (retval == 0) {
|
|
/* Disable ASIC clocks for USB */
|
|
/* Disable ASIC clocks for USB */
|
|
if (machine_is(powermac)) {
|
|
if (machine_is(powermac)) {
|
|
struct device_node *of_node;
|
|
struct device_node *of_node;
|
|
@@ -311,30 +308,24 @@ done:
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE,
|
|
pmac_call_feature(PMAC_FTR_USB_ENABLE,
|
|
of_node, 0, 0);
|
|
of_node, 0, 0);
|
|
}
|
|
}
|
|
-#endif
|
|
|
|
}
|
|
}
|
|
|
|
+#endif
|
|
|
|
|
|
|
|
+ done:
|
|
return retval;
|
|
return retval;
|
|
}
|
|
}
|
|
-EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late);
|
|
|
|
|
|
/**
|
|
/**
|
|
- * usb_hcd_pci_resume - power management resume of a PCI-based HCD
|
|
|
|
|
|
+ * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled
|
|
* @dev: USB Host Controller being resumed
|
|
* @dev: USB Host Controller being resumed
|
|
*
|
|
*
|
|
- * Store this function in the HCD's struct pci_driver as resume().
|
|
|
|
|
|
+ * Store this function in the HCD's struct pci_driver as .resume_early.
|
|
*/
|
|
*/
|
|
-int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
|
|
|
|
+int usb_hcd_pci_resume_early(struct pci_dev *dev)
|
|
{
|
|
{
|
|
- struct usb_hcd *hcd;
|
|
|
|
- int retval;
|
|
|
|
-
|
|
|
|
- hcd = pci_get_drvdata(dev);
|
|
|
|
- if (hcd->state != HC_STATE_SUSPENDED) {
|
|
|
|
- dev_dbg(hcd->self.controller,
|
|
|
|
- "can't resume, not suspended!\n");
|
|
|
|
- return 0;
|
|
|
|
- }
|
|
|
|
|
|
+ int retval = 0;
|
|
|
|
+ pci_power_t state = dev->current_state;
|
|
|
|
|
|
#ifdef CONFIG_PPC_PMAC
|
|
#ifdef CONFIG_PPC_PMAC
|
|
/* Reenable ASIC clocks for USB */
|
|
/* Reenable ASIC clocks for USB */
|
|
@@ -352,7 +343,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
* calls "standby", "suspend to RAM", and so on). There are also
|
|
* calls "standby", "suspend to RAM", and so on). There are also
|
|
* dirty cases when swsusp fakes a suspend in "shutdown" mode.
|
|
* dirty cases when swsusp fakes a suspend in "shutdown" mode.
|
|
*/
|
|
*/
|
|
- if (dev->current_state != PCI_D0) {
|
|
|
|
|
|
+ if (state != PCI_D0) {
|
|
#ifdef DEBUG
|
|
#ifdef DEBUG
|
|
int pci_pm;
|
|
int pci_pm;
|
|
u16 pmcr;
|
|
u16 pmcr;
|
|
@@ -364,8 +355,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
/* Clean case: power to USB and to HC registers was
|
|
/* Clean case: power to USB and to HC registers was
|
|
* maintained; remote wakeup is easy.
|
|
* maintained; remote wakeup is easy.
|
|
*/
|
|
*/
|
|
- dev_dbg(hcd->self.controller, "resume from PCI D%d\n",
|
|
|
|
- pmcr);
|
|
|
|
|
|
+ dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr);
|
|
} else {
|
|
} else {
|
|
/* Clean: HC lost Vcc power, D0 uninitialized
|
|
/* Clean: HC lost Vcc power, D0 uninitialized
|
|
* + Vaux may have preserved port and transceiver
|
|
* + Vaux may have preserved port and transceiver
|
|
@@ -376,32 +366,55 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
* + after BIOS init
|
|
* + after BIOS init
|
|
* + after Linux init (HCD statically linked)
|
|
* + after Linux init (HCD statically linked)
|
|
*/
|
|
*/
|
|
- dev_dbg(hcd->self.controller,
|
|
|
|
- "PCI D0, from previous PCI D%d\n",
|
|
|
|
- dev->current_state);
|
|
|
|
|
|
+ dev_dbg(&dev->dev, "resume from previous PCI D%d\n",
|
|
|
|
+ state);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
- /* yes, ignore these results too... */
|
|
|
|
- (void) pci_enable_wake(dev, dev->current_state, 0);
|
|
|
|
- (void) pci_enable_wake(dev, PCI_D3cold, 0);
|
|
|
|
|
|
+
|
|
|
|
+ retval = pci_set_power_state(dev, PCI_D0);
|
|
} else {
|
|
} else {
|
|
/* Same basic cases: clean (powered/not), dirty */
|
|
/* Same basic cases: clean (powered/not), dirty */
|
|
- dev_dbg(hcd->self.controller, "PCI legacy resume\n");
|
|
|
|
|
|
+ dev_dbg(&dev->dev, "PCI legacy resume\n");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (retval < 0)
|
|
|
|
+ dev_err(&dev->dev, "can't resume: %d\n", retval);
|
|
|
|
+ else
|
|
|
|
+ pci_restore_state(dev);
|
|
|
|
+
|
|
|
|
+ return retval;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * usb_hcd_pci_resume - power management resume of a PCI-based HCD
|
|
|
|
+ * @dev: USB Host Controller being resumed
|
|
|
|
+ *
|
|
|
|
+ * Store this function in the HCD's struct pci_driver as .resume.
|
|
|
|
+ */
|
|
|
|
+int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
|
|
+{
|
|
|
|
+ struct usb_hcd *hcd;
|
|
|
|
+ int retval;
|
|
|
|
+
|
|
|
|
+ hcd = pci_get_drvdata(dev);
|
|
|
|
+ if (hcd->state != HC_STATE_SUSPENDED) {
|
|
|
|
+ dev_dbg(hcd->self.controller,
|
|
|
|
+ "can't resume, not suspended!\n");
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- /* NOTE: the PCI API itself is asymmetric here. We don't need to
|
|
|
|
- * pci_set_power_state(PCI_D0) since that's part of re-enabling;
|
|
|
|
- * but that won't re-enable bus mastering. Yet pci_disable_device()
|
|
|
|
- * explicitly disables bus mastering...
|
|
|
|
- */
|
|
|
|
retval = pci_enable_device(dev);
|
|
retval = pci_enable_device(dev);
|
|
if (retval < 0) {
|
|
if (retval < 0) {
|
|
- dev_err(hcd->self.controller,
|
|
|
|
- "can't re-enable after resume, %d!\n", retval);
|
|
|
|
|
|
+ dev_err(&dev->dev, "can't re-enable after resume, %d!\n",
|
|
|
|
+ retval);
|
|
return retval;
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
+
|
|
pci_set_master(dev);
|
|
pci_set_master(dev);
|
|
- pci_restore_state(dev);
|
|
|
|
|
|
+
|
|
|
|
+ /* yes, ignore this result too... */
|
|
|
|
+ (void) pci_wake_from_d3(dev, 0);
|
|
|
|
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
|
|
|
|
@@ -413,7 +426,6 @@ int usb_hcd_pci_resume(struct pci_dev *dev)
|
|
usb_hc_died(hcd);
|
|
usb_hc_died(hcd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
return retval;
|
|
return retval;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
|
|
EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
|