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

Biju Das <[email protected]> Mon, 27 Jul 2026 13:02:42 +0000
Newsgroups org.kernel.vger.linux-renesas-soc,org.kernel.vger.linux-clk,org.kernel.vger.linux-kernel
Message-ID <TY3PR01MB113466C3E16F634C00C7AFDAD86CC2@TY3PR01MB11346.jpnprd01.prod.outlook.com>
Hi Geert,

Thanks for the feedback.

> -----Original Message-----
> From: Geert Uytterhoeven <[email protected]>
> Sent: 10 July 2026 16:33
> Subject: Re: [PATCH 1/6] clk: renesas: rzg2l: Add DSI divider clock support for RZ/G3L
> 
> Hi Biju,
> 
> On Fri, 19 Jun 2026 at 18:40, Biju <[email protected]> wrote:
> > 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]>
> 
> Thanks for your patch!
> 
> > --- a/drivers/clk/renesas/rzg2l-cpg.c
> > +++ b/drivers/clk/renesas/rzg2l-cpg.c
> 
> > @@ -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);
> 
> Please add and use
> 
>     #define DIV_DSI_A_SET    GENMASK(2, 0)
>     #define DIV_DSI_B_SET    GENMASK(7, 4)

OK.

> 
> (after having seen G3L_SDIV_DSI_C_SET, I kept on looking for  similar  A_SET and B_SET use).

The DSI divider values is proportional to (BPP * 2/ Number of lanes)

VCLK = PLLCLK / DSI_DIV -- (1)
VCLK * bpp <= HSCLK * 8 * num_lanes
VCLK = HSCLK * 8 * num_lanes / bpp -- (2)
PLLCLK / DSI_DIV = HSCLK * 8 * num_lanes / bpp

PLLCLK = HSCLK * 16 -- (3)

DSI_DIV = bpp * 16 / (8 * num_lanes) = bpp * 2 / num_lanes --(4)

Maximum DSI_DIV = 48 (bpp=24, num_lanes=1)

We need a custom divider clk to calculate the divider values

That is the reason for not having G3L_SDIV_DSI_{A,B}_SET

> 
> > +
> > +       return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, (1 << div_a) *
> > + (div_b + 1));
> 
> You can simplify the divider to "(div_b + 1) << div_a".

OK.

> 
> > +}
> > +
> > +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;
> 
> unsigned int

OK.

> 
> > +       bool divider_found = false;
> > +       unsigned int div_a, div_b;
> 
> Perhaps move the declarations of the loop counters inside the for()-statements?

OK.

> 
> > +
> > +       if (dsi_div_target) {
> > +               /* Calculate the DIV_DSI_A and DIV_DSI_B */
> 
> Please drop "the".

Agreed.

> 
> > +               for (div_a = 5; div_a >= 0 && !divider_found; div_a--)
> > + {
> 
> div_a is unsigned, so >= is always true

OK, will change the logic and will use div_a_tmp; 

for (div_a = 0; div_a <= 5 && !divider_found; div_a++)
	uisigned int div_a_tmp = 5 - div_a;

> 
> > +                       for (div_b = 0; div_b < 16; div_b++) {
> > +                               divider = (1 << div_a) * (div_b + 1);
> 
> (div_b + 1) << div_a

Agreed.

> 
> > +                               if (divider == dsi_div_ab_desired) {
> 
> divider is 1..512, while dsi_div_ab_desired is only u8.

The maximum value of dsi_div_ab_desired = 48, so u8 is sufficient.

> 
> > +                                       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 */
> 
> Please drop "the"

OK.

> 
> > +               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),
> 
> FIELD_PREP(DIV_DSI_A_SET, dsi_div->div_a) | FIELD_PREP(DIV_DSI_B_SET, dsi_div->div_b)

Agreed.

Will fix the above in next version.

Cheers,
Biju