Re: [PATCH v3] ACPI: PM: Introduce CONFIG_ACPI_S2IDLE for platform-independent S2Idle support

From: Yaxiong Tian

Date: Sun Mar 22 2026 - 21:06:41 EST


>The ACPI S2Idle (suspend-to-idle) code is currently located in sleep.c
>and guarded by CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT. This config is
>historically tied to x86/ia64 sleep states (S3/S4), which prevents
>ACPI-based platforms like ARM64 from using the OS-centric s2idle
>framework and its associated wakeup event handling (e.g., from ACPI
>buttons or the lid).
>
>Since S2Idle is a software-driven low-power state that does not rely on
>firmware sleep states (S3/S4), it should be available to all
>ACPI-enabled platforms.
>
>To achieve this, introduce a new, independent Kconfig option,
>CONFIG_ACPI_S2IDLE. It is selected by ACPI_SYSTEM_POWER_STATES_SUPPORT
>for backward compatibility, but can now also be enabled separately by
>other platforms.
>
>Move all s2idle-specific code into a dedicated file,
>drivers/acpi/s2idle.c. This creates a clear and independent ACPI s2idle
>framework, centered around a default acpi_s2idle_ops and a weakly
>defined acpi_s2idle_setup() hook.
>
>The change allows all ACPI platforms to use this common s2idle
>infrastructure. Platforms with unique requirements can override the
>defaults via the weak function, while others (like ARM64) can now simply
>enable the config to obtain functional s2idle support with standard ACPI
>wakeup handling.
>
>Signed-off-by: Riwen Lu <luriwen@xxxxxxxxxx>
>
>---
>v1 -> v2:
> - Fix acpi_s2idle_setup() declaration error when SUSPEND disabled in x86.
>
>v2 -> v3:
> - Select ACPI_S2IDLE if SUSPEND enabled for ACPI_SYSTEM_POWER_STATES_SUPPORT.
>---
> drivers/acpi/Kconfig    |  14 ++++
> drivers/acpi/Makefile   |   2 +
> drivers/acpi/bus.c      |   3 +
> drivers/acpi/internal.h |  10 ++-
> drivers/acpi/s2idle.c   | 167 ++++++++++++++++++++++++++++++++++++++++
> drivers/acpi/sleep.c    | 145 ----------------------------------
> 6 files changed, 194 insertions(+), 147 deletions(-)
> create mode 100644 drivers/acpi/s2idle.c
>
>diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
>index ca00a5dbcf75..fa982b784c57 100644
>--- a/drivers/acpi/Kconfig
>+++ b/drivers/acpi/Kconfig
>@@ -54,6 +54,7 @@ config ACPI_GENERIC_GSI
>
> config ACPI_SYSTEM_POWER_STATES_SUPPORT
>     bool
>+    select ACPI_S2IDLE if SUSPEND
>
> config ACPI_CCA_REQUIRED
>     bool
>@@ -112,6 +113,19 @@ config ACPI_SLEEP
>     depends on ACPI_SYSTEM_POWER_STATES_SUPPORT
>     default y
>
>+config ACPI_S2IDLE
>+    bool "ACPI suspend-to-idle support"
>+    depends on SUSPEND
>+    help
>+      This option enables the core ACPI framework for the suspend-to-idle
>+      (S2Idle) power state. This state is software-driven and does not
>+      require platform firmware sleep states (S3/S4).
>+
>+      It is automatically selected by platforms with
>+      CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT (like x86), but can also
>+      be enabled independently by other ACPI platforms (e.g., ARM64) to
>+      gain support for low-power idle and ACPI wakeup events.
>+
> config ACPI_REV_OVERRIDE_POSSIBLE
>     bool "Allow supported ACPI revision to be overridden"
>     depends on X86
>diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
>index d1b0affb844f..821afc165197 100644
>--- a/drivers/acpi/Makefile
>+++ b/drivers/acpi/Makefile
>@@ -35,6 +35,8 @@ acpi-$(CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT) += sleep.o
> acpi-y                += device_sysfs.o device_pm.o
> acpi-$(CONFIG_ACPI_SLEEP)    += proc.o
>
>+acpi-$(CONFIG_ACPI_S2IDLE)    += s2idle.o
>+
>
> #
> # ACPI Bus and Device Drivers
>diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
>index a984ccd4a2a0..f355fc3c201b 100644
>--- a/drivers/acpi/bus.c
>+++ b/drivers/acpi/bus.c
>@@ -1398,6 +1398,9 @@ static int __init acpi_bus_init(void)
>     /* Initialize sleep structures */
>     acpi_sleep_init();
>
>+    /* Initialize default acpi s2idle ops */
>+    acpi_s2idle_init();
>+
>     /*
>      * Get the system interrupt model and evaluate \_PIC.
>      */
>diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
>index 40f875b265a9..7f314acd7148 100644
>--- a/drivers/acpi/internal.h
>+++ b/drivers/acpi/internal.h
>@@ -267,13 +267,19 @@ static inline bool acpi_ec_dispatch_gpe(void)
>                                   Suspend/Resume
> -------------------------------------------------------------------------- */
> #ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT
>-extern bool acpi_s2idle_wakeup(void);
> extern int acpi_sleep_init(void);
> #else
>-static inline bool acpi_s2idle_wakeup(void) { return false; }
> static inline int acpi_sleep_init(void) { return -ENXIO; }
> #endif
>
>+#ifdef CONFIG_ACPI_S2IDLE
>+extern bool acpi_s2idle_wakeup(void);
>+extern int acpi_s2idle_init(void);
>+#else
>+static inline bool acpi_s2idle_wakeup(void) { return false; }
>+static inline int acpi_s2idle_init(void) { return -ENXIO; }
>+#endif
>+
> #ifdef CONFIG_ACPI_SLEEP
> void acpi_sleep_proc_init(void);
> int suspend_nvs_alloc(void);
>diff --git a/drivers/acpi/s2idle.c b/drivers/acpi/s2idle.c
>new file mode 100644
>index 000000000000..0c783b0e1a9b
>--- /dev/null
>+++ b/drivers/acpi/s2idle.c
>@@ -0,0 +1,167 @@
>+// SPDX-License-Identifier: GPL-2.0
>+/*
>+ * s2idle.c - ACPI suspend-to-idle support.
>+ *
>+ */
>+
>+#include <linux/acpi.h>
>+#include <linux/device.h>
>+#include <linux/suspend.h>
>+#include <linux/irq.h>
>+
>+#include "internal.h"
>+#include "sleep.h"
>+
>+#ifdef CONFIG_SUSPEND
>+static bool s2idle_wakeup;
>+
>+int acpi_s2idle_begin(void)
>+{
>+    acpi_scan_lock_acquire();
>+    return 0;
>+}
>+
>+int acpi_s2idle_prepare(void)
>+{
>+    if (acpi_sci_irq_valid()) {
>+        int error;
>+
>+        error = enable_irq_wake(acpi_sci_irq);
>+        if (error)
>+            pr_warn("Warning: Failed to enable wakeup from IRQ %d: %d\n",
>+                acpi_sci_irq, error);
>+
>+        acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE);
>+    }
>+
>+    acpi_enable_wakeup_devices(ACPI_STATE_S0);
>+
>+    /* Change the configuration of GPEs to avoid spurious wakeup. */
>+    acpi_enable_all_wakeup_gpes();
>+    acpi_os_wait_events_complete();
>+
>+    s2idle_wakeup = true;
>+    return 0;
>+}
>+
>+bool acpi_s2idle_wake(void)
>+{
>+    if (!acpi_sci_irq_valid())
>+        return pm_wakeup_pending();
>+
>+    while (pm_wakeup_pending()) {
>+        /*
>+         * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the
>+         * SCI has not triggered while suspended, so bail out (the
>+         * wakeup is pending anyway and the SCI is not the source of
>+         * it).
>+         */
>+        if (irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
>+            pm_pr_dbg("Wakeup unrelated to ACPI SCI\n");
>+            return true;
>+        }
>+
>+        /*
>+         * If the status bit of any enabled fixed event is set, the
>+         * wakeup is regarded as valid.
>+         */
>+        if (acpi_any_fixed_event_status_set()) {
>+            pm_pr_dbg("ACPI fixed event wakeup\n");
>+            return true;
>+        }
>+
>+        /* Check wakeups from drivers sharing the SCI. */
>+        if (acpi_check_wakeup_handlers()) {
>+            pm_pr_dbg("ACPI custom handler wakeup\n");
>+            return true;
>+        }
>+
>+        /*
>+         * Check non-EC GPE wakeups and if there are none, cancel the
>+         * SCI-related wakeup and dispatch the EC GPE.
>+         */
>+        if (acpi_ec_dispatch_gpe()) {
>+            pm_pr_dbg("ACPI non-EC GPE wakeup\n");
>+            return true;
>+        }
>+
>+        acpi_os_wait_events_complete();
>+
>+        /*
>+         * The SCI is in the "suspended" state now and it cannot produce
>+         * new wakeup events till the rearming below, so if any of them
>+         * are pending here, they must be resulting from the processing
>+         * of EC events above or coming from somewhere else.
>+         */
>+        if (pm_wakeup_pending()) {
>+            pm_pr_dbg("Wakeup after ACPI Notify sync\n");
>+            return true;
>+        }
>+
>+        pm_pr_dbg("Rearming ACPI SCI for wakeup\n");
>+
>+        pm_wakeup_clear(acpi_sci_irq);
>+        rearm_wake_irq(acpi_sci_irq);
>+    }
>+
>+    return false;
>+}
>+
>+void acpi_s2idle_restore(void)
>+{
>+    /*
>+     * Drain pending events before restoring the working-state configuration
>+     * of GPEs.
>+     */
>+    acpi_os_wait_events_complete(); /* synchronize GPE processing */
>+    acpi_ec_flush_work(); /* flush the EC driver's workqueues */
>+    acpi_os_wait_events_complete(); /* synchronize Notify handling */
>+
>+    s2idle_wakeup = false;
>+
>+    acpi_enable_all_runtime_gpes();
>+
>+    acpi_disable_wakeup_devices(ACPI_STATE_S0);
>+
>+    if (acpi_sci_irq_valid()) {
>+        acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);
>+        disable_irq_wake(acpi_sci_irq);
>+    }
>+}
>+
>+void acpi_s2idle_end(void)
>+{
>+    acpi_scan_lock_release();
>+}
>+
>+static const struct platform_s2idle_ops acpi_s2idle_ops = {
>+    .begin = acpi_s2idle_begin,
>+    .prepare = acpi_s2idle_prepare,
>+    .wake = acpi_s2idle_wake,
>+    .restore = acpi_s2idle_restore,
>+    .end = acpi_s2idle_end,
>+};
>+
>+void __weak acpi_s2idle_setup(void)
>+{
>+    if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
>+        pr_info("Efficient low-power S0 idle declared\n");
>+
>+    s2idle_set_ops(&acpi_s2idle_ops);
>+}
>+
>+#else /* !CONFIG_SUSPEND */
>+#define s2idle_wakeup        (false)
>+void acpi_s2idle_setup(void) {}
>+#endif /* !CONFIG_SUSPEND */
>+
>+bool acpi_s2idle_wakeup(void)
>+{
>+    return s2idle_wakeup;
>+}
>+
>+int __init acpi_s2idle_init(void)
>+{
>+    acpi_s2idle_setup();
>+    return 0;
>+}
>diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
>index 66ec81e306d4..3dd8eebd7efd 100644
>--- a/drivers/acpi/sleep.c
>+++ b/drivers/acpi/sleep.c
>@@ -716,143 +716,6 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
>     .recover = acpi_pm_finish,
> };
>
>-static bool s2idle_wakeup;
>-
>-int acpi_s2idle_begin(void)
>-{
>-    acpi_scan_lock_acquire();
>-    return 0;
>-}
>-
>-int acpi_s2idle_prepare(void)
>-{
>-    if (acpi_sci_irq_valid()) {
>-        int error;
>-
>-        error = enable_irq_wake(acpi_sci_irq);
>-        if (error)
>-            pr_warn("Warning: Failed to enable wakeup from IRQ %d: %d\n",
>-                acpi_sci_irq, error);
>-
>-        acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE);
>-    }
>-
>-    acpi_enable_wakeup_devices(ACPI_STATE_S0);
>-
>-    /* Change the configuration of GPEs to avoid spurious wakeup. */
>-    acpi_enable_all_wakeup_gpes();
>-    acpi_os_wait_events_complete();
>-
>-    s2idle_wakeup = true;
>-    return 0;
>-}
>-
>-bool acpi_s2idle_wake(void)
>-{
>-    if (!acpi_sci_irq_valid())
>-        return pm_wakeup_pending();
>-
>-    while (pm_wakeup_pending()) {
>-        /*
>-         * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the
>-         * SCI has not triggered while suspended, so bail out (the
>-         * wakeup is pending anyway and the SCI is not the source of
>-         * it).
>-         */
>-        if (irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
>-            pm_pr_dbg("Wakeup unrelated to ACPI SCI\n");
>-            return true;
>-        }
>-
>-        /*
>-         * If the status bit of any enabled fixed event is set, the
>-         * wakeup is regarded as valid.
>-         */
>-        if (acpi_any_fixed_event_status_set()) {
>-            pm_pr_dbg("ACPI fixed event wakeup\n");
>-            return true;
>-        }
>-
>-        /* Check wakeups from drivers sharing the SCI. */
>-        if (acpi_check_wakeup_handlers()) {
>-            pm_pr_dbg("ACPI custom handler wakeup\n");
>-            return true;
>-        }
>-
>-        /*
>-         * Check non-EC GPE wakeups and if there are none, cancel the
>-         * SCI-related wakeup and dispatch the EC GPE.
>-         */
>-        if (acpi_ec_dispatch_gpe()) {
>-            pm_pr_dbg("ACPI non-EC GPE wakeup\n");
>-            return true;
>-        }
>-
>-        acpi_os_wait_events_complete();
>-
>-        /*
>-         * The SCI is in the "suspended" state now and it cannot produce
>-         * new wakeup events till the rearming below, so if any of them
>-         * are pending here, they must be resulting from the processing
>-         * of EC events above or coming from somewhere else.
>-         */
>-        if (pm_wakeup_pending()) {
>-            pm_pr_dbg("Wakeup after ACPI Notify sync\n");
>-            return true;
>-        }
>-
>-        pm_pr_dbg("Rearming ACPI SCI for wakeup\n");
>-
>-        pm_wakeup_clear(acpi_sci_irq);
>-        rearm_wake_irq(acpi_sci_irq);
>-    }
>-
>-    return false;
>-}
>-
>-void acpi_s2idle_restore(void)
>-{
>-    /*
>-     * Drain pending events before restoring the working-state configuration
>-     * of GPEs.
>-     */
>-    acpi_os_wait_events_complete(); /* synchronize GPE processing */
>-    acpi_ec_flush_work(); /* flush the EC driver's workqueues */
>-    acpi_os_wait_events_complete(); /* synchronize Notify handling */
>-
>-    s2idle_wakeup = false;
>-
>-    acpi_enable_all_runtime_gpes();
>-
>-    acpi_disable_wakeup_devices(ACPI_STATE_S0);
>-
>-    if (acpi_sci_irq_valid()) {
>-        acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);
>-        disable_irq_wake(acpi_sci_irq);
>-    }
>-}
>-
>-void acpi_s2idle_end(void)
>-{
>-    acpi_scan_lock_release();
>-}
>-
>-static const struct platform_s2idle_ops acpi_s2idle_ops = {
>-    .begin = acpi_s2idle_begin,
>-    .prepare = acpi_s2idle_prepare,
>-    .wake = acpi_s2idle_wake,
>-    .restore = acpi_s2idle_restore,
>-    .end = acpi_s2idle_end,
>-};
>-
>-void __weak acpi_s2idle_setup(void)
>-{
>-    if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
>-        pr_info("Efficient low-power S0 idle declared\n");
>-
>-    s2idle_set_ops(&acpi_s2idle_ops);
>-}
>-
> static void __init acpi_sleep_suspend_setup(void)
> {
>     bool suspend_ops_needed = false;
>@@ -867,20 +730,12 @@ static void __init acpi_sleep_suspend_setup(void)
>     if (suspend_ops_needed)
>         suspend_set_ops(old_suspend_ordering ?
>                 &acpi_suspend_ops_old : &acpi_suspend_ops);
>-
>-    acpi_s2idle_setup();
> }
>
> #else /* !CONFIG_SUSPEND */
>-#define s2idle_wakeup        (false)
> static inline void acpi_sleep_suspend_setup(void) {}
> #endif /* !CONFIG_SUSPEND */
>
>-bool acpi_s2idle_wakeup(void)
>-{
>-    return s2idle_wakeup;
>-}
>-
> #ifdef CONFIG_PM_SLEEP
> static u32 saved_bm_rld;
>
>--

>2.25.1

Reviewed-by: Yaxiong Tian  <tianyaxiong@xxxxxxxxxx>