Files

969 lines
25 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015 Endless Mobile, Inc.
* Author: Carlo Caione <[email protected]>
*
* Copyright (c) 2018 Baylibre, SAS.
* Author: Jerome Brunet <[email protected]>
*/
/*
* 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/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/math64.h>
#include <linux/module.h>
#include "clk-regmap.h"
#include "clk-pll.h"
#include <linux/amlogic/arm-smccc.h>
#define PLL_LOCK_RETRY_CNT_MAX 10
#define FIXED_FRAC_WEIGHT_PRECISION 100000
#define MESON_PLL_THRESHOLD_RATE 1500000000
/*
* SoC list with threshold rate :
* TXHD2
*/
static inline struct meson_clk_pll_data *
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,
unsigned int m, unsigned int n,
unsigned int frac, unsigned int od,
struct meson_clk_pll_data *pll)
{
u64 rate, frac_rate;
u32 frac_precision;
if (pll->flags & CLK_MESON_PLL_POWER_OF_TWO)
n = (u32)BIT(n);
if (unlikely(!n))
return 0;
if (pll->flags & CLK_MESON_PLL_FIXED_EN0P5)
parent_rate = parent_rate >> 1;
rate = DIV_ROUND_UP_ULL((u64)parent_rate * m, n);
if (frac && MESON_PARM_APPLICABLE(&pll->frac)) {
if (pll->flags & CLK_MESON_PLL_FIXED_FRAC_WEIGHT_PRECISION)
frac_precision = FIXED_FRAC_WEIGHT_PRECISION;
else
frac_precision = (u32)BIT(pll->frac.width);
frac_rate = div_u64((u64)parent_rate * frac, n);
rate += DIV_ROUND_UP_ULL(frac_rate, frac_precision);
}
if (od && MESON_PARM_APPLICABLE(&pll->od))
rate = rate >> od;
return rate;
}
static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int m, n, frac, od;
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;
od = MESON_PARM_APPLICABLE(&pll->od) ?
meson_parm_read(clk->map, &pll->od) :
0;
return __pll_params_to_rate(parent_rate, m, n, frac, od, pll);
}
static unsigned int __pll_params_with_frac(unsigned long rate,
unsigned long parent_rate,
unsigned int m,
unsigned int n,
unsigned int od,
struct meson_clk_pll_data *pll)
{
unsigned int frac_max;
u64 val, vco_rate = rate;
if (MESON_PARM_APPLICABLE(&pll->od))
vco_rate = vco_rate << od;
if (pll->flags & CLK_MESON_PLL_FIXED_EN0P5)
parent_rate = parent_rate >> 1;
if (pll->flags & CLK_MESON_PLL_POWER_OF_TWO) {
val = vco_rate << n;
if (vco_rate < (((u64)parent_rate >> n) * m))
return 0;
} else {
val = vco_rate * n;
/* Bail out if we are already over the requested rate */
if (!n || vco_rate < (div_u64((u64)parent_rate * m, n)))
return 0;
}
if (pll->flags & CLK_MESON_PLL_FIXED_FRAC_WEIGHT_PRECISION)
frac_max = FIXED_FRAC_WEIGHT_PRECISION;
else
frac_max = (1 << pll->frac.width);
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 -= (u64)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,
unsigned int *od,
struct meson_clk_pll_data *pll)
{
/* In some SoCs, n = 0, so check m here */
if (!pll->table[index].m)
return -EINVAL;
*m = pll->table[index].m;
*n = pll->table[index].n;
if (MESON_PARM_APPLICABLE(&pll->od))
*od = pll->table[index].od;
return 0;
}
static unsigned int meson_clk_get_pll_range_m(unsigned long long rate,
unsigned long parent_rate,
unsigned int n,
struct meson_clk_pll_data *pll)
{
u64 val;
if (pll->flags & CLK_MESON_PLL_POWER_OF_TWO)
val = rate << n;
else
val = 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,
unsigned int *od,
struct meson_clk_pll_data *pll)
{
unsigned int idx, od_max, t_m;
u64 vco_rate;
unsigned long now, best = 0;
if (((pll->flags & CLK_MESON_PLL_FIXED_N) && index > 0) ||
(!(pll->flags & CLK_MESON_PLL_FIXED_N) &&
(index >= (1 << pll->n.width))))
return -EINVAL;
if (pll->flags & CLK_MESON_PLL_FIXED_N) {
if (pll->flags & CLK_MESON_PLL_POWER_OF_TWO)
*n = 0;
else
*n = 1;
} else {
if (pll->flags & CLK_MESON_PLL_POWER_OF_TWO)
*n = index;
else
*n = index + 1;
}
if (pll->flags & CLK_MESON_PLL_FIXED_EN0P5)
parent_rate = parent_rate >> 1;
if (!MESON_PARM_APPLICABLE(&pll->od)) {
if (index == 0) {
/* 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;
}
}
*m = meson_clk_get_pll_range_m(rate, parent_rate, *n, pll);
} else {
/* limit od max value for hw limit */
if (unlikely(!!pll->od_max))
od_max = pll->od_max;
else
od_max = BIT(pll->od.width) - 1;
for (idx = 0; idx <= od_max; idx++) {
/*
* All post-PLL od divider have the CLK_POWER_OF_TWO
* feature.
*/
vco_rate = (u64)rate << idx;
t_m = meson_clk_get_pll_range_m(vco_rate, parent_rate, *n, pll);
if (t_m > pll->range->max)
break;
if (t_m < pll->range->min)
continue;
now = __pll_params_to_rate(parent_rate, t_m, *n, 0, idx, pll);
if (meson_clk_pll_is_better(rate, best, now, pll)) {
best = now;
*od = idx;
*m = t_m;
if (now == rate)
break;
}
}
}
/* the pre-divider gives a multiplier too big - stop */
if (*m > pll->range->max)
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,
unsigned int *od,
struct meson_clk_pll_data *pll)
{
if (pll->range)
return meson_clk_get_pll_range_index(rate, parent_rate,
index, m, n, od, pll);
else if (pll->table)
return meson_clk_get_pll_table_index(index, m, n, od, 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,
unsigned int *best_od,
struct meson_clk_pll_data *pll)
{
unsigned long best = 0, now = 0;
unsigned int i, m, n, od = 0;
int ret;
for (i = 0, ret = 0; !ret; i++) {
ret = meson_clk_get_pll_get_index(rate, parent_rate,
i, &m, &n, &od, pll);
if (ret == -EINVAL)
break;
now = __pll_params_to_rate(parent_rate, m, n, 0, od, pll);
if (meson_clk_pll_is_better(rate, best, now, pll)) {
best = now;
*best_m = m;
*best_n = n;
if (MESON_PARM_APPLICABLE(&pll->od))
*best_od = od;
if (now == rate)
break;
}
}
return best ? 0 : -EINVAL;
}
static int meson_clk_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int m, n, frac = 0, od;
int ret;
if (pll->flags & CLK_MESON_PLL_READ_ONLY) {
req->rate = clk_hw_get_rate(hw);
return 0;
}
ret = meson_clk_get_pll_settings(req->rate, req->best_parent_rate,
&m, &n, &od, pll);
if (ret)
return ret;
if (MESON_PARM_APPLICABLE(&pll->frac))
frac = __pll_params_with_frac(req->rate, req->best_parent_rate,
m, n, od, pll);
req->rate = __pll_params_to_rate(req->best_parent_rate, m, n, frac, od,
pll);
return 0;
}
static int meson_clk_pll_wait_lock(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
int delay = 1000;
do {
/* Is the clock locked now ? */
if (meson_parm_read(clk->map, &pll->l))
return 0;
udelay(1);
} while (delay--);
return -ETIMEDOUT;
}
static int meson_clk_pll_is_enabled(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
/* Enable and lock bit equal 1, it locks */
if (meson_parm_read(clk->map, &pll->en) &&
meson_parm_read(clk->map, &pll->l))
return 1;
return 0;
}
static int meson_clk_pll_init(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
/*
* Keep the clock running, which was already initialized and enabled
* from the bootloader stage, to avoid any glitches.
*/
if ((pll->flags & CLK_MESON_PLL_NOINIT_ENABLED) &&
meson_clk_pll_is_enabled(hw))
return 0;
if (pll->init_count) {
if (pll->flags & CLK_MESON_PLL_RSTN) {
meson_parm_write(clk->map, &pll->rst, 0);
regmap_multi_reg_write(clk->map, pll->init_regs,
pll->init_count);
meson_parm_write(clk->map, &pll->rst, 1);
} else {
meson_parm_write(clk->map, &pll->rst, 1);
regmap_multi_reg_write(clk->map, pll->init_regs,
pll->init_count);
meson_parm_write(clk->map, &pll->rst, 0);
}
}
return 0;
}
static int meson_clk_pcie_pll_enable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
int retry = 10, ret = 10;
do {
meson_clk_pll_init(hw);
if (!meson_clk_pll_wait_lock(hw)) {
/* what means here ? */
if (!MESON_PARM_APPLICABLE(&pll->pcie_hcsl))
return 0;
break;
}
pr_info("%s:%d retry = %d\n", __func__, __LINE__, retry);
} while (retry--);
if (retry <= 0)
return -EIO;
if (!MESON_PARM_APPLICABLE(&pll->pcie_hcsl))
return 0;
/*pcie pll clk share use for usb phy, so add this operation from ASIC*/
do {
if (meson_parm_read(clk->map, &pll->pcie_hcsl)) {
meson_parm_write(clk->map, &pll->pcie_exen, 0);
return 0;
}
udelay(1);
} while (ret--);
if (ret <= 0)
pr_info("%s:%d pcie reg1 clear bit29 failed\n", __func__, __LINE__);
return -EIO;
}
static void meson_clk_pll_disable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (bypass_clk_disable)
return;
/* Put the pll is in reset */
if (MESON_PARM_APPLICABLE(&pll->rst)) {
if (pll->flags & CLK_MESON_PLL_RSTN)
meson_parm_write(clk->map, &pll->rst, 0);
else
meson_parm_write(clk->map, &pll->rst, 1);
}
/* Disable the pll */
meson_parm_write(clk->map, &pll->en, 0);
}
static int meson_clk_pll_enable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
int retry = 0;
/* do nothing if the PLL is already enabled */
if (clk_hw_is_enabled(hw))
return 0;
do {
/* Make sure the pll is disabled */
meson_clk_pll_disable(hw);
/* Make sure bit is 0 before enable pll */
if (MESON_PARM_APPLICABLE(&pll->current_en))
meson_parm_write(clk->map, &pll->current_en, 0);
/* Make sure the pll is in lock reset */
if (MESON_PARM_APPLICABLE(&pll->l_rst)) {
if (pll->flags & CLK_MESON_PLL_L_RSTN)
meson_parm_write(clk->map, &pll->l_rst, 0);
else
meson_parm_write(clk->map, &pll->l_rst, 1);
}
/* Enable the pll */
meson_parm_write(clk->map, &pll->en, 1);
/* Make sure the pll force lock is clear */
if (MESON_PARM_APPLICABLE(&pll->fl))
meson_parm_write(clk->map, &pll->fl, 0);
/*
* Compared with the previous SoCs, self-adaption current module
* is newly added for A1, keep the new power-on sequence to enable the
* PLL. The sequence is:
* 1. enable the pll, delay for 10us
* 2. enable the pll self-adaption current module, delay for 40us
* 3. enable the lock detect module
*/
if (MESON_PARM_APPLICABLE(&pll->current_en)) {
udelay(10);
meson_parm_write(clk->map, &pll->current_en, 1);
udelay(40);
} else {
/*
* Delay 50us to wait for PLL to stabilize, otherwise PLL may
* have a lock failure (especially in low temperature scenarios).
*/
udelay(50);
}
if (MESON_PARM_APPLICABLE(&pll->l_detect)) {
meson_parm_write(clk->map, &pll->l_detect, 1);
meson_parm_write(clk->map, &pll->l_detect, 0);
}
/* Take the pll out reset */
if (MESON_PARM_APPLICABLE(&pll->rst)) {
if (pll->flags & CLK_MESON_PLL_RSTN)
meson_parm_write(clk->map, &pll->rst, 1);
else
meson_parm_write(clk->map, &pll->rst, 0);
}
/* Take the pll out lock reset */
if (MESON_PARM_APPLICABLE(&pll->l_rst)) {
udelay(20);
if (pll->flags & CLK_MESON_PLL_L_RSTN)
meson_parm_write(clk->map, &pll->l_rst, 1);
else
meson_parm_write(clk->map, &pll->l_rst, 0);
}
if (!meson_clk_pll_wait_lock(hw)) {
/* Make sure the pll force lock is set */
if (MESON_PARM_APPLICABLE(&pll->fl))
meson_parm_write(clk->map, &pll->fl, 1);
return 0;
}
} while (++retry < PLL_LOCK_RETRY_CNT_MAX);
pr_warn("%s: PLL lock failed!!!\n", clk_hw_get_name(hw));
return -EIO;
}
static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int enabled, m, n, frac, od;
int ret;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, &od, pll);
if (ret)
return ret;
enabled = meson_clk_pll_is_enabled(hw);
/* Don't disable pll if it's just changing frac */
if ((meson_parm_read(clk->map, &pll->m) != m ||
meson_parm_read(clk->map, &pll->n) != n) && enabled)
meson_clk_pll_disable(hw);
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, m, n, od, pll);
meson_parm_write(clk->map, &pll->frac, frac);
}
if (MESON_PARM_APPLICABLE(&pll->od))
meson_parm_write(clk->map, &pll->od, od);
/* If the pll is stopped, bail out now */
if (!enabled)
return 0;
return meson_clk_pll_enable(hw);
}
static int meson_clk_pll_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
int i;
pll->saved_rate = meson_clk_pll_recalc_rate(hw,
clk_hw_get_rate(clk_hw_get_parent(hw)));
pll->saved_is_enabled = meson_clk_pll_is_enabled(hw);
for (i = 0; i < pll->save_reg_count; i++)
regmap_read(clk->map, pll->save_regs[i].reg,
&pll->save_regs[i].def);
return 0;
}
static void meson_clk_pll_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (!meson_clk_pll_is_enabled(hw) && pll->init_count) {
meson_parm_write(clk->map, &pll->rst, 1);
regmap_multi_reg_write(clk->map, pll->init_regs,
pll->init_count);
meson_parm_write(clk->map, &pll->rst, 0);
regmap_multi_reg_write(clk->map, pll->save_regs,
pll->save_reg_count);
}
meson_clk_pll_set_rate(hw, pll->saved_rate,
clk_hw_get_rate(clk_hw_get_parent(hw)));
if (pll->saved_is_enabled)
meson_clk_pll_enable(hw);
else
meson_clk_pll_disable(hw);
}
static int meson_clk_pcie_pll_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
pll->saved_is_enabled = meson_clk_pll_is_enabled(hw);
return 0;
}
static void meson_clk_pcie_pll_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (pll->saved_is_enabled)
meson_clk_pcie_pll_enable(hw);
else
meson_clk_pll_disable(hw);
}
/*
* The Meson G12A PCIE PLL is fined tuned to deliver a very precise
* 100MHz reference clock for the PCIe Analog PHY, and thus requires
* a strict register sequence to enable the PLL.
* To simplify, re-use the _init() op to enable the PLL and keep
* the other ops except set_rate since the rate is fixed.
*/
const struct clk_ops meson_clk_pcie_pll_ops = {
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_clk_pcie_pll_enable,
.disable = meson_clk_pll_disable,
.save_context = meson_clk_pcie_pll_save_context,
.restore_context = meson_clk_pcie_pll_restore_context,
};
EXPORT_SYMBOL_GPL(meson_clk_pcie_pll_ops);
const struct clk_ops meson_clk_pll_ops = {
.init = meson_clk_pll_init,
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.set_rate = meson_clk_pll_set_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_clk_pll_enable,
.disable = meson_clk_pll_disable,
.save_context = meson_clk_pll_save_context,
.restore_context = meson_clk_pll_restore_context,
};
EXPORT_SYMBOL_GPL(meson_clk_pll_ops);
static void meson_secure_pll_v2_disable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
struct arm_smccc_res res;
if (bypass_clk_disable)
return;
arm_smccc_smc(pll->smc_id, pll->secid_disable,
0, 0, 0, 0, 0, 0, &res);
}
static int meson_secure_pll_v2_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
struct arm_smccc_res res;
unsigned int m, n, od = 0;
int ret;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, &od, pll);
if (ret)
return ret;
if (meson_parm_read(clk->map, &pll->en))
meson_secure_pll_v2_disable(hw);
arm_smccc_smc(pll->smc_id, pll->secid,
m, n, od, 0, 0, 0, &res);
return 0;
}
static int meson_secure_pll_v2_enable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int m, n, od;
struct arm_smccc_res res;
/*
* In most case, do nothing if the PLL is already enabled
*/
if (clk_hw_is_enabled(hw))
return 0;
/* If PLL is not enabled because setting the same rate,
* Enable it again, CCF will return when set the same rate
*/
n = meson_parm_read(clk->map, &pll->n);
m = meson_parm_read(clk->map, &pll->m);
/* od is required in arm64 and arm */
od = MESON_PARM_APPLICABLE(&pll->od) ?
meson_parm_read(clk->map, &pll->od) : 0;
arm_smccc_smc(pll->smc_id, pll->secid,
m, n, od, 0, 0, 0, &res);
return 0;
}
static void meson_secure_pll_v2_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (!meson_clk_pll_is_enabled(hw)) {
meson_secure_pll_v2_set_rate(hw, pll->saved_rate,
clk_hw_get_rate(clk_hw_get_parent(hw)));
if (pll->saved_is_enabled)
meson_secure_pll_v2_enable(hw);
else
meson_secure_pll_v2_disable(hw);
}
}
const struct clk_ops meson_secure_pll_v2_ops = {
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.set_rate = meson_secure_pll_v2_set_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_secure_pll_v2_enable,
.disable = meson_secure_pll_v2_disable,
.save_context = meson_clk_pll_save_context,
.restore_context = meson_secure_pll_v2_restore_context,
};
EXPORT_SYMBOL_GPL(meson_secure_pll_v2_ops);
static int meson_clk_pll_v3_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
struct parm *pm = &pll->m;
struct parm *pn = &pll->n;
struct parm *pth = &pll->th;
struct parm *pfrac = &pll->frac;
struct parm *pod = &pll->od;
struct parm *pfl = &pll->fl;
unsigned int m, n, frac, od;
unsigned int val;
const struct reg_sequence *init_regs = pll->init_regs;
int i, ret = 0, retry = 10;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
/* calculate M, N, OD*/
ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, &od, pll);
if (ret)
return ret;
/* calculate frac if need*/
if (MESON_PARM_APPLICABLE(&pll->frac))
frac = __pll_params_with_frac(rate, parent_rate, m, n, od, pll);
if (meson_parm_read(clk->map, &pll->m) == m &&
meson_parm_read(clk->map, &pll->n) == n &&
meson_parm_read(clk->map, &pll->en)) {
if (MESON_PARM_APPLICABLE(&pll->od))
meson_parm_write(clk->map, pod, od);
if (MESON_PARM_APPLICABLE(&pll->frac))
meson_parm_write(clk->map, pfrac, frac);
if (!meson_clk_pll_wait_lock(hw))
return 0;
}
if (meson_parm_read(clk->map, &pll->en))
meson_clk_pll_disable(hw);
do {
for (i = 0; i < pll->init_count; i++) {
if (pn->reg_off == init_regs[i].reg) {
/* Clear M N bits and Update M N value */
val = init_regs[i].def;
if (MESON_PARM_APPLICABLE(pth)) {
val &= CLRPMASK(pth->width, pth->shift);
if (__pll_params_to_rate(parent_rate, m,
n, frac, od,
pll) >=
MESON_PLL_THRESHOLD_RATE)
val |= 1 << pth->shift;
}
val &= CLRPMASK(pn->width, pn->shift);
val &= CLRPMASK(pm->width, pm->shift);
val |= n << pn->shift;
val |= m << pm->shift;
if (MESON_PARM_APPLICABLE(pod)) {
val &= CLRPMASK(pod->width, pod->shift);
val |= od << pod->shift;
}
regmap_write(clk->map, pn->reg_off, val);
} else if (pfrac->reg_off == init_regs[i].reg &&
(MESON_PARM_APPLICABLE(pfrac))) {
/* Clear Frac bits and Update Frac value */
val = init_regs[i].def;
val &= CLRPMASK(pfrac->width, pfrac->shift);
val |= frac << pfrac->shift;
regmap_write(clk->map, pfrac->reg_off, val);
} else if (MESON_PARM_APPLICABLE(pfl) &&
pfl->reg_off == init_regs[i].reg) {
val = init_regs[i].def;
val &= CLRPMASK(pfl->width, pfl->shift);
regmap_write(clk->map, pfl->reg_off, val);
} else {
val = init_regs[i].def;
regmap_write(clk->map, init_regs[i].reg, val);
}
if (init_regs[i].delay_us)
udelay(init_regs[i].delay_us);
}
if (!meson_clk_pll_wait_lock(hw)) {
/*
* In special scenarios (such as ESD interference), the pll clock
* source may fluctuate due to interference, and the fluctuations
* will recover in a short time. enable the fl bit to prevent the
* pll from ignoring interference and force lock
*/
if (MESON_PARM_APPLICABLE(pfl))
regmap_update_bits(clk->map, pfl->reg_off, BIT(pfl->shift),
BIT(pfl->shift));
break;
}
retry--;
} while (retry > 0);
if (retry == 0) {
pr_err("%s pll locked failed\n", clk_hw_get_name(hw));
ret = -EIO;
}
return ret;
}
static int meson_clk_pll_v3_enable(struct clk_hw *hw)
{
unsigned long rate, parent_rate;
/* do nothing if the PLL is already enabled */
if (clk_hw_is_enabled(hw))
return 0;
/* Deal clk_set_rate return when set the same rate */
parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
rate = meson_clk_pll_recalc_rate(hw, parent_rate);
meson_clk_pll_v3_set_rate(hw, rate, parent_rate);
if (meson_clk_pll_wait_lock(hw))
return -EIO;
return 0;
}
static void meson_clk_pll_v3_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
meson_clk_pll_v3_set_rate(hw, pll->saved_rate,
clk_hw_get_rate(clk_hw_get_parent(hw)));
if (pll->saved_is_enabled)
meson_clk_pll_v3_enable(hw);
else
meson_clk_pll_disable(hw);
}
const struct clk_ops meson_clk_pll_v3_ops = {
/* walk the init regs each time when set a new rate,
* init callback is not useful for v3 ops
*/
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.set_rate = meson_clk_pll_v3_set_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_clk_pll_v3_enable,
.disable = meson_clk_pll_disable,
.save_context = meson_clk_pll_save_context,
.restore_context = meson_clk_pll_v3_restore_context,
};
EXPORT_SYMBOL_GPL(meson_clk_pll_v3_ops);
const struct clk_ops meson_clk_pll_ro_ops = {
.recalc_rate = meson_clk_pll_recalc_rate,
.is_enabled = meson_clk_pll_is_enabled,
};
EXPORT_SYMBOL_GPL(meson_clk_pll_ro_ops);
MODULE_DESCRIPTION("Amlogic PLL driver");
MODULE_AUTHOR("Carlo Caione <[email protected]>");
MODULE_AUTHOR("Jerome Brunet <[email protected]>");
MODULE_LICENSE("GPL v2");