summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/amd/pmc/pmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/amd/pmc/pmc.c')
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c83
1 files changed, 80 insertions, 3 deletions
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index cae3fcafd4d7..2cd77a44c7e7 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -16,6 +16,7 @@
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
+#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
@@ -89,6 +90,11 @@ static bool disable_workarounds;
module_param(disable_workarounds, bool, 0644);
MODULE_PARM_DESC(disable_workarounds, "Disable workarounds for platform bugs");
+static int delay_suspend = -1;
+module_param(delay_suspend, int, 0644);
+MODULE_PARM_DESC(delay_suspend,
+ "Delays s2idle by 2.5 seconds to work around buggy ECs, often causing keyboard issues after suspend. 0: don't delay, 1: do delay, -1 (default): let amd_pmc decide. If you need this please report this to: platform-driver-x86@vger.kernel.org");
+
static struct amd_pmc_dev pmc;
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
@@ -598,6 +604,72 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
return rc;
}
+static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev)
+{
+ /*
+ * Starting a new HW sleep cycle right after waking from one
+ * can cause electrical problems triggering the over voltage protection.
+ * That is avoided by delaying the next suspend a bit, see also
+ * https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/
+ */
+ struct smu_metrics table;
+
+ return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status;
+}
+
+static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev)
+{
+ /*
+ * intermediate_wakeup implies that the machine didn't get to deepest sleep
+ * state before - otherwise this function isn't called in amd_pmc_s2idle_check()
+ * because amd_pmc_intermediate_wakeup_need_delay() returns true first.
+ * On some IdeaPads that happens when charging, because the EC seems
+ * to send lots of messages then that wake the machine.
+ *
+ * But even in that case, the sleep here is necessary (on those IdeaPads),
+ * otherwise they wake up completely (resume) after a few seconds.
+ * So this variable is only used to avoid spamming dmesg on each
+ * intermediate wakeup.
+ */
+ bool intermediate_wakeup = !pdev->is_first_check_after_suspend;
+
+ /*
+ * Some Lenovo Laptops (like different IdeaPad 3 Slims) need some
+ * me-time before sleeping or they get uncooperative after waking
+ * up and don't send events for keyboard and lid switch anymore.
+ *
+ * Unfortunately this doesn't entirely fix the problem: It can still
+ * happen when resuming with a timer (wakealarm), but at least the
+ * more common usecases (wakeup by opening lid or pressing a key)
+ * work fine with this workaround.
+ *
+ * See https://bugzilla.kernel.org/show_bug.cgi?id=221383
+ */
+ if (amd_pmc_quirk_need_suspend_delay(pdev)) {
+ /*
+ * delay_suspend=1 force-enables this, otherwise it can be
+ * disabled with disable_workarounds or delay_suspend=0
+ */
+ if (delay_suspend == 1 || (delay_suspend == -1 && !disable_workarounds)) {
+ if (!intermediate_wakeup)
+ dev_info(pdev->dev, "Delaying suspend by 2.5s to avoid platform bug\n");
+ return true;
+ }
+ if (!intermediate_wakeup)
+ dev_info(pdev->dev, "Not delaying suspend because of module parameter, even though your device is assumed to need it!\n");
+ } else if (delay_suspend == 1) {
+ if (!intermediate_wakeup)
+ dev_info(pdev->dev, "Delaying suspend by 2.5s because delay_suspend=1. If this solves problems on your machine, please report this whole line to: platform-driver-x86@vger.kernel.org so it can be automatically detected as affected in the future. System Vendor: \"%s\" Product Name: \"%s\" Product Family: \"%s\" Board Vendor: \"%s\" Board Name: \"%s\"\n",
+ dmi_get_system_info(DMI_SYS_VENDOR) ?: "(Unknown)",
+ dmi_get_system_info(DMI_PRODUCT_NAME) ?: "(Unknown)",
+ dmi_get_system_info(DMI_PRODUCT_FAMILY) ?: "(Unknown)",
+ dmi_get_system_info(DMI_BOARD_VENDOR) ?: "(Unknown)",
+ dmi_get_system_info(DMI_BOARD_NAME) ?: "(Unknown)");
+ return true;
+ }
+ return false;
+}
+
static void amd_pmc_s2idle_prepare(void)
{
struct amd_pmc_dev *pdev = &pmc;
@@ -605,6 +677,9 @@ static void amd_pmc_s2idle_prepare(void)
u8 msg;
u32 arg = 1;
+ /* Reset this variable because this is a fresh suspend */
+ pdev->is_first_check_after_suspend = true;
+
/* Reset and Start SMU logging - to monitor the s0i3 stats */
amd_pmc_setup_smu_logging(pdev);
@@ -632,11 +707,10 @@ static void amd_pmc_s2idle_prepare(void)
static void amd_pmc_s2idle_check(void)
{
struct amd_pmc_dev *pdev = &pmc;
- struct smu_metrics table;
int rc;
- /* Avoid triggering OVP */
- if (!get_metrics_table(pdev, &table) && table.s0i3_last_entry_status)
+ if (amd_pmc_intermediate_wakeup_need_delay(pdev) ||
+ amd_pmc_want_suspend_delay(pdev))
msleep(2500);
/* Dump the IdleMask before we add to the STB */
@@ -645,6 +719,9 @@ static void amd_pmc_s2idle_check(void)
rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_CHECK);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
+
+ /* remember that first check after suspend is done (until next prepare) */
+ pdev->is_first_check_after_suspend = false;
}
static int amd_pmc_dump_data(struct amd_pmc_dev *pdev)