[PATCH 1/6] clk: renesas: rzg2l: Add DSI divider clock support for RZ/G3L
From: Biju
Date: Fri Jun 19 2026 - 12:45:44 EST
From: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>
Add a new DSI divider clock type (CLK_TYPE_G3L_PLLDSI_DIV) for the RZ/G3L
SoC, which requires a different divider implementation than the existing
RZ/G2L DSI divider clock.
The RZ/G3L DSI divider uses two cascaded dividers, DIV_DSI_A and
DIV_DSI_B, where the effective divider is:
rate = parent_rate / ((1 << div_a) * (div_b + 1))
DIV_DSI_A is a power-of-two divider with values in the range [0, 5],
and DIV_DSI_B is a linear divider with values in the range [1, 16].
Introduce the g3l_dsi_div_hw_data structure, rzg3l_cpg_dsi_div_ops, and
rzg3l_cpg_dsi_div_clk_register() to implement the new clock type, and add
the DEF_G3L_PLLDSI_DIV() macro for use in clock table definitions.
Signed-off-by: Biju Das <biju.das.jz@xxxxxxxxxxxxxx>
---
drivers/clk/renesas/rzg2l-cpg.c | 134 ++++++++++++++++++++++++++++++++
drivers/clk/renesas/rzg2l-cpg.h | 5 ++
2 files changed, 139 insertions(+)
diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
index 51c9e19e1575..a60b1d99458e 100644
--- a/drivers/clk/renesas/rzg2l-cpg.c
+++ b/drivers/clk/renesas/rzg2l-cpg.c
@@ -68,6 +68,9 @@
#define CPG_PLL_MON_LOCK BIT(4)
#define CPG_PLL_MON_RESETB BIT(0)
+#define RZG3L_SDIV_DIV_DSI_A_WEN BIT(16)
+#define RZG3L_SDIV_DIV_DSI_B_WEN BIT(20)
+
#define CLK_ON_R(reg) (reg)
#define CLK_MON_R(reg) (0x180 + (reg))
#define CLK_RST_R(reg) (reg)
@@ -834,6 +837,134 @@ rzg2l_cpg_dsi_div_clk_register(const struct cpg_core_clk *core,
return clk_hw->clk;
}
+struct g3l_dsi_div_hw_data {
+ struct clk_hw hw;
+ struct rzg2l_cpg_priv *priv;
+ unsigned long rate;
+ u32 off;
+ u8 div_a;
+ u8 div_b;
+};
+
+#define to_g3l_dsi_div_hw_data(_hw) container_of(_hw, struct g3l_dsi_div_hw_data, hw)
+
+static unsigned long rzg3l_cpg_dsi_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct g3l_dsi_div_hw_data *dsi_div = to_g3l_dsi_div_hw_data(hw);
+ struct rzg2l_cpg_priv *priv = dsi_div->priv;
+ int div_a, div_b, val;
+
+ val = readl(priv->base + dsi_div->off);
+ div_a = FIELD_GET(GENMASK(2, 0), val);
+ div_b = FIELD_GET(GENMASK(7, 4), val);
+
+ return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, (1 << div_a) * (div_b + 1));
+}
+
+static int rzg3l_cpg_dsi_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct g3l_dsi_div_hw_data *dsi_div = to_g3l_dsi_div_hw_data(hw);
+ struct rzg2l_cpg_priv *priv = dsi_div->priv;
+ u32 divider = dsi_div_ab_desired;
+ bool divider_found = false;
+ unsigned int div_a, div_b;
+
+ if (dsi_div_target) {
+ /* Calculate the DIV_DSI_A and DIV_DSI_B */
+ for (div_a = 5; div_a >= 0 && !divider_found; div_a--) {
+ for (div_b = 0; div_b < 16; div_b++) {
+ divider = (1 << div_a) * (div_b + 1);
+ if (divider == dsi_div_ab_desired) {
+ dsi_div->div_a = div_a;
+ dsi_div->div_b = div_b;
+ divider_found = true;
+ break;
+ }
+ }
+ }
+ } else {
+ dsi_div->div_b = 0;
+ /* Calculate the DIV_DSI_A */
+ for (div_a = 5; div_a >= 0 && !divider_found; div_a--) {
+ divider = (1 << div_a);
+ if (divider == dsi_div_ab_desired) {
+ dsi_div->div_a = div_a;
+ divider_found = true;
+ break;
+ }
+ }
+ }
+
+ if (!divider_found) {
+ dev_err(priv->dev, "failed dsi div for: %u\n", divider);
+ return -EINVAL;
+ }
+
+ req->best_parent_rate = req->rate * divider;
+
+ return 0;
+}
+
+static int rzg3l_cpg_dsi_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct g3l_dsi_div_hw_data *dsi_div = to_g3l_dsi_div_hw_data(hw);
+ struct rzg2l_cpg_priv *priv = dsi_div->priv;
+
+ writel(RZG3L_SDIV_DIV_DSI_A_WEN | RZG3L_SDIV_DIV_DSI_B_WEN |
+ (dsi_div->div_a << 0) | (dsi_div->div_b << 4),
+ priv->base + dsi_div->off);
+
+ return 0;
+}
+
+static const struct clk_ops rzg3l_cpg_dsi_div_ops = {
+ .recalc_rate = rzg3l_cpg_dsi_div_recalc_rate,
+ .determine_rate = rzg3l_cpg_dsi_div_determine_rate,
+ .set_rate = rzg3l_cpg_dsi_div_set_rate,
+};
+
+static struct clk * __init
+rzg3l_cpg_dsi_div_clk_register(const struct cpg_core_clk *core,
+ struct rzg2l_cpg_priv *priv)
+{
+ struct g3l_dsi_div_hw_data *clk_hw_data;
+ const struct clk *parent;
+ const char *parent_name;
+ struct clk_init_data init;
+ struct clk_hw *clk_hw;
+ int ret;
+
+ parent = priv->clks[core->parent];
+ if (IS_ERR(parent))
+ return ERR_CAST(parent);
+
+ clk_hw_data = devm_kzalloc(priv->dev, sizeof(*clk_hw_data), GFP_KERNEL);
+ if (!clk_hw_data)
+ return ERR_PTR(-ENOMEM);
+
+ clk_hw_data->priv = priv;
+ clk_hw_data->off = core->conf;
+
+ parent_name = __clk_get_name(parent);
+ init.name = core->name;
+ init.ops = &rzg3l_cpg_dsi_div_ops;
+ init.flags = CLK_SET_RATE_PARENT;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ clk_hw = &clk_hw_data->hw;
+ clk_hw->init = &init;
+
+ ret = devm_clk_hw_register(priv->dev, clk_hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return clk_hw->clk;
+}
+
struct pll5_mux_hw_data {
struct clk_hw hw;
u32 conf;
@@ -1347,6 +1478,9 @@ rzg2l_cpg_register_core_clk(const struct cpg_core_clk *core,
case CLK_TYPE_DSI_DIV:
clk = rzg2l_cpg_dsi_div_clk_register(core, priv);
break;
+ case CLK_TYPE_G3L_DSI_DIV:
+ clk = rzg3l_cpg_dsi_div_clk_register(core, priv);
+ break;
default:
goto fail;
}
diff --git a/drivers/clk/renesas/rzg2l-cpg.h b/drivers/clk/renesas/rzg2l-cpg.h
index bd6169f62538..24642a089b6c 100644
--- a/drivers/clk/renesas/rzg2l-cpg.h
+++ b/drivers/clk/renesas/rzg2l-cpg.h
@@ -142,6 +142,8 @@ enum clk_types {
/* Clock for DSI divider */
CLK_TYPE_DSI_DIV,
+ /* Clock for G3L DSI divider */
+ CLK_TYPE_G3L_DSI_DIV,
};
#define DEF_TYPE(_name, _id, _type...) \
@@ -201,6 +203,9 @@ enum clk_types {
.num_parents = ARRAY_SIZE(_parent_names))
#define DEF_DSI_DIV(_name, _id, _parent, _flag) \
DEF_TYPE(_name, _id, CLK_TYPE_DSI_DIV, .parent = _parent, .flag = _flag)
+#define DEF_G3L_DSI_DIV(_name, _id, _parent, _conf) \
+ DEF_TYPE(_name, _id, CLK_TYPE_G3L_DSI_DIV, .parent = _parent, .conf = _conf, \
+ .flag = CLK_SET_RATE_PARENT)
/**
* struct rzg2l_mod_clk - Module Clocks definitions
--
2.43.0