Re: [PATCH 1/2] platform/x86/amd/pmc: Delay suspend for some Lenovo Laptops
From: Daniel Gibson
Date: Thu May 07 2026 - 20:04:25 EST
On 08.05.26 00:54, Mario Limonciello wrote:
> On 5/7/26 17:43, Daniel Gibson wrote:
>> On 07.05.26 22:19, Daniel Gibson wrote:
>>> Thanks for the review!
>>>
>>> On 07.05.26 15:22, Ilpo Järvinen wrote:
>>>> On Fri, 1 May 2026, Daniel Gibson wrote:
>>>>
>>>>> Some IdeaPad Slim 3 devices and similar with AMD CPUs have a
>>>>> nonfunctional keyboard and lid switch after s2idle.
>>>>> It helps to delay suspend by 2.5 seconds so the EC has some time
>>>>> to do whatever it needs to get done before suspend.
>>>>>
>>>>> This issue has been reported for many different devices, this patch
>>>>> has been tested with the Zen3-based IdeaPad Slim 3 16ABR8 (82XR)
>>>>> and the Zen3+-based IdeaPad Slim 3 14ARP10 (83K6), see
>>>>> https://bugzilla.kernel.org/show_bug.cgi?id=221383
>>>>>
>>>>> Reported-by: Sindre Henriksen <sindrehenriksen93@xxxxxxxxx>
>>>>> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221383
>>>>> Tested-by: Sindre Henriksen <sindrehenriksen93@xxxxxxxxx>
>>>>> Tested-by: Daniel Gibson <daniel@xxxxxxxxx>
>>>>> Suggested-by: Mario Limonciello (AMD) <superm1@xxxxxxxxxx>
>>>>> Reviewed-by: Mario Limonciello (AMD) <superm1@xxxxxxxxxx>
>>>>> Signed-off-by: Daniel Gibson <daniel@xxxxxxxxx>
>>>>> ---
>>>>> drivers/platform/x86/amd/pmc/pmc-quirks.c | 36 ++++++++++++++++++
>>>>> +++++
>>>>> drivers/platform/x86/amd/pmc/pmc.c | 5 +++-
>>>>> drivers/platform/x86/amd/pmc/pmc.h | 1 +
>>>>> 3 files changed, 41 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/
>>>>> platform/x86/amd/pmc/pmc-quirks.c
>>>>> index 24506e342943..cea30f68f8dc 100644
>>>>> --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
>>>>> +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
>>>>> @@ -18,6 +18,7 @@
>>>>> struct quirk_entry {
>>>>> u32 s2idle_bug_mmio;
>>>>> bool spurious_8042;
>>>>> + bool need_suspend_delay;
>>>>> };
>>>>> static struct quirk_entry quirk_s2idle_bug = {
>>>>> @@ -33,6 +34,10 @@ static struct quirk_entry
>>>>> quirk_s2idle_spurious_8042 = {
>>>>> .spurious_8042 = true,
>>>>> };
>>>>> +static struct quirk_entry quirk_s2idle_need_suspend_delay = {
>>>>> + .need_suspend_delay = true
>>>>
>>>> Please add comma to any non-terminating entry.
>>>
>>> will do
>>>
>>>>
>>>>> +};
>>>>> +
>>>>> static const struct dmi_system_id fwbug_list[] = {
>>>>> {
>>>>> .ident = "L14 Gen2 AMD",
>>>>> @@ -203,6 +208,32 @@ static const struct dmi_system_id fwbug_list[]
>>>>> = {
>>>>> DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"),
>>>>> }
>>>>> },
>>>>> + /*
>>>>> + * 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.
>>>>> + * See https://bugzilla.kernel.org/show_bug.cgi?id=221383
>>>>> + */
>>>>> + {
>>>>> + .ident = "Zen3-based IdeaPad Slim and similar",
>>>>> + .driver_data = &quirk_s2idle_need_suspend_delay,
>>>>> + .matches = {
>>>>> + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
>>>>> + /*
>>>>> + * Note: there are also some Zen2-based 82X* devices that
>>>>> + * need different quirks, they're already handled above
>>>>> + */
>>>>> + DMI_MATCH(DMI_PRODUCT_NAME, "82X")
>>>>
>>>> Ditto.
>>>>
>>>>> + }
>>>>> + },
>>>>> + {
>>>>> + .ident = "Zen3+-based IdeaPad Slim and similar",
>>>>> + .driver_data = &quirk_s2idle_need_suspend_delay,
>>>>> + .matches = {
>>>>> + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
>>>>> + DMI_MATCH(DMI_PRODUCT_NAME, "83K")
>>>>
>>>> Ditto.
>>>>
>>>>> + }
>>>>> + },
>>>>> /* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */
>>>>> {
>>>>> .ident = "Thinkpad L14 Gen3",
>>>>> @@ -356,6 +387,11 @@ void amd_pmc_process_restore_quirks(struct
>>>>> amd_pmc_dev *dev)
>>>>> amd_pmc_skip_nvme_smi_handler(dev->quirks->s2idle_bug_mmio);
>>>>> }
>>>>> +bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev)
>>>>> +{
>>>>> + return dev->quirks->need_suspend_delay;
>>>>> +}
>>>>> +
>>>>> void amd_pmc_quirks_init(struct amd_pmc_dev *dev)
>>>>> {
>>>>> const struct dmi_system_id *dmi_id;
>>>>> diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/
>>>>> x86/amd/pmc/pmc.c
>>>>> index cae3fcafd4d7..c604dc7207ed 100644
>>>>> --- a/drivers/platform/x86/amd/pmc/pmc.c
>>>>> +++ b/drivers/platform/x86/amd/pmc/pmc.c
>>>>> @@ -634,10 +634,13 @@ static void amd_pmc_s2idle_check(void)
>>>>> struct amd_pmc_dev *pdev = &pmc;
>>>>> struct smu_metrics table;
>>>>> int rc;
>>>>> + bool ec_needs_sleep = !disable_workarounds &&
>>>>> amd_pmc_quirk_need_suspend_delay(pdev);
>>>>
>>>> Why isn't disable_workarounds inside
>>>> amd_pmc_quirk_need_suspend_delay() ?
>>>
>>> Because disable_workarounds is a static variable in pmc.c and
>>> amd_pmc_quirk_need_suspend_delay() is implemented in pmc-quirks.c
>>>
>>>>
>>>>> /* Avoid triggering OVP */
>>>>> - if (!get_metrics_table(pdev, &table) &&
>>>>> table.s0i3_last_entry_status)
>>>>> + if (ec_needs_sleep || (!get_metrics_table(pdev, &table) &&
>>>>> table.s0i3_last_entry_status)) {
>>>>
>>>> It would be clearer what's going on here if these two checks are
>>>> clearly
>>>> separated.
>>>>
>>>> As is, the comment confuses if it's only meant for the 2nd check.
>>>>
>>>> One of the checks will be in amd_pmc_quirk_need_suspend_delay() and the
>>>> other (the existing one + the comment) should be moved in to own
>>>> function
>>>> (perhaps in a patch preceeding this one) so it's clear the reasoning is
>>>> different(?). You can check the commit that introduced msleep(2500)
>>>> here
>>>> for details about the existing check.
>>>>
>>>
>>> Ok, I'll move that check for the existing workaround into its own
>>> function to make it more clear what is happening
>>
>> While doing this and thinking learning more about this old patch I had a
>> realization:
>> The existing code does this 2.5s sleep before every suspend *except* for
>> the very first one after a (re)boot!
>>
>> (Except on CPUs that don't have this metrics table (AMD_CPU_ID_PCO) or
>> some edge case when pdev->smu_virt_addr is NULL and can't be set with
>> amd_pmc_setup_smu_logging())
>>
>> This by the way explains why in the rare cases suspend randomly worked
>> as intended on my machine (even without my patch) it kept on working in
>> subsequent suspends, until rebooting.
>>
>> Anyway, radical idea: What if we just ALWAYS SLEEP for 2.5 seconds here?
>>
>> I don't think the special case of the very first suspend after boot
>> justifies all the complexity of the existing checks *and* the conditions
>> I'm adding.
>>
>
> That's not how it works - it only applies the 2.5s delay when it has
> reached HW sleep and needs to start a second HW sleep cycle.
>
> That is something like this:
> * close the lid
> * suspend sequence starts
> * (no extra sleep)
> * enter HW sleep
> * plug in ac adapter
> * EC SCI fires
> * exits HW sleep
> * kernel decides to go back to HW sleep (no other interrupts active)
> * (extra sleep here)
> * enter HW sleep
> * open lid
> * exit HW sleep
> * resume sequence
> * back in OS
>
> And that is how every cycle after a reboot works. So this would be the
> second sequence if you tried to suspend again.
>
> * close the lid
> * suspend sequence starts
> * (no extra sleep)
> * enter HW sleep
> * open lid
> * exit HW sleep
> * resume sequence
> * back in OS
>
> The reason for that 2.5s delay on second HW sleep is described in that
> commit message. It's specifically to mitigate a case that a voltage
> regulator gets too much voltage from the rapid swings in and out of HW
> sleep rapidly.
>
> It should not be applied every single cycle unless it's needed (such as
> this bug you found with the EC race).
Are you sure it behaves like this?
The check is basically "is the SMU table's s0i3_last_entry_status != 0".
And as far as I can tell this is the case after every successful suspend,
it only seems to be reset by rebooting or if a suspend fails.
See smu_fw_info_show() (for /sys/kern/debug/amd_pmc/smu_fw_info):
seq_printf(s, "Last S0i3 Status: %s\n", table.s0i3_last_entry_status ? "Success" :
"Unknown/Fail");
When I read /sys/kern/debug/amd_pmc/smu_fw_info right after booting, it prints
"Last S0i3 Status: Unknown/Fail"
After resuming, even minutes (and presumably hours) later, it prints
"Last S0i3 Status: Success"
indicating that s0i3_last_entry_status still is != 0.
>
> Now you might have been confused by not looking at commit
> 4dbd11796f3a8eb95647507befc41995458a4023. This fixes the behavior as
> it's supposed to be.
>
>>>
>>>>> + dev_info(pdev->dev, "Delaying suspend by 2.5s to avoid
>>>>> platform bug\n");
>>>>> msleep(2500);
>>>>> + }
>>>>> /* Dump the IdleMask before we add to the STB */
>>>>> amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
>>>>> diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/
>>>>> x86/amd/pmc/pmc.h
>>>>> index fe3f53eb5955..f5257e47b8c4 100644
>>>>> --- a/drivers/platform/x86/amd/pmc/pmc.h
>>>>> +++ b/drivers/platform/x86/amd/pmc/pmc.h
>>>>> @@ -147,6 +147,7 @@ enum amd_pmc_def {
>>>>> };
>>>>> void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev);
>>>>> +bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev);
>>>>> void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
>>>>> void amd_mp2_stb_init(struct amd_pmc_dev *dev);
>>>>> void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
>>>>>
>>>>
>>>
>>
>