[PATCH v2 2/9] clk: renesas: rzg2l: Add PLL7 DSI 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 fractional PLL clock type (CLK_TYPE_G3L_PLLDSI) for the RZ/G3L SoC's PLL7, which drives the DSI interface and requires a dedicated parameter calculation and programming sequence distinct from other PLLs in the RZ/G2L family. PLL7 output frequency is determined by the formula: Ffdco = (NIR + NFR / 4096) * (Fosc / MR) Ffout = Ffdco / (1 << PR) where: - Fosc = 24 MHz (oscillator input) - PR in [0, 4] (post divider, power-of-two) - MR in [1, 12] (input pre-divider) - NIR in [56, 375], NFR in [0, 4095] (integer and fractional parts) The FDCO must fall within one of two valid ranges: 900–2000 MHz (rangesel=0) or 2000–3000 MHz (rangesel=1). The parameter search in rzg3l_dsi_get_pll_parameters_values() iterates over all valid (MR, PR) combinations, filtering by the required FPFD range of 8–16 MHz, then delegates to rzg3l_dsi_compute_pll_parameters() to find the NIR/NFR pair that best approximates the requested rate. An exact match returns immediately; otherwise the combination with the smallest absolute frequency error is used. Computed parameters are cached in pll7_dsi_params within pll_clk to avoid redundant recalculation in determine_rate() when the requested rate has not changed. Signed-off-by: Biju Das <[email protected]> --- v1->v2: * Dropped the macro CPG_PLL_CLK1_VAL and instead using FIELD_PREP at all callsites. * output_nir updated with mul_u32_u32((p->nir + 1) * MILLI, fpfd) as p->nir is small. * output_nfr_range updated with mul_u32_u32(fpfd, MILLI) * nfr updated with div_s64 as div64_s64 is overkill * mul_u32_u32(p->nir + 1, 4096) computation reused in fdco and output. * Open-coded 64-by-32 division replaced with div_u64() --- drivers/clk/renesas/rzg2l-cpg.c | 231 ++++++++++++++++++++++++++++++++ drivers/clk/renesas/rzg2l-cpg.h | 5 + 2 files changed, 236 insertions(+) diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c index 1660508d523a..9730ef67fbc6 100644 --- a/drivers/clk/renesas/rzg2l-cpg.c +++ b/drivers/clk/renesas/rzg2l-cpg.c @@ -73,6 +73,11 @@ #define RZG3L_SDIV_DIV_DSI_A_WEN BIT(16) #define RZG3L_SDIV_DIV_DSI_B_WEN BIT(20) +#define CPG_PLL_CLK1_DIV_NF GENMASK(12, 1) +#define CPG_PLL_CLK1_DIV_NI GENMASK(21, 13) +#define CPG_PLL_CLK1_DIV_M GENMASK(25, 22) +#define CPG_PLL_CLK1_DIV_P GENMASK(28, 26) + #define CLK_ON_R(reg) (reg) #define CLK_MON_R(reg) (0x180 + (reg)) #define CLK_RST_R(reg) (reg) @@ -98,6 +103,26 @@ #define PLL5_HSCLK_MIN 10000000 #define PLL5_HSCLK_MAX 187500000 +#define RZG3L_OSC_CLK (24 * MEGA) +#define RZG3L_PLL7_FDCO_RANGE_0_MIN (900 * MEGA) +#define RZG3L_PLL7_FDCO_RANGE_0_MAX (2000 * MEGA) +#define RZG3L_PLL7_FDCO_RANGE_1_MIN (2000 * MEGA) +#define RZG3L_PLL7_FDCO_RANGE_1_MAX (3000ULL * MEGA) +#define RZG3L_PLL7_PR_MIN (0) +#define RZG3L_PLL7_PR_MAX (4) +#define RZG3L_PLL7_MR_MIN (0) +#define RZG3L_PLL7_MR_MAX (11) +#define RZG3L_PLL7_NIR_MIN (55) +#define RZG3L_PLL7_NIR_MAX (374) +#define RZG3L_PLL7_NFR_MIN (0) +#define RZG3L_PLL7_NFR_MAX (4095) +#define RZG3L_PLL7_NR_MIN (56250) /* Multiplied value 56.25 * 1000 */ +#define RZG3L_PLL7_NR_MAX (375000) /* Multiplied value 375 * 1000 */ +#define RZG3L_PLL7_MULT_MIN (293) /* Multiplied value 0.293 * 1000 */ +#define RZG3L_PLL7_MULT_MAX (375000) /* Multiplied value 375 * 1000 */ +#define RZG3L_PLL7_FSTD_DIV_MR_MIN (8 * MEGA) +#define RZG3L_PLL7_FSTD_DIV_MR_MAX (16 * MEGA) + /** * struct clk_hw_data - clock hardware data * @hw: clock hw @@ -1208,6 +1233,15 @@ rzg2l_cpg_sipll5_register(const struct cpg_core_clk *core, return clk_hw->clk; } +/* RZ/G3L PLL7 parameters */ +struct rzg3l_plldsi_parameters { + s64 error_millihz; + u64 freq_millihz; + u16 nir, nfr; + u8 rangesel; + u8 pr, mr; +}; + struct pll_clk { struct clk_hw hw; unsigned long default_rate; @@ -1215,6 +1249,7 @@ struct pll_clk { unsigned int type; void __iomem *base; struct rzg2l_cpg_priv *priv; + struct rzg3l_plldsi_parameters pll7_dsi_params; }; #define to_pll(_hw) container_of(_hw, struct pll_clk, hw) @@ -1374,6 +1409,199 @@ static const struct clk_ops rzg3l_cpg_pll_ops = { .recalc_rate = rzg3s_cpg_pll_clk_recalc_rate, }; +static inline bool +rzg3l_dsi_compute_pll_parameters(struct rzg3l_plldsi_parameters *pars, + struct rzg3l_plldsi_parameters *p, + struct rzg3l_plldsi_parameters *best, + u64 freq_millihz, u32 fpfd, u32 pr) +{ + for (p->nir = RZG3L_PLL7_NIR_MIN; p->nir <= RZG3L_PLL7_NIR_MAX; p->nir++) { + u64 output_nir, output_nfr_range; + s64 nfr, output_nfr; + u64 fdco, output; + u64 nr_div_mr_pr; + + /* + * The frequency generated by the PLL is calculated as follows: + * + * With: + * Freq = Ffout = Ffdco / pr + * input frequency(fstd) = 24MHz + * fpfd = fstd / mr + * nr = nir + nfr / 4096 + * Ffdco = nr * fpfd + * Ffdco = (nir + (nfr / 4096)) * fpfd + * + * Freq can also be rewritten as: + * Freq = Ffdco / pr + * = (nir * fpfd) / pr + ((nfr / 4096) * fpfd) / pr + * = output_nir + output_nfr + * + * Every parameter has been determined at this point, but nfr. + * Considering that: + * 0 <= nfr <= 4095 + * Then: + * 0 <= (nfr / 4096) < 1 + * Therefore: + * 0 <= output_nfr < fpfd / pr + */ + + /* Compute output nir component (in mHz) */ + output_nir = DIV_ROUND_CLOSEST_ULL(mul_u32_u32((p->nir + 1) * MILLI, fpfd), pr); + /* Compute range for output nfr (in mHz) */ + output_nfr_range = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fpfd, MILLI), pr); + /* No point in continuing if we can't achieve the desired frequency */ + if (freq_millihz < output_nir || freq_millihz >= (output_nir + output_nfr_range)) + continue; + + /* + * Compute the nfr component + * + * Since: + * Freq = output_nir + output_nfr + * Then: + * output_nfr = Freq - output_nir + * = ((nfr / 4096) * fpfd) / pr + * Therefore: + * nfr = (output_nfr * 4096 * pr) / fpfd + */ + output_nfr = freq_millihz - output_nir; + nfr = div_s64(output_nfr * 4096 * pr, fpfd); + nfr = DIV_S64_ROUND_CLOSEST(nfr, 1000); + + /* Validate nfr value within allowed limits */ + if (nfr < RZG3L_PLL7_NFR_MIN || nfr > RZG3L_PLL7_NFR_MAX) + continue; + + p->nfr = nfr; + + /* Compute (Ffdco * 4096) */ + output = mul_u32_u32(p->nir + 1, 4096); + fdco = (output + p->nfr) * fpfd; + if (fdco < (RZG3L_PLL7_FDCO_RANGE_0_MIN * 4096ULL) || + fdco > (RZG3L_PLL7_FDCO_RANGE_1_MAX * 4096ULL)) + continue; + + if (fdco <= (RZG3L_PLL7_FDCO_RANGE_0_MAX * 4096ULL)) + p->rangesel = 0; + else + p->rangesel = 1; + + /* compute the nr and magnify by 1000 */ + output = mul_u32_u32(p->nir + 1, 4096); + output += p->nfr; + output *= 1000; + nr_div_mr_pr = div_u64(output, 4096); + if (nr_div_mr_pr < RZG3L_PLL7_NR_MIN || nr_div_mr_pr > RZG3L_PLL7_NR_MAX) + continue; + + /* compute the magnified multipier = nr(magnified)/(mr *pr) */ + nr_div_mr_pr = div_u64(nr_div_mr_pr, (p->mr + 1) * pr); + if (nr_div_mr_pr < RZG3L_PLL7_MULT_MIN || nr_div_mr_pr > RZG3L_PLL7_MULT_MAX) + continue; + + output *= RZG3L_OSC_CLK; + output = div_u64(output, (p->mr + 1) * pr * 4096); + + p->error_millihz = freq_millihz - output; + p->freq_millihz = output; + + /* If an exact match is found, return immediately */ + if (p->error_millihz == 0) { + *pars = *p; + return true; + } + + /* Update best match if error is smaller */ + if (abs(p->error_millihz) < abs(best->error_millihz)) + *best = *p; + } + + return false; +} + +static bool +rzg3l_dsi_get_pll_parameters_values(struct rzg3l_plldsi_parameters *pars, + u64 freq_millihz) +{ + struct rzg3l_plldsi_parameters p, best; + + /* Initialize best error to maximum possible value */ + best.error_millihz = S64_MAX; + p.error_millihz = S64_MAX; + for (p.mr = RZG3L_PLL7_MR_MIN; p.mr <= RZG3L_PLL7_MR_MAX; p.mr++) { + u32 fpfd = RZG3L_OSC_CLK / (p.mr + 1); + + if (fpfd > RZG3L_PLL7_FSTD_DIV_MR_MAX || fpfd < RZG3L_PLL7_FSTD_DIV_MR_MIN) + continue; + + for (p.pr = RZG3L_PLL7_PR_MIN; p.pr <= RZG3L_PLL7_PR_MAX; p.pr++) + if (rzg3l_dsi_compute_pll_parameters(pars, &p, &best, + freq_millihz, fpfd, 1 << p.pr)) + return true; + } + + /* If no valid parameters were found, return false */ + if (best.error_millihz == S64_MAX) + return false; + + *pars = best; + return true; +} + +static int rzg3l_cpg_plldsi_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) +{ + struct pll_clk *pll_clk = to_pll(hw); + struct rzg2l_cpg_priv *priv = pll_clk->priv; + struct rzg3l_plldsi_parameters *pll7_dsi_params = &pll_clk->pll7_dsi_params; + u64 rate_millihz; + + rate_millihz = mul_u32_u32(req->rate, MILLI); + if (rate_millihz == pll7_dsi_params->error_millihz + pll7_dsi_params->freq_millihz) + goto exit_determine_rate; + + if (!rzg3l_dsi_get_pll_parameters_values(pll7_dsi_params, rate_millihz)) { + dev_err(priv->dev, "failed %s for req->rate: %lu\n", __func__, req->rate); + return -EINVAL; + } + +exit_determine_rate: + req->rate = DIV_ROUND_CLOSEST_ULL(pll7_dsi_params->freq_millihz, MILLI); + return 0; +} + +static int rzg3l_cpg_plldsi_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk = to_pll(hw); + struct rzg3l_plldsi_parameters *dsi_dividers = &pll_clk->pll7_dsi_params; + u32 val; + + /* Put PLL into standby mode */ + rzg3l_cpg_pll_clk_endisable(hw, false); + + /* Output clock setting 1 */ + val = FIELD_PREP(CPG_PLL_CLK1_DIV_P, dsi_dividers->pr) | + FIELD_PREP(CPG_PLL_CLK1_DIV_M, dsi_dividers->mr) | + FIELD_PREP(CPG_PLL_CLK1_DIV_NI, dsi_dividers->nir) | + FIELD_PREP(CPG_PLL_CLK1_DIV_NF, dsi_dividers->nfr) | + dsi_dividers->rangesel; + writel(val, pll_clk->base + CPG_PLL_CLK1_OFFSET(pll_clk->conf)); + + /* Put PLL to normal mode */ + rzg3l_cpg_pll_clk_endisable(hw, true); + + return 0; +}; + +static const struct clk_ops rzg3l_cpg_plldsi_ops = { + .recalc_rate = rzg3s_cpg_pll_clk_recalc_rate, + .determine_rate = rzg3l_cpg_plldsi_determine_rate, + .set_rate = rzg3l_cpg_plldsi_set_rate, + .is_enabled = rzg3l_cpg_pll_clk_is_enabled, + .enable = rzg3l_cpg_pll_clk_enable, +}; + static struct clk *rzg2l_cpg_clk_src_twocell_get(struct of_phandle_args *clkspec, void *data) @@ -1487,6 +1715,9 @@ rzg2l_cpg_register_core_clk(const struct cpg_core_clk *core, case CLK_TYPE_G3L_DSI_DIV: clk = rzg3l_cpg_dsi_div_clk_register(core, priv); break; + case CLK_TYPE_G3L_PLLDSI: + clk = rzg2l_cpg_pll_clk_register(core, priv, &rzg3l_cpg_plldsi_ops); + break; default: goto fail; } diff --git a/drivers/clk/renesas/rzg2l-cpg.h b/drivers/clk/renesas/rzg2l-cpg.h index 24642a089b6c..41e8f389c566 100644 --- a/drivers/clk/renesas/rzg2l-cpg.h +++ b/drivers/clk/renesas/rzg2l-cpg.h @@ -142,6 +142,9 @@ enum clk_types { /* Clock for DSI divider */ CLK_TYPE_DSI_DIV, + /* Clock for G3L DSI PLL */ + CLK_TYPE_G3L_PLLDSI, + /* Clock for G3L DSI divider */ CLK_TYPE_G3L_DSI_DIV, }; @@ -206,6 +209,8 @@ enum clk_types { #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) +#define DEF_G3L_PLLDSI(_name, _id, _parent, _conf) \ + DEF_TYPE(_name, _id, CLK_TYPE_G3L_PLLDSI, .parent = _parent, .conf = _conf) /** * struct rzg2l_mod_clk - Module Clocks definitions -- 2.43.0