diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c index 4a8c68ae8801..41e16dd7272a 100644 --- a/drivers/clk/meson/clk-pll.c +++ b/drivers/clk/meson/clk-pll.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "clk-regmap.h" #include "clk-pll.h" @@ -42,12 +43,21 @@ meson_clk_pll_data(struct clk_regmap *clk) return (struct meson_clk_pll_data *)clk->data; } +static int __pll_round_closest_mult(struct meson_clk_pll_data *pll) +{ + if ((pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) && + !MESON_PARM_APPLICABLE(&pll->frac)) + return 1; + + return 0; +} + static unsigned long __pll_params_to_rate(unsigned long parent_rate, - const struct pll_params_table *pllt, - u16 frac, + unsigned int m, unsigned int n, + unsigned int frac, struct meson_clk_pll_data *pll) { - u64 rate = (u64)parent_rate * pllt->m; + u64 rate = (u64)parent_rate * m; if (frac && MESON_PARM_APPLICABLE(&pll->frac)) { u64 frac_rate = (u64)parent_rate * frac; @@ -56,7 +66,7 @@ static unsigned long __pll_params_to_rate(unsigned long parent_rate, (1 << pll->frac.width)); } - return DIV_ROUND_UP_ULL(rate, pllt->n); + return DIV_ROUND_UP_ULL(rate, n); } static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, @@ -64,35 +74,39 @@ static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); - struct pll_params_table pllt; - u16 frac; + unsigned int m, n, frac; - pllt.n = meson_parm_read(clk->map, &pll->n); - pllt.m = meson_parm_read(clk->map, &pll->m); + n = meson_parm_read(clk->map, &pll->n); + m = meson_parm_read(clk->map, &pll->m); frac = MESON_PARM_APPLICABLE(&pll->frac) ? meson_parm_read(clk->map, &pll->frac) : 0; - return __pll_params_to_rate(parent_rate, &pllt, frac, pll); + return __pll_params_to_rate(parent_rate, m, n, frac, pll); } -static u16 __pll_params_with_frac(unsigned long rate, - unsigned long parent_rate, - const struct pll_params_table *pllt, - struct meson_clk_pll_data *pll) +static unsigned int __pll_params_with_frac(unsigned long rate, + unsigned long parent_rate, + unsigned int m, + unsigned int n, + struct meson_clk_pll_data *pll) { - u16 frac_max = (1 << pll->frac.width); - u64 val = (u64)rate * pllt->n; + unsigned int frac_max = (1 << pll->frac.width); + u64 val = (u64)rate * n; + + /* Bail out if we are already over the requested rate */ + if (rate < parent_rate * m / n) + return 0; if (pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) val = DIV_ROUND_CLOSEST_ULL(val * frac_max, parent_rate); else val = div_u64(val * frac_max, parent_rate); - val -= pllt->m * frac_max; + val -= m * frac_max; - return min((u16)val, (u16)(frac_max - 1)); + return min((unsigned int)val, (frac_max - 1)); } static bool meson_clk_pll_is_better(unsigned long rate, @@ -100,45 +114,123 @@ static bool meson_clk_pll_is_better(unsigned long rate, unsigned long now, struct meson_clk_pll_data *pll) { - if (!(pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) || - MESON_PARM_APPLICABLE(&pll->frac)) { - /* Round down */ - if (now < rate && best < now) - return true; - } else { + if (__pll_round_closest_mult(pll)) { /* Round Closest */ if (abs(now - rate) < abs(best - rate)) return true; + } else { + /* Round down */ + if (now < rate && best < now) + return true; } return false; } -static const struct pll_params_table * -meson_clk_get_pll_settings(unsigned long rate, - unsigned long parent_rate, - struct meson_clk_pll_data *pll) +static int meson_clk_get_pll_table_index(unsigned int index, + unsigned int *m, + unsigned int *n, + struct meson_clk_pll_data *pll) { - const struct pll_params_table *table = pll->table; - unsigned long best = 0, now = 0; - unsigned int i, best_i = 0; + if (!pll->table[index].n) + return -EINVAL; - if (!table) - return NULL; + *m = pll->table[index].m; + *n = pll->table[index].n; - for (i = 0; table[i].n; i++) { - now = __pll_params_to_rate(parent_rate, &table[i], 0, pll); + return 0; +} - /* If we get an exact match, don't bother any further */ - if (now == rate) { - return &table[i]; - } else if (meson_clk_pll_is_better(rate, best, now, pll)) { - best = now; - best_i = i; +static unsigned int meson_clk_get_pll_range_m(unsigned long rate, + unsigned long parent_rate, + unsigned int n, + struct meson_clk_pll_data *pll) +{ + u64 val = (u64)rate * n; + + if (__pll_round_closest_mult(pll)) + return DIV_ROUND_CLOSEST_ULL(val, parent_rate); + + return div_u64(val, parent_rate); +} + +static int meson_clk_get_pll_range_index(unsigned long rate, + unsigned long parent_rate, + unsigned int index, + unsigned int *m, + unsigned int *n, + struct meson_clk_pll_data *pll) +{ + *n = index + 1; + + /* Check the predivider range */ + if (*n >= (1 << pll->n.width)) + return -EINVAL; + + if (*n == 1) { + /* Get the boundaries out the way */ + if (rate <= pll->range->min * parent_rate) { + *m = pll->range->min; + return -ENODATA; + } else if (rate >= pll->range->max * parent_rate) { + *m = pll->range->max; + return -ENODATA; } } - return (struct pll_params_table *)&table[best_i]; + *m = meson_clk_get_pll_range_m(rate, parent_rate, *n, pll); + + /* the pre-divider gives a multiplier too big - stop */ + if (*m >= (1 << pll->m.width)) + return -EINVAL; + + return 0; +} + +static int meson_clk_get_pll_get_index(unsigned long rate, + unsigned long parent_rate, + unsigned int index, + unsigned int *m, + unsigned int *n, + struct meson_clk_pll_data *pll) +{ + if (pll->range) + return meson_clk_get_pll_range_index(rate, parent_rate, + index, m, n, pll); + else if (pll->table) + return meson_clk_get_pll_table_index(index, m, n, pll); + + return -EINVAL; +} + +static int meson_clk_get_pll_settings(unsigned long rate, + unsigned long parent_rate, + unsigned int *best_m, + unsigned int *best_n, + struct meson_clk_pll_data *pll) +{ + unsigned long best = 0, now = 0; + unsigned int i, m, n; + int ret; + + for (i = 0, ret = 0; !ret; i++) { + ret = meson_clk_get_pll_get_index(rate, parent_rate, + i, &m, &n, pll); + if (ret == -EINVAL) + break; + + now = __pll_params_to_rate(parent_rate, m, n, 0, pll); + if (meson_clk_pll_is_better(rate, best, now, pll)) { + best = now; + *best_m = m; + *best_n = n; + + if (now == rate) + break; + } + } + + return best ? 0 : -EINVAL; } static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, @@ -146,15 +238,15 @@ static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); - const struct pll_params_table *pllt = - meson_clk_get_pll_settings(rate, *parent_rate, pll); + unsigned int m, n, frac; unsigned long round; - u16 frac; + int ret; - if (!pllt) + ret = meson_clk_get_pll_settings(rate, *parent_rate, &m, &n, pll); + if (ret) return meson_clk_pll_recalc_rate(hw, *parent_rate); - round = __pll_params_to_rate(*parent_rate, pllt, 0, pll); + round = __pll_params_to_rate(*parent_rate, m, n, 0, pll); if (!MESON_PARM_APPLICABLE(&pll->frac) || rate == round) return round; @@ -163,9 +255,9 @@ static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, * The rate provided by the setting is not an exact match, let's * try to improve the result using the fractional parameter */ - frac = __pll_params_with_frac(rate, *parent_rate, pllt, pll); + frac = __pll_params_with_frac(rate, *parent_rate, m, n, pll); - return __pll_params_to_rate(*parent_rate, pllt, frac, pll); + return __pll_params_to_rate(*parent_rate, m, n, frac, pll); } static int meson_clk_pll_wait_lock(struct clk_hw *hw) @@ -252,30 +344,27 @@ static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); - const struct pll_params_table *pllt; - unsigned int enabled; + unsigned int enabled, m, n, frac = 0, ret; unsigned long old_rate; - u16 frac = 0; if (parent_rate == 0 || rate == 0) return -EINVAL; old_rate = rate; - pllt = meson_clk_get_pll_settings(rate, parent_rate, pll); - if (!pllt) - return -EINVAL; + ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, pll); + if (ret) + return ret; enabled = meson_parm_read(clk->map, &pll->en); if (enabled) meson_clk_pll_disable(hw); - meson_parm_write(clk->map, &pll->n, pllt->n); - meson_parm_write(clk->map, &pll->m, pllt->m); - + meson_parm_write(clk->map, &pll->n, n); + meson_parm_write(clk->map, &pll->m, m); if (MESON_PARM_APPLICABLE(&pll->frac)) { - frac = __pll_params_with_frac(rate, parent_rate, pllt, pll); + frac = __pll_params_with_frac(rate, parent_rate, m, n, pll); meson_parm_write(clk->map, &pll->frac, frac); } diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h index 5ccf0854d932..55af2e285b1b 100644 --- a/drivers/clk/meson/clk-pll.h +++ b/drivers/clk/meson/clk-pll.h @@ -12,8 +12,13 @@ #include "parm.h" struct pll_params_table { - u16 m; - u16 n; + unsigned int m; + unsigned int n; +}; + +struct pll_mult_range { + unsigned int min; + unsigned int max; }; #define PLL_PARAMS(_m, _n) \ @@ -34,6 +39,7 @@ struct meson_clk_pll_data { const struct reg_sequence *init_regs; unsigned int init_count; const struct pll_params_table *table; + const struct pll_mult_range *range; u8 flags; };