Message ID | 20090730020623.GB26389@srcf.ucam.org (mailing list archive) |
---|---|
State | Superseded, archived |
Headers | show |
On Thu, Jul 30, 2009 at 10:06:23AM +0800, Matthew Garrett wrote: > I've been playing with putting host controllers into D3 when the root > hub is suspended, with a certain amount of success. My current > implementation simply calls the suspend and resume functions in the HCD > device's pm struct on bus_suspend and bus_resume. It's also necessary to > flag the ACPI GPEs for the HCDs as runtime and enabled in order to get > wakeup events. This is good enough for already plugged devices to work, > and running lsusb powers up any idle ports and notices that devices have > been plugged in. I also get an event when devices are unplugged. > > This is obviously not entirely ideal, for a couple of reasons. > > The first is that UHCI works perfectly providing I provide a remote > wakeup. An SCI is fired via ACPI, a notification is sent to the > appropriate device and we resume it quite happily. Unplugs also trigger > the ACPI event. However, plugging in doesn't generate any kind of wakeup > event, even though port0en and port1n are set in USB_RES. Is there any > way to get UHCI to do this? I'm guessing that the power is being cut to > the port when it suspends with no device connected. > > The second is that EHCI generates a PME# notification on device plugin. > This triggers an SCI and the PME# notification shows up as a GPE event. > This causes the kernel to evaluate _L0D. On this Dell, that looks > something like this: > > _L0D { > SMI(foo, Local0) > if (Local0 & 0x1) > Notify(some hardware) > if (Local0 & 0x2) > Notify(EHCI) > if (Local0 & 0x4) > Notify(EHCI 2) > } There are patches to support both ACPI based wakeup event and PCIe native wakeup event in below bugzilla, but it never gets into upstream. One problem is how to check which device under a bridge generates a wakeup event, because pci devices can share a wakeup event. There are discussion in pm list about the patches too, please search. http://bugzilla.kernel.org/show_bug.cgi?id=6892 Thanks, Shaohua -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Jul 30, 2009 at 10:33:13AM +0800, Shaohua Li wrote: > There are patches to support both ACPI based wakeup event and PCIe native > wakeup event in below bugzilla, but it never gets into upstream. One problem > is how to check which device under a bridge generates a wakeup event, because > pci devices can share a wakeup event. > There are discussion in pm list about the patches too, please search. Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
On Thu, Jul 30, 2009 at 10:56:57AM +0800, Matthew Garrett wrote: > On Thu, Jul 30, 2009 at 10:33:13AM +0800, Shaohua Li wrote: > > > There are patches to support both ACPI based wakeup event and PCIe native > > wakeup event in below bugzilla, but it never gets into upstream. One problem > > is how to check which device under a bridge generates a wakeup event, because > > pci devices can share a wakeup event. > > There are discussion in pm list about the patches too, please search. > > Yes, that would basically work for me. I'm still slightly curious as to > why my hardware's not giving me the correct ACPI notification, though - > I guess this may be entirely untested under Windows. IIRC, you don't need to know which GPE for wakeup. The GPE is already enabled. And all required are just to register a notifier handler (register_acpi_bus_notifier), and check the ACPI_NOTIFY_DEVICE_WAKE event. -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Jul 30, 2009 at 11:17:23AM +0800, Shaohua Li wrote: > On Thu, Jul 30, 2009 at 10:56:57AM +0800, Matthew Garrett wrote: > > Yes, that would basically work for me. I'm still slightly curious as to > > why my hardware's not giving me the correct ACPI notification, though - > > I guess this may be entirely untested under Windows. > IIRC, you don't need to know which GPE for wakeup. The GPE is already enabled. > And all required are just to register a notifier handler (register_acpi_bus_notifier), and check the ACPI_NOTIFY_DEVICE_WAKE event. The problem is that the GPE method in the DSDT calls an SMI to work out which device generated the notification, and that SMI claims that none of them did. This means that no notification is sent. I may have to try to find someone in Dell to ask about this.
On Thu, 30 Jul 2009, Matthew Garrett wrote: > I've been playing with putting host controllers into D3 when the root > hub is suspended, with a certain amount of success. My current > implementation simply calls the suspend and resume functions in the HCD > device's pm struct on bus_suspend and bus_resume. It's also necessary to > flag the ACPI GPEs for the HCDs as runtime and enabled in order to get > wakeup events. This is good enough for already plugged devices to work, > and running lsusb powers up any idle ports and notices that devices have > been plugged in. I also get an event when devices are unplugged. > > This is obviously not entirely ideal, for a couple of reasons. > > The first is that UHCI works perfectly providing I provide a remote > wakeup. An SCI is fired via ACPI, a notification is sent to the > appropriate device and we resume it quite happily. Unplugs also trigger > the ACPI event. However, plugging in doesn't generate any kind of wakeup > event, even though port0en and port1n are set in USB_RES. Is there any > way to get UHCI to do this? I'm guessing that the power is being cut to > the port when it suspends with no device connected. No, that doesn't sound right. On the other hand, there's only one way to find out for certain. Voltmeters do come in handy at times... UHCI isn't very flexible or configurable. It doesn't sound like there's anything more you can do about this. > The second is that EHCI generates a PME# notification on device plugin. > This triggers an SCI and the PME# notification shows up as a GPE event. > This causes the kernel to evaluate _L0D. On this Dell, that looks > something like this: > > _L0D { > SMI(foo, Local0) > if (Local0 & 0x1) > Notify(some hardware) > if (Local0 & 0x2) > Notify(EHCI) > if (Local0 & 0x4) > Notify(EHCI 2) > } > > However, Local0 ends up being 0 and no notification is fired. I'm > therefore not able to acknowledge the PME and it fires again. This is > obviously not ideal. Does anyone have any idea why this might be? The > fact that this ends up in SMI code obviously makes things more awkward. > > I've thought of a way that this could be made to work, but it's kind of > hacky. A generic GPE handler could be registered and then scan all PCIe > devices for a raised PME bit. Something like that is probably needed anyway -- for all PCI devices, not just PCIe. You have to do this on systems that support PCI-PM but don't have ACPI. > It could then flag it and send a > notification to the attached driver. EHCI could then force a rescan of > all currently powered off USB devices. It should send the notification for each such device to the bus subsystem (i.e., the PCI core). The bus is then responsible for telling the drivers what to do. > The downside to this (other than it being a hack) is that I can't see an > obvious way to work out what the appropriate GPE is. On Intel, internal > PCI devices generate an event on GPE 0xd - but the PCI object only > claims 0xb in its _PRW object. > > Anyone have any ideas? I've attached the current version of my code, > which is hacked up in various ways as I try to understand what's going > on but should give some idea what I'm trying. Ugh... Hacked is right. I can't comment on the ACPI stuff, but the USB parts are a mess. You put stuff in the USB core that really belongs in the PCI core and you put stuff in the kernel that belongs in userspace. Of course, there's nothing wrong with doing this as part of a "proof-of-principle" thing. Alan Stern -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, 30 Jul 2009, Matthew Garrett wrote: > Yes, that would basically work for me. I'm still slightly curious as to > why my hardware's not giving me the correct ACPI notification, though - > I guess this may be entirely untested under Windows. To put it mildly... Alan Stern -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Jul 30, 2009 at 02:58:26PM -0400, Alan Stern wrote: > On Thu, 30 Jul 2009, Matthew Garrett wrote: > > > Yes, that would basically work for me. I'm still slightly curious as to > > why my hardware's not giving me the correct ACPI notification, though - > > I guess this may be entirely untested under Windows. > > To put it mildly... Windows doesn't D3 the host?
On Thu, Jul 30, 2009 at 02:57:48PM -0400, Alan Stern wrote: > > event, even though port0en and port1n are set in USB_RES. Is there any > > way to get UHCI to do this? I'm guessing that the power is being cut to > > the port when it suspends with no device connected. > > No, that doesn't sound right. On the other hand, there's only one way > to find out for certain. Voltmeters do come in handy at times... Thinking about it, I get plug events from EHCI, so it can't be that the port is powered down. Maybe it's something to do with the port switching logic. > UHCI isn't very flexible or configurable. It doesn't sound like > there's anything more you can do about this. Which possibly rules out the ability to do this. > > I've thought of a way that this could be made to work, but it's kind of > > hacky. A generic GPE handler could be registered and then scan all PCIe > > devices for a raised PME bit. > > Something like that is probably needed anyway -- for all PCI devices, > not just PCIe. You have to do this on systems that support PCI-PM but > don't have ACPI. Yes, they'll need to have their own platform hook into this. > > Anyone have any ideas? I've attached the current version of my code, > > which is hacked up in various ways as I try to understand what's going > > on but should give some idea what I'm trying. > > Ugh... Hacked is right. I can't comment on the ACPI stuff, but the > USB parts are a mess. You put stuff in the USB core that really > belongs in the PCI core and you put stuff in the kernel that belongs in > userspace. Of course, there's nothing wrong with doing this as part of > a "proof-of-principle" thing. Yes, I guess the correct model is for this to be in PCI and have the USB code do nothing other than indicate that the PCI device is now idle.
On Thu, 30 Jul 2009, Matthew Garrett wrote: > On Thu, Jul 30, 2009 at 02:58:26PM -0400, Alan Stern wrote: > > On Thu, 30 Jul 2009, Matthew Garrett wrote: > > > > > Yes, that would basically work for me. I'm still slightly curious as to > > > why my hardware's not giving me the correct ACPI notification, though - > > > I guess this may be entirely untested under Windows. > > > > To put it mildly... > > Windows doesn't D3 the host? As far as I know, Windows doesn't even suspend the root hub. However my knowledge of Windows internals is out of date by at least 10 years. Thank goodness... :-) Alan Stern P.S.: Besides which, you can't put these Intel UHCI controllers into D3 because they don't support PCI PM. Controllers from other vendors (i.e., VIA) do. -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, 30 Jul 2009, Matthew Garrett wrote: > On Thu, Jul 30, 2009 at 02:57:48PM -0400, Alan Stern wrote: > > > event, even though port0en and port1n are set in USB_RES. Is there any > > > way to get UHCI to do this? I'm guessing that the power is being cut to > > > the port when it suspends with no device connected. > > > > No, that doesn't sound right. On the other hand, there's only one way > > to find out for certain. Voltmeters do come in handy at times... > > Thinking about it, I get plug events from EHCI, so it can't be that the > port is powered down. Maybe it's something to do with the port switching > logic. Seems likely. What happens if you suspend the UHCI controller but leave the EHCI controller active? Then the port-switching logic should kick in. > > > Anyone have any ideas? I've attached the current version of my code, > > > which is hacked up in various ways as I try to understand what's going > > > on but should give some idea what I'm trying. > > > > Ugh... Hacked is right. I can't comment on the ACPI stuff, but the > > USB parts are a mess. You put stuff in the USB core that really > > belongs in the PCI core and you put stuff in the kernel that belongs in > > userspace. Of course, there's nothing wrong with doing this as part of > > a "proof-of-principle" thing. > > Yes, I guess the correct model is for this to be in PCI and have the USB > code do nothing other than indicate that the PCI device is now idle. Which reminds me... One of the things you had to do was enable remote wakeup for the host controllers. The current initial state is disabled, for a good reason. People don't like it if they suspend their laptop only to find that the computer wakes back up again when they unplug the USB mouse. If we do end up implementing runtime power management for USB host controllers, something (a userspace program?) will have to turn off remote wakeup before system sleeps and turn it back on afterward. Alan Stern -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Jul 30, 2009 at 03:15:46PM -0400, Alan Stern wrote: > P.S.: Besides which, you can't put these Intel UHCI controllers into D3 > because they don't support PCI PM. Controllers from other vendors > (i.e., VIA) do. There's an out of band wakeup mechanism via ACPI for the Intel ones. It works for remote wakeups and disconnects - it's the connects I'm having trouble with. Given that all PCI PM events are delivered via ACPI on standard x86, the only difference it makes from an implementation viewpoint is whether or not you have to enable the PME bits.
On Thu, Jul 30, 2009 at 03:22:32PM -0400, Alan Stern wrote: > Seems likely. What happens if you suspend the UHCI controller but > leave the EHCI controller active? Then the port-switching logic should > kick in. Yeah, I'll give that a go. > Which reminds me... One of the things you had to do was enable remote > wakeup for the host controllers. The current initial state is > disabled, for a good reason. People don't like it if they suspend > their laptop only to find that the computer wakes back up again when > they unplug the USB mouse. > > If we do end up implementing runtime power management for USB host > controllers, something (a userspace program?) will have to turn off > remote wakeup before system sleeps and turn it back on afterward. I think we'll probably want a call for "transitioning from runtime suspend to system suspend", at which point the driver can clean that up itself.
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index ea15b05..6199f9e 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -120,12 +120,45 @@ static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable) return error; } +static int acpi_pci_runtime_wake(struct pci_dev *dev, bool enable) +{ + acpi_status status; + acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev); + struct acpi_device *acpi_dev; + + if (!handle) { + printk("Fail2\n"); + return -ENODEV; + } + + status = acpi_bus_get_device(handle, &acpi_dev); + if (ACPI_FAILURE(status)) { + printk("Fail3\n"); + return -ENODEV; + } + + printk("Setting GPE type\n"); + acpi_set_gpe_type(acpi_dev->wakeup.gpe_device, + acpi_dev->wakeup.gpe_number, ACPI_GPE_TYPE_RUNTIME); + if (enable) { + printk("Enabling GPE\n"); + acpi_enable_gpe(acpi_dev->wakeup.gpe_device, + acpi_dev->wakeup.gpe_number); + } + + printk("Sleep/wake\n"); + acpi_pm_device_sleep_wake(&dev->dev, enable); + + return 0; +} + static struct pci_platform_pm_ops acpi_pci_platform_pm = { .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, .choose_state = acpi_pci_choose_state, .can_wakeup = acpi_pci_can_wakeup, .sleep_wake = acpi_pci_sleep_wake, + .runtime_wake = acpi_pci_runtime_wake, }; /* ACPI bus type */ diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index dbd0f94..7933f08 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -428,6 +428,12 @@ static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable) pci_platform_pm->sleep_wake(dev, enable) : -ENODEV; } +static inline int platform_pci_runtime_wake(struct pci_dev *dev, bool enable) +{ + return pci_platform_pm ? + pci_platform_pm->runtime_wake(dev, enable) : -ENODEV; +} + /** * pci_raw_set_power_state - Use PCI PM registers to set the power state of * given PCI device @@ -1215,6 +1221,8 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable) int error = 0; bool pme_done = false; + return 0; + if (enable && !device_may_wakeup(&dev->dev)) return -EINVAL; @@ -1239,6 +1247,33 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable) } /** + * pci_enable_runtime_wake - enable PCI device as runtime wakeup event source + * @dev: PCI device affected + * @enable: True to enable event generation; false to disable + * + * This enables the device as a runtime wakeup event source, or disables it. + * This typically requires platform support. + * + * RETURN VALUE: + * 0 is returned on success + * -EINVAL is returned if device is not supposed to wake up the system + * -ENODEV is returned if platform cannot support runtime PM on the device + */ +int pci_enable_runtime_wake(struct pci_dev *dev, bool enable) +{ + int error = 0; + + if (!platform_pci_can_wakeup(dev)) { + printk("Fail1\n"); + return -EINVAL; + } + + error = platform_pci_runtime_wake(dev, enable); + + return error; +} + +/** * pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold * @dev: PCI device to prepare * @enable: True to enable wake-up event generation; false to disable diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f73bcbe..6516931 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -34,6 +34,8 @@ extern int pci_mmap_fits(struct pci_dev *pdev, int resno, * * @sleep_wake: enables/disables the system wake up capability of given device * + * @runtime_wake: enables/disables runtime wake up capability of given device + * * If given platform is generally capable of power managing PCI devices, all of * these callbacks are mandatory. */ @@ -43,6 +45,7 @@ struct pci_platform_pm_ops { pci_power_t (*choose_state)(struct pci_dev *dev); bool (*can_wakeup)(struct pci_dev *dev); int (*sleep_wake)(struct pci_dev *dev, bool enable); + int (*runtime_wake)(struct pci_dev *dev, bool enable); }; extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops); diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 91f2885..9b11f6a 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -250,6 +250,11 @@ static int hcd_pci_suspend(struct device *dev) * link (except maybe for PME# resume signaling). We'll enter a * low power state during suspend_noirq, if the hardware allows. */ + + device_set_wakeup_capable(dev, 1); + device_set_wakeup_enable(dev, 1); +// pci_enable_wake(pci_dev, PCI_D3hot, 1); + pci_enable_runtime_wake(pci_dev, 1); pci_disable_device(pci_dev); return retval; } @@ -319,6 +324,7 @@ static int hcd_pci_resume_noirq(struct device *dev) /* Go back to D0 and disable remote wakeup */ pci_back_from_sleep(pci_dev); + pci_pme_active(pci_dev, 0); return 0; } @@ -333,6 +339,7 @@ static int resume_common(struct device *dev, bool hibernated) return 0; } + pci_enable_runtime_wake(pci_dev, 0); retval = pci_enable_device(pci_dev); if (retval < 0) { dev_err(dev, "can't re-enable after resume, %d!\n", retval); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 95ccfa0..f53b0b8 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -29,6 +29,7 @@ #include <linux/completion.h> #include <linux/utsname.h> #include <linux/mm.h> +#include <linux/acpi.h> #include <asm/io.h> #include <linux/device.h> #include <linux/dma-mapping.h> @@ -45,6 +46,13 @@ #include "hcd.h" #include "hub.h" +#ifdef CONFIG_ACPI +static void hcd_setup_runtime_wakeup(struct usb_hcd *hcd); +static void hcd_remove_runtime_wakeup(struct usb_hcd *hcd); +#else +static inline void hcd_setup_runtime_wakeup(struct usb_hcd *hcd) { } +static inline void hcd_remove_runtime_wakeup(struct usb_hcd *hcd) { } +#endif /*-------------------------------------------------------------------------*/ @@ -1730,6 +1738,47 @@ int usb_hcd_get_frame_number (struct usb_device *udev) #ifdef CONFIG_PM + +static int hcd_suspend(struct usb_hcd *hcd) +{ + int rc = 0; + struct device *dev = hcd->self.controller; + struct device_driver *drv = dev->driver; + + if (hcd->driver->runtime_wake) + rc = hcd->driver->runtime_wake(hcd); + else + rc = -EINVAL; + + if (rc) + return rc; + + if (drv->pm->suspend) + rc = drv->pm->suspend(dev); + if (rc) + return rc; + + if (drv->pm->suspend_noirq) + rc = drv->pm->suspend_noirq(dev); + return rc; +} + +static int hcd_resume(struct usb_hcd *hcd) +{ + int rc = 0; + struct device *dev = hcd->self.controller; + struct device_driver *drv = dev->driver; + + if (drv->pm->resume_noirq) + rc = drv->pm->resume_noirq(dev); + if (rc) + return rc; + + if (drv->pm->resume) + rc = drv->pm->resume(dev); + return rc; +} + int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) { struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); @@ -1741,12 +1790,15 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) if (!hcd->driver->bus_suspend) { status = -ENOENT; } else { + rhdev->do_remote_wakeup = 1; hcd->state = HC_STATE_QUIESCING; status = hcd->driver->bus_suspend(hcd); } if (status == 0) { usb_set_device_state(rhdev, USB_STATE_SUSPENDED); hcd->state = HC_STATE_SUSPENDED; + if (device_can_wakeup(hcd->self.controller)) + status = hcd_suspend(hcd); } else { hcd->state = old_state; dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", @@ -1758,7 +1810,7 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) { struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); - int status; + int status = 0; int old_state = hcd->state; dev_dbg(&rhdev->dev, "usb %s%s\n", @@ -1768,8 +1820,14 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) if (hcd->state == HC_STATE_RUNNING) return 0; - hcd->state = HC_STATE_RESUMING; - status = hcd->driver->bus_resume(hcd); + if (device_can_wakeup(hcd->self.controller)) + status = hcd_resume(hcd); + + if (!status) { + hcd->state = HC_STATE_RESUMING; + status = hcd->driver->bus_resume(hcd); + } + if (status == 0) { /* TRSMRCY = 10 msec */ msleep(10); @@ -1966,6 +2024,7 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, hcd->rh_timer.data = (unsigned long) hcd; #ifdef CONFIG_PM INIT_WORK(&hcd->wakeup_work, hcd_resume_work); + hcd_setup_runtime_wakeup(hcd); #endif hcd->driver = driver; @@ -1979,6 +2038,9 @@ static void hcd_release (struct kref *kref) { struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); +#ifdef CONFIG_PM + hcd_remove_runtime_wakeup(hcd); +#endif kfree(hcd); } @@ -2195,6 +2257,40 @@ usb_hcd_platform_shutdown(struct platform_device* dev) } EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); +#ifdef CONFIG_ACPI +static void hcd_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct usb_hcd *hcd = data; + pm_message_t msg; + + msg.event = PM_EVENT_AUTO | PM_EVENT_RESUME; + + if (event == 0x2) + hcd_bus_resume(hcd->self.root_hub, msg); +} + +static void hcd_setup_runtime_wakeup(struct usb_hcd *hcd) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(hcd->self.controller); + + if (!handle) + return; + + acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + hcd_acpi_notify, hcd); +} + +static void hcd_remove_runtime_wakeup(struct usb_hcd *hcd) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(hcd->self.controller); + + if (!handle) + return; + + acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + hcd_acpi_notify); +} +#endif /*-------------------------------------------------------------------------*/ #if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index ec5c67e..2bffe71 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -195,6 +195,9 @@ struct hc_driver { /* shutdown HCD */ void (*shutdown) (struct usb_hcd *hcd); + /* does the HCD support waking at runtime ? */ + int (*runtime_wake) (struct usb_hcd *hcd); + /* return current frame number */ int (*get_frame_number) (struct usb_hcd *hcd); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index c2f1b7d..51f824b 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -357,6 +357,12 @@ static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) hcd->state = HC_STATE_SUSPENDED; return 0; } + +static int ehci_pci_runtime_wake(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + return pci_enable_runtime_wake(pdev, 1); +} #endif static const struct hc_driver ehci_pci_hc_driver = { @@ -378,6 +384,7 @@ static const struct hc_driver ehci_pci_hc_driver = { #ifdef CONFIG_PM .pci_suspend = ehci_pci_suspend, .pci_resume = ehci_pci_resume, + .runtime_wake = ehci_pci_runtime_wake, #endif .stop = ehci_stop, .shutdown = ehci_shutdown, diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index d2ba04d..bad6015 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -414,6 +414,11 @@ static int ohci_pci_resume(struct usb_hcd *hcd, bool hibernated) return 0; } +static int ohci_pci_runtime_wake(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + return pci_enable_runtime_wake(pdev, 1); +} #endif /* CONFIG_PM */ @@ -441,6 +446,7 @@ static const struct hc_driver ohci_pci_hc_driver = { #ifdef CONFIG_PM .pci_suspend = ohci_pci_suspend, .pci_resume = ohci_pci_resume, + .runtime_wake = ohci_pci_runtime_wake, #endif /* diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 274751b..c194c76 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -849,6 +849,12 @@ static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated) } return 0; } + +static int uhci_pci_runtime_wake(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + return pci_enable_runtime_wake(pdev, 1); +} #endif /* Wait until a particular device/endpoint's QH is idle, and free it */ @@ -910,6 +916,7 @@ static const struct hc_driver uhci_driver = { .pci_resume = uhci_pci_resume, .bus_suspend = uhci_rh_suspend, .bus_resume = uhci_rh_resume, + .runtime_wake = uhci_pci_runtime_wake, #endif .stop = uhci_stop, diff --git a/include/linux/pci.h b/include/linux/pci.h index 115fb7b..ac9290a 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -734,6 +734,7 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state); bool pci_pme_capable(struct pci_dev *dev, pci_power_t state); void pci_pme_active(struct pci_dev *dev, bool enable); int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable); +int pci_enable_runtime_wake(struct pci_dev *dev, bool enable); int pci_wake_from_d3(struct pci_dev *dev, bool enable); pci_power_t pci_target_state(struct pci_dev *dev); int pci_prepare_to_sleep(struct pci_dev *dev);