Commit 4808f141 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] USB: more functional HCD PCI PM glue

This patch makes the usbcore PCI suspend/resume logic behave
much better.  In particular:

  - Even HCs without PCI PM support will normally be able
    to support global suspend, saving power ... and will
    need to resume later.  Let them try to suspend; lots
    of not-that-old USB controllers don't have PM caps.

  - Saner order for the boilerplate PCI stuff.  It also
    explicitly disables the IRQ and DMA, which aren't
    available in D1/D2/D3 states anyway.

  - Uses pci_enable_wake() when the root hub supports
    remote wakeup.  Didn't fully work in one test setup;
    that controller's PME# was evidently ignored.  (Not
    enabled unless CONFIG_USB_SUSPEND.)

It worked for me with brief tests with the current 2.6.6-rc
uhci-hcd with one old UHCI; more extensive ones with various
OHCIs (using patches which I'll post soonish); and not at all
with EHCI (where PM hasn't ever worked).

Those of you who've been having PM problems might find
this helpful as-is, though I think that unless you're
using UHCI you'll also need an HCD patch.

- Dave
parent 3a6e35ad
...@@ -279,15 +279,18 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state) ...@@ -279,15 +279,18 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state)
{ {
struct usb_hcd *hcd; struct usb_hcd *hcd;
int retval = 0; int retval = 0;
int has_pci_pm;
hcd = pci_get_drvdata(dev); hcd = pci_get_drvdata(dev);
dev_dbg (hcd->self.controller, "suspend D%d --> D%d\n",
dev->current_state, state);
if (pci_find_capability(dev, PCI_CAP_ID_PM)) { /* even when the PCI layer rejects some of the PCI calls
dev_dbg(hcd->self.controller, "No PM capability\n"); * below, HCs can try global suspend and reduce DMA traffic.
return 0; * PM-sensitive HCDs may already have done this.
} */
has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
if (has_pci_pm)
dev_dbg(hcd->self.controller, "suspend D%d --> D%d\n",
dev->current_state, state);
switch (hcd->state) { switch (hcd->state) {
case USB_STATE_HALT: case USB_STATE_HALT:
...@@ -297,23 +300,32 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state) ...@@ -297,23 +300,32 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state)
dev_dbg (hcd->self.controller, "hcd already suspended\n"); dev_dbg (hcd->self.controller, "hcd already suspended\n");
break; break;
default: default:
/* remote wakeup needs hub->suspend() cooperation */
// pci_enable_wake (dev, 3, 1);
pci_save_state (dev, hcd->pci_state);
/* driver may want to disable DMA etc */
hcd->state = USB_STATE_QUIESCING;
retval = hcd->driver->suspend (hcd, state); retval = hcd->driver->suspend (hcd, state);
if (retval) if (retval)
dev_dbg (hcd->self.controller, dev_dbg (hcd->self.controller,
"suspend fail, retval %d\n", "suspend fail, retval %d\n",
retval); retval);
else else {
hcd->state = HCD_STATE_SUSPENDED; hcd->state = HCD_STATE_SUSPENDED;
} pci_save_state (dev, hcd->pci_state);
#ifdef CONFIG_USB_SUSPEND
pci_enable_wake (dev, state, hcd->remote_wakeup);
pci_enable_wake (dev, 4, hcd->remote_wakeup);
#endif
/* no DMA or IRQs except in D0 */
pci_disable_device (dev);
free_irq (hcd->irq, hcd);
pci_set_power_state (dev, state); if (has_pci_pm)
retval = pci_set_power_state (dev, state);
if (retval < 0) {
dev_dbg (&dev->dev,
"PCI suspend fail, %d\n",
retval);
(void) usb_hcd_pci_resume (dev);
}
}
}
return retval; return retval;
} }
EXPORT_SYMBOL (usb_hcd_pci_suspend); EXPORT_SYMBOL (usb_hcd_pci_suspend);
...@@ -328,9 +340,12 @@ int usb_hcd_pci_resume (struct pci_dev *dev) ...@@ -328,9 +340,12 @@ int usb_hcd_pci_resume (struct pci_dev *dev)
{ {
struct usb_hcd *hcd; struct usb_hcd *hcd;
int retval; int retval;
int has_pci_pm;
hcd = pci_get_drvdata(dev); hcd = pci_get_drvdata(dev);
dev_dbg (hcd->self.controller, "resume from state D%d\n", has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
if (has_pci_pm)
dev_dbg(hcd->self.controller, "resume from state D%d\n",
dev->current_state); dev->current_state);
if (hcd->state != HCD_STATE_SUSPENDED) { if (hcd->state != HCD_STATE_SUSPENDED) {
...@@ -340,11 +355,21 @@ int usb_hcd_pci_resume (struct pci_dev *dev) ...@@ -340,11 +355,21 @@ int usb_hcd_pci_resume (struct pci_dev *dev)
} }
hcd->state = USB_STATE_RESUMING; hcd->state = USB_STATE_RESUMING;
if (has_pci_pm)
pci_set_power_state (dev, 0); pci_set_power_state (dev, 0);
retval = request_irq (dev->irq, usb_hcd_irq, SA_SHIRQ,
hcd->description, hcd);
if (retval < 0) {
dev_err (hcd->self.controller,
"can't restore IRQ after resume!\n");
return retval;
}
pci_set_master (dev);
pci_restore_state (dev, hcd->pci_state); pci_restore_state (dev, hcd->pci_state);
#ifdef CONFIG_USB_SUSPEND
/* remote wakeup needs hub->suspend() cooperation */ pci_enable_wake (dev, dev->current_state, 0);
// pci_enable_wake (dev, 3, 0); pci_enable_wake (dev, 4, 0);
#endif
retval = hcd->driver->resume (hcd); retval = hcd->driver->resume (hcd);
if (!HCD_IS_RUNNING (hcd->state)) { if (!HCD_IS_RUNNING (hcd->state)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment