| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| /* |
| * In the most basic form, a Meson PLL is composed as follows: |
| * |
| * PLL |
| * +--------------------------------+ |
| * | | |
| * | +--+ | |
| * in >>-----[ /N ]--->| | +-----+ | |
| * | | |------| DCO |---->> out |
| * | +--------->| | +--v--+ | |
| * | | +--+ | | |
| * | | | | |
| * | +--[ *(M + (F/Fmax) ]<--+ | |
| * | | |
| * +--------------------------------+ |
| * |
| * out = in * (m + frac / frac_max) / n |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/math64.h> |
| #include <div64.h> |
| #include "clk-pll.h" |
| #include <amlogic/clk_measure.h> |
| #include <asm/arch/timer.h> |
| |
| #define PARENT_RATE 24000000 |
| //#define MESON_PLL_DEBUG /* pll debug macro */ |
| |
| 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 int m, unsigned int n, |
| unsigned int frac, |
| struct meson_clk_pll_data *pll, |
| unsigned int od) |
| { |
| u64 rate = (u64)PARENT_RATE * m; |
| u64 frac_rate; |
| |
| if (frac && MESON_PARM_APPLICABLE(&pll->frac)) { |
| frac_rate = (u64)PARENT_RATE * frac; |
| if (frac & (1 << (pll->frac.width - 1))) |
| rate -= DIV_ROUND_UP_ULL(frac_rate, |
| (1 << (pll->frac.width - 2))); |
| else |
| rate += DIV_ROUND_UP_ULL(frac_rate, |
| (1 << (pll->frac.width - 2))); |
| } |
| |
| if (n == 0) |
| return 0; |
| |
| return DIV_ROUND_UP_ULL(rate, n) >> od; |
| } |
| |
| static unsigned int __pll_params_with_frac(unsigned long rate, |
| unsigned int m, |
| unsigned int n, |
| struct meson_clk_pll_data *pll) |
| { |
| 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 -= m * frac_max; |
| |
| return min((unsigned int)val, (frac_max - 1)); |
| } |
| |
| static bool meson_clk_pll_is_better(unsigned long rate, |
| unsigned long best, |
| unsigned long now, |
| struct meson_clk_pll_data *pll) |
| { |
| 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 int meson_clk_get_pll_table_index(unsigned int index, |
| unsigned int *m, |
| unsigned int *n, |
| struct meson_clk_pll_data *pll, |
| unsigned int *od) |
| { |
| if (!pll->table[index].n) |
| return -EINVAL; |
| |
| *m = pll->table[index].m; |
| *n = pll->table[index].n; |
| *od = pll->table[index].od; |
| |
| return 0; |
| } |
| |
| static int meson_clk_get_pll_get_index(unsigned long rate, |
| unsigned int index, |
| unsigned int *m, |
| unsigned int *n, |
| struct meson_clk_pll_data *pll, |
| unsigned int *od) |
| { |
| /* only support table in arm32 */ |
| if (pll->table) |
| return meson_clk_get_pll_table_index(index, m, n, pll, od); |
| |
| return -EINVAL; |
| } |
| |
| static int meson_clk_get_pll_settings(unsigned long rate, |
| unsigned int *best_m, |
| unsigned int *best_n, |
| struct meson_clk_pll_data *pll, |
| unsigned int *best_od) |
| { |
| unsigned long best = 0, now = 0; |
| unsigned int i, m, n, od; |
| int ret; |
| |
| for (i = 0, ret = 0; !ret; i++) { |
| ret = meson_clk_get_pll_get_index(rate, |
| i, &m, &n, pll, &od); |
| if (ret == -EINVAL) |
| break; |
| |
| now = __pll_params_to_rate(m, n, 0, pll, od); |
| if (meson_clk_pll_is_better(rate, best, now, pll)) { |
| best = now; |
| *best_m = m; |
| *best_n = n; |
| *best_od = od; |
| |
| if (now == rate) |
| break; |
| } |
| } |
| |
| return best ? 0 : -EINVAL; |
| } |
| |
| #if 0 |
| static long meson_clk_pll_round_rate(struct meson_clk_pll_data *pll, unsigned long rate) |
| { |
| unsigned int m, n, frac, od; |
| unsigned long round; |
| int ret; |
| |
| ret = meson_clk_get_pll_settings(rate, &m, &n, pll, &od); |
| if (ret) |
| return meson_clk_pll_recalc_rate(pll); |
| |
| round = __pll_params_to_rate(m, n, 0, pll, od); |
| |
| if (!MESON_PARM_APPLICABLE(&pll->frac) || rate == round) |
| return round; |
| |
| /* |
| * 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, m, n, pll); |
| |
| return __pll_params_to_rate(m, n, frac, pll, od); |
| } |
| #endif |
| |
| static int meson_clk_pll_wait_lock(struct meson_clk_pll_data *pll) |
| { |
| int delay = 1000; |
| |
| do { |
| /* Is the clock locked now ? */ |
| if (meson_parm_read(&pll->l)) |
| return 0; |
| _udelay(1); |
| } while (delay--); |
| |
| return -ETIMEDOUT; |
| } |
| |
| void meson_clk_pll_disable(struct meson_clk_pll_data *pll) |
| { |
| /* Put the pll is in reset */ |
| setbits_le32(pll->rst.reg, 1 << pll->rst.shift); |
| |
| /* Disable the pll */ |
| setbits_le32(pll->en.reg, 0 << pll->en.shift); |
| } |
| |
| int meson_pll_set_rate(struct meson_clk_pll_data *pll, unsigned long rate) |
| { |
| unsigned int enabled, m, n, frac = 0, od, i, ret, val; |
| struct parm *pm = &pll->m; |
| struct parm *pn = &pll->n; |
| struct parm *pod = &pll->od; |
| struct parm *pfrac = &pll->frac; |
| const struct reg_sequence *init_regs = pll->init_regs; |
| |
| if (PARENT_RATE == 0 || rate == 0) { |
| printf("%s, target rate is invalid\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = meson_clk_get_pll_settings(rate, &m, &n, pll, &od); |
| if (ret) |
| return ret; |
| |
| if (MESON_PARM_APPLICABLE(&pll->frac)) |
| frac = __pll_params_with_frac(rate, m, n, pll); |
| |
| #ifdef MESON_PLL_DEBUG |
| printf("meson_pll_set_rate: %s trate = %lu, m = %d, n = %d, od = %d\n", |
| pll->name, rate, m, n, od); |
| #endif |
| enabled = meson_parm_read(&pll->en); |
| if (enabled) |
| meson_clk_pll_disable(pll); |
| |
| /* run the same sequence provided by vlsi */ |
| for (i = 0; i < pll->init_count; i++) { |
| if (pn->reg == init_regs[i].reg) { |
| /* Clear M N Vbits and Update M N value */ |
| val = init_regs[i].def; |
| val &= CLRPMASK(pn->width, pn->shift); |
| val &= CLRPMASK(pm->width, pm->shift); |
| val &= CLRPMASK(pod->width, pod->shift); |
| val |= n << pn->shift; |
| val |= m << pm->shift; |
| val |= od << pod->shift; |
| writel(val, pn->reg); |
| } else if (pfrac->reg == init_regs[i].reg && |
| (MESON_PARM_APPLICABLE(&pll->frac))) { |
| /* Clear Frac bits and Update Frac value */ |
| val = init_regs[i].def; |
| val &= CLRPMASK(pfrac->width, pfrac->shift); |
| val |= frac << pfrac->shift; |
| writel(val, pfrac->reg); |
| } else { |
| val = init_regs[i].def; |
| writel(val, init_regs[i].reg); |
| } |
| if (init_regs[i].delay_us) |
| _udelay(init_regs[i].delay_us); |
| } |
| |
| if (meson_clk_pll_wait_lock(pll)) |
| printf("%s pll did not lock, retrying?\n", pll->name); |
| |
| return 0; |
| } |
| |
| int meson_pll_set_one_rate(struct meson_clk_pll_data *pll, unsigned long rate) |
| { |
| unsigned int enabled, i, val, j = 10; |
| const struct reg_sequence *init_regs = pll->init_regs; |
| |
| if (PARENT_RATE == 0 || rate == 0) |
| return -EINVAL; |
| |
| enabled = meson_parm_read(&pll->en); |
| if (enabled) |
| meson_clk_pll_disable(pll); |
| |
| do { |
| /* run the same sequence provided by vlsi */ |
| for (i = 0; i < pll->init_count; i++) { |
| val = init_regs[i].def; |
| writel(val, init_regs[i].reg); |
| if (init_regs[i].delay_us) |
| _udelay(init_regs[i].delay_us); |
| } |
| |
| if (meson_clk_pll_wait_lock(pll)) |
| printf("pcie pll did not lock, retrying %d\n", 10 - j); |
| else |
| break; |
| j--; |
| } while (j); |
| |
| return 0; |
| } |
| |
| void meson_secure_pll_disable(struct meson_clk_pll_data *pll) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(pll->smc_id, pll->secid_disable, |
| 0, 0, 0, 0, 0, 0, &res); |
| } |
| |
| int meson_secure_pll_set_rate(struct meson_clk_pll_data *pll, unsigned long rate) |
| { |
| struct arm_smccc_res res; |
| unsigned int enabled, m, n, ret = 0; |
| unsigned int od; |
| |
| if (PARENT_RATE == 0 || rate == 0) |
| return -EINVAL; |
| |
| ret = meson_clk_get_pll_settings(rate, &m, &n, pll, &od); |
| if (ret) |
| return ret; |
| |
| enabled = meson_parm_read(&pll->en); |
| if (enabled) |
| meson_secure_pll_disable(pll); |
| /*Send m,n for arm64 */ |
| arm_smccc_smc(pll->smc_id, pll->secid, |
| m, n, od, 0, 0, 0, &res); |
| |
| return 0; |
| } |
| |
| void meson_pll_report(struct meson_clk_pll_data *pll, unsigned int target, unsigned long res) |
| { |
| if (((target-2) <= res) && (res <= (target+2))) |
| printf("%s PLL lock ok, target rate = %uM, clkmsr rate = %luM : Match\n", pll->name, target, res); |
| else |
| printf("%s PLL lock failed, target rate = %uM, clkmsr rate = %luM: Not Match\n", pll->name, target, res); |
| } |
| |
| void meson_pll_test(struct meson_clk_pll_data *pll) |
| { |
| int i; |
| unsigned long result; |
| |
| for (i = 0; i < pll->def_cnt;i++) { |
| meson_pll_set_rate(pll, (unsigned long)pll->def_rate[i] * (unsigned long)1000000); |
| |
| result = clk_util_clk_msr(pll->clkmsr_id); |
| |
| meson_pll_report(pll, pll->def_rate[i], result); |
| } |
| } |
| |
| void meson_secure_pll_test(struct meson_clk_pll_data *pll) |
| { |
| int i; |
| unsigned long result; |
| |
| for (i = 0; i < pll->def_cnt;i++) { |
| meson_secure_pll_set_rate(pll, (unsigned long)pll->def_rate[i] * (unsigned long)1000000); |
| |
| result = clk_util_clk_msr(pll->clkmsr_id); |
| /* for sys_pll, clkmsr result is actual value/16 */ |
| if (pll->clkmsr_div16_en) |
| result *= 16; |
| |
| if (((pll->def_rate[i]-2*5) <= result) && (result <= (pll->def_rate[i]+2*5))) |
| printf("%s PLL lock ok, target rate = %u M, clkmsr rate = %lu: Match\n", pll->name, pll->def_rate[i], result); |
| else |
| printf("%s PLL lock failed, target rate = %u M, clkmsr rate = %lu: Not Match\n", pll->name, pll->def_rate[i], result); |
| } |
| } |
| |
| void meson_pll_test_one(struct meson_clk_pll_data *pll) |
| { |
| int i; |
| unsigned long result; |
| |
| for (i = 0; i < pll->def_cnt;i++) { |
| meson_pll_set_one_rate(pll, (unsigned long)pll->def_rate[i] * (unsigned long)1000000); |
| |
| result = clk_util_clk_msr(pll->clkmsr_id); |
| |
| meson_pll_report(pll, pll->def_rate[i], result); |
| } |
| } |
| |
| void meson_switch_cpu_clk(unsigned int smc_id, unsigned int secid, unsigned int flag) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(smc_id, secid, |
| 0x1 << 11, flag << 11, |
| 0, 0, 0, 0, &res); |
| } |
| |
| void one_pll_test(struct meson_clk_pll_data **pll_list, int pll_cnt, |
| struct meson_clk_mpll_data **mpll_list, int mpll_cnt, |
| char *arg) |
| { |
| struct meson_clk_pll_data *pll; |
| struct meson_clk_mpll_data *mpll; |
| unsigned int i, j; |
| |
| for (i = 0; i < pll_cnt;i++) { |
| pll = pll_list[i]; |
| if (0 == strcmp(pll->name, arg)) { |
| meson_pll_test(pll); |
| return; |
| } |
| } |
| for (j = 0; j < mpll_cnt;j++) { |
| mpll = mpll_list[j]; |
| if (0 == strcmp(mpll->name, arg)) { |
| meson_mpll_test(mpll); |
| return; |
| } |
| } |
| if (i == pll_cnt && (j == mpll_cnt)) |
| printf("The pll is not supported Or wrong pll name\n"); |
| } |