diff options
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/pci-acpi.c | 211 | ||||
-rw-r--r-- | drivers/pci/pci.c | 67 | ||||
-rw-r--r-- | drivers/pci/pci.h | 7 |
3 files changed, 285 insertions, 0 deletions
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 7e2829538a4c..c0c73913833d 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -16,8 +16,144 @@ #include <acpi/acpi_bus.h> #include <linux/pci-acpi.h> +#include <linux/pm_runtime.h> #include "pci.h" +static DEFINE_MUTEX(pci_acpi_pm_notify_mtx); + +/** + * pci_acpi_wake_bus - Wake-up notification handler for root buses. + * @handle: ACPI handle of a device the notification is for. + * @event: Type of the signaled event. + * @context: PCI root bus to wake up devices on. + */ +static void pci_acpi_wake_bus(acpi_handle handle, u32 event, void *context) +{ + struct pci_bus *pci_bus = context; + + if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_bus) + pci_pme_wakeup_bus(pci_bus); +} + +/** + * pci_acpi_wake_dev - Wake-up notification handler for PCI devices. + * @handle: ACPI handle of a device the notification is for. + * @event: Type of the signaled event. + * @context: PCI device object to wake up. + */ +static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context) +{ + struct pci_dev *pci_dev = context; + + if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_dev) { + pci_check_pme_status(pci_dev); + pm_runtime_resume(&pci_dev->dev); + if (pci_dev->subordinate) + pci_pme_wakeup_bus(pci_dev->subordinate); + } +} + +/** + * add_pm_notifier - Register PM notifier for given ACPI device. + * @dev: ACPI device to add the notifier for. + * @context: PCI device or bus to check for PME status if an event is signaled. + * + * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of + * PM wake-up events. For example, wake-up events may be generated for bridges + * if one of the devices below the bridge is signaling PME, even if the bridge + * itself doesn't have a wake-up GPE associated with it. + */ +static acpi_status add_pm_notifier(struct acpi_device *dev, + acpi_notify_handler handler, + void *context) +{ + acpi_status status = AE_ALREADY_EXISTS; + + mutex_lock(&pci_acpi_pm_notify_mtx); + + if (dev->wakeup.flags.notifier_present) + goto out; + + status = acpi_install_notify_handler(dev->handle, + ACPI_SYSTEM_NOTIFY, + handler, context); + if (ACPI_FAILURE(status)) + goto out; + + dev->wakeup.flags.notifier_present = true; + + out: + mutex_unlock(&pci_acpi_pm_notify_mtx); + return status; +} + +/** + * remove_pm_notifier - Unregister PM notifier from given ACPI device. + * @dev: ACPI device to remove the notifier from. + */ +static acpi_status remove_pm_notifier(struct acpi_device *dev, + acpi_notify_handler handler) +{ + acpi_status status = AE_BAD_PARAMETER; + + mutex_lock(&pci_acpi_pm_notify_mtx); + + if (!dev->wakeup.flags.notifier_present) + goto out; + + status = acpi_remove_notify_handler(dev->handle, + ACPI_SYSTEM_NOTIFY, + handler); + if (ACPI_FAILURE(status)) + goto out; + + dev->wakeup.flags.notifier_present = false; + + out: + mutex_unlock(&pci_acpi_pm_notify_mtx); + return status; +} + +/** + * pci_acpi_add_bus_pm_notifier - Register PM notifier for given PCI bus. + * @dev: ACPI device to add the notifier for. + * @pci_bus: PCI bus to walk checking for PME status if an event is signaled. + */ +acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev, + struct pci_bus *pci_bus) +{ + return add_pm_notifier(dev, pci_acpi_wake_bus, pci_bus); +} + +/** + * pci_acpi_remove_bus_pm_notifier - Unregister PCI bus PM notifier. + * @dev: ACPI device to remove the notifier from. + */ +acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev) +{ + return remove_pm_notifier(dev, pci_acpi_wake_bus); +} + +/** + * pci_acpi_add_pm_notifier - Register PM notifier for given PCI device. + * @dev: ACPI device to add the notifier for. + * @pci_dev: PCI device to check for the PME status if an event is signaled. + */ +acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev, + struct pci_dev *pci_dev) +{ + return add_pm_notifier(dev, pci_acpi_wake_dev, pci_dev); +} + +/** + * pci_acpi_remove_pm_notifier - Unregister PCI device PM notifier. + * @dev: ACPI device to remove the notifier from. + */ +acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev) +{ + return remove_pm_notifier(dev, pci_acpi_wake_dev); +} + /* * _SxD returns the D-state with the highest power * (lowest D-state number) supported in the S-state "x". @@ -131,12 +267,87 @@ static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable) return 0; } +/** + * acpi_dev_run_wake - Enable/disable wake-up for given device. + * @phys_dev: Device to enable/disable the platform to wake-up the system for. + * @enable: Whether enable or disable the wake-up functionality. + * + * Find the ACPI device object corresponding to @pci_dev and try to + * enable/disable the GPE associated with it. + */ +static int acpi_dev_run_wake(struct device *phys_dev, bool enable) +{ + struct acpi_device *dev; + acpi_handle handle; + int error = -ENODEV; + + if (!device_run_wake(phys_dev)) + return -EINVAL; + + handle = DEVICE_ACPI_HANDLE(phys_dev); + if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) { + dev_dbg(phys_dev, "ACPI handle has no context in %s!\n", + __func__); + return -ENODEV; + } + + if (enable) { + if (!dev->wakeup.run_wake_count++) { + acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0); + acpi_enable_gpe(dev->wakeup.gpe_device, + dev->wakeup.gpe_number, + ACPI_GPE_TYPE_RUNTIME); + } + } else if (dev->wakeup.run_wake_count > 0) { + if (!--dev->wakeup.run_wake_count) { + acpi_disable_gpe(dev->wakeup.gpe_device, + dev->wakeup.gpe_number, + ACPI_GPE_TYPE_RUNTIME); + acpi_disable_wakeup_device_power(dev); + } + } else { + error = -EALREADY; + } + + return error; +} + +static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable) +{ + while (bus->parent) { + struct pci_dev *bridge = bus->self; + + if (bridge->pme_interrupt) + return; + if (!acpi_dev_run_wake(&bridge->dev, enable)) + return; + bus = bus->parent; + } + + /* We have reached the root bus. */ + if (bus->bridge) + acpi_dev_run_wake(bus->bridge, enable); +} + +static int acpi_pci_run_wake(struct pci_dev *dev, bool enable) +{ + if (dev->pme_interrupt) + return 0; + + if (!acpi_dev_run_wake(&dev->dev, enable)) + return 0; + + acpi_pci_propagate_run_wake(dev->bus, 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, + .run_wake = acpi_pci_run_wake, }; /* ACPI bus type */ diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 5723446544fd..df55a2f351b3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -20,6 +20,7 @@ #include <linux/pm_wakeup.h> #include <linux/interrupt.h> #include <linux/device.h> +#include <linux/pm_runtime.h> #include <asm/setup.h> #include "pci.h" @@ -462,6 +463,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_run_wake(struct pci_dev *dev, bool enable) +{ + return pci_platform_pm ? + pci_platform_pm->run_wake(dev, enable) : -ENODEV; +} + /** * pci_raw_set_power_state - Use PCI PM registers to set the power state of * given PCI device @@ -1230,6 +1237,31 @@ bool pci_check_pme_status(struct pci_dev *dev) } /** + * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set. + * @dev: Device to handle. + * @ign: Ignored. + * + * Check if @dev has generated PME and queue a resume request for it in that + * case. + */ +static int pci_pme_wakeup(struct pci_dev *dev, void *ign) +{ + if (pci_check_pme_status(dev)) + pm_request_resume(&dev->dev); + return 0; +} + +/** + * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary. + * @bus: Top bus of the subtree to walk. + */ +void pci_pme_wakeup_bus(struct pci_bus *bus) +{ + if (bus) + pci_walk_bus(bus, pci_pme_wakeup, NULL); +} + +/** * pci_pme_capable - check the capability of PCI device to generate PME# * @dev: PCI device to handle. * @state: PCI state from which device will issue PME#. @@ -1434,6 +1466,41 @@ int pci_back_from_sleep(struct pci_dev *dev) } /** + * pci_dev_run_wake - Check if device can generate run-time wake-up events. + * @dev: Device to check. + * + * Return true if the device itself is cabable of generating wake-up events + * (through the platform or using the native PCIe PME) or if the device supports + * PME and one of its upstream bridges can generate wake-up events. + */ +bool pci_dev_run_wake(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->bus; + + if (device_run_wake(&dev->dev)) + return true; + + if (!dev->pme_support) + return false; + + while (bus->parent) { + struct pci_dev *bridge = bus->self; + + if (device_run_wake(&bridge->dev)) + return true; + + bus = bus->parent; + } + + /* We have reached the root bus. */ + if (bus->bridge) + return device_run_wake(bus->bridge); + + return false; +} +EXPORT_SYMBOL_GPL(pci_dev_run_wake); + +/** * pci_pm_init - Initialize PM functions of given PCI device * @dev: PCI device to handle. */ diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index b95b0a077d31..286c50821949 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_dev *dev); * * @sleep_wake: enables/disables the system wake up capability of given device * + * @run_wake: enables/disables the platform to generate run-time wake-up events + * for given device (the device's wake-up capability has to be + * enabled by @sleep_wake for this feature to work) + * * If given platform is generally capable of power managing PCI devices, all of * these callbacks are mandatory. */ @@ -44,12 +48,15 @@ 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 (*run_wake)(struct pci_dev *dev, bool enable); }; extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops); extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state); extern void pci_disable_enabled_device(struct pci_dev *dev); extern bool pci_check_pme_status(struct pci_dev *dev); +extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); +extern void pci_pme_wakeup_bus(struct pci_bus *bus); extern void pci_pm_init(struct pci_dev *dev); extern void platform_pci_wakeup_init(struct pci_dev *dev); extern void pci_allocate_cap_save_buffers(struct pci_dev *dev); |