From: Biju Das <biju.das.jz@bp.renesas.com>
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 deasserting critical resets, ensuring the
correct clock-before-reset restore ordering.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* No change
v4:
* Moved this patch from [1] as it is boot-dependent
[1] https://lore.kernel.org/all/20260306134228.871815-1-biju.das.jz@bp.renesas.com/
---
drivers/clk/renesas/rzg2l-cpg.c | 41 +++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
index 8165c163143a..c2d31b93f62b 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,6 +1762,13 @@ 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;
@@ -2086,8 +2118,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
Hi Biju,
On Wed, 18 Mar 2026 at 09:42, Biju <biju.das.au@gmail.com> wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> 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 deasserting critical resets, ensuring the
> correct clock-before-reset restore ordering.
>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
Thanks for your patch!
> --- 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);
This ends up allocating quite some memory to store just a single
clk_hw pointer. Alternatively, you could use an array and size,
and grow that using devm_krealloc().
Another alternative would be saving and restoring all clocks during
suspend/resume, like renesas-cpg-mssr.c does.
Thoughts?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
Hi Biju,
On Wed, 18 Mar 2026 at 15:54, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> On Wed, 18 Mar 2026 at 09:42, Biju <biju.das.au@gmail.com> wrote:
> > From: Biju Das <biju.das.jz@bp.renesas.com>
> >
> > 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 deasserting critical resets, ensuring the
> > correct clock-before-reset restore ordering.
> >
> > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
>
> Thanks for your patch!
>
> > --- 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);
>
> This ends up allocating quite some memory to store just a single
> clk_hw pointer. Alternatively, you could use an array and size,
> and grow that using devm_krealloc().
Upon second thought, you already know how many there are upfront,
thanks to rzg2l_cpg_info.num_crit_mod_clks? You even already have an
array (but it's __initconst).
> Another alternative would be saving and restoring all clocks during
> suspend/resume, like renesas-cpg-mssr.c does.
Another alternative: rzg2l_mod_clock_init_mstop() already iterates
over all module clocks during resume, so it could be modified to also
force-enable all critical module clocks.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
© 2016 - 2026 Red Hat, Inc.