PCI/PM: add PCIe runtime D3cold support
authorHuang Ying <ying.huang@intel.com>
Sat, 23 Jun 2012 02:23:51 +0000 (10:23 +0800)
committerBjorn Helgaas <bhelgaas@google.com>
Sat, 23 Jun 2012 16:50:59 +0000 (10:50 -0600)
This patch adds runtime D3cold support and corresponding ACPI platform
support.  This patch only enables runtime D3cold support; it does not
enable D3cold support during system suspend/hibernate.

D3cold is the deepest power saving state for a PCIe device, where its main
power is removed.  While it is in D3cold, you can't access the device at
all, not even its configuration space (which is still accessible in D3hot).
Therefore the PCI PM registers can not be used to transition into/out of
the D3cold state; that must be done by platform logic such as ACPI _PR3.

To support wakeup from D3cold, a system may provide auxiliary power, which
allows a device to request wakeup using a Beacon or the sideband WAKE#
signal.  WAKE# is usually connected to platform logic such as ACPI GPE.
This is quite different from other power saving states, where devices
request wakeup via a PME message on the PCIe link.

Some devices, such as those in plug-in slots, have no direct platform
logic.  For example, there is usually no ACPI _PR3 for them.  D3cold
support for these devices can be done via the PCIe Downstream Port leading
to the device.  When the PCIe port is powered on/off, the device is powered
on/off too.  Wakeup events from the device will be notified to the
corresponding PCIe port.

For more information about PCIe D3cold and corresponding ACPI support,
please refer to:

- PCI Express Base Specification Revision 2.0
- Advanced Configuration and Power Interface Specification Revision 5.0

[bhelgaas: changelog]
Reviewed-by: Rafael J. Wysocki <rjw@sisk.pl>
Originally-by: Zheng Yan <zheng.z.yan@intel.com>
Signed-off-by: Huang Ying <ying.huang@intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/pci-acpi.c
drivers/pci/pci-driver.c
drivers/pci/pci-sysfs.c
drivers/pci/pci.c
drivers/pci/pci.h
drivers/pci/pcie/portdrv_pci.c
include/linux/pci.h

index a9efebc586b4712293259fd677c6006e57a9bca5..e1658afef873668660dc2589eda17535c8797569 100644 (file)
@@ -48,6 +48,12 @@ static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context)
        if (event != ACPI_NOTIFY_DEVICE_WAKE || !pci_dev)
                return;
 
+       if (pci_dev->current_state == PCI_D3cold) {
+               pci_wakeup_event(pci_dev);
+               pm_runtime_resume(&pci_dev->dev);
+               return;
+       }
+
        if (!pci_dev->pm_cap || !pci_dev->pme_support
             || pci_check_pme_status(pci_dev)) {
                if (pci_dev->pme_poll)
@@ -187,10 +193,13 @@ acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
 
 static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev)
 {
-       int acpi_state;
+       int acpi_state, d_max;
 
-       acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL,
-                                               ACPI_STATE_D3);
+       if (pdev->no_d3cold)
+               d_max = ACPI_STATE_D3_HOT;
+       else
+               d_max = ACPI_STATE_D3_COLD;
+       acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL, d_max);
        if (acpi_state < 0)
                return PCI_POWER_ERROR;
 
