[PATCH RFC 1/3] clk: Add a way to restore the clocks of a provider after a power cycle

From: Abel Vesa

Date: Wed Oct 28 2026 - 05:54:26 EST


When a hibernation image is restored, the hardware has been through a
power cycle and a boot kernel, while the clock framework still has the
rates, parents and enable state the consumers asked for before the image
was created. The consumers, when resumed, ask for the same rates again,
which the framework sees as no change and never programs, so the clocks
the boot kernel did not touch keep whatever the boot firmware left in
them.

The existing clk_restore_context() depends on every clock implementing
the restore_context operation and acts on all clocks at once, which does
not fit a provider that can only touch its own registers once its power
domain is back.

Add clk_restore_context_dev(), which re-applies the cached parent, rate
and prepare/enable state to the hardware of the clocks registered by the
given device, parents before children. It is meant for the hibernation
restore callback of a provider, so it is declared in clk-provider.h.

The restore_context operation is not used, as it relies on a previous
save_context, which only clk_save_context() does. Clocks left enabled
by the firmware but not by the framework stay enabled, and the phase
and duty cycle are not restored.

Signed-off-by: Abel Vesa <abel.vesa@xxxxxxxxxxxxxxxx>
---
drivers/clk/clk.c | 68 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/clk-provider.h | 2 ++
2 files changed, 70 insertions(+)

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 4d2cc86bfbeb..316c3c85f247 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -1449,6 +1449,74 @@ void clk_restore_context(void)
}
EXPORT_SYMBOL_GPL(clk_restore_context);

+static int clk_fetch_parent_index(struct clk_core *core,
+ struct clk_core *parent);
+
+/* Program the cached parent, rate and prepare/enable state into the hardware */
+static void clk_core_restore_state(struct clk_core *core)
+{
+ struct clk_core *parent = core->parent;
+ unsigned long flags;
+ int index;
+
+ lockdep_assert_held(&prepare_lock);
+
+ if (parent && core->num_parents > 1 && core->ops->set_parent) {
+ index = clk_fetch_parent_index(core, parent);
+ if (index >= 0)
+ core->ops->set_parent(core->hw, index);
+ }
+
+ if (core->rate && core->ops->set_rate)
+ core->ops->set_rate(core->hw, core->rate,
+ parent ? parent->rate : 0);
+
+ if (core->prepare_count && core->ops->prepare)
+ core->ops->prepare(core->hw);
+
+ if (core->enable_count && core->ops->enable) {
+ flags = clk_enable_lock();
+ core->ops->enable(core->hw);
+ clk_enable_unlock(flags);
+ }
+}
+
+static void clk_core_restore_context_dev(struct clk_core *core,
+ struct device *dev)
+{
+ struct clk_core *child;
+
+ if (core->dev == dev)
+ clk_core_restore_state(core);
+
+ hlist_for_each_entry(child, &core->children, child_node)
+ clk_core_restore_context_dev(child, dev);
+}
+
+/**
+ * clk_restore_context_dev - restore the clocks of a provider after poweroff
+ * @dev: the device that registered the clocks
+ *
+ * Program the cached state of the clocks registered by @dev back into the
+ * hardware, parents before children, after a power cycle the framework does
+ * not know about, such as a hibernation restore.
+ */
+void clk_restore_context_dev(struct device *dev)
+{
+ struct clk_core *core;
+
+ clk_prepare_lock();
+
+ hlist_for_each_entry(core, &clk_root_list, child_node)
+ clk_core_restore_context_dev(core, dev);
+
+ hlist_for_each_entry(core, &clk_orphan_list, child_node)
+ clk_core_restore_context_dev(core, dev);
+
+ clk_prepare_unlock();
+}
+EXPORT_SYMBOL_GPL(clk_restore_context_dev);
+
/**
* clk_enable - ungate a clock
* @clk: the clk being ungated
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a355d1ee5189..c3a095e8da5a 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -1439,6 +1439,8 @@ void clk_unregister(struct clk *clk);

void clk_hw_unregister(struct clk_hw *hw);

+void clk_restore_context_dev(struct device *dev);
+
/* helper functions */
const char *__clk_get_name(const struct clk *clk);
const char *clk_hw_get_name(const struct clk_hw *hw);

--
2.54.0