From: Taniya Das Date: Wed, 2 Jul 2025 09:04:21 +0000 (+0530) Subject: clk: qcom: clk-alpha-pll: Add support for dynamic update for slewing PLLs X-Git-Tag: v6.17-rc1~86^2~2^6^2~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=48d2c6dec1c46460ee7028915595d49a644e8a77;p=thirdparty%2Fkernel%2Flinux.git clk: qcom: clk-alpha-pll: Add support for dynamic update for slewing PLLs The alpha PLLs which slew to a new frequency at runtime would require the PLL to calibrate at the mid point of the VCO. Add the new PLL ops which can support the slewing of the PLL to a new frequency. Reviewed-by: Imran Shaik Signed-off-by: Taniya Das Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250702-qcs615-mm-v10-clock-controllers-v11-1-9c216e1615ab@quicinc.com Signed-off-by: Bjorn Andersson --- diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index d8e1cd1263317..fec6eb376e270 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -790,6 +790,29 @@ static int clk_alpha_pll_update_latch(struct clk_alpha_pll *pll, return __clk_alpha_pll_update_latch(pll); } +static void clk_alpha_pll_update_configs(struct clk_alpha_pll *pll, const struct pll_vco *vco, + u32 l, u64 alpha, u32 alpha_width, bool alpha_en) +{ + regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); + + if (alpha_width > ALPHA_BITWIDTH) + alpha <<= alpha_width - ALPHA_BITWIDTH; + + if (alpha_width > 32) + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL_U(pll), upper_32_bits(alpha)); + + regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), lower_32_bits(alpha)); + + if (vco) { + regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), + PLL_VCO_MASK << PLL_VCO_SHIFT, + vco->val << PLL_VCO_SHIFT); + } + + if (alpha_en) + regmap_set_bits(pll->clkr.regmap, PLL_USER_CTL(pll), PLL_ALPHA_EN); +} + static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate, int (*is_enabled)(struct clk_hw *)) @@ -807,24 +830,7 @@ static int __clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, return -EINVAL; } - regmap_write(pll->clkr.regmap, PLL_L_VAL(pll), l); - - if (alpha_width > ALPHA_BITWIDTH) - a <<= alpha_width - ALPHA_BITWIDTH; - - if (alpha_width > 32) - regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL_U(pll), a >> 32); - - regmap_write(pll->clkr.regmap, PLL_ALPHA_VAL(pll), a); - - if (vco) { - regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), - PLL_VCO_MASK << PLL_VCO_SHIFT, - vco->val << PLL_VCO_SHIFT); - } - - regmap_update_bits(pll->clkr.regmap, PLL_USER_CTL(pll), - PLL_ALPHA_EN, PLL_ALPHA_EN); + clk_alpha_pll_update_configs(pll, vco, l, a, alpha_width, true); return clk_alpha_pll_update_latch(pll, is_enabled); } @@ -3017,3 +3023,153 @@ void qcom_clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regm } } EXPORT_SYMBOL_GPL(qcom_clk_alpha_pll_configure); + +static int clk_alpha_pll_slew_update(struct clk_alpha_pll *pll) +{ + u32 val; + int ret; + + regmap_set_bits(pll->clkr.regmap, PLL_MODE(pll), PLL_UPDATE); + regmap_read(pll->clkr.regmap, PLL_MODE(pll), &val); + + ret = wait_for_pll_update(pll); + if (ret) + return ret; + /* + * Hardware programming mandates a wait of at least 570ns before polling the LOCK + * detect bit. Have a delay of 1us just to be safe. + */ + udelay(1); + + return wait_for_pll_enable_lock(pll); +} + +static int clk_alpha_pll_slew_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + const struct pll_vco *curr_vco, *vco; + unsigned long freq_hz; + u64 a; + u32 l; + + freq_hz = alpha_pll_round_rate(rate, parent_rate, &l, &a, ALPHA_REG_BITWIDTH); + if (freq_hz != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + curr_vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw)); + if (!curr_vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + vco = alpha_pll_find_vco(pll, freq_hz); + if (!vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + /* + * Dynamic pll update will not support switching frequencies across + * vco ranges. In those cases fall back to normal alpha set rate. + */ + if (curr_vco->val != vco->val) + return clk_alpha_pll_set_rate(hw, rate, parent_rate); + + clk_alpha_pll_update_configs(pll, NULL, l, a, ALPHA_REG_BITWIDTH, false); + + /* Ensure that the write above goes before slewing the PLL */ + mb(); + + if (clk_hw_is_enabled(hw)) + return clk_alpha_pll_slew_update(pll); + + return 0; +} + +/* + * Slewing plls should be bought up at frequency which is in the middle of the + * desired VCO range. So after bringing up the pll at calibration freq, set it + * back to desired frequency(that was set by previous clk_set_rate). + */ +static int clk_alpha_pll_calibrate(struct clk_hw *hw) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + struct clk_hw *parent; + const struct pll_vco *vco; + unsigned long calibration_freq, freq_hz; + u64 a; + u32 l; + int rc; + + parent = clk_hw_get_parent(hw); + if (!parent) { + pr_err("alpha pll: no valid parent found\n"); + return -EINVAL; + } + + vco = alpha_pll_find_vco(pll, clk_hw_get_rate(hw)); + if (!vco) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + /* + * As during slewing plls vco_sel won't be allowed to change, vco table + * should have only one entry table, i.e. index = 0, find the + * calibration frequency. + */ + calibration_freq = (pll->vco_table[0].min_freq + pll->vco_table[0].max_freq) / 2; + + freq_hz = alpha_pll_round_rate(calibration_freq, clk_hw_get_rate(parent), + &l, &a, ALPHA_REG_BITWIDTH); + if (freq_hz != calibration_freq) { + pr_err("alpha_pll: call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + clk_alpha_pll_update_configs(pll, vco, l, a, ALPHA_REG_BITWIDTH, false); + + /* Bringup the pll at calibration frequency */ + rc = clk_alpha_pll_enable(hw); + if (rc) { + pr_err("alpha pll calibration failed\n"); + return rc; + } + + /* + * PLL is already running at calibration frequency. + * So slew pll to the previously set frequency. + */ + freq_hz = alpha_pll_round_rate(clk_hw_get_rate(hw), + clk_hw_get_rate(parent), &l, &a, ALPHA_REG_BITWIDTH); + + pr_debug("pll %s: setting back to required rate %lu, freq_hz %ld\n", + clk_hw_get_name(hw), clk_hw_get_rate(hw), freq_hz); + + clk_alpha_pll_update_configs(pll, NULL, l, a, ALPHA_REG_BITWIDTH, true); + + return clk_alpha_pll_slew_update(pll); +} + +static int clk_alpha_pll_slew_enable(struct clk_hw *hw) +{ + int rc; + + rc = clk_alpha_pll_calibrate(hw); + if (rc) + return rc; + + return clk_alpha_pll_enable(hw); +} + +const struct clk_ops clk_alpha_pll_slew_ops = { + .enable = clk_alpha_pll_slew_enable, + .disable = clk_alpha_pll_disable, + .recalc_rate = clk_alpha_pll_recalc_rate, + .round_rate = clk_alpha_pll_round_rate, + .set_rate = clk_alpha_pll_slew_set_rate, +}; +EXPORT_SYMBOL(clk_alpha_pll_slew_ops); diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h index 7f35aaa7a35d8..ff41aeab0ab98 100644 --- a/drivers/clk/qcom/clk-alpha-pll.h +++ b/drivers/clk/qcom/clk-alpha-pll.h @@ -206,6 +206,7 @@ extern const struct clk_ops clk_alpha_pll_rivian_evo_ops; #define clk_alpha_pll_postdiv_rivian_evo_ops clk_alpha_pll_postdiv_fabia_ops extern const struct clk_ops clk_alpha_pll_regera_ops; +extern const struct clk_ops clk_alpha_pll_slew_ops; void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap, const struct alpha_pll_config *config);