[PATCH 3/3] clk: renesas: rzg2l-cpg: Re-enable critical module clocks during resume
From: Biju
Date: Fri Mar 06 2026 - 08:43:22 EST
From: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>
After a suspend/resume cycle, critical module clocks may be left
disabled as the hardware state is not automatically restored. Unlike
regular clocks which are re-enabled by their respective drivers, critical
clocks (CLK_IS_CRITICAL) have no owning driver to restore them, so the
CPG driver must take responsibility for re-enabling them on resume.
Introduce struct rzg2l_crit_clk_hw to track critical module clock
hardware entries in a singly-linked list anchored at crit_clk_hw_head
in rzg2l_cpg_priv. Populate the list during module clock registration
by checking for the CLK_IS_CRITICAL flag after clk_hw_register()
succeeds.
On resume, walk the list and re-enable any critical module clock that
is found to be disabled, before deassering critical resets, ensuring
the correct clock-before-reset restore ordering.
Signed-off-by: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>
---
drivers/clk/renesas/rzg2l-cpg.c | 42 ++++++++++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
index 8165c163143a..f16c3962e0bd 100644
--- a/drivers/clk/renesas/rzg2l-cpg.c
+++ b/drivers/clk/renesas/rzg2l-cpg.c
@@ -130,6 +130,12 @@ struct div_hw_data {
u32 width;
};
+/* Critical clk list */
+struct rzg2l_crit_clk_hw {
+ struct clk_hw *hw;
+ struct rzg2l_crit_clk_hw *next;
+};
+
#define to_div_hw_data(_hw) container_of(_hw, struct div_hw_data, hw_data)
struct rzg2l_pll5_param {
@@ -168,6 +174,7 @@ struct rzg2l_pll5_mux_dsi_div_param {
* @info: Pointer to platform data
* @genpd: PM domain
* @mux_dsi_div_params: pll5 mux and dsi div parameters
+ * @crit_clk_hw_head: Head of the linked list critical clk entries
*/
struct rzg2l_cpg_priv {
struct reset_controller_dev rcdev;
@@ -186,8 +193,26 @@ struct rzg2l_cpg_priv {
struct generic_pm_domain genpd;
struct rzg2l_pll5_mux_dsi_div_param mux_dsi_div_params;
+
+ struct rzg2l_crit_clk_hw *crit_clk_hw_head;
};
+static int rzg2l_cpg_add_crit_clk_hw_entry(struct rzg2l_cpg_priv *priv,
+ struct clk_hw *hw)
+{
+ struct rzg2l_crit_clk_hw *node;
+
+ node = devm_kzalloc(priv->dev, sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return -ENOMEM;
+
+ node->hw = hw;
+ node->next = priv->crit_clk_hw_head;
+ priv->crit_clk_hw_head = node;
+
+ return 0;
+}
+
static inline u8 rzg2l_cpg_div_ab(u8 a, u8 b)
{
return (b + 1) << a;
@@ -1737,10 +1762,16 @@ rzg2l_cpg_register_mod_clk(const struct rzg2l_mod_clk *mod,
goto fail;
}
+ if (init.flags & CLK_IS_CRITICAL) {
+ if (rzg2l_cpg_add_crit_clk_hw_entry(priv, &clock->hw)) {
+ clk = ERR_PTR(-ENOMEM);
+ goto fail;
+ }
+ }
+
clk = clock->hw.clk;
dev_dbg(dev, "Module clock %pC at %lu Hz\n", clk, clk_get_rate(clk));
priv->clks[id] = clk;
-
return;
fail:
@@ -2086,8 +2117,17 @@ static int __init rzg2l_cpg_probe(struct platform_device *pdev)
static int rzg2l_cpg_resume(struct device *dev)
{
struct rzg2l_cpg_priv *priv = dev_get_drvdata(dev);
+ struct rzg2l_crit_clk_hw *node;
int ret;
+ for (node = priv->crit_clk_hw_head; node; node = node->next) {
+ if (!rzg2l_mod_clock_is_enabled(node->hw)) {
+ ret = rzg2l_mod_clock_endisable(node->hw, true);
+ if (ret)
+ return ret;
+ }
+ }
+
ret = rzg2l_cpg_deassert_crit_resets(&priv->rcdev, priv->info);
if (ret)
return ret;
--
2.43.0