diff options
Diffstat (limited to 'drivers/pci/pci-acpi.c')
-rw-r--r-- | drivers/pci/pci-acpi.c | 57 |
1 files changed, 56 insertions, 1 deletions
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 8f75ba068d45..79c8e955a0ab 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -519,6 +519,46 @@ static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev) return PCI_POWER_ERROR; } +static struct acpi_device *acpi_pci_find_companion(struct device *dev); + +static bool acpi_pci_bridge_d3(struct pci_dev *dev) +{ + const struct fwnode_handle *fwnode; + struct acpi_device *adev; + struct pci_dev *root; + u8 val; + + if (!dev->is_hotplug_bridge) + return false; + + /* + * Look for a special _DSD property for the root port and if it + * is set we know the hierarchy behind it supports D3 just fine. + */ + root = pci_find_pcie_root_port(dev); + if (!root) + return false; + + adev = ACPI_COMPANION(&root->dev); + if (root == dev) { + /* + * It is possible that the ACPI companion is not yet bound + * for the root port so look it up manually here. + */ + if (!adev && !pci_dev_is_added(root)) + adev = acpi_pci_find_companion(&root->dev); + } + + if (!adev) + return false; + + fwnode = acpi_fwnode_handle(adev); + if (fwnode_property_read_u8(fwnode, "HotPlugSupportInD3", &val)) + return false; + + return val == 1; +} + static bool acpi_pci_power_manageable(struct pci_dev *dev) { struct acpi_device *adev = ACPI_COMPANION(&dev->dev); @@ -635,6 +675,7 @@ static bool acpi_pci_need_resume(struct pci_dev *dev) } static const struct pci_platform_pm_ops acpi_pci_platform_pm = { + .bridge_d3 = acpi_pci_bridge_d3, .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, .get_state = acpi_pci_get_power_state, @@ -767,19 +808,33 @@ static void pci_acpi_setup(struct device *dev) return; device_set_wakeup_capable(dev, true); + /* + * For bridges that can do D3 we enable wake automatically (as + * we do for the power management itself in that case). The + * reason is that the bridge may have additional methods such as + * _DSW that need to be called. + */ + if (pci_dev->bridge_d3) + device_wakeup_enable(dev); + acpi_pci_wakeup(pci_dev, false); } static void pci_acpi_cleanup(struct device *dev) { struct acpi_device *adev = ACPI_COMPANION(dev); + struct pci_dev *pci_dev = to_pci_dev(dev); if (!adev) return; pci_acpi_remove_pm_notifier(adev); - if (adev->wakeup.flags.valid) + if (adev->wakeup.flags.valid) { + if (pci_dev->bridge_d3) + device_wakeup_disable(dev); + device_set_wakeup_capable(dev, false); + } } static bool pci_acpi_bus_match(struct device *dev) |