[PATCH v2 1/9] clk: renesas: rzg2l: Add DSI divider clock support for RZ/G3L

Biju <[email protected]>
Newsgroups org.kernel.vger.linux-renesas-soc,org.kernel.vger.linux-clk,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Biju Das <[email protected]>

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 <[email protected]>
---
v1->v2:
 * Introduced the macro DIV_DSI_{A,B}_SET and used inside 
   rzg3l_cpg_dsi_div_recalc_rate() and rzg3l_cpg_dsi_div_set_rate()
 * Simplified the divider to "(div_b + 1) << div_a" in
   rzg3l_cpg_dsi_div_recalc_rate().
 * Replaced the data type u32->unsigned int for the variable divider in
   rzg3l_cpg_dsi_div_determine_rate()
 * Moved the declarations of the loop counters inside the for()-statements
 * Updated the comment sections in rzg3l_cpg_dsi_div_determine_rate().
 * Updated loop logic in rzg3l_cpg_dsi_div_determine_rate().
---
 drivers/clk/renesas/rzg2l-cpg.c | 141 ++++++++++++++++++++++++++++++++
 drivers/clk/renesas/rzg2l-cpg.h |   5 ++
 2 files changed, 146 insertions(+)

diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
index 975b705d3a2b..1660508d523a 100644
--- a/drivers/clk/renesas/rzg2l-cpg.c
+++ b/drivers/clk/renesas/rzg2l-cpg.c
@@ -67,6 +67,12 @@
 #define CPG_PLL_MON_LOCK		BIT(4)
 #define CPG_PLL_MON_RESETB		BIT(0)
 
+#define DIV_DSI_A_SET    GENMASK(2, 0)
+#define DIV_DSI_B_SET    GENMASK(7, 4)
+
+#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)
@@ -833,6 +839,138 @@ 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(DIV_DSI_A_SET, val);
+	div_b = FIELD_GET(DIV_DSI_B_SET, val);
+
+	return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, (div_b + 1) << div_a);
+}
+
+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;
+	unsigned int divider = dsi_div_ab_desired;
+	bool divider_found = false;
+
+	if (dsi_div_target) {
+		/* Calculate DIV_DSI_A and DIV_DSI_B */
+		for (unsigned int div_a = 0; div_a <= 5 && !divider_found; div_a++) {
+			unsigned int div_a_tmp = 5 - div_a;
+
+			for (unsigned int div_b = 0; div_b < 16; div_b++) {
+				divider = (div_b + 1) << div_a_tmp;
+				if (divider == dsi_div_ab_desired) {
+					dsi_div->div_a = div_a_tmp;
+					dsi_div->div_b = div_b;
+					divider_found = true;
+					break;
+				}
+			}
+		}
+	} else {
+		dsi_div->div_b = 0;
+		/* Calculate DIV_DSI_A */
+		for (unsigned int div_a = 0; div_a <= 5 && !divider_found; div_a++) {
+			unsigned int div_a_tmp = 5 - div_a;
+
+			divider = 1 << div_a_tmp;
+			if (divider == dsi_div_ab_desired) {
+				dsi_div->div_a = div_a_tmp;
+				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 |
+	       FIELD_PREP(DIV_DSI_A_SET, dsi_div->div_a) |
+	       FIELD_PREP(DIV_DSI_B_SET, dsi_div->div_b),
+	       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;
@@ -1346,6 +1484,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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.