2205 lines
60 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2014 MundoReader S.L.
* Author: Heiko Stuebner <heiko@sntech.de>
*
* Copyright (c) 2015 Rockchip Electronics Co., Ltd.
* Author: Xing Zheng <zhengxing@rock-chips.com>
*/
#include <asm/div64.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/clk-provider.h>
#include <linux/iopoll.h>
#include <linux/regmap.h>
#include <linux/clk.h>
#include <linux/gcd.h>
#include <linux/clk/rockchip.h>
#include <linux/mfd/syscon.h>
#include "clk.h"
#define PLL_MODE_MASK 0x3
#define PLL_MODE_SLOW 0x0
#define PLL_MODE_NORM 0x1
#define PLL_MODE_DEEP 0x2
#define PLL_RK3328_MODE_MASK 0x1
struct rockchip_clk_pll {
struct clk_hw hw;
struct clk_mux pll_mux;
const struct clk_ops *pll_mux_ops;
struct notifier_block clk_nb;
void __iomem *reg_base;
int lock_offset;
unsigned int lock_shift;
enum rockchip_pll_type type;
u8 flags;
const struct rockchip_pll_rate_table *rate_table;
unsigned int rate_count;
int sel;
unsigned long scaling;
spinlock_t *lock;
struct rockchip_clk_provider *ctx;
#ifdef CONFIG_ROCKCHIP_CLK_BOOST
bool boost_enabled;
u32 boost_backup_pll_usage;
unsigned long boost_backup_pll_rate;
unsigned long boost_low_rate;
unsigned long boost_high_rate;
struct regmap *boost;
#endif
#ifdef CONFIG_DEBUG_FS
struct hlist_node debug_node;
#endif
};
#define to_rockchip_clk_pll(_hw) container_of(_hw, struct rockchip_clk_pll, hw)
#define to_rockchip_clk_pll_nb(nb) \
container_of(nb, struct rockchip_clk_pll, clk_nb)
#ifdef CONFIG_ROCKCHIP_CLK_BOOST
static void rockchip_boost_disable_low(struct rockchip_clk_pll *pll);
#ifdef CONFIG_DEBUG_FS
static HLIST_HEAD(clk_boost_list);
static DEFINE_MUTEX(clk_boost_lock);
#endif
#else
static inline void rockchip_boost_disable_low(struct rockchip_clk_pll *pll) {}
#endif
#define MHZ (1000UL * 1000UL)
#define KHZ (1000UL)
/* CLK_PLL_TYPE_RK3066_AUTO type ops */
#define PLL_FREF_MIN (269 * KHZ)
#define PLL_FREF_MAX (2200 * MHZ)
#define PLL_FVCO_MIN (440 * MHZ)
#define PLL_FVCO_MAX (2200 * MHZ)
#define PLL_FOUT_MIN (27500 * KHZ)
#define PLL_FOUT_MAX (2200 * MHZ)
#define PLL_NF_MAX (4096)
#define PLL_NR_MAX (64)
#define PLL_NO_MAX (16)
/* CLK_PLL_TYPE_RK3036/3366/3399_AUTO type ops */
#define MIN_FOUTVCO_FREQ (800 * MHZ)
#define MAX_FOUTVCO_FREQ (2000 * MHZ)
static struct rockchip_pll_rate_table auto_table;
int rockchip_pll_clk_adaptive_scaling(struct clk *clk, int sel)
{
struct clk *parent = clk_get_parent(clk);
struct rockchip_clk_pll *pll;
if (IS_ERR_OR_NULL(parent))
return -EINVAL;
pll = to_rockchip_clk_pll(__clk_get_hw(parent));
if (!pll)
return -EINVAL;
pll->sel = sel;
return 0;
}
EXPORT_SYMBOL(rockchip_pll_clk_adaptive_scaling);
int rockchip_pll_clk_rate_to_scale(struct clk *clk, unsigned long rate)
{
const struct rockchip_pll_rate_table *rate_table;
struct clk *parent = clk_get_parent(clk);
struct rockchip_clk_pll *pll;
unsigned int i;
if (IS_ERR_OR_NULL(parent))
return -EINVAL;
pll = to_rockchip_clk_pll(__clk_get_hw(parent));
if (!pll)
return -EINVAL;
rate_table = pll->rate_table;
for (i = 0; i < pll->rate_count; i++) {
if (rate >= rate_table[i].rate)
return i;
}
return -EINVAL;
}
EXPORT_SYMBOL(rockchip_pll_clk_rate_to_scale);
int rockchip_pll_clk_scale_to_rate(struct clk *clk, unsigned int scale)
{
const struct rockchip_pll_rate_table *rate_table;
struct clk *parent = clk_get_parent(clk);
struct rockchip_clk_pll *pll;
unsigned int i;
if (IS_ERR_OR_NULL(parent))
return -EINVAL;
pll = to_rockchip_clk_pll(__clk_get_hw(parent));
if (!pll)
return -EINVAL;
rate_table = pll->rate_table;
for (i = 0; i < pll->rate_count; i++) {
if (i == scale)
return rate_table[i].rate;
}
return -EINVAL;
}
EXPORT_SYMBOL(rockchip_pll_clk_scale_to_rate);
static struct rockchip_pll_rate_table *rk_pll_rate_table_get(void)
{
return &auto_table;
}
static int rockchip_pll_clk_set_postdiv(unsigned long fout_hz,
u32 *postdiv1,
u32 *postdiv2,
u32 *foutvco)
{
unsigned long freq;
if (fout_hz < MIN_FOUTVCO_FREQ) {
for (*postdiv1 = 1; *postdiv1 <= 7; (*postdiv1)++) {
for (*postdiv2 = 1; *postdiv2 <= 7; (*postdiv2)++) {
freq = fout_hz * (*postdiv1) * (*postdiv2);
if (freq >= MIN_FOUTVCO_FREQ &&
freq <= MAX_FOUTVCO_FREQ) {
*foutvco = freq;
return 0;
}
}
}
pr_err("CANNOT FIND postdiv1/2 to make fout in range from 800M to 2000M,fout = %lu\n",
fout_hz);
} else {
*postdiv1 = 1;
*postdiv2 = 1;
}
return 0;
}
static struct rockchip_pll_rate_table *
rockchip_pll_clk_set_by_auto(struct rockchip_clk_pll *pll,
unsigned long fin_hz,
unsigned long fout_hz)
{
struct rockchip_pll_rate_table *rate_table = rk_pll_rate_table_get();
/* FIXME set postdiv1/2 always 1*/
u32 foutvco = fout_hz;
u64 fin_64, frac_64;
u32 f_frac, postdiv1, postdiv2;
unsigned long clk_gcd = 0;
if (fin_hz == 0 || fout_hz == 0 || fout_hz == fin_hz)
return NULL;
rockchip_pll_clk_set_postdiv(fout_hz, &postdiv1, &postdiv2, &foutvco);
rate_table->postdiv1 = postdiv1;
rate_table->postdiv2 = postdiv2;
rate_table->dsmpd = 1;
if (fin_hz / MHZ * MHZ == fin_hz && fout_hz / MHZ * MHZ == fout_hz) {
fin_hz /= MHZ;
foutvco /= MHZ;
clk_gcd = gcd(fin_hz, foutvco);
rate_table->refdiv = fin_hz / clk_gcd;
rate_table->fbdiv = foutvco / clk_gcd;
rate_table->frac = 0;
pr_debug("fin = %lu, fout = %lu, clk_gcd = %lu, refdiv = %u, fbdiv = %u, postdiv1 = %u, postdiv2 = %u, frac = %u\n",
fin_hz, fout_hz, clk_gcd, rate_table->refdiv,
rate_table->fbdiv, rate_table->postdiv1,
rate_table->postdiv2, rate_table->frac);
} else {
pr_debug("frac div running, fin_hz = %lu, fout_hz = %lu, fin_INT_mhz = %lu, fout_INT_mhz = %lu\n",
fin_hz, fout_hz,
fin_hz / MHZ * MHZ,
fout_hz / MHZ * MHZ);
pr_debug("frac get postdiv1 = %u, postdiv2 = %u, foutvco = %u\n",
rate_table->postdiv1, rate_table->postdiv2, foutvco);
clk_gcd = gcd(fin_hz / MHZ, foutvco / MHZ);
rate_table->refdiv = fin_hz / MHZ / clk_gcd;
rate_table->fbdiv = foutvco / MHZ / clk_gcd;
pr_debug("frac get refdiv = %u, fbdiv = %u\n",
rate_table->refdiv, rate_table->fbdiv);
rate_table->frac = 0;
f_frac = (foutvco % MHZ);
fin_64 = fin_hz;
do_div(fin_64, (u64)rate_table->refdiv);
frac_64 = (u64)f_frac << 24;
do_div(frac_64, fin_64);
rate_table->frac = (u32)frac_64;
if (rate_table->frac > 0)
rate_table->dsmpd = 0;
pr_debug("frac = %x\n", rate_table->frac);
}
return rate_table;
}
static struct rockchip_pll_rate_table *
rockchip_rk3066_pll_clk_set_by_auto(struct rockchip_clk_pll *pll,
unsigned long fin_hz,
unsigned long fout_hz)
{
struct rockchip_pll_rate_table *rate_table = rk_pll_rate_table_get();
u32 nr, nf, no, nonr;
u32 nr_out, nf_out, no_out;
u32 n;
u32 numerator, denominator;
u64 fref, fvco, fout;
unsigned long clk_gcd = 0;
nr_out = PLL_NR_MAX + 1;
no_out = 0;
nf_out = 0;
if (fin_hz == 0 || fout_hz == 0 || fout_hz == fin_hz)
return NULL;
clk_gcd = gcd(fin_hz, fout_hz);
numerator = fout_hz / clk_gcd;
denominator = fin_hz / clk_gcd;
for (n = 1;; n++) {
nf = numerator * n;
nonr = denominator * n;
if (nf > PLL_NF_MAX || nonr > (PLL_NO_MAX * PLL_NR_MAX))
break;
for (no = 1; no <= PLL_NO_MAX; no++) {
if (!(no == 1 || !(no % 2)))
continue;
if (nonr % no)
continue;
nr = nonr / no;
if (nr > PLL_NR_MAX)
continue;
fref = fin_hz / nr;
if (fref < PLL_FREF_MIN || fref > PLL_FREF_MAX)
continue;
fvco = fref * nf;
if (fvco < PLL_FVCO_MIN || fvco > PLL_FVCO_MAX)
continue;
fout = fvco / no;
if (fout < PLL_FOUT_MIN || fout > PLL_FOUT_MAX)
continue;
/* select the best from all available PLL settings */
if ((no > no_out) ||
((no == no_out) && (nr < nr_out))) {
nr_out = nr;
nf_out = nf;
no_out = no;
}
}
}
/* output the best PLL setting */
if ((nr_out <= PLL_NR_MAX) && (no_out > 0)) {
rate_table->nr = nr_out;
rate_table->nf = nf_out;
rate_table->no = no_out;
} else {
return NULL;
}
return rate_table;
}
static u32
rockchip_rk3588_pll_frac_get(u32 m, u32 p, u32 s, u64 fin_hz, u64 fvco)
{
u64 fref, fout, ffrac;
u32 k = 0;
fref = fin_hz / p;
ffrac = fvco - (m * fref);
fout = ffrac * 65536;
k = fout / fref;
if (k > 32767) {
fref = fin_hz / p;
ffrac = ((m + 1) * fref) - fvco;
fout = ffrac * 65536;
k = ((fout * 10 / fref) + 7) / 10;
if (k > 32767)
k = 0;
else
k = ~k + 1;
}
return k;
}
static struct rockchip_pll_rate_table *
rockchip_rk3588_pll_frac_by_auto(unsigned long fin_hz, unsigned long fout_hz)
{
struct rockchip_pll_rate_table *rate_table = rk_pll_rate_table_get();
u64 fvco_min = 2250 * MHZ, fvco_max = 4500 * MHZ;
u32 p, m, s, k;
u64 fvco;
for (s = 0; s <= 6; s++) {
fvco = (u64)fout_hz << s;
if (fvco < fvco_min || fvco > fvco_max)
continue;
for (p = 1; p <= 4; p++) {
for (m = 64; m <= 1023; m++) {
if ((fvco >= m * fin_hz / p) && (fvco < (m + 1) * fin_hz / p)) {
k = rockchip_rk3588_pll_frac_get(m, p, s,
(u64)fin_hz,
fvco);
if (!k)
continue;
rate_table->p = p;
rate_table->s = s;
rate_table->k = k;
if (k > 32767)
rate_table->m = m + 1;
else
rate_table->m = m;
return rate_table;
}
}
}
}
return NULL;
}
static struct rockchip_pll_rate_table *
rockchip_rk3588_pll_clk_set_by_auto(struct rockchip_clk_pll *pll,
unsigned long fin_hz,
unsigned long fout_hz)
{
struct rockchip_pll_rate_table *rate_table = rk_pll_rate_table_get();
u64 fvco_min = 2250 * MHZ, fvco_max = 4500 * MHZ;
u64 fout_min = 37 * MHZ, fout_max = 4500 * MHZ;
u32 p, m, s;
u64 fvco;
if (fin_hz == 0 || fout_hz == 0 || fout_hz == fin_hz)
return NULL;
if (fout_hz > fout_max || fout_hz < fout_min)
return NULL;
if (fin_hz / MHZ * MHZ == fin_hz && fout_hz / MHZ * MHZ == fout_hz) {
for (s = 0; s <= 6; s++) {
fvco = (u64)fout_hz << s;
if (fvco < fvco_min || fvco > fvco_max)
continue;
for (p = 2; p <= 4; p++) {
for (m = 64; m <= 1023; m++) {
if (fvco == m * fin_hz / p) {
rate_table->p = p;
rate_table->m = m;
rate_table->s = s;
rate_table->k = 0;
return rate_table;
}
}
}
}
pr_err("CANNOT FIND Fout by auto,fout = %lu\n", fout_hz);
} else {
rate_table = rockchip_rk3588_pll_frac_by_auto(fin_hz, fout_hz);
if (!rate_table)
pr_err("CANNOT FIND Fout by auto,fout = %lu\n", fout_hz);
else
return rate_table;
}
return NULL;
}
static const struct rockchip_pll_rate_table *rockchip_get_pll_settings(
struct rockchip_clk_pll *pll, unsigned long rate)
{
const struct rockchip_pll_rate_table *rate_table = pll->rate_table;
int i;
for (i = 0; i < pll->rate_count; i++) {
if (rate == rate_table[i].rate) {
if (i < pll->sel) {
pll->scaling = rate;
return &rate_table[pll->sel];
}
pll->scaling = 0;
return &rate_table[i];
}
}
pll->scaling = 0;
if (pll->type == pll_rk3066)
return rockchip_rk3066_pll_clk_set_by_auto(pll, 24 * MHZ, rate);
else if (pll->type == pll_rk3588 || pll->type == pll_rk3588_core)
return rockchip_rk3588_pll_clk_set_by_auto(pll, 24 * MHZ, rate);
else
return rockchip_pll_clk_set_by_auto(pll, 24 * MHZ, rate);
}
static long rockchip_pll_round_rate(struct clk_hw *hw,
unsigned long drate, unsigned long *prate)
{
return drate;
}
/*
* Wait for the pll to reach the locked state.
* The calling set_rate function is responsible for making sure the
* grf regmap is available.
*/
static int rockchip_pll_wait_lock(struct rockchip_clk_pll *pll)
{
struct regmap *grf = pll->ctx->grf;
unsigned int val;
int ret;
ret = regmap_read_poll_timeout(grf, pll->lock_offset, val,
val & BIT(pll->lock_shift), 0, 1000);
if (ret)
pr_err("%s: timeout waiting for pll to lock\n", __func__);
return ret;
}
/*
* PLL used in RK3036
*/
#define RK3036_PLLCON(i) (i * 0x4)
#define RK3036_PLLCON0_FBDIV_MASK 0xfff
#define RK3036_PLLCON0_FBDIV_SHIFT 0
#define RK3036_PLLCON0_POSTDIV1_MASK 0x7
#define RK3036_PLLCON0_POSTDIV1_SHIFT 12
#define RK3036_PLLCON1_REFDIV_MASK 0x3f
#define RK3036_PLLCON1_REFDIV_SHIFT 0
#define RK3036_PLLCON1_POSTDIV2_MASK 0x7
#define RK3036_PLLCON1_POSTDIV2_SHIFT 6
#define RK3036_PLLCON1_LOCK_STATUS BIT(10)
#define RK3036_PLLCON1_DSMPD_MASK 0x1
#define RK3036_PLLCON1_DSMPD_SHIFT 12
#define RK3036_PLLCON1_PWRDOWN BIT(13)
#define RK3036_PLLCON1_PLLPDSEL BIT(15)
#define RK3036_PLLCON2_FRAC_MASK 0xffffff
#define RK3036_PLLCON2_FRAC_SHIFT 0
static int rockchip_rk3036_pll_wait_lock(struct rockchip_clk_pll *pll)
{
u32 pllcon;
int ret;
/*
* Lock time typical 250, max 500 input clock cycles @24MHz
* So define a very safe maximum of 1000us, meaning 24000 cycles.
*/
ret = readl_relaxed_poll_timeout(pll->reg_base + RK3036_PLLCON(1),
pllcon,
pllcon & RK3036_PLLCON1_LOCK_STATUS,
0, 1000);
if (ret)
pr_err("%s: timeout waiting for pll to lock\n", __func__);
return ret;
}
static unsigned long __maybe_unused
rockchip_rk3036_pll_con_to_rate(struct rockchip_clk_pll *pll,
u32 con0, u32 con1)
{
unsigned int fbdiv, postdiv1, refdiv, postdiv2;
u64 rate64 = 24000000;
fbdiv = ((con0 >> RK3036_PLLCON0_FBDIV_SHIFT) &
RK3036_PLLCON0_FBDIV_MASK);
postdiv1 = ((con0 >> RK3036_PLLCON0_POSTDIV1_SHIFT) &
RK3036_PLLCON0_POSTDIV1_MASK);
refdiv = ((con1 >> RK3036_PLLCON1_REFDIV_SHIFT) &
RK3036_PLLCON1_REFDIV_MASK);
postdiv2 = ((con1 >> RK3036_PLLCON1_POSTDIV2_SHIFT) &
RK3036_PLLCON1_POSTDIV2_MASK);
rate64 *= fbdiv;
do_div(rate64, refdiv);
do_div(rate64, postdiv1);
do_div(rate64, postdiv2);
return (unsigned long)rate64;
}
static void rockchip_rk3036_pll_get_params(struct rockchip_clk_pll *pll,
struct rockchip_pll_rate_table *rate)
{
u32 pllcon;
pllcon = readl_relaxed(pll->reg_base + RK3036_PLLCON(0));
rate->fbdiv = ((pllcon >> RK3036_PLLCON0_FBDIV_SHIFT)
& RK3036_PLLCON0_FBDIV_MASK);
rate->postdiv1 = ((pllcon >> RK3036_PLLCON0_POSTDIV1_SHIFT)
& RK3036_PLLCON0_POSTDIV1_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3036_PLLCON(1));
rate->refdiv = ((pllcon >> RK3036_PLLCON1_REFDIV_SHIFT)
& RK3036_PLLCON1_REFDIV_MASK);
rate->postdiv2 = ((pllcon >> RK3036_PLLCON1_POSTDIV2_SHIFT)
& RK3036_PLLCON1_POSTDIV2_MASK);
rate->dsmpd = ((pllcon >> RK3036_PLLCON1_DSMPD_SHIFT)
& RK3036_PLLCON1_DSMPD_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3036_PLLCON(2));
rate->frac = ((pllcon >> RK3036_PLLCON2_FRAC_SHIFT)
& RK3036_PLLCON2_FRAC_MASK);
}
static unsigned long rockchip_rk3036_pll_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
struct rockchip_pll_rate_table cur;
u64 rate64 = prate, frac_rate64 = prate;
if (pll->sel && pll->scaling)
return pll->scaling;
rockchip_rk3036_pll_get_params(pll, &cur);
rate64 *= cur.fbdiv;
do_div(rate64, cur.refdiv);
if (cur.dsmpd == 0) {
/* fractional mode */
frac_rate64 *= cur.frac;
do_div(frac_rate64, cur.refdiv);
rate64 += frac_rate64 >> 24;
}
do_div(rate64, cur.postdiv1);
do_div(rate64, cur.postdiv2);
return (unsigned long)rate64;
}
static int rockchip_rk3036_pll_set_params(struct rockchip_clk_pll *pll,
const struct rockchip_pll_rate_table *rate)
{
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
struct rockchip_pll_rate_table cur;
u32 pllcon;
int rate_change_remuxed = 0;
int cur_parent;
int ret;
pr_debug("%s: rate settings for %lu fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
__func__, rate->rate, rate->fbdiv, rate->postdiv1, rate->refdiv,
rate->postdiv2, rate->dsmpd, rate->frac);
rockchip_rk3036_pll_get_params(pll, &cur);
cur.rate = 0;
if (!(pll->flags & ROCKCHIP_PLL_FIXED_MODE)) {
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
}
/* update pll values */
writel_relaxed(HIWORD_UPDATE(rate->fbdiv, RK3036_PLLCON0_FBDIV_MASK,
RK3036_PLLCON0_FBDIV_SHIFT) |
HIWORD_UPDATE(rate->postdiv1, RK3036_PLLCON0_POSTDIV1_MASK,
RK3036_PLLCON0_POSTDIV1_SHIFT),
pll->reg_base + RK3036_PLLCON(0));
writel_relaxed(HIWORD_UPDATE(rate->refdiv, RK3036_PLLCON1_REFDIV_MASK,
RK3036_PLLCON1_REFDIV_SHIFT) |
HIWORD_UPDATE(rate->postdiv2, RK3036_PLLCON1_POSTDIV2_MASK,
RK3036_PLLCON1_POSTDIV2_SHIFT) |
HIWORD_UPDATE(rate->dsmpd, RK3036_PLLCON1_DSMPD_MASK,
RK3036_PLLCON1_DSMPD_SHIFT),
pll->reg_base + RK3036_PLLCON(1));
/* GPLL CON2 is not HIWORD_MASK */
pllcon = readl_relaxed(pll->reg_base + RK3036_PLLCON(2));
pllcon &= ~(RK3036_PLLCON2_FRAC_MASK << RK3036_PLLCON2_FRAC_SHIFT);
pllcon |= rate->frac << RK3036_PLLCON2_FRAC_SHIFT;
writel_relaxed(pllcon, pll->reg_base + RK3036_PLLCON(2));
if (IS_ENABLED(CONFIG_ROCKCHIP_CLK_BOOST))
rockchip_boost_disable_low(pll);
/* wait for the pll to lock */
ret = rockchip_rk3036_pll_wait_lock(pll);
if (ret) {
pr_warn("%s: pll update unsuccessful, trying to restore old params\n",
__func__);
rockchip_rk3036_pll_set_params(pll, &cur);
}
if (rate_change_remuxed)
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return ret;
}
static int rockchip_rk3036_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
pr_debug("%s: changing %s to %lu with a parent rate of %lu\n",
__func__, __clk_get_name(hw->clk), drate, prate);
/* Get required rate settings from table */
rate = rockchip_get_pll_settings(pll, drate);
if (!rate) {
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
drate, __clk_get_name(hw->clk));
return -EINVAL;
}
return rockchip_rk3036_pll_set_params(pll, rate);
}
static int rockchip_rk3036_pll_enable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
writel(HIWORD_UPDATE(0, RK3036_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3036_PLLCON(1));
rockchip_rk3036_pll_wait_lock(pll);
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return 0;
}
static void rockchip_rk3036_pll_disable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
writel(HIWORD_UPDATE(RK3036_PLLCON1_PWRDOWN,
RK3036_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3036_PLLCON(1));
}
static int rockchip_rk3036_pll_is_enabled(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
u32 pllcon = readl(pll->reg_base + RK3036_PLLCON(1));
return !(pllcon & RK3036_PLLCON1_PWRDOWN);
}
static int rockchip_rk3036_pll_init(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
struct rockchip_pll_rate_table cur;
unsigned long drate;
if (!(pll->flags & ROCKCHIP_PLL_SYNC_RATE))
return 0;
drate = clk_hw_get_rate(hw);
rate = rockchip_get_pll_settings(pll, drate);
/* when no rate setting for the current rate, rely on clk_set_rate */
if (!rate)
return 0;
rockchip_rk3036_pll_get_params(pll, &cur);
pr_debug("%s: pll %s@%lu: Hz\n", __func__, __clk_get_name(hw->clk),
drate);
pr_debug("old - fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
cur.fbdiv, cur.postdiv1, cur.refdiv, cur.postdiv2,
cur.dsmpd, cur.frac);
pr_debug("new - fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
rate->fbdiv, rate->postdiv1, rate->refdiv, rate->postdiv2,
rate->dsmpd, rate->frac);
if (rate->fbdiv != cur.fbdiv || rate->postdiv1 != cur.postdiv1 ||
rate->refdiv != cur.refdiv || rate->postdiv2 != cur.postdiv2 ||
rate->dsmpd != cur.dsmpd ||
(!cur.dsmpd && (rate->frac != cur.frac))) {
struct clk *parent = clk_get_parent(hw->clk);
if (!parent) {
pr_warn("%s: parent of %s not available\n",
__func__, __clk_get_name(hw->clk));
return 0;
}
pr_debug("%s: pll %s: rate params do not match rate table, adjusting\n",
__func__, __clk_get_name(hw->clk));
rockchip_rk3036_pll_set_params(pll, rate);
}
return 0;
}
static const struct clk_ops rockchip_rk3036_pll_clk_norate_ops = {
.recalc_rate = rockchip_rk3036_pll_recalc_rate,
.enable = rockchip_rk3036_pll_enable,
.disable = rockchip_rk3036_pll_disable,
.is_enabled = rockchip_rk3036_pll_is_enabled,
};
static const struct clk_ops rockchip_rk3036_pll_clk_ops = {
.recalc_rate = rockchip_rk3036_pll_recalc_rate,
.round_rate = rockchip_pll_round_rate,
.set_rate = rockchip_rk3036_pll_set_rate,
.enable = rockchip_rk3036_pll_enable,
.disable = rockchip_rk3036_pll_disable,
.is_enabled = rockchip_rk3036_pll_is_enabled,
.init = rockchip_rk3036_pll_init,
};
/*
* PLL used in RK3066, RK3188 and RK3288
*/
#define RK3066_PLL_RESET_DELAY(nr) ((nr * 500) / 24 + 1)
#define RK3066_PLLCON(i) (i * 0x4)
#define RK3066_PLLCON0_OD_MASK 0xf
#define RK3066_PLLCON0_OD_SHIFT 0
#define RK3066_PLLCON0_NR_MASK 0x3f
#define RK3066_PLLCON0_NR_SHIFT 8
#define RK3066_PLLCON1_NF_MASK 0x1fff
#define RK3066_PLLCON1_NF_SHIFT 0
#define RK3066_PLLCON2_NB_MASK 0xfff
#define RK3066_PLLCON2_NB_SHIFT 0
#define RK3066_PLLCON3_RESET (1 << 5)
#define RK3066_PLLCON3_PWRDOWN (1 << 1)
#define RK3066_PLLCON3_BYPASS (1 << 0)
static void rockchip_rk3066_pll_get_params(struct rockchip_clk_pll *pll,
struct rockchip_pll_rate_table *rate)
{
u32 pllcon;
pllcon = readl_relaxed(pll->reg_base + RK3066_PLLCON(0));
rate->nr = ((pllcon >> RK3066_PLLCON0_NR_SHIFT)
& RK3066_PLLCON0_NR_MASK) + 1;
rate->no = ((pllcon >> RK3066_PLLCON0_OD_SHIFT)
& RK3066_PLLCON0_OD_MASK) + 1;
pllcon = readl_relaxed(pll->reg_base + RK3066_PLLCON(1));
rate->nf = ((pllcon >> RK3066_PLLCON1_NF_SHIFT)
& RK3066_PLLCON1_NF_MASK) + 1;
pllcon = readl_relaxed(pll->reg_base + RK3066_PLLCON(2));
rate->nb = ((pllcon >> RK3066_PLLCON2_NB_SHIFT)
& RK3066_PLLCON2_NB_MASK) + 1;
}
static unsigned long rockchip_rk3066_pll_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
struct rockchip_pll_rate_table cur;
u64 rate64 = prate;
u32 pllcon;
pllcon = readl_relaxed(pll->reg_base + RK3066_PLLCON(3));
if (pllcon & RK3066_PLLCON3_BYPASS) {
pr_debug("%s: pll %s is bypassed\n", __func__,
clk_hw_get_name(hw));
return prate;
}
if (pll->sel && pll->scaling)
return pll->scaling;
rockchip_rk3066_pll_get_params(pll, &cur);
rate64 *= cur.nf;
do_div(rate64, cur.nr);
do_div(rate64, cur.no);
return (unsigned long)rate64;
}
static int rockchip_rk3066_pll_set_params(struct rockchip_clk_pll *pll,
const struct rockchip_pll_rate_table *rate)
{
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
struct rockchip_pll_rate_table cur;
int rate_change_remuxed = 0;
int cur_parent;
int ret;
pr_debug("%s: rate settings for %lu (nr, no, nf): (%d, %d, %d)\n",
__func__, rate->rate, rate->nr, rate->no, rate->nf);
rockchip_rk3066_pll_get_params(pll, &cur);
cur.rate = 0;
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
/* enter reset mode */
writel(HIWORD_UPDATE(RK3066_PLLCON3_RESET, RK3066_PLLCON3_RESET, 0),
pll->reg_base + RK3066_PLLCON(3));
/* update pll values */
writel(HIWORD_UPDATE(rate->nr - 1, RK3066_PLLCON0_NR_MASK,
RK3066_PLLCON0_NR_SHIFT) |
HIWORD_UPDATE(rate->no - 1, RK3066_PLLCON0_OD_MASK,
RK3066_PLLCON0_OD_SHIFT),
pll->reg_base + RK3066_PLLCON(0));
writel_relaxed(HIWORD_UPDATE(rate->nf - 1, RK3066_PLLCON1_NF_MASK,
RK3066_PLLCON1_NF_SHIFT),
pll->reg_base + RK3066_PLLCON(1));
writel_relaxed(HIWORD_UPDATE(rate->nb - 1, RK3066_PLLCON2_NB_MASK,
RK3066_PLLCON2_NB_SHIFT),
pll->reg_base + RK3066_PLLCON(2));
/* leave reset and wait the reset_delay */
writel(HIWORD_UPDATE(0, RK3066_PLLCON3_RESET, 0),
pll->reg_base + RK3066_PLLCON(3));
udelay(RK3066_PLL_RESET_DELAY(rate->nr));
/* wait for the pll to lock */
ret = rockchip_pll_wait_lock(pll);
if (ret) {
pr_warn("%s: pll update unsuccessful, trying to restore old params\n",
__func__);
rockchip_rk3066_pll_set_params(pll, &cur);
}
if (rate_change_remuxed)
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return ret;
}
static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
unsigned long old_rate = rockchip_rk3066_pll_recalc_rate(hw, prate);
struct regmap *grf = pll->ctx->grf;
int ret;
if (IS_ERR(grf)) {
pr_debug("%s: grf regmap not available, aborting rate change\n",
__func__);
return PTR_ERR(grf);
}
pr_debug("%s: changing %s from %lu to %lu with a parent rate of %lu\n",
__func__, clk_hw_get_name(hw), old_rate, drate, prate);
/* Get required rate settings from table */
rate = rockchip_get_pll_settings(pll, drate);
if (!rate) {
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
drate, clk_hw_get_name(hw));
return -EINVAL;
}
ret = rockchip_rk3066_pll_set_params(pll, rate);
if (ret)
pll->scaling = 0;
return ret;
}
static int rockchip_rk3066_pll_enable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
writel(HIWORD_UPDATE(0, RK3066_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3066_PLLCON(3));
rockchip_pll_wait_lock(pll);
return 0;
}
static void rockchip_rk3066_pll_disable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
writel(HIWORD_UPDATE(RK3066_PLLCON3_PWRDOWN,
RK3066_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3066_PLLCON(3));
}
static int rockchip_rk3066_pll_is_enabled(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
u32 pllcon = readl(pll->reg_base + RK3066_PLLCON(3));
return !(pllcon & RK3066_PLLCON3_PWRDOWN);
}
static int rockchip_rk3066_pll_init(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
struct rockchip_pll_rate_table cur;
unsigned long drate;
if (!(pll->flags & ROCKCHIP_PLL_SYNC_RATE))
return 0;
drate = clk_hw_get_rate(hw);
rate = rockchip_get_pll_settings(pll, drate);
/* when no rate setting for the current rate, rely on clk_set_rate */
if (!rate)
return 0;
rockchip_rk3066_pll_get_params(pll, &cur);
pr_debug("%s: pll %s@%lu: nr (%d:%d); no (%d:%d); nf(%d:%d), nb(%d:%d)\n",
__func__, clk_hw_get_name(hw), drate, rate->nr, cur.nr,
rate->no, cur.no, rate->nf, cur.nf, rate->nb, cur.nb);
if (rate->nr != cur.nr || rate->no != cur.no || rate->nf != cur.nf
|| rate->nb != cur.nb) {
pr_debug("%s: pll %s: rate params do not match rate table, adjusting\n",
__func__, clk_hw_get_name(hw));
rockchip_rk3066_pll_set_params(pll, rate);
}
return 0;
}
static const struct clk_ops rockchip_rk3066_pll_clk_norate_ops = {
.recalc_rate = rockchip_rk3066_pll_recalc_rate,
.enable = rockchip_rk3066_pll_enable,
.disable = rockchip_rk3066_pll_disable,
.is_enabled = rockchip_rk3066_pll_is_enabled,
};
static const struct clk_ops rockchip_rk3066_pll_clk_ops = {
.recalc_rate = rockchip_rk3066_pll_recalc_rate,
.round_rate = rockchip_pll_round_rate,
.set_rate = rockchip_rk3066_pll_set_rate,
.enable = rockchip_rk3066_pll_enable,
.disable = rockchip_rk3066_pll_disable,
.is_enabled = rockchip_rk3066_pll_is_enabled,
.init = rockchip_rk3066_pll_init,
};
/*
* PLL used in RK3399
*/
#define RK3399_PLLCON(i) (i * 0x4)
#define RK3399_PLLCON0_FBDIV_MASK 0xfff
#define RK3399_PLLCON0_FBDIV_SHIFT 0
#define RK3399_PLLCON1_REFDIV_MASK 0x3f
#define RK3399_PLLCON1_REFDIV_SHIFT 0
#define RK3399_PLLCON1_POSTDIV1_MASK 0x7
#define RK3399_PLLCON1_POSTDIV1_SHIFT 8
#define RK3399_PLLCON1_POSTDIV2_MASK 0x7
#define RK3399_PLLCON1_POSTDIV2_SHIFT 12
#define RK3399_PLLCON2_FRAC_MASK 0xffffff
#define RK3399_PLLCON2_FRAC_SHIFT 0
#define RK3399_PLLCON2_LOCK_STATUS BIT(31)
#define RK3399_PLLCON3_PWRDOWN BIT(0)
#define RK3399_PLLCON3_DSMPD_MASK 0x1
#define RK3399_PLLCON3_DSMPD_SHIFT 3
static int rockchip_rk3399_pll_wait_lock(struct rockchip_clk_pll *pll)
{
u32 pllcon;
int ret;
/*
* Lock time typical 250, max 500 input clock cycles @24MHz
* So define a very safe maximum of 1000us, meaning 24000 cycles.
*/
ret = readl_relaxed_poll_timeout(pll->reg_base + RK3399_PLLCON(2),
pllcon,
pllcon & RK3399_PLLCON2_LOCK_STATUS,
0, 1000);
if (ret)
pr_err("%s: timeout waiting for pll to lock\n", __func__);
return ret;
}
static void rockchip_rk3399_pll_get_params(struct rockchip_clk_pll *pll,
struct rockchip_pll_rate_table *rate)
{
u32 pllcon;
pllcon = readl_relaxed(pll->reg_base + RK3399_PLLCON(0));
rate->fbdiv = ((pllcon >> RK3399_PLLCON0_FBDIV_SHIFT)
& RK3399_PLLCON0_FBDIV_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3399_PLLCON(1));
rate->refdiv = ((pllcon >> RK3399_PLLCON1_REFDIV_SHIFT)
& RK3399_PLLCON1_REFDIV_MASK);
rate->postdiv1 = ((pllcon >> RK3399_PLLCON1_POSTDIV1_SHIFT)
& RK3399_PLLCON1_POSTDIV1_MASK);
rate->postdiv2 = ((pllcon >> RK3399_PLLCON1_POSTDIV2_SHIFT)
& RK3399_PLLCON1_POSTDIV2_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3399_PLLCON(2));
rate->frac = ((pllcon >> RK3399_PLLCON2_FRAC_SHIFT)
& RK3399_PLLCON2_FRAC_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3399_PLLCON(3));
rate->dsmpd = ((pllcon >> RK3399_PLLCON3_DSMPD_SHIFT)
& RK3399_PLLCON3_DSMPD_MASK);
}
static unsigned long rockchip_rk3399_pll_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
struct rockchip_pll_rate_table cur;
u64 rate64 = prate;
if (pll->sel && pll->scaling)
return pll->scaling;
rockchip_rk3399_pll_get_params(pll, &cur);
rate64 *= cur.fbdiv;
do_div(rate64, cur.refdiv);
if (cur.dsmpd == 0) {
/* fractional mode */
u64 frac_rate64 = prate * cur.frac;
do_div(frac_rate64, cur.refdiv);
rate64 += frac_rate64 >> 24;
}
do_div(rate64, cur.postdiv1);
do_div(rate64, cur.postdiv2);
return (unsigned long)rate64;
}
static int rockchip_rk3399_pll_set_params(struct rockchip_clk_pll *pll,
const struct rockchip_pll_rate_table *rate)
{
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
struct rockchip_pll_rate_table cur;
u32 pllcon;
int rate_change_remuxed = 0;
int cur_parent;
int ret;
pr_debug("%s: rate settings for %lu fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
__func__, rate->rate, rate->fbdiv, rate->postdiv1, rate->refdiv,
rate->postdiv2, rate->dsmpd, rate->frac);
rockchip_rk3399_pll_get_params(pll, &cur);
cur.rate = 0;
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
/* set pll power down */
writel(HIWORD_UPDATE(RK3399_PLLCON3_PWRDOWN,
RK3399_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3399_PLLCON(3));
/* update pll values */
writel_relaxed(HIWORD_UPDATE(rate->fbdiv, RK3399_PLLCON0_FBDIV_MASK,
RK3399_PLLCON0_FBDIV_SHIFT),
pll->reg_base + RK3399_PLLCON(0));
writel_relaxed(HIWORD_UPDATE(rate->refdiv, RK3399_PLLCON1_REFDIV_MASK,
RK3399_PLLCON1_REFDIV_SHIFT) |
HIWORD_UPDATE(rate->postdiv1, RK3399_PLLCON1_POSTDIV1_MASK,
RK3399_PLLCON1_POSTDIV1_SHIFT) |
HIWORD_UPDATE(rate->postdiv2, RK3399_PLLCON1_POSTDIV2_MASK,
RK3399_PLLCON1_POSTDIV2_SHIFT),
pll->reg_base + RK3399_PLLCON(1));
/* xPLL CON2 is not HIWORD_MASK */
pllcon = readl_relaxed(pll->reg_base + RK3399_PLLCON(2));
pllcon &= ~(RK3399_PLLCON2_FRAC_MASK << RK3399_PLLCON2_FRAC_SHIFT);
pllcon |= rate->frac << RK3399_PLLCON2_FRAC_SHIFT;
writel_relaxed(pllcon, pll->reg_base + RK3399_PLLCON(2));
writel_relaxed(HIWORD_UPDATE(rate->dsmpd, RK3399_PLLCON3_DSMPD_MASK,
RK3399_PLLCON3_DSMPD_SHIFT),
pll->reg_base + RK3399_PLLCON(3));
/* set pll power up */
writel(HIWORD_UPDATE(0,
RK3399_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3399_PLLCON(3));
/* wait for the pll to lock */
ret = rockchip_rk3399_pll_wait_lock(pll);
if (ret) {
pr_warn("%s: pll update unsuccessful, trying to restore old params\n",
__func__);
rockchip_rk3399_pll_set_params(pll, &cur);
}
if (rate_change_remuxed)
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return ret;
}
static int rockchip_rk3399_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
unsigned long old_rate = rockchip_rk3399_pll_recalc_rate(hw, prate);
int ret;
pr_debug("%s: changing %s from %lu to %lu with a parent rate of %lu\n",
__func__, __clk_get_name(hw->clk), old_rate, drate, prate);
/* Get required rate settings from table */
rate = rockchip_get_pll_settings(pll, drate);
if (!rate) {
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
drate, __clk_get_name(hw->clk));
return -EINVAL;
}
ret = rockchip_rk3399_pll_set_params(pll, rate);
if (ret)
pll->scaling = 0;
return ret;
}
static int rockchip_rk3399_pll_enable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
writel(HIWORD_UPDATE(0, RK3399_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3399_PLLCON(3));
rockchip_rk3399_pll_wait_lock(pll);
return 0;
}
static void rockchip_rk3399_pll_disable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
writel(HIWORD_UPDATE(RK3399_PLLCON3_PWRDOWN,
RK3399_PLLCON3_PWRDOWN, 0),
pll->reg_base + RK3399_PLLCON(3));
}
static int rockchip_rk3399_pll_is_enabled(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
u32 pllcon = readl(pll->reg_base + RK3399_PLLCON(3));
return !(pllcon & RK3399_PLLCON3_PWRDOWN);
}
static int rockchip_rk3399_pll_init(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
struct rockchip_pll_rate_table cur;
unsigned long drate;
if (!(pll->flags & ROCKCHIP_PLL_SYNC_RATE))
return 0;
drate = clk_hw_get_rate(hw);
rate = rockchip_get_pll_settings(pll, drate);
/* when no rate setting for the current rate, rely on clk_set_rate */
if (!rate)
return 0;
rockchip_rk3399_pll_get_params(pll, &cur);
pr_debug("%s: pll %s@%lu: Hz\n", __func__, __clk_get_name(hw->clk),
drate);
pr_debug("old - fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
cur.fbdiv, cur.postdiv1, cur.refdiv, cur.postdiv2,
cur.dsmpd, cur.frac);
pr_debug("new - fbdiv: %d, postdiv1: %d, refdiv: %d, postdiv2: %d, dsmpd: %d, frac: %d\n",
rate->fbdiv, rate->postdiv1, rate->refdiv, rate->postdiv2,
rate->dsmpd, rate->frac);
if (rate->fbdiv != cur.fbdiv || rate->postdiv1 != cur.postdiv1 ||
rate->refdiv != cur.refdiv || rate->postdiv2 != cur.postdiv2 ||
rate->dsmpd != cur.dsmpd ||
(!cur.dsmpd && (rate->frac != cur.frac))) {
struct clk *parent = clk_get_parent(hw->clk);
if (!parent) {
pr_warn("%s: parent of %s not available\n",
__func__, __clk_get_name(hw->clk));
return 0;
}
pr_debug("%s: pll %s: rate params do not match rate table, adjusting\n",
__func__, __clk_get_name(hw->clk));
rockchip_rk3399_pll_set_params(pll, rate);
}
return 0;
}
static const struct clk_ops rockchip_rk3399_pll_clk_norate_ops = {
.recalc_rate = rockchip_rk3399_pll_recalc_rate,
.enable = rockchip_rk3399_pll_enable,
.disable = rockchip_rk3399_pll_disable,
.is_enabled = rockchip_rk3399_pll_is_enabled,
};
static const struct clk_ops rockchip_rk3399_pll_clk_ops = {
.recalc_rate = rockchip_rk3399_pll_recalc_rate,
.round_rate = rockchip_pll_round_rate,
.set_rate = rockchip_rk3399_pll_set_rate,
.enable = rockchip_rk3399_pll_enable,
.disable = rockchip_rk3399_pll_disable,
.is_enabled = rockchip_rk3399_pll_is_enabled,
.init = rockchip_rk3399_pll_init,
};
/**
* PLL used in RK3588
*/
#define RK3588_PLLCON(i) (i * 0x4)
#define RK3588_PLLCON0_M_MASK 0x3ff
#define RK3588_PLLCON0_M_SHIFT 0
#define RK3588_PLLCON1_P_MASK 0x3f
#define RK3588_PLLCON1_P_SHIFT 0
#define RK3588_PLLCON1_S_MASK 0x7
#define RK3588_PLLCON1_S_SHIFT 6
#define RK3588_PLLCON2_K_MASK 0xffff
#define RK3588_PLLCON2_K_SHIFT 0
#define RK3588_PLLCON1_PWRDOWN BIT(13)
#define RK3588_PLLCON6_LOCK_STATUS BIT(15)
static int rockchip_rk3588_pll_wait_lock(struct rockchip_clk_pll *pll)
{
u32 pllcon;
int ret;
/*
* Lock time typical 250, max 500 input clock cycles @24MHz
* So define a very safe maximum of 1000us, meaning 24000 cycles.
*/
ret = readl_relaxed_poll_timeout(pll->reg_base + RK3588_PLLCON(6),
pllcon,
pllcon & RK3588_PLLCON6_LOCK_STATUS,
0, 1000);
if (ret)
pr_err("%s: timeout waiting for pll to lock\n", __func__);
return ret;
}
static long rockchip_rk3588_pll_round_rate(struct clk_hw *hw,
unsigned long drate, unsigned long *prate)
{
if ((drate < 37 * MHZ) || (drate > 4500 * MHZ))
return -EINVAL;
else
return drate;
}
static void rockchip_rk3588_pll_get_params(struct rockchip_clk_pll *pll,
struct rockchip_pll_rate_table *rate)
{
u32 pllcon;
pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(0));
rate->m = ((pllcon >> RK3588_PLLCON0_M_SHIFT)
& RK3588_PLLCON0_M_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(1));
rate->p = ((pllcon >> RK3588_PLLCON1_P_SHIFT)
& RK3588_PLLCON1_P_MASK);
rate->s = ((pllcon >> RK3588_PLLCON1_S_SHIFT)
& RK3588_PLLCON1_S_MASK);
pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(2));
rate->k = ((pllcon >> RK3588_PLLCON2_K_SHIFT)
& RK3588_PLLCON2_K_MASK);
}
static unsigned long rockchip_rk3588_pll_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
struct rockchip_pll_rate_table cur;
u64 rate64 = prate, postdiv;
if (pll->sel && pll->scaling)
return pll->scaling;
rockchip_rk3588_pll_get_params(pll, &cur);
if (cur.p == 0)
return prate;
rate64 *= cur.m;
do_div(rate64, cur.p);
if (cur.k & BIT(15)) {
/* fractional mode */
u64 frac_rate64;
cur.k = (~(cur.k - 1)) & RK3588_PLLCON2_K_MASK;
frac_rate64 = prate * cur.k;
postdiv = cur.p;
postdiv *= 65536;
do_div(frac_rate64, postdiv);
rate64 -= frac_rate64;
} else {
/* fractional mode */
u64 frac_rate64 = prate * cur.k;
postdiv = cur.p;
postdiv *= 65536;
do_div(frac_rate64, postdiv);
rate64 += frac_rate64;
}
rate64 = rate64 >> cur.s;
if (pll->type == pll_rk3588_ddr)
return (unsigned long)rate64 * 2;
else
return (unsigned long)rate64;
}
static int rockchip_rk3588_pll_set_params(struct rockchip_clk_pll *pll,
const struct rockchip_pll_rate_table *rate)
{
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
struct rockchip_pll_rate_table cur;
int rate_change_remuxed = 0;
int cur_parent;
int ret;
pr_debug("%s: rate settings for %lu p: %d, m: %d, s: %d, k: %d\n",
__func__, rate->rate, rate->p, rate->m, rate->s, rate->k);
rockchip_rk3588_pll_get_params(pll, &cur);
cur.rate = 0;
if (pll->type == pll_rk3588) {
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
}
/* set pll power down */
writel(HIWORD_UPDATE(RK3588_PLLCON1_PWRDOWN,
RK3588_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3588_PLLCON(1));
/* update pll values */
writel_relaxed(HIWORD_UPDATE(rate->m, RK3588_PLLCON0_M_MASK,
RK3588_PLLCON0_M_SHIFT),
pll->reg_base + RK3588_PLLCON(0));
writel_relaxed(HIWORD_UPDATE(rate->p, RK3588_PLLCON1_P_MASK,
RK3588_PLLCON1_P_SHIFT) |
HIWORD_UPDATE(rate->s, RK3588_PLLCON1_S_MASK,
RK3588_PLLCON1_S_SHIFT),
pll->reg_base + RK3588_PLLCON(1));
writel_relaxed(HIWORD_UPDATE(rate->k, RK3588_PLLCON2_K_MASK,
RK3588_PLLCON2_K_SHIFT),
pll->reg_base + RK3588_PLLCON(2));
/* set pll power up */
writel(HIWORD_UPDATE(0,
RK3588_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3588_PLLCON(1));
/* wait for the pll to lock */
ret = rockchip_rk3588_pll_wait_lock(pll);
if (ret) {
pr_warn("%s: pll update unsuccessful, trying to restore old params\n",
__func__);
rockchip_rk3588_pll_set_params(pll, &cur);
}
if ((pll->type == pll_rk3588) && rate_change_remuxed)
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return ret;
}
static int rockchip_rk3588_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct rockchip_pll_rate_table *rate;
unsigned long old_rate = rockchip_rk3588_pll_recalc_rate(hw, prate);
int ret;
pr_debug("%s: changing %s from %lu to %lu with a parent rate of %lu\n",
__func__, __clk_get_name(hw->clk), old_rate, drate, prate);
/* Get required rate settings from table */
rate = rockchip_get_pll_settings(pll, drate);
if (!rate) {
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
drate, __clk_get_name(hw->clk));
return -EINVAL;
}
ret = rockchip_rk3588_pll_set_params(pll, rate);
if (ret)
pll->scaling = 0;
return ret;
}
static int rockchip_rk3588_pll_enable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
writel(HIWORD_UPDATE(0, RK3588_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3588_PLLCON(1));
rockchip_rk3588_pll_wait_lock(pll);
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return 0;
}
static void rockchip_rk3588_pll_disable(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
struct clk_mux *pll_mux = &pll->pll_mux;
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
writel(HIWORD_UPDATE(RK3588_PLLCON1_PWRDOWN,
RK3588_PLLCON1_PWRDOWN, 0),
pll->reg_base + RK3588_PLLCON(1));
}
static int rockchip_rk3588_pll_is_enabled(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
u32 pllcon = readl(pll->reg_base + RK3588_PLLCON(1));
return !(pllcon & RK3588_PLLCON1_PWRDOWN);
}
static int rockchip_rk3588_pll_init(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll(hw);
if (!(pll->flags & ROCKCHIP_PLL_SYNC_RATE))
return 0;
return 0;
}
static const struct clk_ops rockchip_rk3588_pll_clk_norate_ops = {
.recalc_rate = rockchip_rk3588_pll_recalc_rate,
.enable = rockchip_rk3588_pll_enable,
.disable = rockchip_rk3588_pll_disable,
.is_enabled = rockchip_rk3588_pll_is_enabled,
};
static const struct clk_ops rockchip_rk3588_pll_clk_ops = {
.recalc_rate = rockchip_rk3588_pll_recalc_rate,
.round_rate = rockchip_rk3588_pll_round_rate,
.set_rate = rockchip_rk3588_pll_set_rate,
.enable = rockchip_rk3588_pll_enable,
.disable = rockchip_rk3588_pll_disable,
.is_enabled = rockchip_rk3588_pll_is_enabled,
.init = rockchip_rk3588_pll_init,
};
#ifdef CONFIG_ROCKCHIP_CLK_COMPENSATION
int rockchip_pll_clk_compensation(struct clk *clk, int ppm)
{
struct clk *parent = clk_get_parent(clk);
struct rockchip_clk_pll *pll;
static u32 frac, fbdiv, s, p;
bool negative;
u32 pllcon, pllcon0, pllcon2, fbdiv_mask, frac_mask, frac_shift;
u64 fracdiv, m, n;
if ((ppm > 1000) || (ppm < -1000))
return -EINVAL;
if (IS_ERR_OR_NULL(parent))
return -EINVAL;
pll = to_rockchip_clk_pll(__clk_get_hw(parent));
if (!pll)
return -EINVAL;
switch (pll->type) {
case pll_rk3036:
case pll_rk3328:
pllcon0 = RK3036_PLLCON(0);
pllcon2 = RK3036_PLLCON(2);
fbdiv_mask = RK3036_PLLCON0_FBDIV_MASK;
frac_mask = RK3036_PLLCON2_FRAC_MASK;
frac_shift = RK3036_PLLCON2_FRAC_SHIFT;
if (!frac)
writel(HIWORD_UPDATE(RK3036_PLLCON1_PLLPDSEL,
RK3036_PLLCON1_PLLPDSEL, 0),
pll->reg_base + RK3036_PLLCON(1));
break;
case pll_rk3066:
return -EINVAL;
case pll_rk3399:
pllcon0 = RK3399_PLLCON(0);
pllcon2 = RK3399_PLLCON(2);
fbdiv_mask = RK3399_PLLCON0_FBDIV_MASK;
frac_mask = RK3399_PLLCON2_FRAC_MASK;
frac_shift = RK3399_PLLCON2_FRAC_SHIFT;
break;
case pll_rk3588:
pllcon0 = RK3588_PLLCON(0);
pllcon2 = RK3588_PLLCON(2);
fbdiv_mask = RK3588_PLLCON0_M_MASK;
frac_mask = RK3588_PLLCON2_K_MASK;
frac_shift = RK3588_PLLCON2_K_SHIFT;
break;
default:
return -EINVAL;
}
negative = !!(ppm & BIT(31));
ppm = negative ? ~ppm + 1 : ppm;
switch (pll->type) {
case pll_rk3036:
case pll_rk3328:
case pll_rk3066:
case pll_rk3399:
/*
* delta frac frac ppm
* -------------- = (fbdiv + ----------) * ---------
* 1 << 24 1 << 24 1000000
*
*/
if (!frac) {
frac = readl_relaxed(pll->reg_base + pllcon2) & frac_mask;
fbdiv = readl_relaxed(pll->reg_base + pllcon0) & fbdiv_mask;
}
m = div64_u64((uint64_t)frac * ppm, 1000000);
n = div64_u64((uint64_t)ppm << 24, 1000000) * fbdiv;
fracdiv = negative ? frac - (m + n) : frac + (m + n);
if (!frac || fracdiv > frac_mask)
return -EINVAL;
pllcon = readl_relaxed(pll->reg_base + pllcon2);
pllcon &= ~(frac_mask << frac_shift);
pllcon |= fracdiv << frac_shift;
writel_relaxed(pllcon, pll->reg_base + pllcon2);
break;
case pll_rk3588:
if (!fbdiv) {
frac = readl_relaxed(pll->reg_base + pllcon2) & frac_mask;
fbdiv = readl_relaxed(pll->reg_base + pllcon0) & fbdiv_mask;
}
if (!frac) {
pllcon = readl_relaxed(pll->reg_base + RK3588_PLLCON(1));
s = ((pllcon >> RK3588_PLLCON1_S_SHIFT)
& RK3588_PLLCON1_S_MASK);
p = ((pllcon >> RK3588_PLLCON1_P_SHIFT)
& RK3588_PLLCON1_P_MASK);
m = div64_u64((uint64_t)clk_get_rate(clk) * ppm, 24000000);
n = div64_u64((uint64_t)m * 65536 * p * (1 << s), 1000000);
if (n > 32767)
return -EINVAL;
fracdiv = negative ? ~n + 1 : n;
} else if (frac & BIT(15)) {
frac = (~(frac - 1)) & RK3588_PLLCON2_K_MASK;
m = div64_u64((uint64_t)frac * ppm, 100000);
n = div64_u64((uint64_t)ppm * 65536 * fbdiv, 100000);
if (negative) {
fracdiv = frac + (div64_u64(m + n, 10));
if (fracdiv > 32767)
return -EINVAL;
fracdiv = ~fracdiv + 1;
} else {
s = div64_u64(m + n, 10);
if (frac >= s) {
fracdiv = frac - s;
if (fracdiv > 32767)
return -EINVAL;
fracdiv = ~fracdiv + 1;
} else {
fracdiv = s - frac;
if (fracdiv > 32767)
return -EINVAL;
}
}
} else {
m = div64_u64((uint64_t)frac * ppm, 100000);
n = div64_u64((uint64_t)ppm * 65536 * fbdiv, 100000);
if (!negative) {
fracdiv = frac + (div64_u64(m + n, 10));
if (fracdiv > 32767)
return -EINVAL;
} else {
s = div64_u64(m + n, 10);
if (frac >= s) {
fracdiv = frac - s;
if (fracdiv > 32767)
return -EINVAL;
} else {
fracdiv = s - frac;
if (fracdiv > 32767)
return -EINVAL;
fracdiv = ~fracdiv + 1;
}
}
}
writel_relaxed(HIWORD_UPDATE(fracdiv, frac_mask, frac_shift),
pll->reg_base + pllcon2);
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(rockchip_pll_clk_compensation);
#endif
/*
* Common registering of pll clocks
*/
struct clk *rockchip_clk_register_pll(struct rockchip_clk_provider *ctx,
enum rockchip_pll_type pll_type,
const char *name, const char *const *parent_names,
u8 num_parents, int con_offset, int grf_lock_offset,
int lock_shift, int mode_offset, int mode_shift,
struct rockchip_pll_rate_table *rate_table,
unsigned long flags, u8 clk_pll_flags)
{
const char *pll_parents[3];
struct clk_init_data init;
struct rockchip_clk_pll *pll;
struct clk_mux *pll_mux;
struct clk *pll_clk, *mux_clk;
char pll_name[20];
if ((pll_type != pll_rk3328 && num_parents != 2) ||
(pll_type == pll_rk3328 && num_parents != 1)) {
pr_err("%s: needs two parent clocks\n", __func__);
return ERR_PTR(-EINVAL);
}
/* name the actual pll */
snprintf(pll_name, sizeof(pll_name), "pll_%s", name);
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
/* create the mux on top of the real pll */
pll->pll_mux_ops = &clk_mux_ops;
pll_mux = &pll->pll_mux;
pll_mux->reg = ctx->reg_base + mode_offset;
pll_mux->shift = mode_shift;
if (pll_type == pll_rk3328)
pll_mux->mask = PLL_RK3328_MODE_MASK;
else
pll_mux->mask = PLL_MODE_MASK;
pll_mux->flags = 0;
pll_mux->lock = &ctx->lock;
pll_mux->hw.init = &init;
pll_mux->flags |= CLK_MUX_HIWORD_MASK;
/* the actual muxing is xin24m, pll-output, xin32k */
pll_parents[0] = parent_names[0];
pll_parents[1] = pll_name;
pll_parents[2] = parent_names[1];
init.name = name;
init.flags = CLK_SET_RATE_PARENT;
init.ops = pll->pll_mux_ops;
init.parent_names = pll_parents;
if (pll_type == pll_rk3328)
init.num_parents = 2;
else
init.num_parents = ARRAY_SIZE(pll_parents);
mux_clk = clk_register(NULL, &pll_mux->hw);
if (IS_ERR(mux_clk))
goto err_mux;
/* now create the actual pll */
init.name = pll_name;
#ifndef CONFIG_ROCKCHIP_LOW_PERFORMANCE
if (clk_pll_flags & ROCKCHIP_PLL_ALLOW_POWER_DOWN)
init.flags = flags;
else
/* keep all plls untouched for now */
init.flags = flags | CLK_IGNORE_UNUSED;
#else
init.flags = flags;
#endif
init.parent_names = &parent_names[0];
init.num_parents = 1;
if (rate_table) {
int len;
/* find count of rates in rate_table */
for (len = 0; rate_table[len].rate != 0; )
len++;
pll->rate_count = len;
pll->rate_table = kmemdup(rate_table,
pll->rate_count *
sizeof(struct rockchip_pll_rate_table),
GFP_KERNEL);
WARN(!pll->rate_table,
"%s: could not allocate rate table for %s\n",
__func__, name);
}
switch (pll_type) {
case pll_rk3036:
case pll_rk3328:
if (!pll->rate_table)
init.ops = &rockchip_rk3036_pll_clk_norate_ops;
else
init.ops = &rockchip_rk3036_pll_clk_ops;
break;
#ifdef CONFIG_ROCKCHIP_PLL_RK3066
case pll_rk3066:
if (!pll->rate_table || IS_ERR(ctx->grf))
init.ops = &rockchip_rk3066_pll_clk_norate_ops;
else
init.ops = &rockchip_rk3066_pll_clk_ops;
break;
#endif
#ifdef CONFIG_ROCKCHIP_PLL_RK3399
case pll_rk3399:
if (!pll->rate_table)
init.ops = &rockchip_rk3399_pll_clk_norate_ops;
else
init.ops = &rockchip_rk3399_pll_clk_ops;
break;
#endif
#ifdef CONFIG_ROCKCHIP_PLL_RK3588
case pll_rk3588:
case pll_rk3588_core:
case pll_rk3588_ddr:
if (!pll->rate_table)
init.ops = &rockchip_rk3588_pll_clk_norate_ops;
else
init.ops = &rockchip_rk3588_pll_clk_ops;
init.flags = flags;
break;
#endif
default:
pr_warn("%s: Unknown pll type for pll clk %s\n",
__func__, name);
}
pll->hw.init = &init;
pll->type = pll_type;
pll->reg_base = ctx->reg_base + con_offset;
pll->lock_offset = grf_lock_offset;
pll->lock_shift = lock_shift;
pll->flags = clk_pll_flags;
pll->lock = &ctx->lock;
pll->ctx = ctx;
pll_clk = clk_register(NULL, &pll->hw);
if (IS_ERR(pll_clk)) {
pr_err("%s: failed to register pll clock %s : %ld\n",
__func__, name, PTR_ERR(pll_clk));
goto err_pll;
}
return mux_clk;
err_pll:
kfree(pll->rate_table);
clk_unregister(mux_clk);
mux_clk = pll_clk;
err_mux:
kfree(pll);
return mux_clk;
}
#ifdef CONFIG_ROCKCHIP_CLK_BOOST
static unsigned long rockchip_pll_con_to_rate(struct rockchip_clk_pll *pll,
u32 con0, u32 con1)
{
switch (pll->type) {
case pll_rk3036:
case pll_rk3328:
return rockchip_rk3036_pll_con_to_rate(pll, con0, con1);
case pll_rk3066:
break;
case pll_rk3399:
break;
default:
pr_warn("%s: Unknown pll type\n", __func__);
}
return 0;
}
void rockchip_boost_init(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll;
struct device_node *np;
u32 value, con0, con1;
if (!hw)
return;
pll = to_rockchip_clk_pll(hw);
np = of_parse_phandle(pll->ctx->cru_node, "rockchip,boost", 0);
if (!np) {
pr_debug("%s: failed to get boost np\n", __func__);
return;
}
pll->boost = syscon_node_to_regmap(np);
if (IS_ERR(pll->boost)) {
pr_debug("%s: failed to get boost regmap\n", __func__);
return;
}
if (!of_property_read_u32(np, "rockchip,boost-low-con0", &con0) &&
!of_property_read_u32(np, "rockchip,boost-low-con1", &con1)) {
pr_debug("boost-low-con=0x%x 0x%x\n", con0, con1);
regmap_write(pll->boost, BOOST_PLL_L_CON(0),
HIWORD_UPDATE(con0, BOOST_PLL_CON_MASK, 0));
regmap_write(pll->boost, BOOST_PLL_L_CON(1),
HIWORD_UPDATE(con1, BOOST_PLL_CON_MASK, 0));
pll->boost_low_rate = rockchip_pll_con_to_rate(pll, con0,
con1);
pr_debug("boost-low-rate=%lu\n", pll->boost_low_rate);
}
if (!of_property_read_u32(np, "rockchip,boost-high-con0", &con0) &&
!of_property_read_u32(np, "rockchip,boost-high-con1", &con1)) {
pr_debug("boost-high-con=0x%x 0x%x\n", con0, con1);
regmap_write(pll->boost, BOOST_PLL_H_CON(0),
HIWORD_UPDATE(con0, BOOST_PLL_CON_MASK, 0));
regmap_write(pll->boost, BOOST_PLL_H_CON(1),
HIWORD_UPDATE(con1, BOOST_PLL_CON_MASK, 0));
pll->boost_high_rate = rockchip_pll_con_to_rate(pll, con0,
con1);
pr_debug("boost-high-rate=%lu\n", pll->boost_high_rate);
}
if (!of_property_read_u32(np, "rockchip,boost-backup-pll", &value)) {
pr_debug("boost-backup-pll=0x%x\n", value);
regmap_write(pll->boost, BOOST_CLK_CON,
HIWORD_UPDATE(value, BOOST_BACKUP_PLL_MASK,
BOOST_BACKUP_PLL_SHIFT));
}
if (!of_property_read_u32(np, "rockchip,boost-backup-pll-usage",
&pll->boost_backup_pll_usage)) {
pr_debug("boost-backup-pll-usage=0x%x\n",
pll->boost_backup_pll_usage);
regmap_write(pll->boost, BOOST_CLK_CON,
HIWORD_UPDATE(pll->boost_backup_pll_usage,
BOOST_BACKUP_PLL_USAGE_MASK,
BOOST_BACKUP_PLL_USAGE_SHIFT));
}
if (!of_property_read_u32(np, "rockchip,boost-switch-threshold",
&value)) {
pr_debug("boost-switch-threshold=0x%x\n", value);
regmap_write(pll->boost, BOOST_SWITCH_THRESHOLD, value);
}
if (!of_property_read_u32(np, "rockchip,boost-statis-threshold",
&value)) {
pr_debug("boost-statis-threshold=0x%x\n", value);
regmap_write(pll->boost, BOOST_STATIS_THRESHOLD, value);
}
if (!of_property_read_u32(np, "rockchip,boost-statis-enable",
&value)) {
pr_debug("boost-statis-enable=0x%x\n", value);
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(value, BOOST_STATIS_ENABLE_MASK,
BOOST_STATIS_ENABLE_SHIFT));
}
if (!of_property_read_u32(np, "rockchip,boost-enable", &value)) {
pr_debug("boost-enable=0x%x\n", value);
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(value, BOOST_ENABLE_MASK,
BOOST_ENABLE_SHIFT));
if (value)
pll->boost_enabled = true;
}
#ifdef CONFIG_DEBUG_FS
if (pll->boost_enabled) {
mutex_lock(&clk_boost_lock);
hlist_add_head(&pll->debug_node, &clk_boost_list);
mutex_unlock(&clk_boost_lock);
}
#endif
}
void rockchip_boost_enable_recovery_sw_low(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll;
unsigned int val;
if (!hw)
return;
pll = to_rockchip_clk_pll(hw);
if (!pll->boost_enabled)
return;
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(1, BOOST_RECOVERY_MASK,
BOOST_RECOVERY_SHIFT));
do {
regmap_read(pll->boost, BOOST_FSM_STATUS, &val);
} while (!(val & BOOST_BUSY_STATE));
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(1, BOOST_SW_CTRL_MASK,
BOOST_SW_CTRL_SHIFT) |
HIWORD_UPDATE(1, BOOST_LOW_FREQ_EN_MASK,
BOOST_LOW_FREQ_EN_SHIFT));
}
static void rockchip_boost_disable_low(struct rockchip_clk_pll *pll)
{
if (!pll->boost_enabled)
return;
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(0, BOOST_LOW_FREQ_EN_MASK,
BOOST_LOW_FREQ_EN_SHIFT));
}
void rockchip_boost_disable_recovery_sw(struct clk_hw *hw)
{
struct rockchip_clk_pll *pll;
if (!hw)
return;
pll = to_rockchip_clk_pll(hw);
if (!pll->boost_enabled)
return;
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(0, BOOST_RECOVERY_MASK,
BOOST_RECOVERY_SHIFT));
regmap_write(pll->boost, BOOST_BOOST_CON,
HIWORD_UPDATE(0, BOOST_SW_CTRL_MASK,
BOOST_SW_CTRL_SHIFT));
}
void rockchip_boost_add_core_div(struct clk_hw *hw, unsigned long prate)
{
struct rockchip_clk_pll *pll;
unsigned int div;
if (!hw)
return;
pll = to_rockchip_clk_pll(hw);
if (!pll->boost_enabled || pll->boost_backup_pll_rate == prate)
return;
/* todo */
if (pll->boost_backup_pll_usage == BOOST_BACKUP_PLL_USAGE_TARGET)
return;
/*
* cpu clock rate should be less than or equal to
* low rate when change pll rate in boost module
*/
if (pll->boost_low_rate && prate > pll->boost_low_rate) {
div = DIV_ROUND_UP(prate, pll->boost_low_rate) - 1;
regmap_write(pll->boost, BOOST_CLK_CON,
HIWORD_UPDATE(div, BOOST_CORE_DIV_MASK,
BOOST_CORE_DIV_SHIFT));
pll->boost_backup_pll_rate = prate;
}
}
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#ifndef MODULE
static int boost_summary_show(struct seq_file *s, void *data)
{
struct rockchip_clk_pll *pll = (struct rockchip_clk_pll *)s->private;
u32 boost_count = 0;
u32 freq_cnt0 = 0, freq_cnt1 = 0;
u64 freq_cnt = 0, high_freq_time = 0;
u32 short_count = 0, short_threshold = 0;
u32 interval_time = 0;
seq_puts(s, " device boost_count high_freq_count high_freq_time short_count short_threshold interval_count\n");
seq_puts(s, "------------------------------------------------------------------------------------------------------\n");
seq_printf(s, " %s\n", clk_hw_get_name(&pll->hw));
regmap_read(pll->boost, BOOST_SWITCH_CNT, &boost_count);
regmap_read(pll->boost, BOOST_HIGH_PERF_CNT0, &freq_cnt0);
regmap_read(pll->boost, BOOST_HIGH_PERF_CNT1, &freq_cnt1);
freq_cnt = ((u64)freq_cnt1 << 32) + (u64)freq_cnt0;
high_freq_time = freq_cnt;
do_div(high_freq_time, 24);
regmap_read(pll->boost, BOOST_SHORT_SWITCH_CNT, &short_count);
regmap_read(pll->boost, BOOST_STATIS_THRESHOLD, &short_threshold);
regmap_read(pll->boost, BOOST_SWITCH_THRESHOLD, &interval_time);
seq_printf(s, "%22u %17llu %15llu %12u %16u %15u\n",
boost_count, freq_cnt, high_freq_time, short_count,
short_threshold, interval_time);
return 0;
}
static int boost_summary_open(struct inode *inode, struct file *file)
{
return single_open(file, boost_summary_show, inode->i_private);
}
static const struct file_operations boost_summary_fops = {
.open = boost_summary_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int boost_config_show(struct seq_file *s, void *data)
{
struct rockchip_clk_pll *pll = (struct rockchip_clk_pll *)s->private;
seq_printf(s, "boost_enabled: %d\n", pll->boost_enabled);
seq_printf(s, "boost_low_rate: %lu\n", pll->boost_low_rate);
seq_printf(s, "boost_high_rate: %lu\n", pll->boost_high_rate);
return 0;
}
static int boost_config_open(struct inode *inode, struct file *file)
{
return single_open(file, boost_config_show, inode->i_private);
}
static const struct file_operations boost_config_fops = {
.open = boost_config_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int boost_debug_create_one(struct rockchip_clk_pll *pll,
struct dentry *rootdir)
{
struct dentry *pdentry, *d;
pdentry = debugfs_lookup(clk_hw_get_name(&pll->hw), rootdir);
if (!pdentry) {
pr_err("%s: failed to lookup %s dentry\n", __func__,
clk_hw_get_name(&pll->hw));
return -ENOMEM;
}
d = debugfs_create_file("boost_summary", 0444, pdentry,
pll, &boost_summary_fops);
if (!d) {
pr_err("%s: failed to create boost_summary file\n", __func__);
return -ENOMEM;
}
d = debugfs_create_file("boost_config", 0444, pdentry,
pll, &boost_config_fops);
if (!d) {
pr_err("%s: failed to create boost config file\n", __func__);
return -ENOMEM;
}
return 0;
}
static int __init boost_debug_init(void)
{
struct rockchip_clk_pll *pll;
struct dentry *rootdir;
rootdir = debugfs_lookup("clk", NULL);
if (!rootdir) {
pr_err("%s: failed to lookup clk dentry\n", __func__);
return -ENOMEM;
}
mutex_lock(&clk_boost_lock);
hlist_for_each_entry(pll, &clk_boost_list, debug_node)
boost_debug_create_one(pll, rootdir);
mutex_unlock(&clk_boost_lock);
return 0;
}
late_initcall(boost_debug_init);
#endif /* MODULE */
#endif /* CONFIG_DEBUG_FS */
#endif /* CONFIG_ROCKCHIP_CLK_BOOST */