@@ -297,7 +306,13 @@ static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
 
 static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
 {
-       if (dev->pme_interrupt)
+       /*
+        * Per PCI Express Base Specification Revision 2.0 section
+        * 5.3.3.2 Link Wakeup, platform support is needed for D3cold
+        * waking up to power on the main link even if there is PME
+        * support for D3cold
+        */
+       if (dev->pme_interrupt && !dev->runtime_d3cold)
                return 0;
 
        if (!acpi_pm_device_run_wake(&dev->dev, enable))
index bf0cee629b60f8bb763b4407f631e7ba7e778aa8..ca2e4c79a588f2e2bcd03fc8ab65a2a7dfe7e2c3 100644 (file)
@@ -1019,10 +1019,13 @@ static int pci_pm_runtime_suspend(struct device *dev)
        if (!pm || !pm->runtime_suspend)
                return -ENOSYS;
 
+       pci_dev->no_d3cold = false;
        error = pm->runtime_suspend(dev);
        suspend_report_result(pm->runtime_suspend, error);
        if (error)
                return error;
+       if (!pci_dev->d3cold_allowed)
+               pci_dev->no_d3cold = true;
 
        pci_fixup_device(pci_fixup_suspend, pci_dev);
 
@@ -1044,6 +1047,7 @@ static int pci_pm_runtime_suspend(struct device *dev)
 
 static int pci_pm_runtime_resume(struct device *dev)
 {
+       int rc;
        struct pci_dev *pci_dev = to_pci_dev(dev);
        const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
 
@@ -1054,7 +1058,11 @@ static int pci_pm_runtime_resume(struct device *dev)
        __pci_enable_wake(pci_dev, PCI_D0, true, false);
        pci_fixup_device(pci_fixup_resume, pci_dev);
 
-       return pm->runtime_resume(dev);
+       rc = pm->runtime_resume(dev);
+
+       pci_dev->runtime_d3cold = false;
+
+       return rc;
 }
 
 static int pci_pm_runtime_idle(struct device *dev)
index 86c63fe45d11bf22225afffd1317777edefcc8c7..1426db0c0607b3b1d60baa1bab1f3bde8b31c296 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/pci-aspm.h>
 #include <linux/slab.h>
 #include <linux/vgaarb.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
 static int sysfs_initialized;  /* = 0 */
@@ -378,6 +379,31 @@ dev_bus_rescan_store(struct device *dev, struct device_attribute *attr,
 
 #endif
 
+#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI)
+static ssize_t d3cold_allowed_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       unsigned long val;
+
+       if (strict_strtoul(buf, 0, &val) < 0)
+               return -EINVAL;
+
+       pdev->d3cold_allowed = !!val;
+       pm_runtime_resume(dev);
+
+       return count;
+}
+
+static ssize_t d3cold_allowed_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       return sprintf (buf, "%u\n", pdev->d3cold_allowed);
+}
+#endif
+
 struct device_attribute pci_dev_attrs[] = {
        __ATTR_RO(resource),
        __ATTR_RO(vendor),
@@ -401,6 +427,9 @@ struct device_attribute pci_dev_attrs[] = {
 #ifdef CONFIG_HOTPLUG
        __ATTR(remove, (S_IWUSR|S_IWGRP), NULL, remove_store),
        __ATTR(rescan, (S_IWUSR|S_IWGRP), NULL, dev_rescan_store),
+#endif
+#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI)
+       __ATTR(d3cold_allowed, 0644, d3cold_allowed_show, d3cold_allowed_store),
 #endif
        __ATTR_NULL,
 };
index 9eae64b17954b742b3d8291164cbf6af988c2eee..8effb9b23eec89cc512720b34b2022d9d986696b 100644 (file)
@@ -622,7 +622,8 @@ static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
                dev_info(&dev->dev, "Refused to change power state, "
                        "currently in D%d\n", dev->current_state);
 
-       /* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
+       /*
+        * According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
         * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning
         * from D3hot to D0 _may_ perform an internal reset, thereby
         * going to "D0 Uninitialized" rather than "D0 Initialized".
@@ -654,6 +655,16 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
        if (dev->pm_cap) {
                u16 pmcsr;
 
+               /*
+                * Configuration space is not accessible for device in
+                * D3cold, so just keep or set D3cold for safety
+                */
+               if (dev->current_state == PCI_D3cold)
+                       return;
+               if (state == PCI_D3cold) {
+                       dev->current_state = PCI_D3cold;
+                       return;
+               }
                pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
                dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
        } else {
@@ -694,8 +705,50 @@ static int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state)
  */
 static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
 {
-       if (state == PCI_D0)
+       if (state == PCI_D0) {
                pci_platform_power_transition(dev, PCI_D0);
+               /*
+                * Mandatory power management transition delays, see
+                * PCI Express Base Specification Revision 2.0 Section
+                * 6.6.1: Conventional Reset.  Do not delay for
+                * devices powered on/off by corresponding bridge,
+                * because have already delayed for the bridge.
+                */
+               if (dev->runtime_d3cold) {
+                       msleep(dev->d3cold_delay);
+                       /*
+                        * When powering on a bridge from D3cold, the
+                        * whole hierarchy may be powered on into
+                        * D0uninitialized state, resume them to give
+                        * them a chance to suspend again
+                        */
+                       pci_wakeup_bus(dev->subordinate);
+               }
+       }
+}
+
+/**
+ * __pci_dev_set_current_state - Set current state of a PCI device
+ * @dev: Device to handle
+ * @data: pointer to state to be set
+ */
+static int __pci_dev_set_current_state(struct pci_dev *dev, void *data)
+{
+       pci_power_t state = *(pci_power_t *)data;
+
+       dev->current_state = state;
+       return 0;
+}
+
+/**
+ * __pci_bus_set_current_state - Walk given bus and set current state of devices
+ * @bus: Top bus of the subtree to walk.
+ * @state: state to be set
+ */
+static void __pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state)
+{
+       if (bus)
+               pci_walk_bus(bus, __pci_dev_set_current_state, &state);
 }
 
 /**
@@ -707,8 +760,15 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
  */
 int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state)
 {
-       return state >= PCI_D0 ?
-                       pci_platform_power_transition(dev, state) : -EINVAL;
+       int ret;
+
+       if (state < PCI_D0)
+               return -EINVAL;
+       ret = pci_platform_power_transition(dev, state);
+       /* Power off the bridge may power off the whole hierarchy */
+       if (!ret && state == PCI_D3cold)
+               __pci_bus_set_current_state(dev->subordinate, PCI_D3cold);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(__pci_complete_power_transition);
 
@@ -732,8 +792,8 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
        int error;
 
        /* bound the state we're entering */
-       if (state > PCI_D3hot)
-               state = PCI_D3hot;
+       if (state > PCI_D3cold)
+               state = PCI_D3cold;
        else if (state < PCI_D0)
                state = PCI_D0;
        else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev))
