PCI: Query platform firmware for device power state
authorLukas Wunner <lukas@wunner.de>
Sun, 18 Sep 2016 03:39:20 +0000 (05:39 +0200)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 28 Sep 2016 16:46:51 +0000 (11:46 -0500)
Usually the most accurate way to determine a PCI device's power state is to
read its PM Control & Status Register.  There are two cases however when
this is not an option:  If the device doesn't have the PM capability at
all, or if it is in D3cold (in which case its config space is
inaccessible).

In both cases, we can alternatively query the platform firmware for its
opinion on the device's power state.  To facilitate this, augment struct
pci_platform_pm_ops with a ->get_power callback and implement it for
acpi_pci_platform_pm (the only pci_platform_pm_ops existing so far).

It is used by a forthcoming commit to let pci_update_current_state()
recognize D3cold.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/pci/pci-acpi.c
drivers/pci/pci.c
drivers/pci/pci.h

index 9a033e8ee9a438317cc67aecf90ff7d8793846a4..d966d47c9e80fbccbf904ed8c546db6645ca5be5 100644 (file)
@@ -452,6 +452,27 @@ static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state)
        return error;
 }
 
+static pci_power_t acpi_pci_get_power_state(struct pci_dev *dev)
+{
+       struct acpi_device *adev = ACPI_COMPANION(&dev->dev);
+       static const pci_power_t state_conv[] = {
+               [ACPI_STATE_D0]      = PCI_D0,
+               [ACPI_STATE_D1]      = PCI_D1,
+               [ACPI_STATE_D2]      = PCI_D2,
+               [ACPI_STATE_D3_HOT]  = PCI_D3hot,
+               [ACPI_STATE_D3_COLD] = PCI_D3cold,
+       };
+       int state;
+
+       if (!adev || !acpi_device_power_manageable(adev))
+               return PCI_UNKNOWN;
+
+       if (acpi_device_get_power(adev, &state) || state == ACPI_STATE_UNKNOWN)
+               return PCI_UNKNOWN;
+
+       return state_conv[state];
+}
+
 static bool acpi_pci_can_wakeup(struct pci_dev *dev)
 {
        struct acpi_device *adev = ACPI_COMPANION(&dev->dev);
@@ -534,6 +555,7 @@ static bool acpi_pci_need_resume(struct pci_dev *dev)
 static const struct pci_platform_pm_ops acpi_pci_platform_pm = {
        .is_manageable = acpi_pci_power_manageable,
        .set_state = acpi_pci_set_power_state,
+       .get_state = acpi_pci_get_power_state,
        .choose_state = acpi_pci_choose_state,
        .sleep_wake = acpi_pci_sleep_wake,
        .run_wake = acpi_pci_run_wake,
index 2f818c3e6571c881b8a6c5c5df315e1c4a5fcbe5..2e18e9adcc151ef5830822452b6bd98edded7048 100644 (file)
@@ -552,8 +552,9 @@ static const struct pci_platform_pm_ops *pci_platform_pm;
 
 int pci_set_platform_pm(const struct pci_platform_pm_ops *ops)
 {
-       if (!ops->is_manageable || !ops->set_state || !ops->choose_state ||
-           !ops->sleep_wake || !ops->run_wake || !ops->need_resume)
+       if (!ops->is_manageable || !ops->set_state  || !ops->get_state ||
+           !ops->choose_state  || !ops->sleep_wake || !ops->run_wake  ||
+           !ops->need_resume)
                return -EINVAL;
        pci_platform_pm = ops;
        return 0;
@@ -570,6 +571,11 @@ static inline int platform_pci_set_power_state(struct pci_dev *dev,
        return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS;
 }
 
+static inline pci_power_t platform_pci_get_power_state(struct pci_dev *dev)
+{
+       return pci_platform_pm ? pci_platform_pm->get_state(dev) : PCI_UNKNOWN;
+}
+
 static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
 {
        return pci_platform_pm ?
index 9730c474b0163ccab1d9aa8027cf1c00d6ca823a..01d520648e1d5b43d3711199e8ba8d10bec3de07 100644 (file)
@@ -42,6 +42,8 @@ int pci_probe_reset_function(struct pci_dev *dev);
  *
  * @set_state: invokes the platform firmware to set the device's power state
  *
+ * @get_state: queries the platform firmware for a device's current power state
+ *
  * @choose_state: returns PCI power state of given device preferred by the
  *                platform; to be used during system-wide transitions from a
  *                sleeping state to the working state and vice versa
@@ -62,6 +64,7 @@ int pci_probe_reset_function(struct pci_dev *dev);
 struct pci_platform_pm_ops {
        bool (*is_manageable)(struct pci_dev *dev);
        int (*set_state)(struct pci_dev *dev, pci_power_t state);
+       pci_power_t (*get_state)(struct pci_dev *dev);
        pci_power_t (*choose_state)(struct pci_dev *dev);
        int (*sleep_wake)(struct pci_dev *dev, bool enable);
        int (*run_wake)(struct pci_dev *dev, bool enable);