]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/usb/core/hcd-pci.c
USB: Fix suspend-resume of PCI USB controllers
[linux-2.6-omap-h63xx.git] / drivers / usb / core / hcd-pci.c
index 424674203fd37baa51d2d2c10f495071c68bbc31..c54fc40458b171aa863eec63aa4821a45287d4e2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * (C) Copyright David Brownell 2000-2002
- * 
+ *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
  * Free Software Foundation; either version 2 of the License, or (at your
@@ -55,7 +55,7 @@
  *
  * Store this function in the HCD's struct pci_driver as probe().
  */
-int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
+int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 {
        struct hc_driver        *driver;
        struct usb_hcd          *hcd;
@@ -64,66 +64,70 @@ int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
        if (usb_disabled())
                return -ENODEV;
 
-       if (!id || !(driver = (struct hc_driver *) id->driver_data))
+       if (!id)
+               return -EINVAL;
+       driver = (struct hc_driver *)id->driver_data;
+       if (!driver)
                return -EINVAL;
 
-       if (pci_enable_device (dev) < 0)
+       if (pci_enable_device(dev) < 0)
                return -ENODEV;
        dev->current_state = PCI_D0;
-       dev->dev.power.power_state = PMSG_ON;
-       
-        if (!dev->irq) {
-               dev_err (&dev->dev,
+
+       if (!dev->irq) {
+               dev_err(&dev->dev,
                        "Found HC with no IRQ.  Check BIOS/PCI %s setup!\n",
                        pci_name(dev));
-               retval = -ENODEV;
+               retval = -ENODEV;
                goto err1;
-        }
+       }
 
-       hcd = usb_create_hcd (driver, &dev->dev, pci_name(dev));
+       hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));
        if (!hcd) {
                retval = -ENOMEM;
                goto err1;
        }
 
-       if (driver->flags & HCD_MEMORY) {       // EHCI, OHCI
-               hcd->rsrc_start = pci_resource_start (dev, 0);
-               hcd->rsrc_len = pci_resource_len (dev, 0);
-               if (!request_mem_region (hcd->rsrc_start, hcd->rsrc_len,
+       if (driver->flags & HCD_MEMORY) {
+               /* EHCI, OHCI */
+               hcd->rsrc_start = pci_resource_start(dev, 0);
+               hcd->rsrc_len = pci_resource_len(dev, 0);
+               if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
                                driver->description)) {
-                       dev_dbg (&dev->dev, "controller already in use\n");
+                       dev_dbg(&dev->dev, "controller already in use\n");
                        retval = -EBUSY;
                        goto err2;
                }
-               hcd->regs = ioremap_nocache (hcd->rsrc_start, hcd->rsrc_len);
+               hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
                if (hcd->regs == NULL) {
-                       dev_dbg (&dev->dev, "error mapping memory\n");
+                       dev_dbg(&dev->dev, "error mapping memory\n");
                        retval = -EFAULT;
                        goto err3;
                }
 
-       } else {                                // UHCI
+       } else {
+               /* UHCI */
                int     region;
 
                for (region = 0; region < PCI_ROM_RESOURCE; region++) {
-                       if (!(pci_resource_flags (dev, region) &
+                       if (!(pci_resource_flags(dev, region) &
                                        IORESOURCE_IO))
                                continue;
 
-                       hcd->rsrc_start = pci_resource_start (dev, region);
-                       hcd->rsrc_len = pci_resource_len (dev, region);
-                       if (request_region (hcd->rsrc_start, hcd->rsrc_len,
+                       hcd->rsrc_start = pci_resource_start(dev, region);
+                       hcd->rsrc_len = pci_resource_len(dev, region);
+                       if (request_region(hcd->rsrc_start, hcd->rsrc_len,
                                        driver->description))
                                break;
                }
                if (region == PCI_ROM_RESOURCE) {
-                       dev_dbg (&dev->dev, "no i/o regions available\n");
+                       dev_dbg(&dev->dev, "no i/o regions available\n");
                        retval = -EBUSY;
                        goto err1;
                }
        }
 
-       pci_set_master (dev);
+       pci_set_master(dev);
 
        retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
        if (retval != 0)
@@ -132,19 +136,19 @@ int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
 
  err4:
        if (driver->flags & HCD_MEMORY) {
-               iounmap (hcd->regs);
+               iounmap(hcd->regs);
  err3:
-               release_mem_region (hcd->rsrc_start, hcd->rsrc_len);
+               release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
        } else
-               release_region (hcd->rsrc_start, hcd->rsrc_len);
+               release_region(hcd->rsrc_start, hcd->rsrc_len);
  err2:
-       usb_put_hcd (hcd);
+       usb_put_hcd(hcd);
  err1:
-       pci_disable_device (dev);
-       dev_err (&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);
+       pci_disable_device(dev);
+       dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);
        return retval;
-} 
-EXPORT_SYMBOL (usb_hcd_pci_probe);
+}
+EXPORT_SYMBOL_GPL(usb_hcd_pci_probe);
 
 
 /* may be called without controller electrically present */
@@ -161,7 +165,7 @@ EXPORT_SYMBOL (usb_hcd_pci_probe);
  *
  * Store this function in the HCD's struct pci_driver as remove().
  */
-void usb_hcd_pci_remove (struct pci_dev *dev)
+void usb_hcd_pci_remove(struct pci_dev *dev)
 {
        struct usb_hcd          *hcd;
 
@@ -169,17 +173,17 @@ void usb_hcd_pci_remove (struct pci_dev *dev)
        if (!hcd)
                return;
 
-       usb_remove_hcd (hcd);
+       usb_remove_hcd(hcd);
        if (hcd->driver->flags & HCD_MEMORY) {
-               iounmap (hcd->regs);
-               release_mem_region (hcd->rsrc_start, hcd->rsrc_len);
+               iounmap(hcd->regs);
+               release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
        } else {
-               release_region (hcd->rsrc_start, hcd->rsrc_len);
+               release_region(hcd->rsrc_start, hcd->rsrc_len);
        }
-       usb_put_hcd (hcd);
+       usb_put_hcd(hcd);
        pci_disable_device(dev);
 }
-EXPORT_SYMBOL (usb_hcd_pci_remove);
+EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
 
 
 #ifdef CONFIG_PM
@@ -187,18 +191,17 @@ EXPORT_SYMBOL (usb_hcd_pci_remove);
 /**
  * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
  * @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                     wake, w;
        int                     has_pci_pm;
 
-       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
@@ -208,215 +211,162 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
         * otherwise the swsusp will save (and restore) garbage state.
         */
        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->suspend) {
-               retval = hcd->driver->suspend(hcd, message);
-               suspend_report_result(hcd->driver->suspend, retval);
+       if (hcd->driver->pci_suspend) {
+               retval = hcd->driver->pci_suspend(hcd, message);
+               suspend_report_result(hcd->driver->pci_suspend, retval);
                if (retval)
                        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.)
-        */
 
-       /* 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);
+       synchronize_irq(dev->irq);
 
        /* 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.
         */
-       if (hcd->state == HC_STATE_SUSPENDED) {
+       pci_disable_device(dev);
 
-               /* no DMA or IRQs except when HC is active */
-               if (dev->current_state == PCI_D0) {
-                       pci_save_state (dev);
-                       pci_disable_device (dev);
-               }
+       pci_save_state(dev);
 
-               if (message.event == PM_EVENT_FREEZE ||
-                               message.event == PM_EVENT_PRETHAW) {
-                       dev_dbg(hcd->self.controller, "--> no state change\n");
-                       goto done;
-               }
+       /* 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.
+        */
+       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);
+
+       /* 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;
+       }
 
-               if (!has_pci_pm) {
-                       dev_dbg (hcd->self.controller, "--> PCI D0/legacy\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
                 * only for devices that support PCI PM.  Also, exiting
                 * PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset
                 * some device state (e.g. as part of clock reinit).
                 */
-               retval = pci_set_power_state (dev, PCI_D3hot);
+               retval = pci_set_power_state(dev, PCI_D3hot);
                suspend_report_result(pci_set_power_state, retval);
                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 {
-                       dev_dbg (&dev->dev, "PCI D3 suspend fail, %d\n",
+                       dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
                                        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) {
-               dev->dev.power.power_state = PMSG_SUSPEND;
-
 #ifdef CONFIG_PPC_PMAC
+       if (retval == 0) {
                /* Disable ASIC clocks for USB */
                if (machine_is(powermac)) {
                        struct device_node      *of_node;
 
-                       of_node = pci_device_to_OF_node (dev);
+                       of_node = pci_device_to_OF_node(dev);
                        if (of_node)
                                pmac_call_feature(PMAC_FTR_USB_ENABLE,
                                                        of_node, 0, 0);
                }
-#endif
        }
+#endif
 
+ done:
        return retval;
 }
-EXPORT_SYMBOL (usb_hcd_pci_suspend);
+EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
+
+/**
+ * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled
+ * @dev: USB Host Controller being resumed
+ *
+ * Store this function in the HCD's struct pci_driver as .resume_early.
+ */
+int usb_hcd_pci_resume_early(struct pci_dev *dev)
+{
+       pci_restore_state(dev);
+       return 0;
+}
+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().
+ * Store this function in the HCD's struct pci_driver as .resume.
  */
-int usb_hcd_pci_resume (struct pci_dev *dev)
+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;
-       }
-
 #ifdef CONFIG_PPC_PMAC
        /* Reenable ASIC clocks for USB */
        if (machine_is(powermac)) {
                struct device_node *of_node;
 
-               of_node = pci_device_to_OF_node (dev);
+               of_node = pci_device_to_OF_node(dev);
                if (of_node)
-                       pmac_call_feature (PMAC_FTR_USB_ENABLE,
+                       pmac_call_feature(PMAC_FTR_USB_ENABLE,
                                                of_node, 0, 1);
        }
 #endif
 
-       /* NOTE:  chip docs cover clean "real suspend" cases (what Linux
-        * calls "standby", "suspend to RAM", and so on).  There are also
-        * dirty cases when swsusp fakes a suspend in "shutdown" mode.
-        */
-       if (dev->current_state != PCI_D0) {
-#ifdef DEBUG
-               int     pci_pm;
-               u16     pmcr;
-
-               pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
-               pci_read_config_word(dev, pci_pm + PCI_PM_CTRL, &pmcr);
-               pmcr &= PCI_PM_CTRL_STATE_MASK;
-               if (pmcr) {
-                       /* Clean case:  power to USB and to HC registers was
-                        * maintained; remote wakeup is easy.
-                        */
-                       dev_dbg(hcd->self.controller, "resume from PCI D%d\n",
-                                       pmcr);
-               } else {
-                       /* Clean:  HC lost Vcc power, D0 uninitialized
-                        *   + Vaux may have preserved port and transceiver
-                        *     state ... for remote wakeup from D3cold
-                        *   + or not; HCD must reinit + re-enumerate
-                        *
-                        * Dirty: D0 semi-initialized cases with swsusp
-                        *   + after BIOS init
-                        *   + after Linux init (HCD statically linked)
-                        */
-                       dev_dbg(hcd->self.controller,
-                               "PCI D0, from previous PCI D%d\n",
-                               dev->current_state);
-               }
-#endif
-               /* 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");
+       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);
+       pci_enable_wake(dev, PCI_D0, false);
+
+       retval = pci_enable_device(dev);
        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;
        }
-       pci_set_master (dev);
-       pci_restore_state (dev);
 
-       dev->dev.power.power_state = PMSG_ON;
+       pci_set_master(dev);
+
+       /* yes, ignore this result too... */
+       (void) pci_wake_from_d3(dev, 0);
 
        clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
 
-       if (hcd->driver->resume) {
-               retval = hcd->driver->resume(hcd);
+       if (hcd->driver->pci_resume) {
+               retval = hcd->driver->pci_resume(hcd);
                if (retval) {
-                       dev_err (hcd->self.controller,
+                       dev_err(hcd->self.controller,
                                "PCI post-resume error %d!\n", retval);
-                       usb_hc_died (hcd);
+                       usb_hc_died(hcd);
                }
        }
-
        return retval;
 }
-EXPORT_SYMBOL (usb_hcd_pci_resume);
+EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);
 
 #endif /* CONFIG_PM */
 
@@ -424,7 +374,7 @@ EXPORT_SYMBOL (usb_hcd_pci_resume);
  * usb_hcd_pci_shutdown - shutdown host controller
  * @dev: USB Host Controller being shutdown
  */
-void usb_hcd_pci_shutdown (struct pci_dev *dev)
+void usb_hcd_pci_shutdown(struct pci_dev *dev)
 {
        struct usb_hcd          *hcd;
 
@@ -435,5 +385,5 @@ void usb_hcd_pci_shutdown (struct pci_dev *dev)
        if (hcd->driver->shutdown)
                hcd->driver->shutdown(hcd);
 }
-EXPORT_SYMBOL (usb_hcd_pci_shutdown);
+EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown);