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) { }
+#endif
+
 #endif
-- 
2.50.1

Reply via email to