diff options
Diffstat (limited to 'SOURCES/v2-ally-suspend-fix.patch')
-rw-r--r-- | SOURCES/v2-ally-suspend-fix.patch | 913 |
1 files changed, 913 insertions, 0 deletions
diff --git a/SOURCES/v2-ally-suspend-fix.patch b/SOURCES/v2-ally-suspend-fix.patch new file mode 100644 index 0000000..c37d49e --- /dev/null +++ b/SOURCES/v2-ally-suspend-fix.patch @@ -0,0 +1,913 @@ +From ff87284862db614a4067b2d7383ecb173e1454de Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Wed, 18 Sep 2024 23:53:53 +0200 +Subject: [PATCH v3 01/10] acpi/x86: s2idle: add support for Display Off and + Display On callbacks + +The Display Off and Display On firmware notifications are meant to signify +the system entering a state where the user is not actively interacting +with it (i.e., in Windows this state is called "Screen Off" and the +system enters it once it turns the screen off e.g., due to inactivity). + +Currently, these functions are called within the suspend sequence, which +causes issues when these notifications interact with e.g., a USB device +and makes them unable to be called as part of the screen turning off. + +This patch adds a set of callbacks to allow calling the Display On/Off +notifications outside of the suspend/resume path. + +Co-developed-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + include/linux/suspend.h | 5 +++++ + kernel/power/suspend.c | 12 ++++++++++++ + 2 files changed, 17 insertions(+) + +diff --git a/include/linux/suspend.h b/include/linux/suspend.h +index da6ebca3ff77..8f33249cc067 100644 +--- a/include/linux/suspend.h ++++ b/include/linux/suspend.h +@@ -132,6 +132,7 @@ struct platform_suspend_ops { + }; + + struct platform_s2idle_ops { ++ int (*display_off)(void); + int (*begin)(void); + int (*prepare)(void); + int (*prepare_late)(void); +@@ -140,6 +141,7 @@ struct platform_s2idle_ops { + void (*restore_early)(void); + void (*restore)(void); + void (*end)(void); ++ int (*display_on)(void); + }; + + #ifdef CONFIG_SUSPEND +@@ -160,6 +162,9 @@ extern unsigned int pm_suspend_global_flags; + #define PM_SUSPEND_FLAG_FW_RESUME BIT(1) + #define PM_SUSPEND_FLAG_NO_PLATFORM BIT(2) + ++int platform_suspend_display_off(void); ++int platform_suspend_display_on(void); ++ + static inline void pm_suspend_clear_flags(void) + { + pm_suspend_global_flags = 0; +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index 09f8397bae15..c527dc0ae5ae 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -254,6 +254,18 @@ static bool sleep_state_supported(suspend_state_t state) + (valid_state(state) && !cxl_mem_active()); + } + ++int platform_suspend_display_off(void) ++{ ++ return s2idle_ops && s2idle_ops->display_off ? s2idle_ops->display_off() : 0; ++} ++EXPORT_SYMBOL_GPL(platform_suspend_display_off); ++ ++int platform_suspend_display_on(void) ++{ ++ return s2idle_ops && s2idle_ops->display_on ? s2idle_ops->display_on() : 0; ++} ++EXPORT_SYMBOL_GPL(platform_suspend_display_on); ++ + static int platform_suspend_prepare(suspend_state_t state) + { + return state != PM_SUSPEND_TO_IDLE && suspend_ops->prepare ? +-- +2.46.2 + + +From 0f25cd27999b0fe6c0382208452422fd1866da1e Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Thu, 19 Sep 2024 00:02:32 +0200 +Subject: [PATCH v3 02/10] acpi/x86: s2idle: handle Display On/Off calls + outside of suspend sequence + +Currently, the Display On/Off calls are handled within the suspend +sequence, which is a deviation from Windows. This causes issues with +certain devices, where the notification interacts with a USB device +that expects the kernel to be fully awake. + +This patch calls the Display On/Off callbacks before entering the suspend +sequence, which fixes this issue. In addition, it opens the possibility +of modelling a state such as "Screen Off" that mirrors Windows, as the +callbacks will be accessible and validated to work outside of the +suspend sequence. + +Suggested-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + kernel/power/suspend.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index c527dc0ae5ae..610f8ecaeebd 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -589,6 +589,13 @@ static int enter_state(suspend_state_t state) + if (state == PM_SUSPEND_TO_IDLE) + s2idle_begin(); + ++ /* ++ * Linux does not have the concept of a "Screen Off" state, so call ++ * the platform functions for Display On/Off prior to the suspend ++ * sequence, mirroring Windows which calls them outside of it as well. ++ */ ++ platform_suspend_display_off(); ++ + if (sync_on_suspend_enabled) { + trace_suspend_resume(TPS("sync_filesystems"), 0, true); + ksys_sync_helper(); +@@ -616,6 +623,8 @@ static int enter_state(suspend_state_t state) + suspend_finish(); + Unlock: + mutex_unlock(&system_transition_mutex); ++ ++ platform_suspend_display_on(); + return error; + } + +-- +2.46.2 + + +From e0cad271a26ff16ea9456e0104ea10e1195f6d47 Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Sun, 22 Sep 2024 11:47:29 +0200 +Subject: [PATCH v3 03/10] acpi/x86: s2idle: add quirk table for modern standby + delays + +Unfortunately, some modern standby systems, including the ROG Ally, rely +on a delay between modern standby transitions. Add a quirk table for +introducing delays between modern standby transitions, and quirk the +ROG Ally on "Display Off", which needs a bit of time to turn off its +controllers prior to suspending (i.e., entering DRIPS). + +Reported-by: Denis Benato <benato.denis96@gmail.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + include/linux/suspend.h | 5 +++++ + kernel/power/suspend.c | 41 +++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 46 insertions(+) + +diff --git a/include/linux/suspend.h b/include/linux/suspend.h +index 8f33249cc067..d7e2a4d8ab0c 100644 +--- a/include/linux/suspend.h ++++ b/include/linux/suspend.h +@@ -144,6 +144,11 @@ struct platform_s2idle_ops { + int (*display_on)(void); + }; + ++struct platform_s2idle_quirks { ++ int delay_display_off; ++ int delay_display_on; ++}; ++ + #ifdef CONFIG_SUSPEND + extern suspend_state_t pm_suspend_target_state; + extern suspend_state_t mem_sleep_current; +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index 610f8ecaeebd..af2abdd2f8c3 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -11,6 +11,7 @@ + + #include <linux/string.h> + #include <linux/delay.h> ++#include <linux/dmi.h> + #include <linux/errno.h> + #include <linux/init.h> + #include <linux/console.h> +@@ -61,6 +62,30 @@ static DECLARE_SWAIT_QUEUE_HEAD(s2idle_wait_head); + enum s2idle_states __read_mostly s2idle_state; + static DEFINE_RAW_SPINLOCK(s2idle_lock); + ++// The ROG Ally series disconnects its controllers on Display Off, without ++// holding a lock, introducing a race condition. Add a delay to allow the ++// controller to disconnect cleanly prior to suspend. ++static const struct platform_s2idle_quirks rog_ally_quirks = { ++ .delay_display_off = 500, ++}; ++ ++static const struct dmi_system_id platform_s2idle_quirks[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "RC71L"), ++ }, ++ .driver_data = (void *)&rog_ally_quirks ++ }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "RC72L"), ++ }, ++ .driver_data = (void *)&rog_ally_quirks ++ }, ++ {} ++}; ++ ++ + /** + * pm_suspend_default_s2idle - Check if suspend-to-idle is the default suspend. + * +@@ -589,12 +614,26 @@ static int enter_state(suspend_state_t state) + if (state == PM_SUSPEND_TO_IDLE) + s2idle_begin(); + ++ /* ++ * Windows transitions between Modern Standby states slowly, over multiple ++ * seconds. Certain manufacturers may rely on this, introducing race ++ * conditions. Until Linux can support modern standby, add the relevant ++ * delays between transitions here. ++ */ ++ const struct dmi_system_id *s2idle_sysid = dmi_first_match( ++ platform_s2idle_quirks ++ ); ++ const struct platform_s2idle_quirks *s2idle_quirks = s2idle_sysid ? ++ s2idle_sysid->driver_data : NULL; ++ + /* + * Linux does not have the concept of a "Screen Off" state, so call + * the platform functions for Display On/Off prior to the suspend + * sequence, mirroring Windows which calls them outside of it as well. + */ + platform_suspend_display_off(); ++ if (s2idle_quirks && s2idle_quirks->delay_display_off) ++ msleep(s2idle_quirks->delay_display_off); + + if (sync_on_suspend_enabled) { + trace_suspend_resume(TPS("sync_filesystems"), 0, true); +@@ -624,6 +663,8 @@ static int enter_state(suspend_state_t state) + Unlock: + mutex_unlock(&system_transition_mutex); + ++ if (s2idle_quirks && s2idle_quirks->delay_display_on) ++ msleep(s2idle_quirks->delay_display_on); + platform_suspend_display_on(); + return error; + } +-- +2.46.2 + + +From 230395bedb589220c3c126d7f33156edbb3dd1c7 Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Thu, 19 Sep 2024 00:22:03 +0200 +Subject: [PATCH v3 04/10] acpi/x86: s2idle: call Display On/Off as part of + callbacks and rename + +Move the Display On/Off notifications into dedicated callbacks that gate +the ACPI mutex, so they can be called outside of the suspend path. +This fixes issues on certain devices that expect kernel drivers to be +fully active during the calls, and allows for the flexibility of calling +them as part of a more elaborate userspace suspend sequence (such as +with "Screen Off" in Windows Modern Standby). + +In addition, rename the notifications from "screen_" to "display_", as +there is no documentation referring to them as screen, either by +Intel or Microsoft. + +Co-developed-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + drivers/acpi/x86/s2idle.c | 89 +++++++++++++++++++++++++++------------ + 1 file changed, 62 insertions(+), 27 deletions(-) + +diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c +index dd0b40b9bbe8..a17e28b91326 100644 +--- a/drivers/acpi/x86/s2idle.c ++++ b/drivers/acpi/x86/s2idle.c +@@ -39,8 +39,8 @@ static const struct acpi_device_id lps0_device_ids[] = { + #define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" + + #define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1 +-#define ACPI_LPS0_SCREEN_OFF 3 +-#define ACPI_LPS0_SCREEN_ON 4 ++#define ACPI_LPS0_DISPLAY_OFF 3 ++#define ACPI_LPS0_DISPLAY_ON 4 + #define ACPI_LPS0_ENTRY 5 + #define ACPI_LPS0_EXIT 6 + #define ACPI_LPS0_MS_ENTRY 7 +@@ -50,8 +50,8 @@ static const struct acpi_device_id lps0_device_ids[] = { + #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" + #define ACPI_LPS0_ENTRY_AMD 2 + #define ACPI_LPS0_EXIT_AMD 3 +-#define ACPI_LPS0_SCREEN_OFF_AMD 4 +-#define ACPI_LPS0_SCREEN_ON_AMD 5 ++#define ACPI_LPS0_DISPLAY_OFF_AMD 4 ++#define ACPI_LPS0_DISPLAY_ON_AMD 5 + + static acpi_handle lps0_device_handle; + static guid_t lps0_dsm_guid; +@@ -60,6 +60,7 @@ static int lps0_dsm_func_mask; + static guid_t lps0_dsm_guid_microsoft; + static int lps0_dsm_func_mask_microsoft; + static int lps0_dsm_state; ++static bool lsp0_dsm_in_display_off; + + /* Device constraint entry structure */ + struct lpi_device_info { +@@ -361,9 +362,9 @@ static const char *acpi_sleep_dsm_state_to_str(unsigned int state) + { + if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) { + switch (state) { +- case ACPI_LPS0_SCREEN_OFF: ++ case ACPI_LPS0_DISPLAY_OFF: + return "screen off"; +- case ACPI_LPS0_SCREEN_ON: ++ case ACPI_LPS0_DISPLAY_ON: + return "screen on"; + case ACPI_LPS0_ENTRY: + return "lps0 entry"; +@@ -376,9 +377,9 @@ static const char *acpi_sleep_dsm_state_to_str(unsigned int state) + } + } else { + switch (state) { +- case ACPI_LPS0_SCREEN_ON_AMD: ++ case ACPI_LPS0_DISPLAY_ON_AMD: + return "screen on"; +- case ACPI_LPS0_SCREEN_OFF_AMD: ++ case ACPI_LPS0_DISPLAY_OFF_AMD: + return "screen off"; + case ACPI_LPS0_ENTRY_AMD: + return "lps0 entry"; +@@ -539,27 +540,69 @@ static struct acpi_scan_handler lps0_handler = { + .attach = lps0_device_attach, + }; + +-int acpi_s2idle_prepare_late(void) ++static int acpi_s2idle_display_off(void) + { +- struct acpi_s2idle_dev_ops *handler; +- + if (!lps0_device_handle || sleep_no_lps0) + return 0; + +- if (pm_debug_messages_on) +- lpi_check_constraints(); ++ if (unlikely(WARN_ON(lsp0_dsm_in_display_off))) ++ return -EINVAL; ++ ++ lsp0_dsm_in_display_off = true; ++ acpi_scan_lock_acquire(); + +- /* Screen off */ ++ /* Display off */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? +- ACPI_LPS0_SCREEN_OFF_AMD : +- ACPI_LPS0_SCREEN_OFF, ++ ACPI_LPS0_DISPLAY_OFF_AMD : ++ ACPI_LPS0_DISPLAY_OFF, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) +- acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, ++ acpi_sleep_run_lps0_dsm(ACPI_LPS0_DISPLAY_OFF, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + ++ acpi_scan_lock_release(); ++ ++ return 0; ++} ++ ++static int acpi_s2idle_display_on(void) ++{ ++ if (!lps0_device_handle || sleep_no_lps0) ++ return 0; ++ ++ if (unlikely(WARN_ON(!lsp0_dsm_in_display_off))) ++ return -EINVAL; ++ ++ lsp0_dsm_in_display_off = false; ++ acpi_scan_lock_acquire(); ++ ++ /* Display on */ ++ if (lps0_dsm_func_mask_microsoft > 0) ++ acpi_sleep_run_lps0_dsm(ACPI_LPS0_DISPLAY_ON, ++ lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); ++ if (lps0_dsm_func_mask > 0) ++ acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? ++ ACPI_LPS0_DISPLAY_ON_AMD : ++ ACPI_LPS0_DISPLAY_ON, ++ lps0_dsm_func_mask, lps0_dsm_guid); ++ ++ acpi_scan_lock_release(); ++ ++ return 0; ++} ++ ++int acpi_s2idle_prepare_late(void) ++{ ++ struct acpi_s2idle_dev_ops *handler; ++ ++ if (!lps0_device_handle || sleep_no_lps0) ++ return 0; ++ ++ if (pm_debug_messages_on) ++ lpi_check_constraints(); ++ + /* LPS0 entry */ + if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, +@@ -623,19 +666,10 @@ void acpi_s2idle_restore_early(void) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + } +- +- /* Screen on */ +- if (lps0_dsm_func_mask_microsoft > 0) +- acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, +- lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); +- if (lps0_dsm_func_mask > 0) +- acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? +- ACPI_LPS0_SCREEN_ON_AMD : +- ACPI_LPS0_SCREEN_ON, +- lps0_dsm_func_mask, lps0_dsm_guid); + } + + static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { ++ .display_off = acpi_s2idle_display_off, + .begin = acpi_s2idle_begin, + .prepare = acpi_s2idle_prepare, + .prepare_late = acpi_s2idle_prepare_late, +@@ -644,6 +678,7 @@ static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { + .restore_early = acpi_s2idle_restore_early, + .restore = acpi_s2idle_restore, + .end = acpi_s2idle_end, ++ .display_on = acpi_s2idle_display_on, + }; + + void __init acpi_s2idle_setup(void) +-- +2.46.2 + + +From 69cdca368e53713088771d2866adc48ac19cbf29 Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Thu, 19 Sep 2024 00:29:59 +0200 +Subject: [PATCH v3 05/10] platform/x86: asus-wmi: remove Ally (1st gen) and + Ally X suspend quirk + +By moving the Display On/Off calls outside of the suspend sequence and +introducing a slight delay after Display Off, the ROG Ally controller +functions exactly as it does in Windows. + +Therefore, remove the quirk that fixed the controller only when the +mcu_powersave attribute was disabled, while adding a large amount of +delay to the suspend and wake process. + +Reviewed-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + drivers/platform/x86/asus-wmi.c | 54 --------------------------------- + 1 file changed, 54 deletions(-) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 37636e5a38e3..2c9656e0afda 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -137,11 +137,6 @@ + #define ASUS_MINI_LED_2024_STRONG 0x01 + #define ASUS_MINI_LED_2024_OFF 0x02 + +-/* Controls the power state of the USB0 hub on ROG Ally which input is on */ +-#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE" +-/* 300ms so far seems to produce a reliable result on AC and battery */ +-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500 +- + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -269,9 +264,6 @@ + u32 tablet_switch_dev_id; + bool tablet_switch_inverted; + +- /* The ROG Ally device requires the MCU USB device be disconnected before suspend */ +- bool ally_mcu_usb_switch; +- + enum fan_type fan_type; + enum fan_type gpu_fan_type; + enum fan_type mid_fan_type; +@@ -4708,8 +4700,6 @@ + asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); + asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); +- asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) +- && dmi_check_system(asus_ally_mcu_quirk); + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) + asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; +@@ -4892,34 +4868,6 @@ static int asus_hotk_resume(struct device *device) + return 0; + } + +-static int asus_hotk_resume_early(struct device *device) +-{ +- struct asus_wmi *asus = dev_get_drvdata(device); +- +- if (asus->ally_mcu_usb_switch) { +- /* sleep required to prevent USB0 being yanked then reappearing rapidly */ +- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8))) +- dev_err(device, "ROG Ally MCU failed to connect USB dev\n"); +- else +- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); +- } +- return 0; +-} +- +-static int asus_hotk_prepare(struct device *device) +-{ +- struct asus_wmi *asus = dev_get_drvdata(device); +- +- if (asus->ally_mcu_usb_switch) { +- /* sleep required to ensure USB0 is disabled before sleep continues */ +- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7))) +- dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n"); +- else +- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT); +- } +- return 0; +-} +- + static int asus_hotk_restore(struct device *device) + { + struct asus_wmi *asus = dev_get_drvdata(device); +@@ -4964,8 +4912,6 @@ static const struct dev_pm_ops asus_pm_ops = { + .thaw = asus_hotk_thaw, + .restore = asus_hotk_restore, + .resume = asus_hotk_resume, +- .resume_early = asus_hotk_resume_early, +- .prepare = asus_hotk_prepare, + }; + + /* Registration ***************************************************************/ +-- +2.46.2 + + +From 2d446e1e66be5ae07bb7c243a7c4383b49b1aa3e Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Wed, 25 Sep 2024 14:10:11 +0200 +Subject: [PATCH v3 06/10] acpi/x86: s2idle: add support for Sleep Entry and + Sleep Exit callbacks + +The Sleep Entry and Sleep Exit firmware notifications allow the platform +to enter Modern Standby. In this state, if supported, the platform turns +off auxiliary USB devices (e.g., the controllers of the Legion Go), +makes the power light of the device flash, and lowers the power envelope +to a minimum that still allows for software activity without affecting +battery life. + +Allow for entering this state prior to initiating the suspend sequence. +This fixes issues where the EC or the USB of the device need time to +power down before entering the suspend sequence, and allows for entering +this power state without suspending the device. + +Suggested-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + include/linux/suspend.h | 4 ++++ + kernel/power/suspend.c | 12 ++++++++++++ + 2 files changed, 16 insertions(+) + +diff --git a/include/linux/suspend.h b/include/linux/suspend.h +index d7e2a4d8ab0c..66c5b434334d 100644 +--- a/include/linux/suspend.h ++++ b/include/linux/suspend.h +@@ -133,6 +133,7 @@ struct platform_suspend_ops { + + struct platform_s2idle_ops { + int (*display_off)(void); ++ int (*sleep_entry)(void); + int (*begin)(void); + int (*prepare)(void); + int (*prepare_late)(void); +@@ -141,6 +142,7 @@ struct platform_s2idle_ops { + void (*restore_early)(void); + void (*restore)(void); + void (*end)(void); ++ int (*sleep_exit)(void); + int (*display_on)(void); + }; + +@@ -168,6 +170,8 @@ extern unsigned int pm_suspend_global_flags; + #define PM_SUSPEND_FLAG_NO_PLATFORM BIT(2) + + int platform_suspend_display_off(void); ++int platform_suspend_sleep_entry(void); ++int platform_suspend_sleep_exit(void); + int platform_suspend_display_on(void); + + static inline void pm_suspend_clear_flags(void) +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index af2abdd2f8c3..dab299e28195 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -285,6 +285,18 @@ int platform_suspend_display_off(void) + } + EXPORT_SYMBOL_GPL(platform_suspend_display_off); + ++int platform_suspend_sleep_entry(void) ++{ ++ return s2idle_ops && s2idle_ops->sleep_entry ? s2idle_ops->sleep_entry() : 0; ++} ++EXPORT_SYMBOL_GPL(platform_suspend_sleep_entry); ++ ++int platform_suspend_sleep_exit(void) ++{ ++ return s2idle_ops && s2idle_ops->sleep_exit ? s2idle_ops->sleep_exit() : 0; ++} ++EXPORT_SYMBOL_GPL(platform_suspend_sleep_exit); ++ + int platform_suspend_display_on(void) + { + return s2idle_ops && s2idle_ops->display_on ? s2idle_ops->display_on() : 0; +-- +2.46.2 + + +From ec33ea0c341d5b75a090910664c572d8d4793bbc Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Wed, 25 Sep 2024 14:19:00 +0200 +Subject: [PATCH v3 07/10] acpi/x86: s2idle: handle Sleep Entry/Exit calls + outside of suspend sequence + +As with Display On/Off, these calls should be made outside the suspend +sequence, to allow the EC and USB devices that are affected to complete +their power off sequence before the kernel suspends their power rails +and interrupts. + +Suggested-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + kernel/power/suspend.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index dab299e28195..9dcdd5273318 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -547,6 +547,13 @@ int suspend_devices_and_enter(suspend_state_t state) + if (state == PM_SUSPEND_TO_IDLE) + pm_set_suspend_no_platform(); + ++ /* ++ * Linux does not have the concept of a "Sleep" state. As with Display ++ * On/Off, call the platform functions for Sleep Entry/Exit prior to the ++ * suspend sequence. ++ */ ++ platform_suspend_sleep_entry(); ++ + error = platform_suspend_begin(state); + if (error) + goto Close; +@@ -577,6 +584,8 @@ int suspend_devices_and_enter(suspend_state_t state) + Close: + platform_resume_end(state); + pm_suspend_target_state = PM_SUSPEND_ON; ++ ++ platform_suspend_sleep_exit(); + return error; + + Recover_platform: +-- +2.46.2 + + +From 0825b095f26cbea829078f70d4c757ff9b2eab38 Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Wed, 25 Sep 2024 14:29:42 +0200 +Subject: [PATCH v3 08/10] acpi/x86: s2idle: update quirk table for Sleep + Entry/Exit + +Add delays between the Sleep Entry and Sleep Exit calls, to avoid issues +in devices that rely on them that need time to power off. + +Especially for the ROG Ally, this should allow its EC to suspend gracefully, +avoiding issues where it is stuck in its suspend state. Since the delays +are additive, steal some of the delay from Display On/Off. + +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + include/linux/suspend.h | 2 ++ + kernel/power/suspend.c | 21 +++++++++++++++++++-- + 2 files changed, 21 insertions(+), 2 deletions(-) + +diff --git a/include/linux/suspend.h b/include/linux/suspend.h +index 66c5b434334d..5b4d4d9ef65a 100644 +--- a/include/linux/suspend.h ++++ b/include/linux/suspend.h +@@ -148,6 +148,8 @@ struct platform_s2idle_ops { + + struct platform_s2idle_quirks { + int delay_display_off; ++ int delay_sleep_entry; ++ int delay_sleep_exit; + int delay_display_on; + }; + +diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c +index 9dcdd5273318..1352c4066822 100644 +--- a/kernel/power/suspend.c ++++ b/kernel/power/suspend.c +@@ -65,8 +65,11 @@ static DEFINE_RAW_SPINLOCK(s2idle_lock); + // The ROG Ally series disconnects its controllers on Display Off, without + // holding a lock, introducing a race condition. Add a delay to allow the + // controller to disconnect cleanly prior to suspend. ++// In addition, the EC of the device rarely (1/20 attempts) may get stuck ++// after suspend in an invalid state, where it mirros Sleep behavior. + static const struct platform_s2idle_quirks rog_ally_quirks = { +- .delay_display_off = 500, ++ .delay_display_off = 200, ++ .delay_sleep_entry = 300, + }; + + static const struct dmi_system_id platform_s2idle_quirks[] = { +@@ -548,11 +551,23 @@ int suspend_devices_and_enter(suspend_state_t state) + pm_set_suspend_no_platform(); + + /* +- * Linux does not have the concept of a "Sleep" state. As with Display ++ * Windows transitions between Modern Standby states slowly, as with ++ * Display On/Off, query the appropriate delays here for Sleep Entry/Exit. ++ */ ++ const struct dmi_system_id *s2idle_sysid = dmi_first_match( ++ platform_s2idle_quirks ++ ); ++ const struct platform_s2idle_quirks *s2idle_quirks = s2idle_sysid ? ++ s2idle_sysid->driver_data : NULL; ++ ++ /* ++ * Linux does not have the concept of a "Sleep" state. As done with Display + * On/Off, call the platform functions for Sleep Entry/Exit prior to the + * suspend sequence. + */ + platform_suspend_sleep_entry(); ++ if (s2idle_quirks && s2idle_quirks->delay_sleep_entry) ++ msleep(s2idle_quirks->delay_sleep_entry); + + error = platform_suspend_begin(state); + if (error) +@@ -585,6 +600,8 @@ int suspend_devices_and_enter(suspend_state_t state) + platform_resume_end(state); + pm_suspend_target_state = PM_SUSPEND_ON; + ++ if (s2idle_quirks && s2idle_quirks->delay_sleep_exit) ++ msleep(s2idle_quirks->delay_sleep_exit); + platform_suspend_sleep_exit(); + return error; + +-- +2.46.2 + + +From 49cafd9d1cf20250e31f34a849c505d205968ef5 Mon Sep 17 00:00:00 2001 +From: Antheas Kapenekakis <lkml@antheas.dev> +Date: Wed, 25 Sep 2024 14:41:03 +0200 +Subject: [PATCH v3 09/10] acpi/x86: s2idle: call Sleep Entry/Exit as part of + callbacks. + +Move the Sleep Entry/Exit notifications outside the suspend sequence, +with their own ACPI lock, as was done for Display On/Off. + +Suggested-by: Mario Limonciello <mario.limonciello@amd.com> +Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev> +--- + drivers/acpi/x86/s2idle.c | 69 ++++++++++++++++++++++++++++++--------- + 1 file changed, 53 insertions(+), 16 deletions(-) + +diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c +index a17e28b91326..6ff5e34c016e 100644 +--- a/drivers/acpi/x86/s2idle.c ++++ b/drivers/acpi/x86/s2idle.c +@@ -43,8 +43,8 @@ static const struct acpi_device_id lps0_device_ids[] = { + #define ACPI_LPS0_DISPLAY_ON 4 + #define ACPI_LPS0_ENTRY 5 + #define ACPI_LPS0_EXIT 6 +-#define ACPI_LPS0_MS_ENTRY 7 +-#define ACPI_LPS0_MS_EXIT 8 ++#define ACPI_LPS0_SLEEP_ENTRY 7 ++#define ACPI_LPS0_SLEEP_EXIT 8 + + /* AMD */ + #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" +@@ -61,6 +61,7 @@ static guid_t lps0_dsm_guid_microsoft; + static int lps0_dsm_func_mask_microsoft; + static int lps0_dsm_state; + static bool lsp0_dsm_in_display_off; ++static bool lsp0_dsm_in_sleep; + + /* Device constraint entry structure */ + struct lpi_device_info { +@@ -370,10 +371,10 @@ static const char *acpi_sleep_dsm_state_to_str(unsigned int state) + return "lps0 entry"; + case ACPI_LPS0_EXIT: + return "lps0 exit"; +- case ACPI_LPS0_MS_ENTRY: +- return "lps0 ms entry"; +- case ACPI_LPS0_MS_EXIT: +- return "lps0 ms exit"; ++ case ACPI_LPS0_SLEEP_ENTRY: ++ return "lps0 sleep entry"; ++ case ACPI_LPS0_SLEEP_EXIT: ++ return "lps0 sleep exit"; + } + } else { + switch (state) { +@@ -567,6 +568,48 @@ static int acpi_s2idle_display_off(void) + return 0; + } + ++static int acpi_s2idle_sleep_entry(void) ++{ ++ if (!lps0_device_handle || sleep_no_lps0 || lps0_dsm_func_mask_microsoft <= 0) ++ return 0; ++ ++ if (WARN_ON(lsp0_dsm_in_sleep)) ++ return -EINVAL; ++ ++ lsp0_dsm_in_sleep = true; ++ acpi_scan_lock_acquire(); ++ ++ /* Modern Standby Sleep Entry */ ++ if (lps0_dsm_func_mask_microsoft > 0) ++ acpi_sleep_run_lps0_dsm(ACPI_LPS0_SLEEP_ENTRY, ++ lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); ++ ++ acpi_scan_lock_release(); ++ ++ return 0; ++} ++ ++static int acpi_s2idle_sleep_exit(void) ++{ ++ if (!lps0_device_handle || sleep_no_lps0 || lps0_dsm_func_mask_microsoft <= 0) ++ return 0; ++ ++ if (WARN_ON(!lsp0_dsm_in_sleep)) ++ return -EINVAL; ++ ++ lsp0_dsm_in_sleep = false; ++ acpi_scan_lock_acquire(); ++ ++ /* Modern Standby Sleep Exit */ ++ if (lps0_dsm_func_mask_microsoft > 0) ++ acpi_sleep_run_lps0_dsm(ACPI_LPS0_SLEEP_EXIT, ++ lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); ++ ++ acpi_scan_lock_release(); ++ ++ return 0; ++} ++ + static int acpi_s2idle_display_on(void) + { + if (!lps0_device_handle || sleep_no_lps0) +@@ -608,13 +651,9 @@ int acpi_s2idle_prepare_late(void) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, + lps0_dsm_func_mask, lps0_dsm_guid); + +- if (lps0_dsm_func_mask_microsoft > 0) { +- /* Modern Standby entry */ +- acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, +- lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); ++ if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); +- } + + if (lps0_dsm_func_mask > 0 && !acpi_s2idle_vendor_amd()) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, +@@ -659,17 +698,14 @@ void acpi_s2idle_restore_early(void) + ACPI_LPS0_EXIT, + lps0_dsm_func_mask, lps0_dsm_guid); + +- if (lps0_dsm_func_mask_microsoft > 0) { ++ if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); +- /* Modern Standby exit */ +- acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, +- lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); +- } + } + + static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { + .display_off = acpi_s2idle_display_off, ++ .sleep_entry = acpi_s2idle_sleep_entry, + .begin = acpi_s2idle_begin, + .prepare = acpi_s2idle_prepare, + .prepare_late = acpi_s2idle_prepare_late, +@@ -678,6 +714,7 @@ static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { + .restore_early = acpi_s2idle_restore_early, + .restore = acpi_s2idle_restore, + .end = acpi_s2idle_end, ++ .sleep_exit = acpi_s2idle_sleep_exit, + .display_on = acpi_s2idle_display_on, + }; + +-- +2.46.2 |