blob: eaf3c92cf80745b32622d096b8ad3c1a8cb819ef [file] [log] [blame]
// 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");
}