diff mbox series

[v17,4/4] platform/x86/amd: pmc: Report device constraints

Message ID 20230906184354.45846-5-mario.limonciello@amd.com
State New
Headers show
Series Allow drivers to influence D3 behavior for bridges | expand

Commit Message

Mario Limonciello Sept. 6, 2023, 6:43 p.m. UTC
Iain reports that USB devices can't be used to wake a Lenovo Z13
from suspend. This is because the PCIe root port has been put
into D3hot and AMD's platform can't handle USB devices waking from
a hardware sleep state in this case.

This problem only occurs on Linux, and only when the AMD PMC driver
is utilized to put the device into a hardware sleep state. When the AMD
PMC driver is enabled it will notify the hardware that the OS is ready for
it to try to enter a hardware sleep state. Comparing the behavior on
Windows and Linux, Windows doesn't put the root ports into D3.

This is because the Windows uPEP driver takes into account constraints
that are advertised by the platform in an ACPI device. The constraints
are available for individual devices via `acpi_get_lps0_constraint`.

Add support for the amd-pmc driver to fetch constraints for devices and
report them as either an 'optin' or a 'veto' to the PCI core when it
evaluates whether a device should support 'D3'. This interface
intentionally doesn't specify D3hot or D3cold, it's collectively 'D3'.

Any enabled constraints set to ACPI_STATE_S3 or greater will be reported
to the 'optin' callback to ensure that they policy is set to select 'D3'.

Any disabled constraints, missing constraints, or constraints set to less
than ACPI_STATE_S3 will be reported to the 'veto' callback to ensure the
policy matches the Windows behavior of disabling 'D3'. This behavior is
necessary due to the policy enacted by commit 9d26d3a8f1b0 ("PCI: Put
PCIe ports into D3 during suspend").

Fixes: 9d26d3a8f1b0 ("PCI: Put PCIe ports into D3 during suspend")
Reported-by: Iain Lane <iain@orangesquash.org.uk>
Closes: https://forums.lenovo.com/t5/Ubuntu/Z13-can-t-resume-from-suspend-with-external-USB-keyboard/m-p/5217121
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
 drivers/platform/x86/amd/pmc/pmc.c | 57 ++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)
diff mbox series

Patch

diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index c1e788b67a74..ed5d2a0c6a55 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -741,6 +741,49 @@  static int amd_pmc_czn_wa_irq1(struct amd_pmc_dev *pdev)
 	return 0;
 }
 
+/*
+ * Constraints are specified in the ACPI LPS0 device and specify what the
+ * platform intended for the device.
+ *
+ * If a constraint is present and >= to ACPI_STATE_S3, then enable D3 for the
+ * device with the 'optin' callback.
+ * If a constraint is not present or < ACPI_STATE_S3, then disable D3 for the
+ * device with the 'veto' callback.
+ */
+static int amd_pmc_get_constraint(struct pci_dev *pci_dev)
+{
+	struct acpi_device *adev = ACPI_COMPANION(&pci_dev->dev);
+
+	if (!adev)
+		return ACPI_STATE_UNKNOWN;
+
+	return acpi_get_lps0_constraint(adev);
+}
+
+static bool amd_pmc_d3_optin_check(struct pci_dev *pci_dev)
+{
+	int constraint = amd_pmc_get_constraint(pci_dev);
+
+	if (constraint == ACPI_STATE_UNKNOWN ||
+	    constraint < ACPI_STATE_S3)
+		return false;
+
+	dev_dbg(&pci_dev->dev, "Opting in to D3 due to constraint %d\n", constraint);
+	return true;
+}
+
+static bool amd_pmc_d3_veto_check(struct pci_dev *pci_dev)
+{
+	int constraint = amd_pmc_get_constraint(pci_dev);
+
+	if (constraint != ACPI_STATE_UNKNOWN &&
+	    constraint >= ACPI_STATE_S3)
+		return false;
+
+	dev_dbg(&pci_dev->dev, "Vetoing D3 due to constraint %d\n", constraint);
+	return true;
+}
+
 static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
 {
 	struct rtc_device *rtc_device;
@@ -881,6 +924,12 @@  static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = {
 	.restore = amd_pmc_s2idle_restore,
 };
 
+static struct pci_d3_driver_ops amd_pmc_d3_veto_ops = {
+	.optin = amd_pmc_d3_optin_check,
+	.veto = amd_pmc_d3_veto_check,
+	.priority = 50,
+};
+
 static int amd_pmc_suspend_handler(struct device *dev)
 {
 	struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
@@ -1074,10 +1123,17 @@  static int amd_pmc_probe(struct platform_device *pdev)
 			amd_pmc_quirks_init(dev);
 	}
 
+	err = pci_register_d3_possible_cb(&amd_pmc_d3_veto_ops);
+	if (err)
+		goto err_register_lps0;
+
 	amd_pmc_dbgfs_register(dev);
 	pm_report_max_hw_sleep(U64_MAX);
 	return 0;
 
+err_register_lps0:
+	if (IS_ENABLED(CONFIG_SUSPEND))
+		acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops);
 err_pci_dev_put:
 	pci_dev_put(rdev);
 	return err;
@@ -1089,6 +1145,7 @@  static void amd_pmc_remove(struct platform_device *pdev)
 
 	if (IS_ENABLED(CONFIG_SUSPEND))
 		acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops);
+	pci_unregister_d3_possible_cb(&amd_pmc_d3_veto_ops);
 	amd_pmc_dbgfs_unregister(dev);
 	pci_dev_put(dev->rdev);
 	mutex_destroy(&dev->lock);