@@ -748,10 +808,15 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
 
        /* This device is quirked not to be put into D3, so
           don't put it in D3 */
-       if (state == PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
+       if (state >= PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
                return 0;
 
-       error = pci_raw_set_power_state(dev, state);
+       /*
+        * To put device in D3cold, we put device into D3hot in native
+        * way, then put device into D3cold with platform ops
+        */
+       error = pci_raw_set_power_state(dev, state > PCI_D3hot ?
+                                       PCI_D3hot : state);
 
        if (!__pci_complete_power_transition(dev, state))
                error = 0;
@@ -1497,6 +1562,28 @@ void pci_pme_wakeup_bus(struct pci_bus *bus)
                pci_walk_bus(bus, pci_pme_wakeup, (void *)true);
 }
 
+/**
+ * pci_wakeup - Wake up a PCI device
+ * @dev: Device to handle.
+ * @ign: ignored parameter
+ */
+static int pci_wakeup(struct pci_dev *pci_dev, void *ign)
+{
+       pci_wakeup_event(pci_dev);
+       pm_request_resume(&pci_dev->dev);
+       return 0;
+}
+
+/**
+ * pci_wakeup_bus - Walk given bus and wake up devices on it
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_wakeup_bus(struct pci_bus *bus)
+{
+       if (bus)
+               pci_walk_bus(bus, pci_wakeup, NULL);
+}
+
 /**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
@@ -1754,6 +1841,10 @@ int pci_prepare_to_sleep(struct pci_dev *dev)
        if (target_state == PCI_POWER_ERROR)
                return -EIO;
 
+       /* D3cold during system suspend/hibernate is not supported */
+       if (target_state > PCI_D3hot)
+               target_state = PCI_D3hot;
+
        pci_enable_wake(dev, target_state, device_may_wakeup(&dev->dev));
 
        error = pci_set_power_state(dev, target_state);
@@ -1791,12 +1882,16 @@ int pci_finish_runtime_suspend(struct pci_dev *dev)
        if (target_state == PCI_POWER_ERROR)
                return -EIO;
 
+       dev->runtime_d3cold = target_state == PCI_D3cold;
+
        __pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev));
 
        error = pci_set_power_state(dev, target_state);
 
-       if (error)
+       if (error) {
                __pci_enable_wake(dev, target_state, true, false);
+               dev->runtime_d3cold = false;
+       }
 
        return error;
 }
@@ -1866,6 +1961,7 @@ void pci_pm_init(struct pci_dev *dev)
 
        dev->pm_cap = pm;
        dev->d3_delay = PCI_PM_D3_WAIT;
+       dev->d3cold_delay = PCI_PM_D3COLD_WAIT;
 
        dev->d1_support = false;
        dev->d2_support = false;
index e4943479b234d1f4ff6fc5121a28dbb322fa2ba6..5cd3dce7a24593f92035b21d083f1ff70738c530 100644 (file)
@@ -70,6 +70,7 @@ 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 int pci_finish_runtime_suspend(struct pci_dev *dev);
 extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_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);
