[PATCH] PM: Use IRQ safety to handle sleepable PM clock operations

From: Loic Poulain

Date: Fri Oct 30 2026 - 12:10:07 EST


The PM clock runtime PM handling currently performs unprepare/prepare
in suspend/resume operations only for clocks that exclusively implement
prepare/unprepare operations.

However, this logic only considers the clock itself and ignores its
parent hierarchy. As a result, a clock that uses enable/disable
operations but has an ancestor that relies solely on prepare/unprepare
may leave permanent prepare references on both itself and its parents.
This prevents those ancestor clocks from being fully disabled.

For example, on Qualcomm platforms, this can keep the RPM XO clock
prepared (and so active) during runtime or system suspend even after
its consumers have been disabled.

Use the device's IRQ-safety setting to determine whether PM clock
operations may sleep. For usual non-IRQ-safe devices, defer clock
preparation and mark the clock operations as sleepable. Suspend can
then use clk_disable_unprepare() to release the clock's prepare and
enable references.

This changes behavior for all pm_clk users. Preparing and unpreparing
clocks across suspend and resume is generally expected, devices that
require PM clock operations to run in atomic context remain IRQ-safe.

Reported-by: Nihal Kumar Gupta <nihal.gupta@xxxxxxxxxxxxxxxx>
Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
---
drivers/base/power/clock_ops.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c
index 3b8ba27802a800bac29abd8e21756ccdb0f2a716..b919a1d08cf4611067e626dd1131be870200838b 100644
--- a/drivers/base/power/clock_ops.c
+++ b/drivers/base/power/clock_ops.c
@@ -33,7 +33,7 @@ struct pm_clock_entry {
char *con_id;
struct clk *clk;
enum pce_status status;
- bool enabled_when_prepared;
+ bool sleepable;
};

/**
@@ -177,10 +177,10 @@ static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce)
if (IS_ERR(ce->clk)) {
ce->status = PCE_STATUS_ERROR;
return;
- } else if (clk_is_enabled_when_prepared(ce->clk)) {
+ } else if (!dev->power.irq_safe) {
/* we defer preparing the clock in that case */
ce->status = PCE_STATUS_ACQUIRED;
- ce->enabled_when_prepared = true;
+ ce->sleepable = true;
} else if (clk_prepare(ce->clk)) {
ce->status = PCE_STATUS_ERROR;
dev_err(dev, "clk_prepare() failed\n");
@@ -223,7 +223,7 @@ static int __pm_clk_add(struct device *dev, const char *con_id,

pm_clk_list_lock(psd);
list_add_tail(&ce->node, &psd->clock_list);
- if (ce->enabled_when_prepared)
+ if (ce->sleepable)
psd->clock_op_might_sleep++;
pm_clk_list_unlock(psd);
return 0;
@@ -371,7 +371,7 @@ void pm_clk_remove_clk(struct device *dev, struct clk *clk)

remove:
list_del(&ce->node);
- if (ce->enabled_when_prepared)
+ if (ce->sleepable)
psd->clock_op_might_sleep--;
pm_clk_list_unlock(psd);

@@ -485,7 +485,7 @@ int pm_clk_suspend(struct device *dev)

list_for_each_entry_reverse(ce, &psd->clock_list, node) {
if (ce->status == PCE_STATUS_ENABLED) {
- if (ce->enabled_when_prepared) {
+ if (ce->sleepable) {
clk_disable_unprepare(ce->clk);
ce->status = PCE_STATUS_ACQUIRED;
} else {

---
base-commit: e726cf08ee6ba7e8b852c77760f12c59f90f1338
change-id: 20261030-clk-leak-3ea6f7c7da25

Best regards,
--
Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>