On Tue, 18 Nov 2025 21:27:43 -0500
Chris Brandt <[email protected]> wrote:

> Convert the limited MIPI clock calculations to a full range of settings
> based on math including H/W limitation validation.
> Since the required DSI division setting must be specified from external
> sources before calculations, expose a new API to set it.
> 
> Signed-off-by: Chris Brandt <[email protected]>
> Reviewed-by: Biju Das <[email protected]>
> Tested-by: Biju Das <[email protected]>
> 
> ---
> v1->v2:
> - Remove unnecessary parentheses
> - Add target argument to new API
> - DPI mode has more restrictions on DIV_A and DIV_B
> 
> v2->v3:
> - Removed Empty lines (Hugo)
> - Add dummy for compile-testing CONFIG_CLK_RZG2L=n case (Geert)
> - Renamed label found_dsi_div to calc_pll_clk (Hugo)
> - Renamed label found_clk to clk_valid (Hugo)
> - Removed 'found' var because not needed
> - Move 'foutpostdiv_rate =' after if(foutvco_rate > 1500000000) (Hugo)
> - Move PLL5_TARGET_* for new API to renesas.h (Hugo,Geert)
> - Convert #define macros PLL5_TARGET_* to enum (Geert)
> - static {unsigned} int dsi_div_ab; (Geert)
> - {unsigned} int a, b;  (Geert)
> - Change "((1 << a) * (b + 1))" to "(b + 1) << a"  (Geert)
> - Change "foutvco_rate = rate * (1 << xxx ) * ..." to " = rate * ... * << xxx 
> (Geert)
> - Move (u64) outside of modulo operation to avoid helper on 32-bit compiles 
> (Geert)
> - Change DIV_ROUND_CLOSEST_ULL() to DIV_ROUND_CLOSEST() (Geert)
> - void rzg2l_cpg_dsi_div_set_divider({unsinged} int divider, int target)
> - Change "dsi_div_ab = (1 << AAA) * (BBB + 1)" to " = (BBB + 1) << AAA (Geert)
> - Added Reviewed-by and Tested-by (Biju)
> 
> v3->v4:
> - Changed <,> to <=,>=  (Hugo)
> - Removed duplicate code bock (copy/paste mistake) (Hugo)
> - Fix dummy for rzg2l_cpg_dsi_div_set_divider when CONFIG_CLK_RZG2L=n (Geert)
> - Removed comment "Below conditions must be set.." (Hugo)
> - Removed +1,-1 from pl5_intin comparison math because it was not correct
> - Removed default register settings (PLL5_xxx_DEF) because makes no sense
> - If any calcualtion error, print a message and return a rate of 0
> - Rename global var "dsi_div_ab" to "dsi_div_ab_desired"
> - Check the range of hsclk
> - The correct clock parent is determined by if the divider is even/odd
> - Add in all the restrictions from DIV A,B from the hardware manual
> - No more need to be a recursive function
> - DPI settings must have DSI_DIV_B be '0' (divide 1/1)
> 
> v4->v5:
> - Change dsi_div_ab_desired to u8 (Hugo)
> - Create the helper function rzg2l_cpg_div_ab (Hugo)
> - Remove odd/even comments because implied (Hugo)
> - Change continue to break for the for loop (Hugo)
> - Change if{} if{} to if{} else if{} (Hugo)
> - Remove function rzg2l_cpg_get_vclk_rate (Chris)
> - Set default clksrc,div_a,b using set_divider function (Biju)
> - Return -EINVAL if rzg2l_cpg_dsi_div_determine_rate fails (Hugo)
> ---
>  drivers/clk/renesas/rzg2l-cpg.c | 162 ++++++++++++++++++++++++++------
>  include/linux/clk/renesas.h     |  12 +++
>  2 files changed, 146 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
> index 6743e50d44d0..69a96fa5a272 100644
> --- a/drivers/clk/renesas/rzg2l-cpg.c
> +++ b/drivers/clk/renesas/rzg2l-cpg.c
> @@ -74,6 +74,17 @@
>  #define MSTOP_OFF(conf)              FIELD_GET(GENMASK(31, 16), (conf))
>  #define MSTOP_MASK(conf)     FIELD_GET(GENMASK(15, 0), (conf))
>  
> +#define PLL5_FOUTVCO_MIN     800000000
> +#define PLL5_FOUTVCO_MAX     3000000000
> +#define PLL5_POSTDIV_MIN     1
> +#define PLL5_POSTDIV_MAX     7
> +#define PLL5_REFDIV_MIN              1
> +#define PLL5_REFDIV_MAX              2
> +#define PLL5_INTIN_MIN               20
> +#define PLL5_INTIN_MAX               320
> +#define PLL5_HSCLK_MIN               10000000
> +#define PLL5_HSCLK_MAX               187500000
> +
>  /**
>   * struct clk_hw_data - clock hardware data
>   * @hw: clock hw
> @@ -129,6 +140,12 @@ struct rzg2l_pll5_param {
>       u8 pl5_spread;
>  };
>  
> +/* PLL5 output will be used for DPI or MIPI-DSI */
> +static int dsi_div_target = PLL5_TARGET_DPI;
> +
> +/* Required division ratio for MIPI D-PHY clock depending on number of lanes 
> and bpp. */
> +static u8 dsi_div_ab_desired;
> +
>  struct rzg2l_pll5_mux_dsi_div_param {
>       u8 clksrc;
>       u8 dsi_div_a;
> @@ -170,6 +187,11 @@ struct rzg2l_cpg_priv {
>       struct rzg2l_pll5_mux_dsi_div_param mux_dsi_div_params;
>  };
>  
> +static inline u8 rzg2l_cpg_div_ab(u8 a, u8 b)
> +{
> +     return (b + 1) << a;
> +}
> +
>  static void rzg2l_cpg_del_clk_provider(void *data)
>  {
>       of_clk_del_provider(data);
> @@ -557,16 +579,108 @@ rzg2l_cpg_sd_mux_clk_register(const struct 
> cpg_core_clk *core,
>  }
>  
>  static unsigned long
> -rzg2l_cpg_get_foutpostdiv_rate(struct rzg2l_pll5_param *params,
> +rzg2l_cpg_get_foutpostdiv_rate(struct rzg2l_cpg_priv *priv,
> +                            struct rzg2l_pll5_param *params,
>                              unsigned long rate)
>  {
>       unsigned long foutpostdiv_rate, foutvco_rate;
> +     unsigned long hsclk;
> +     unsigned int a, b, odd;
> +     unsigned int dsi_div_ab_calc;
> +
> +     if (dsi_div_target == PLL5_TARGET_DSI) {
> +             /*
> +              * VCO-->[POSTDIV1,2]--FOUTPOSTDIV-->|   |-->[1/(DSI DIV A * 
> B)]--> MIPI_DSI_VCLK
> +              *            |                      |-->|
> +              *            |-->[1/2]---FOUT1PH0-->|   
> |-->[1/16]---------------> hsclk (MIPI-PHY)
> +              */
> +
> +             /* Check hsclk */
> +             hsclk = rate * dsi_div_ab_desired / 16;
> +             if (hsclk < PLL5_HSCLK_MIN || hsclk > PLL5_HSCLK_MAX) {
> +                     dev_err(priv->dev, "hsclk out of range\n");
> +                     return 0;
> +             }
> +
> +             /* Determine the correct clock source based on even/odd of the 
> divider */
> +             odd = dsi_div_ab_desired & 1;
> +             if (odd) {
> +                     priv->mux_dsi_div_params.clksrc = 0;    /* FOUTPOSTDIV 
> */
> +                     dsi_div_ab_calc = dsi_div_ab_desired;
> +             } else {
> +                     priv->mux_dsi_div_params.clksrc = 1;    /*  FOUT1PH0 */
> +                     dsi_div_ab_calc = dsi_div_ab_desired / 2;
> +             }
> +
> +             /* Calculate the DIV_DSI_A and DIV_DSI_B based on the desired 
> divider */
> +             for (a = 0; a < 4; a++) {
> +                     /* FOUT1PH0: Max output of DIV_DSI_A is 750MHz so at 
> least 1/2 to be safe */
> +                     if (!odd && a == 0)
> +                             continue;
> +
> +                     /* FOUTPOSTDIV: DIV_DSI_A must always be 1/1 */
> +                     if (odd && a != 0)
> +                             break;
> +
> +                     for (b = 0; b < 16; b++) {
> +                             /* FOUTPOSTDIV: DIV_DSI_B must always be odd 
> divider 1/(b+1) */
> +                             if (odd && b & 1)
> +                                     continue;
> +
> +                             if (rzg2l_cpg_div_ab(a, b) == dsi_div_ab_calc) {
> +                                     priv->mux_dsi_div_params.dsi_div_a = a;
> +                                     priv->mux_dsi_div_params.dsi_div_b = b;
> +                                     goto calc_pll_clk;
> +                             }
> +                     }
> +             }
> +
> +             dev_err(priv->dev, "Failed to calculate DIV_DSI_A,B\n");
> +
> +             return 0;
> +     } else if (dsi_div_target == PLL5_TARGET_DPI) {
> +             /* Fixed settings for DPI */
> +             priv->mux_dsi_div_params.clksrc = 0;
> +             priv->mux_dsi_div_params.dsi_div_a = 3; /* Divided by 8 */
> +             priv->mux_dsi_div_params.dsi_div_b = 0; /* Divided by 1 */
> +             dsi_div_ab_desired = 8;                 /* (1 << a) * (b + 1) */
> +     }
> +
> +calc_pll_clk:
> +     /* PLL5 (MIPI_DSI_PLLCLK) = VCO / POSTDIV1 / POSTDIV2 */
> +     for (params->pl5_postdiv1 = PLL5_POSTDIV_MIN;
> +          params->pl5_postdiv1 <= PLL5_POSTDIV_MAX;
> +          params->pl5_postdiv1++) {
> +             for (params->pl5_postdiv2 = PLL5_POSTDIV_MIN;
> +                  params->pl5_postdiv2 <= PLL5_POSTDIV_MAX;
> +                  params->pl5_postdiv2++) {
> +                     foutvco_rate = rate * params->pl5_postdiv1 * 
> params->pl5_postdiv2 *
> +                                    dsi_div_ab_desired;
> +                     if (foutvco_rate <= PLL5_FOUTVCO_MIN || foutvco_rate >= 
> PLL5_FOUTVCO_MAX)
> +                             continue;
> +
> +                     for (params->pl5_refdiv = PLL5_REFDIV_MIN;
> +                          params->pl5_refdiv <= PLL5_REFDIV_MAX;
> +                          params->pl5_refdiv++) {
> +                             params->pl5_intin = (foutvco_rate * 
> params->pl5_refdiv) /
> +                                                 (EXTAL_FREQ_IN_MEGA_HZ * 
> MEGA);
> +                             if (params->pl5_intin < PLL5_INTIN_MIN ||
> +                                 params->pl5_intin > PLL5_INTIN_MAX)
> +                                     continue;
> +
> +                             params->pl5_fracin = div_u64(((u64)
> +                                                  (foutvco_rate * 
> params->pl5_refdiv) %
> +                                                  (EXTAL_FREQ_IN_MEGA_HZ * 
> MEGA)) << 24,
> +                                                  EXTAL_FREQ_IN_MEGA_HZ * 
> MEGA);
> +                             goto clk_valid;
> +                     }
> +             }
> +     }
>  
> -     params->pl5_intin = rate / MEGA;
> -     params->pl5_fracin = div_u64(((u64)rate % MEGA) << 24, MEGA);
> -     params->pl5_refdiv = 2;
> -     params->pl5_postdiv1 = 1;
> -     params->pl5_postdiv2 = 1;
> +     dev_err(priv->dev, "Failed to calculate PLL5 settings\n");
> +     return 0;
> +
> +clk_valid:
>       params->pl5_spread = 0x16;
>  
>       foutvco_rate = div_u64(mul_u32_u32(EXTAL_FREQ_IN_MEGA_HZ * MEGA,
> @@ -607,7 +721,7 @@ static unsigned long 
> rzg2l_cpg_get_vclk_parent_rate(struct clk_hw *hw,
>       struct rzg2l_pll5_param params;
>       unsigned long parent_rate;
>  
> -     parent_rate = rzg2l_cpg_get_foutpostdiv_rate(&params, rate);
> +     parent_rate = rzg2l_cpg_get_foutpostdiv_rate(priv, &params, rate);
>  
>       if (priv->mux_dsi_div_params.clksrc)
>               parent_rate /= 2;
> @@ -623,9 +737,19 @@ static int rzg2l_cpg_dsi_div_determine_rate(struct 
> clk_hw *hw,
>  
>       req->best_parent_rate = rzg2l_cpg_get_vclk_parent_rate(hw, req->rate);
>  
> +     if (!req->best_parent_rate)
> +             return -EINVAL;
> +
>       return 0;
>  }
>  
> +void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target)
> +{
> +     dsi_div_ab_desired = divider;
> +     dsi_div_target = target;
> +}
> +EXPORT_SYMBOL_GPL(rzg2l_cpg_dsi_div_set_divider);
> +
>  static int rzg2l_cpg_dsi_div_set_rate(struct clk_hw *hw,
>                                     unsigned long rate,
>                                     unsigned long parent_rate)
> @@ -796,22 +920,6 @@ struct sipll5 {
>  
>  #define to_sipll5(_hw)       container_of(_hw, struct sipll5, hw)
>  
> -static unsigned long rzg2l_cpg_get_vclk_rate(struct clk_hw *hw,
> -                                          unsigned long rate)
> -{
> -     struct sipll5 *sipll5 = to_sipll5(hw);
> -     struct rzg2l_cpg_priv *priv = sipll5->priv;
> -     unsigned long vclk;
> -
> -     vclk = rate / ((1 << priv->mux_dsi_div_params.dsi_div_a) *
> -                    (priv->mux_dsi_div_params.dsi_div_b + 1));
> -
> -     if (priv->mux_dsi_div_params.clksrc)
> -             vclk /= 2;
> -
> -     return vclk;
> -}
> -
>  static unsigned long rzg2l_cpg_sipll5_recalc_rate(struct clk_hw *hw,
>                                                 unsigned long parent_rate)
>  {
> @@ -856,9 +964,9 @@ static int rzg2l_cpg_sipll5_set_rate(struct clk_hw *hw,
>       if (!rate)
>               return -EINVAL;
>  
> -     vclk_rate = rzg2l_cpg_get_vclk_rate(hw, rate);
> +     vclk_rate = rate / dsi_div_ab_desired;
>       sipll5->foutpostdiv_rate =
> -             rzg2l_cpg_get_foutpostdiv_rate(&params, vclk_rate);
> +             rzg2l_cpg_get_foutpostdiv_rate(priv, &params, vclk_rate);
>  
>       /* Put PLL5 into standby mode */
>       writel(CPG_SIPLL5_STBY_RESETB_WEN, priv->base + CPG_SIPLL5_STBY);
> @@ -945,9 +1053,7 @@ rzg2l_cpg_sipll5_register(const struct cpg_core_clk 
> *core,
>       if (ret)
>               return ERR_PTR(ret);
>  
> -     priv->mux_dsi_div_params.clksrc = 1; /* Use clk src 1 for DSI */
> -     priv->mux_dsi_div_params.dsi_div_a = 1; /* Divided by 2 */
> -     priv->mux_dsi_div_params.dsi_div_b = 2; /* Divided by 3 */
> +     rzg2l_cpg_dsi_div_set_divider(8, PLL5_TARGET_DPI);
>  
>       return clk_hw->clk;
>  }
> diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h
> index 0ebbe2f0b45e..96c5e8f3b5d7 100644
> --- a/include/linux/clk/renesas.h
> +++ b/include/linux/clk/renesas.h
> @@ -16,6 +16,11 @@ struct device;
>  struct device_node;
>  struct generic_pm_domain;
>  
> +enum {
> +     PLL5_TARGET_DPI,
> +     PLL5_TARGET_DSI
> +};
> +
>  void cpg_mstp_add_clk_domain(struct device_node *np);
>  #ifdef CONFIG_CLK_RENESAS_CPG_MSTP
>  int cpg_mstp_attach_dev(struct generic_pm_domain *unused, struct device 
> *dev);
> @@ -32,4 +37,11 @@ void cpg_mssr_detach_dev(struct generic_pm_domain *unused, 
> struct device *dev);
>  #define cpg_mssr_attach_dev  NULL
>  #define cpg_mssr_detach_dev  NULL
>  #endif
> +
> +#ifdef CONFIG_CLK_RZG2L
> +void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target);
> +#else
> +static inline void rzg2l_cpg_dsi_div_set_divider(u8, int target) { }

Maybe use:

#define rzg2l_cpg_dsi_div_set_divider(...) do { } while (0)


> +#endif
> +
>  #endif
> -- 
> 2.50.1
> 
> 


-- 
Hugo Villeneuve

Reply via email to