[RFC] usb: Add support for runtime power management of the hcd

From: Matthew Garrett
Date: Wed Aug 12 2009 - 20:35:56 EST


The PCI runtime power management code means that we can implement support
for powering down PCI host controllers. This patchset adds core support code
along with a new hcd flag (HCD_RUNTIME_PM) that allows hosts to opt in if
they support this functionality. Successfully tested with Intel EHCI and
UHCI, though the UHCI code may need to pay more attention to vendor-specific
features.

The power savings from this are measurable but not huge - it still seems
like a decent optimisation. The main problem is that BIOS bugs on some
Dell laptops will kill USB if this is used, so we either default to off
or add some quirks to handle that case (I have some ideas in that
respect).

---
drivers/usb/core/hcd-pci.c | 13 +++++++++++++
drivers/usb/core/hcd.c | 9 +++++++++
drivers/usb/core/hcd.h | 1 +
drivers/usb/host/ehci-pci.c | 2 +-
drivers/usb/host/uhci-hcd.c | 2 +-
5 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index 91f2885..e86324b 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -363,6 +363,18 @@ static int hcd_pci_restore(struct device *dev)
return resume_common(dev, true);
}

+static int hcd_pci_runtime_suspend(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ struct usb_hcd *hcd = pci_get_drvdata(pci_dev);
+ struct usb_device *rhdev = hcd->self.root_hub;
+
+ if (!(hcd->driver->flags & HCD_RUNTIME_PM))
+ return -EINVAL;
+
+ return 0;
+}
+
struct dev_pm_ops usb_hcd_pci_pm_ops = {
.suspend = hcd_pci_suspend,
.suspend_noirq = hcd_pci_suspend_noirq,
@@ -376,6 +388,7 @@ struct dev_pm_ops usb_hcd_pci_pm_ops = {
.poweroff_noirq = hcd_pci_suspend_noirq,
.restore_noirq = hcd_pci_resume_noirq,
.restore = hcd_pci_restore,
+ .runtime_suspend = hcd_pci_runtime_suspend,
};
EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops);

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 95ccfa0..a8f8784 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -38,6 +38,7 @@
#include <asm/unaligned.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>

#include <linux/usb.h>

@@ -1747,6 +1748,7 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg)
if (status == 0) {
usb_set_device_state(rhdev, USB_STATE_SUSPENDED);
hcd->state = HC_STATE_SUSPENDED;
+ pm_runtime_put(hcd->self.controller);
} else {
hcd->state = old_state;
dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
@@ -1768,6 +1770,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg)
if (hcd->state == HC_STATE_RUNNING)
return 0;

+ pm_runtime_get_sync(hcd->self.controller);
hcd->state = HC_STATE_RESUMING;
status = hcd->driver->bus_resume(hcd);
if (status == 0) {
@@ -1781,6 +1784,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg)
hcd->state = old_state;
dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
"resume", status);
+ pm_runtime_put(hcd->self.controller);
if (status != -ESHUTDOWN)
usb_hc_died(hcd);
}
@@ -1968,6 +1972,9 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif

+ pm_runtime_enable(dev);
+ pm_runtime_get(dev);
+
hcd->driver = driver;
hcd->product_desc = (driver->product_desc) ? driver->product_desc :
"USB Host Controller";
@@ -1979,6 +1986,8 @@ static void hcd_release (struct kref *kref)
{
struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref);

+ pm_runtime_put_sync(hcd->self.controller);
+
kfree(hcd);
}

diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index ec5c67e..4dc12a8 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -171,6 +171,7 @@ struct hc_driver {
int flags;
#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */
#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */
+#define HCD_RUNTIME_PM 0x0004 /* HC supports handling runtime PM */
#define HCD_USB11 0x0010 /* USB 1.1 */
#define HCD_USB2 0x0020 /* USB 2.0 */
#define HCD_USB3 0x0040 /* USB 3.0 */
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c
index c2f1b7d..9583621 100644
--- a/drivers/usb/host/ehci-pci.c
+++ b/drivers/usb/host/ehci-pci.c
@@ -368,7 +368,7 @@ static const struct hc_driver ehci_pci_hc_driver = {
* generic hardware linkage
*/
.irq = ehci_irq,
- .flags = HCD_MEMORY | HCD_USB2,
+ .flags = HCD_MEMORY | HCD_USB2 | HCD_RUNTIME_PM,

/*
* basic lifecycle operations
diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c
index 274751b..36a3a4a 100644
--- a/drivers/usb/host/uhci-hcd.c
+++ b/drivers/usb/host/uhci-hcd.c
@@ -900,7 +900,7 @@ static const struct hc_driver uhci_driver = {

/* Generic hardware linkage */
.irq = uhci_irq,
- .flags = HCD_USB11,
+ .flags = HCD_USB11 | HCD_RUNTIME_PM,

/* Basic lifecycle operations */
.reset = uhci_init,

--
Matthew Garrett | mjg59@xxxxxxxxxxxxx
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/