ChangeSet 1.1587.3.48, 2004/05/11 15:41:37-07:00, david-b@pacbell.net

[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


 drivers/usb/core/hcd-pci.c |   69 ++++++++++++++++++++++++++++++---------------
 1 files changed, 47 insertions(+), 22 deletions(-)


diff -Nru a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
--- a/drivers/usb/core/hcd-pci.c	Fri May 14 15:28:52 2004
+++ b/drivers/usb/core/hcd-pci.c	Fri May 14 15:28:52 2004
@@ -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)) {