index 7c576b9aa01da142993e1c5dcbe0868b95d219dd..3a7eefcb270a5dda9a2dcbcead63086da4433c3e 100644 (file)
@@ -101,12 +101,48 @@ static int pcie_port_resume_noirq(struct device *dev)
 }
 
 #ifdef CONFIG_PM_RUNTIME
-static int pcie_port_runtime_pm(struct device *dev)
+struct d3cold_info {
+       bool no_d3cold;
+       unsigned int d3cold_delay;
+};
+
+static int pci_dev_d3cold_info(struct pci_dev *pdev, void *data)
+{
+       struct d3cold_info *info = data;
+
+       info->d3cold_delay = max_t(unsigned int, pdev->d3cold_delay,
+                                  info->d3cold_delay);
+       if (pdev->no_d3cold)
+               info->no_d3cold = true;
+       return 0;
+}
+
+static int pcie_port_runtime_suspend(struct device *dev)
+{
+       struct pci_dev *pdev = to_pci_dev(dev);
+       struct d3cold_info d3cold_info = {
+               .no_d3cold      = false,
+               .d3cold_delay   = PCI_PM_D3_WAIT,
+       };
+
+       /*
+        * If any subordinate device disable D3cold, we should not put
+        * the port into D3cold.  The D3cold delay of port should be
+        * the max of that of all subordinate devices.
+        */
+       pci_walk_bus(pdev->subordinate, pci_dev_d3cold_info, &d3cold_info);
+       pdev->no_d3cold = d3cold_info.no_d3cold;
+       pdev->d3cold_delay = d3cold_info.d3cold_delay;
+       return 0;
+}
+
+static int pcie_port_runtime_resume(struct device *dev)
 {
        return 0;
 }
 #else
-#define pcie_port_runtime_pm   NULL
+#define pcie_port_runtime_suspend      NULL
+#define pcie_port_runtime_resume       NULL
 #endif
 
 static const struct dev_pm_ops pcie_portdrv_pm_ops = {
@@ -117,8 +153,8 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = {
        .poweroff       = pcie_port_device_suspend,
        .restore        = pcie_port_device_resume,
        .resume_noirq   = pcie_port_resume_noirq,
-       .runtime_suspend = pcie_port_runtime_pm,
-       .runtime_resume = pcie_port_runtime_pm,
+       .runtime_suspend = pcie_port_runtime_suspend,
+       .runtime_resume = pcie_port_runtime_resume,
 };
 
 #define PCIE_PORTDRV_PM_OPS    (&pcie_portdrv_pm_ops)
index d8c379dba6adbb36ae9df6c18adea304e2c6b45c..002cfd3e33cadcf375d1b223d6a34de7a4b0819c 100644 (file)
@@ -132,9 +132,10 @@ static inline const char *pci_power_name(pci_power_t state)
        return pci_power_names[1 + (int) state];
 }
 
-#define PCI_PM_D2_DELAY        200
-#define PCI_PM_D3_WAIT 10
-#define PCI_PM_BUS_WAIT        50
+#define PCI_PM_D2_DELAY                200
+#define PCI_PM_D3_WAIT         10
+#define PCI_PM_D3COLD_WAIT     100
+#define PCI_PM_BUS_WAIT                50
 
 /** The pci_channel state describes connectivity between the CPU and
  *  the pci device.  If some PCI bus between here and the pci device
@@ -278,11 +279,18 @@ struct pci_dev {
        unsigned int    pme_poll:1;     /* Poll device's PME status bit */
        unsigned int    d1_support:1;   /* Low power state D1 is supported */
        unsigned int    d2_support:1;   /* Low power state D2 is supported */
-       unsigned int    no_d1d2:1;      /* Only allow D0 and D3 */
+       unsigned int    no_d1d2:1;      /* D1 and D2 are forbidden */
+       unsigned int    no_d3cold:1;    /* D3cold is forbidden */
+       unsigned int    d3cold_allowed:1;       /* D3cold is allowed by user */
        unsigned int    mmio_always_on:1;       /* disallow turning off io/mem
                                                   decoding during bar sizing */
        unsigned int    wakeup_prepared:1;
+       unsigned int    runtime_d3cold:1;       /* whether go through runtime
+                                                  D3cold, not set for devices
+                                                  powered on/off by the
+                                                  corresponding bridge */
        unsigned int    d3_delay;       /* D3->D0 transition time in ms */
+       unsigned int    d3cold_delay;   /* D3cold->D0 transition time in ms */
 
 #ifdef CONFIG_PCIEASPM
        struct pcie_link_state  *link_state;    /* ASPM link state. */