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
--- bk2/xu26/drivers/usb/core/hcd-pci.c 2004-05-03 09:59:18.000000000 -0700
+++ gadget-2.6/drivers/usb/core/hcd-pci.c       2004-05-06 12:41:30.000000000 -0700
@@ -279,15 +279,18 @@
 {
        struct usb_hcd          *hcd;
        int                     retval = 0;
+       int                     has_pci_pm;
 
        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)) {
-               dev_dbg(hcd->self.controller, "No PM capability\n");
-               return 0;
-       }
+       /* 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.
+        */
+       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) {
        case USB_STATE_HALT:
@@ -297,23 +300,32 @@
                dev_dbg (hcd->self.controller, "hcd already suspended\n");
                break;
        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);
                if (retval)
                        dev_dbg (hcd->self.controller, 
                                        "suspend fail, retval %d\n",
                                        retval);
-               else
+               else {
                        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);
+                       
+                       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);
+                       }
+               }
        }
-
-       pci_set_power_state (dev, state);
        return retval;
 }
 EXPORT_SYMBOL (usb_hcd_pci_suspend);
@@ -328,10 +340,13 @@
 {
        struct usb_hcd          *hcd;
        int                     retval;
+       int                     has_pci_pm;
 
        hcd = pci_get_drvdata(dev);
-       dev_dbg (hcd->self.controller, "resume from state D%d\n",
-                       dev->current_state);
+       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);
 
        if (hcd->state != HCD_STATE_SUSPENDED) {
                dev_dbg (hcd->self.controller, 
@@ -340,11 +355,21 @@
        }
        hcd->state = USB_STATE_RESUMING;
 
-       pci_set_power_state (dev, 0);
+       if (has_pci_pm)
+               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);
-
-       /* remote wakeup needs hub->suspend() cooperation */
-       // pci_enable_wake (dev, 3, 0);
+#ifdef CONFIG_USB_SUSPEND
+       pci_enable_wake (dev, dev->current_state, 0);
+       pci_enable_wake (dev, 4, 0);
+#endif
 
        retval = hcd->driver->resume (hcd);
        if (!HCD_IS_RUNNING (hcd->state)) {

Reply via email to