diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index d8e1cd126331..fec6eb376e27 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 7f35aaa7a35d..ff41aeab0ab9 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);