diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-06-25 18:20:23 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-06-26 17:51:56 -0400 |
commit | 471a739a47aa7d582f0cdf9d392957d04632bae2 (patch) | |
tree | 2ab59a00c854751954f5d669fda6225a80e70732 | |
parent | 4b972a01a7da614b4796475f933094751a295a2f (diff) |
PCI: PM: Avoid skipping bus-level PM on platforms without ACPI
There are platforms that do not call pm_set_suspend_via_firmware(),
so pm_suspend_via_firmware() returns 'false' on them, but the power
states of PCI devices (PCIe ports in particular) are changed as a
result of powering down core platform components during system-wide
suspend. Thus the pm_suspend_via_firmware() checks in
pci_pm_suspend_noirq() and pci_pm_resume_noirq() introduced by
commit 3e26c5feed2a ("PCI: PM: Skip devices in D0 for suspend-to-
idle") are not sufficient to determine that devices left in D0
during suspend will remain in D0 during resume and so the bus-level
power management can be skipped for them.
For this reason, introduce a new global suspend flag,
PM_SUSPEND_FLAG_NO_PLATFORM, set it for suspend-to-idle only
and replace the pm_suspend_via_firmware() checks mentioned above
with checks against this flag.
Fixes: 3e26c5feed2a ("PCI: PM: Skip devices in D0 for suspend-to-idle")
Reported-by: Jon Hunter <jonathanh@nvidia.com>
Tested-by: Jon Hunter <jonathanh@nvidia.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
-rw-r--r-- | drivers/pci/pci-driver.c | 8 | ||||
-rw-r--r-- | include/linux/suspend.h | 26 | ||||
-rw-r--r-- | kernel/power/suspend.c | 3 |
3 files changed, 31 insertions, 6 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 98af9ecd4a90..ca3793002e2f 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c | |||
@@ -859,7 +859,7 @@ static int pci_pm_suspend_noirq(struct device *dev) | |||
859 | pci_dev->bus->self->skip_bus_pm = true; | 859 | pci_dev->bus->self->skip_bus_pm = true; |
860 | } | 860 | } |
861 | 861 | ||
862 | if (pci_dev->skip_bus_pm && !pm_suspend_via_firmware()) { | 862 | if (pci_dev->skip_bus_pm && pm_suspend_no_platform()) { |
863 | dev_dbg(dev, "PCI PM: Skipped\n"); | 863 | dev_dbg(dev, "PCI PM: Skipped\n"); |
864 | goto Fixup; | 864 | goto Fixup; |
865 | } | 865 | } |
@@ -914,10 +914,10 @@ static int pci_pm_resume_noirq(struct device *dev) | |||
914 | /* | 914 | /* |
915 | * In the suspend-to-idle case, devices left in D0 during suspend will | 915 | * In the suspend-to-idle case, devices left in D0 during suspend will |
916 | * stay in D0, so it is not necessary to restore or update their | 916 | * stay in D0, so it is not necessary to restore or update their |
917 | * configuration here and attempting to put them into D0 again may | 917 | * configuration here and attempting to put them into D0 again is |
918 | * confuse some firmware, so avoid doing that. | 918 | * pointless, so avoid doing that. |
919 | */ | 919 | */ |
920 | if (!pci_dev->skip_bus_pm || pm_suspend_via_firmware()) | 920 | if (!(pci_dev->skip_bus_pm && pm_suspend_no_platform())) |
921 | pci_pm_default_resume_early(pci_dev); | 921 | pci_pm_default_resume_early(pci_dev); |
922 | 922 | ||
923 | pci_fixup_device(pci_fixup_resume_early, pci_dev); | 923 | pci_fixup_device(pci_fixup_resume_early, pci_dev); |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 8594001e8be8..f0d262ad7b78 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
@@ -209,8 +209,9 @@ extern int suspend_valid_only_mem(suspend_state_t state); | |||
209 | 209 | ||
210 | extern unsigned int pm_suspend_global_flags; | 210 | extern unsigned int pm_suspend_global_flags; |
211 | 211 | ||
212 | #define PM_SUSPEND_FLAG_FW_SUSPEND (1 << 0) | 212 | #define PM_SUSPEND_FLAG_FW_SUSPEND BIT(0) |
213 | #define PM_SUSPEND_FLAG_FW_RESUME (1 << 1) | 213 | #define PM_SUSPEND_FLAG_FW_RESUME BIT(1) |
214 | #define PM_SUSPEND_FLAG_NO_PLATFORM BIT(2) | ||
214 | 215 | ||
215 | static inline void pm_suspend_clear_flags(void) | 216 | static inline void pm_suspend_clear_flags(void) |
216 | { | 217 | { |
@@ -227,6 +228,11 @@ static inline void pm_set_resume_via_firmware(void) | |||
227 | pm_suspend_global_flags |= PM_SUSPEND_FLAG_FW_RESUME; | 228 | pm_suspend_global_flags |= PM_SUSPEND_FLAG_FW_RESUME; |
228 | } | 229 | } |
229 | 230 | ||
231 | static inline void pm_set_suspend_no_platform(void) | ||
232 | { | ||
233 | pm_suspend_global_flags |= PM_SUSPEND_FLAG_NO_PLATFORM; | ||
234 | } | ||
235 | |||
230 | /** | 236 | /** |
231 | * pm_suspend_via_firmware - Check if platform firmware will suspend the system. | 237 | * pm_suspend_via_firmware - Check if platform firmware will suspend the system. |
232 | * | 238 | * |
@@ -268,6 +274,22 @@ static inline bool pm_resume_via_firmware(void) | |||
268 | return !!(pm_suspend_global_flags & PM_SUSPEND_FLAG_FW_RESUME); | 274 | return !!(pm_suspend_global_flags & PM_SUSPEND_FLAG_FW_RESUME); |
269 | } | 275 | } |
270 | 276 | ||
277 | /** | ||
278 | * pm_suspend_no_platform - Check if platform may change device power states. | ||
279 | * | ||
280 | * To be called during system-wide power management transitions to sleep states | ||
281 | * or during the subsequent system-wide transitions back to the working state. | ||
282 | * | ||
283 | * Return 'true' if the power states of devices remain under full control of the | ||
284 | * kernel throughout the system-wide suspend and resume cycle in progress (that | ||
285 | * is, if a device is put into a certain power state during suspend, it can be | ||
286 | * expected to remain in that state during resume). | ||
287 | */ | ||
288 | static inline bool pm_suspend_no_platform(void) | ||
289 | { | ||
290 | return !!(pm_suspend_global_flags & PM_SUSPEND_FLAG_NO_PLATFORM); | ||
291 | } | ||
292 | |||
271 | /* Suspend-to-idle state machnine. */ | 293 | /* Suspend-to-idle state machnine. */ |
272 | enum s2idle_states { | 294 | enum s2idle_states { |
273 | S2IDLE_STATE_NONE, /* Not suspended/suspending. */ | 295 | S2IDLE_STATE_NONE, /* Not suspended/suspending. */ |
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index 9505101ed2bc..096211299c07 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c | |||
@@ -493,6 +493,9 @@ int suspend_devices_and_enter(suspend_state_t state) | |||
493 | 493 | ||
494 | pm_suspend_target_state = state; | 494 | pm_suspend_target_state = state; |
495 | 495 | ||
496 | if (state == PM_SUSPEND_TO_IDLE) | ||
497 | pm_set_suspend_no_platform(); | ||
498 | |||
496 | error = platform_suspend_begin(state); | 499 | error = platform_suspend_begin(state); |
497 | if (error) | 500 | if (error) |
498 | goto Close; | 501 | goto Close; |