blob: 3aeae8667ff248cfb0e336ab33297c639d394a78 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Synaptics Incorporated
*
* Author: Jisheng Zhang <jszhang@kernel.org>
*
*/
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gcd.h>
#define CTRL 0x0
#define PD BIT(0)
#define RESETN BIT(1)
#define DM_SHIFT 2
#define DM_MASK (0x3f << 2)
#define DN_SHIFT 8
#define DN_MASK (0x7ff << 8)
#define MODE_SHIFT 19
#define MODE_MASK (0x3 << 19)
#define MODE_INT 0x0
#define MODE_FRAC 0x1
#define MODE_SSC 0x2
#define CTRL1 0x4
#define CTRL2 0x8
#define CTRL3 0xC
#define PDDP BIT(24)
#define DP_SHIFT 25
#define DP_MASK (0x7 << 25)
#define PDDP1 BIT(28)
#define DP1_SHIFT 29
#define DP1_MASK (0x7 << 29)
#define CTRL4 0x10
#define BYPASS BIT(0)
#define STATUS 0x14
#define PLL_LOCK BIT(0)
#define VCO_HIGH_LIMIT 3200
#define VCO_LOW_LIMIT 800
#define MAX_DP ((1 << 3) - 1)
#define MAX_DM ((1 << 6) - 1)
#define MAX_DN ((1 << 11) - 1)
struct as370_pll {
void __iomem *ctrl;
void __iomem *bypass;
u8 bypass_shift;
struct clk *clks[2];
struct clk_hw hw;
struct clk_hw hw1;
struct clk_onecell_data onecell_data;
};
#define hw_to_as370_pll(hw) container_of(hw, struct as370_pll, hw)
#define hw1_to_as370_pll(hw) container_of(hw, struct as370_pll, hw1)
static inline u32 rdl(struct as370_pll *pll, u32 offset)
{
return readl_relaxed(pll->ctrl + offset);
}
static inline void wrl(struct as370_pll *pll, u32 offset, u32 data)
{
writel_relaxed(data, pll->ctrl + offset);
}
static unsigned long
as370_pll_clko_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 val, dp, dn, dm, mode;
unsigned long rate;
struct as370_pll *pll = hw_to_as370_pll(hw);
val = rdl(pll, CTRL4);
if (val & BYPASS)
return parent_rate;
val = rdl(pll, CTRL3);
if (val & PDDP)
return 0;
dp = (val & DP_MASK) >> DP_SHIFT;
val = rdl(pll, CTRL);
mode = (val & MODE_MASK) >> MODE_SHIFT;
dn = (val & DN_MASK) >> DN_SHIFT;
dm = (val & DM_MASK) >> DM_SHIFT;
rate = parent_rate * dn / dm / dp;
if (mode == MODE_INT) {
return rate;
} else if (mode == MODE_FRAC || mode == MODE_SSC) {
val = rdl(pll, CTRL1);
return rate + parent_rate * val / dm / dp / (1 << 24);
} else {
return 0;
}
}
static unsigned long
as370_pll_clko1_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 val, dp, dn, dm, mode;
unsigned long rate;
struct as370_pll *pll = hw1_to_as370_pll(hw);
val = rdl(pll, CTRL4);
if (val & BYPASS)
return parent_rate;
val = rdl(pll, CTRL3);
if (val & PDDP1)
return 0;
dp = (val & DP1_MASK) >> DP1_SHIFT;
val = rdl(pll, CTRL);
mode = (val & MODE_MASK) >> MODE_SHIFT;
dn = (val & DN_MASK) >> DN_SHIFT;
dm = (val & DM_MASK) >> DM_SHIFT;
rate = parent_rate * dn / dm / dp;
if (mode == MODE_INT) {
return rate;
} else if (mode == MODE_FRAC || mode == MODE_SSC) {
val = rdl(pll, CTRL1);
return rate + parent_rate * val / dm / dp / (1 << 24);
} else {
return 0;
}
}
static long
as370_pll_clko_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
u32 dp, dn, dm, vco, div;
unsigned long parent = *parent_rate;
u64 frac = 0;
parent /= 1000000;
rate /= 1000000;
if (!parent || !rate)
return as370_pll_clko_recalc_rate(hw, *parent_rate);
if (rate > VCO_HIGH_LIMIT)
return as370_pll_clko_recalc_rate(hw, *parent_rate);
if (rate < VCO_LOW_LIMIT) {
dp = VCO_LOW_LIMIT / rate + 1;
if (dp > MAX_DP)
return as370_pll_clko_recalc_rate(hw, *parent_rate);
vco = rate * dp;
} else {
dp = 1;
vco = rate;
}
div = gcd(vco, parent);
dn = vco / div;
dm = parent / div;
if ((dm > MAX_DM) || (dn > MAX_DN)) {
/*frac mode*/
dn /= dm;
if (dn > MAX_DN)
return as370_pll_clko_recalc_rate(hw, *parent_rate);
frac = dn % dm;
frac = (frac<<24)/dm + 1;
dm = 1;
}
rate = parent * dn / dm / dp;
if (frac) {
rate += parent * frac / dm / dp / (1 << 24);
}
return rate * 1000000;
}
static int
as370_pll_clko_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
u32 val, dp, dn, dm, vco, div;
u64 frac = 0;
struct as370_pll *pll = hw_to_as370_pll(hw);
unsigned long flags;
parent_rate /= 1000000;
rate /= 1000000;
if (!parent_rate || !rate)
return -EPERM;
if (rate > VCO_HIGH_LIMIT)
return -EPERM;
if (rate < VCO_LOW_LIMIT) {
dp = VCO_LOW_LIMIT / rate + 1;
if (dp > MAX_DP)
return -EPERM;
vco = rate * dp;
} else {
dp = 1;
vco = rate;
}
div = gcd(vco, parent_rate);
dn = vco / div;
dm = parent_rate / div;
if ((dm > MAX_DM) || (dn > MAX_DN)) {
/*frac mode*/
dn /= dm;
if (dn > MAX_DN)
return -EPERM;
frac = dn % dm;
frac = (frac<<24)/dm + 1;
dm = 1;
}
local_irq_save(flags);
val = readl_relaxed(pll->bypass);
val |= 1 << pll->bypass_shift;
writel_relaxed(val, pll->bypass);
/* enable PLL bypass */
val = rdl(pll, CTRL4);
wrl(pll, CTRL4, val | BYPASS);
/* power down pll*/
val = rdl(pll, CTRL3);
wrl(pll, CTRL3, val | PDDP);
val = rdl(pll, CTRL);
if (frac) {
val &= ~RESETN;
}
wrl(pll, CTRL, val);
/* update vco*/
val &= (~MODE_MASK) & (~DM_MASK) & (~DN_MASK);
if(frac) {
val |= MODE_FRAC;
wrl(pll, CTRL1, frac);
}
val |= dn << DN_SHIFT;
val |= dm << DM_SHIFT;
wrl(pll, CTRL, val);
/* udpate dp*/
val = rdl(pll, CTRL3);
val &= ~DP_MASK;
val |= (dp << DP_SHIFT);
wrl(pll, CTRL3, val);
/* wait at least 2 ref_clock */
udelay(1);
/* power up pll*/
val = rdl(pll, CTRL);
if (frac) {
val |= RESETN;
}
wrl(pll, CTRL, val);
val = rdl(pll, CTRL3);
val &= ~PDDP;
wrl(pll, CTRL3, val);
/* delay 500 reference divided cycles */
udelay(50);
/* wait pll lock */
val = rdl(pll, STATUS);
while (!(val & PLL_LOCK)) {
val = rdl(pll, STATUS);
cpu_relax();
}
/* disable bypass */
val = rdl(pll, CTRL4);
wrl(pll, CTRL4, val & (~BYPASS));
val = readl_relaxed(pll->bypass);
val &= ~(1 << pll->bypass_shift);
writel_relaxed(val, pll->bypass);
local_irq_restore(flags);
return 0;
}
static const struct clk_ops as370_pll_clko_ops = {
.recalc_rate = as370_pll_clko_recalc_rate,
.round_rate = as370_pll_clko_round_rate,
.set_rate = as370_pll_clko_set_rate,
};
static const struct clk_ops as370_pll_clko1_ops = {
.recalc_rate = as370_pll_clko1_recalc_rate,
};
static void __init as370_pll_setup(struct device_node *np)
{
struct clk_init_data init;
struct as370_pll *pll;
const char *parent_name;
char name[16];
int ret;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return;
pll->ctrl = of_iomap(np, 0);
if (WARN_ON(!pll->ctrl))
goto err_iomap_ctrl;
pll->bypass = of_iomap(np, 1);
if (WARN_ON(!pll->bypass))
goto err_iomap_bypass;
ret = of_property_read_u8(np, "bypass-shift", &pll->bypass_shift);
if (WARN_ON(ret))
goto err_read_bypass_shift;
parent_name = of_clk_get_parent_name(np, 0);
init.name = name;
snprintf(name, sizeof(name), "%s_clko", np->name);
init.ops = &as370_pll_clko_ops;
init.parent_names = &parent_name;
init.num_parents = 1;
init.flags = 0;
pll->hw.init = &init;
pll->clks[0] = clk_register(NULL, &pll->hw);
if (WARN_ON(IS_ERR(pll->clks[0])))
goto err_clk_register;
snprintf(name, sizeof(name), "%s_clko1", np->name);
init.ops = &as370_pll_clko1_ops;
init.parent_names = &parent_name;
init.num_parents = 1;
init.flags = 0;
pll->hw1.init = &init;
pll->clks[1] = clk_register(NULL, &pll->hw1);
if (WARN_ON(IS_ERR(pll->clks[1])))
goto err_clk1_register;
pll->onecell_data.clks = pll->clks;
pll->onecell_data.clk_num = 2;
ret = of_clk_add_provider(np, of_clk_src_onecell_get,
&pll->onecell_data);
if (WARN_ON(ret))
goto err_clk_add;
return;
err_clk_add:
clk_unregister(pll->clks[1]);
err_clk1_register:
clk_unregister(pll->clks[0]);
err_clk_register:
err_read_bypass_shift:
iounmap(pll->bypass);
err_iomap_bypass:
iounmap(pll->ctrl);
err_iomap_ctrl:
kfree(pll);
}
CLK_OF_DECLARE(as370_pll, "syna,as370-pll", as370_pll_setup);