[PATCH v2] regulator: core: flush deferred disables before suspend
From: Jiale Yao
Date: Tue Oct 27 2026 - 02:20:51 EST
regulator_disable_deferred() can leave hardware access pending while the
system enters suspend. Allowing that work to run during device suspend can
access a regulator after its backing bus has been suspended.
Moving deferred disables to a freezable workqueue is not sufficient.
DAPM uses deferred disables while suspending audio devices, and work queued
after the workqueue is frozen would leave the supply enabled until resume.
Mark the regulator core as suspending in the PM prepare phase. Flush each
regulator's pending disable work before device suspend starts. Process new
deferred-disable requests synchronously until PM completes. Synchronize
the transition with the regulator lock so a request racing with prepare
is either flushed or handled synchronously.
Fixes: da07ecd93b19 ("regulator: Implement deferred disable support")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Jiale Yao <yaojiale02@xxxxxxx>
---
drivers/regulator/core.c | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 14422cfe80e2..160c588fbdd3 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -44,6 +44,7 @@ static LIST_HEAD(regulator_ena_gpio_list);
static LIST_HEAD(regulator_supply_alias_list);
static LIST_HEAD(regulator_coupler_list);
static bool has_full_constraints;
+static bool regulator_suspending;
static const struct bus_type regulator_bus;
@@ -3463,10 +3464,15 @@ int regulator_disable_deferred(struct regulator *regulator, int ms)
{
struct regulator_dev *rdev = regulator->rdev;
- if (!ms)
+ if (!ms || READ_ONCE(regulator_suspending))
return regulator_disable(regulator);
regulator_lock(rdev);
+ if (unlikely(READ_ONCE(regulator_suspending))) {
+ regulator_unlock(rdev);
+ return regulator_disable(regulator);
+ }
+
regulator->deferred_disables++;
mod_delayed_work(system_power_efficient_wq, &rdev->disable_work,
msecs_to_jiffies(ms));
@@ -6327,6 +6333,24 @@ void regulator_unregister(struct regulator_dev *rdev)
EXPORT_SYMBOL_GPL(regulator_unregister);
#ifdef CONFIG_SUSPEND
+static int regulator_prepare(struct device *dev)
+{
+ struct regulator_dev *rdev = dev_to_rdev(dev);
+
+ regulator_lock(rdev);
+ WRITE_ONCE(regulator_suspending, true);
+ regulator_unlock(rdev);
+
+ flush_delayed_work(&rdev->disable_work);
+
+ return 0;
+}
+
+static void regulator_complete(struct device *dev)
+{
+ WRITE_ONCE(regulator_suspending, false);
+}
+
/**
* regulator_suspend - prepare regulators for system wide suspend
* @dev: ``&struct device`` pointer that is passed to _regulator_suspend()
@@ -6380,6 +6404,8 @@ static int regulator_resume(struct device *dev)
}
#else /* !CONFIG_SUSPEND */
+#define regulator_prepare NULL
+#define regulator_complete NULL
#define regulator_suspend NULL
#define regulator_resume NULL
@@ -6387,6 +6413,8 @@ static int regulator_resume(struct device *dev)
#ifdef CONFIG_PM
static const struct dev_pm_ops __maybe_unused regulator_pm_ops = {
+ .prepare = regulator_prepare,
+ .complete = regulator_complete,
.suspend = regulator_suspend,
.resume = regulator_resume,
};
--
2.34.1