]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
firmware: arm_scmi: power_control: Ensure SCMI_SYSPOWER_IDLE is set early during...
authorPeng Fan <peng.fan@nxp.com>
Fri, 4 Jul 2025 03:09:36 +0000 (11:09 +0800)
committerSudeep Holla <sudeep.holla@arm.com>
Mon, 7 Jul 2025 15:53:16 +0000 (16:53 +0100)
Fix a race condition where a second suspend notification from another
SCMI agent wakes the system before SCMI_SYSPOWER_IDLE is set, leading
to ignored suspend requests. This is due to interrupts triggering early
execution of `scmi_userspace_notifier()` before the SCMI state is updated.

To resolve this, set SCMI_SYSPOWER_IDLE earlier in the device resume
path, prior to `thaw_processes()`. This ensures the SCMI state is
correct when the notifier runs, allowing the system to suspend again
as expected.

On some platforms using SCMI, SCP cannot distinguish between CPU idle
and suspend since both result in cluster power-off. By explicitly setting
the idle state early, the Linux SCMI agent can correctly re-suspend in
response to external notifications.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
Message-Id: <20250704-scmi-pm-v2-2-9316cec2f9cc@nxp.com>
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
drivers/firmware/arm_scmi/scmi_power_control.c

index 21f467a92942883be66074c37c2cab08c3e8a5cc..ab0cee0d4beca1cf6a146b339ce1b462bc91ff54 100644 (file)
@@ -46,6 +46,7 @@
 #include <linux/math.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pm.h>
 #include <linux/printk.h>
 #include <linux/reboot.h>
 #include <linux/scmi_protocol.h>
@@ -324,12 +325,7 @@ static int scmi_userspace_notifier(struct notifier_block *nb,
 
 static void scmi_suspend_work_func(struct work_struct *work)
 {
-       struct scmi_syspower_conf *sc =
-               container_of(work, struct scmi_syspower_conf, suspend_work);
-
        pm_suspend(PM_SUSPEND_MEM);
-
-       sc->state = SCMI_SYSPOWER_IDLE;
 }
 
 static int scmi_syspower_probe(struct scmi_device *sdev)
@@ -354,6 +350,7 @@ static int scmi_syspower_probe(struct scmi_device *sdev)
        sc->required_transition = SCMI_SYSTEM_MAX;
        sc->userspace_nb.notifier_call = &scmi_userspace_notifier;
        sc->dev = &sdev->dev;
+       dev_set_drvdata(&sdev->dev, sc);
 
        INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);
 
@@ -363,6 +360,18 @@ static int scmi_syspower_probe(struct scmi_device *sdev)
                                                       NULL, &sc->userspace_nb);
 }
 
+static int scmi_system_power_resume(struct device *dev)
+{
+       struct scmi_syspower_conf *sc = dev_get_drvdata(dev);
+
+       sc->state = SCMI_SYSPOWER_IDLE;
+       return 0;
+}
+
+static const struct dev_pm_ops scmi_system_power_pmops = {
+       SET_SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)
+};
+
 static const struct scmi_device_id scmi_id_table[] = {
        { SCMI_PROTOCOL_SYSTEM, "syspower" },
        { },
@@ -370,6 +379,9 @@ static const struct scmi_device_id scmi_id_table[] = {
 MODULE_DEVICE_TABLE(scmi, scmi_id_table);
 
 static struct scmi_driver scmi_system_power_driver = {
+       .driver = {
+               .pm = &scmi_system_power_pmops,
+       },
        .name = "scmi-system-power",
        .probe = scmi_syspower_probe,
        .id_table = scmi_id_table,