/*
* Copyright 2018-2019 Arkmicro, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "clk-ark.h"
static int clk_pll_is_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
return !!(reg & ARK_PLL_ENA);
}
static int clk_pll_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg |= ARK_PLL_ENA;
writel(reg, clk->reg);
return 0;
}
static void clk_pll_disable(struct clk_hw *hwclk)
{
/* struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg &= ~ARK_PLL_ENA;
writel(reg, clk->reg); */
return;
}
static unsigned long clk_pll_recalc_rate(struct clk_hw *hwclk,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
unsigned long div, no, reg;
reg = readl(clk->reg);
no = (reg >> ARK_PLL_NO_OFFSET) & ARK_PLL_NO_MASK;
div = (reg >> ARK_PLL_DIV_OFFSET) & ARK_PLL_DIV_MASK;
pr_info("clk %s rate %lu.\n", clk_hw_get_name(hwclk), (parent_rate * div) / (1 << no));
return (parent_rate * div) / (1 << no);
}
static const struct clk_ops clk_pll_ops = {
.is_enabled = clk_pll_is_enable,
.enable = clk_pll_enable,
.disable = clk_pll_disable,
.recalc_rate = clk_pll_recalc_rate,
};
static int clk_dds_is_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg2);
return !!(reg & ARK_DDS_ENA);
}
static int clk_dds_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg2);
reg |= ARK_DDS_ENA;
writel(reg, clk->reg2);
return 0;
}
static void clk_dds_disable(struct clk_hw *hwclk)
{
/* struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg2);
reg &= ~ARK_DDS_ENA;
writel(reg, clk->reg2); */
return;
}
static unsigned long clk_dds_recalc_rate(struct clk_hw *hwclk,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 dto_inc, mul, div, reg, reg2;
u64 freqm;
reg = readl(clk->reg);
reg2 = readl(clk->reg2);
div = (reg2 >> ARK_DDS_DIV_OFFSET) & ARK_DDS_DIV_MASK;
div = 1 << div;
mul = (reg2 >> ARK_DDS_MUL_OFFSET) & ARK_DDS_MUL_MASK;
mul = 32 * (1 << mul);
dto_inc = (reg >> ARK_DDS_DTOINC_OFFSET) & ARK_DDS_DTOINC_MASK;
freqm = parent_rate * dto_inc * mul;
return do_div(freqm, (1 << 22) * div);
}
static void clk_dds_calc(unsigned long rate, unsigned long ref_freq, u32 *dto_inc)
{
u64 freqm;
if (rate < ARK_DDS_MIN_FREQ)
rate = ARK_DDS_MIN_FREQ;
if (rate > ARK_DDS_MAX_FREQ)
rate = ARK_DDS_MAX_FREQ;
freqm = rate * (1 << 22);
*dto_inc = do_div(freqm, ref_freq * 32);
}
static long clk_dds_round_rate(struct clk_hw *hwclk, unsigned long rate,
unsigned long *parent_rate)
{
u32 dto_inc;
u32 ref_freq = *parent_rate;
u64 freqm;
clk_dds_calc(rate, ref_freq, &dto_inc);
freqm = ref_freq * dto_inc * 32;
return do_div(freqm, 1 << 22);
}
static int clk_dds_set_rate(struct clk_hw *hwclk, unsigned long rate,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 dto_inc, reg, reg2;
reg = readl(clk->reg);
reg2 = readl(clk->reg2);
reg2 &= ~((ARK_DDS_DIV_MASK << ARK_DDS_DIV_OFFSET) | (ARK_DDS_MUL_MASK << ARK_DDS_MUL_OFFSET));
writel(reg2, clk->reg2);
clk_dds_calc(rate, parent_rate, &dto_inc);
reg &= ~(ARK_DDS_DTOINC_MASK << ARK_DDS_DTOINC_OFFSET);
reg |= (dto_inc << ARK_DDS_DTOINC_OFFSET);
writel(reg, clk->reg);
return 0;
}
static const struct clk_ops clk_dds_ops = {
.is_enabled = clk_dds_is_enable,
.enable = clk_dds_enable,
.disable = clk_dds_disable,
.recalc_rate = clk_dds_recalc_rate,
.round_rate = clk_dds_round_rate,
.set_rate = clk_dds_set_rate,
};
static int clk_arke_sscg_is_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
return !!(reg & ARKE_SSCG_ENA);
}
static int clk_arke_sscg_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg |= ARKE_SSCG_ENA;
writel(reg, clk->reg);
return 0;
}
static void clk_arke_sscg_disable(struct clk_hw *hwclk)
{
/* struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg &= ~ARKE_SSCG_ENA;
writel(reg, clk->reg); */
return;
}
/*
* fref=24MHz, nr=3, fint=8MHz
* fvco=1000~2000MHz
* rs = 7 (fvcomax/fint=250)
* nf[23:15] integer nf[14:0] fraction
*/
static int clk_arke_sscg_set_rate(struct clk_hw *hwclk, unsigned long rate,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
unsigned int nr = 3, fint = 8000, rs = 7;
unsigned int od, fvco, nfx, nff, cpa, cpax;
int i;
u32 regval;
u32 freq_khz = rate / 1000;
regval = readl(clk->reg);
nr = (regval >> ARKE_SSCG_NR_OFFSET) & ARKE_SSCG_NR_MASK;
if (nr == 2) {
fint = 12000;
rs = 4;
} else if (nr == 3) {
fint = 8000;
rs = 7;
}
if (!clk->can_change)
return 0;
freq_khz = freq_khz / fint * fint;
for (i = 0; i < 8; i++) {
fvco = freq_khz * (1 << i);
if (fvco >= 1000 * 1000 && fvco <= 2000 * 1000) {
od = i;
break;
}
}
if (i == 8)
goto fail;
nfx = fvco / fint;
if (nfx >= 50 && nfx <= 100) {
cpa = nfx * 4;
cpax = 3;
} else if (nfx > 100 && nfx <= 200) {
cpa = nfx * 2;
cpax = 1;
} else if (nfx > 200 && nfx <= 400) {
cpa = nfx;
cpax = 0;
}
else
goto fail;
nff = ((1 << 15) * (fvco / 1000) / (fint / 1000)) & ((1 << 15) - 1);
/* disable clk first */
writel(readl(clk->reg2) | (1 << 27), clk->reg2);
regval = readl(clk->reg);
regval &= ~0x3FFFFF;
regval |= (cpa << 0) | (cpax << 9) | (rs << 11) | (nr << 15);
writel(regval, clk->reg);
regval = readl(clk->reg2);
regval &= ~0x7FFFFFF;
regval |= ((nfx<<15) | nff) | (od << 24);
writel(regval, clk->reg2) ;
/* enable clk */
writel(readl(clk->reg2) & ~(1 << 27), clk->reg2);
mdelay(10);
return 0;
fail:
pr_err("Unsupported sscg freq.\n");
return -1;
}
static unsigned long clk_arke_sscg_recalc_rate(struct clk_hw *hwclk,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 nr, fint, fvco, nfx, nff, od;
u32 cfg0, cfg1;
cfg0 = readl(clk->reg);
nr = (cfg0 >> ARKE_SSCG_NR_OFFSET) & ARKE_SSCG_NR_MASK;
nr = nr ? nr : 1;
fint = parent_rate / nr;
cfg1 = readl(clk->reg2);
nff = (cfg1 >> ARKE_SSCG_NFF_OFFSET) & ARKE_SSCG_NFF_MASK;
nfx = (cfg1 >> ARKE_SSCG_NFX_OFFSET) & ARKE_SSCG_NFX_MASK;
fvco = fint * nfx + (u64)fint * nff / (ARKE_SSCG_NFF_MASK + 1);
od = (cfg1 >> ARKE_SSCG_OD_OFFSET) & ARKE_SSCG_OD_MASK;
pr_info("clk %s rate %u.\n", clk_hw_get_name(hwclk), fvco / (1 << od));
return fvco / (1 << od);
}
static long clk_arke_sscg_round_rate(struct clk_hw *hwclk, unsigned long rate,
unsigned long *parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 regval, nr, freq;
if (clk->can_change) {
regval = readl(clk->reg);
nr = (regval >> ARKE_SSCG_NR_OFFSET) & ARKE_SSCG_NR_MASK;
freq = *parent_rate / nr;
freq = rate / freq * freq;
return freq;
}
return rate;
}
static const struct clk_ops clk_arke_sscg_ops = {
.is_enabled = clk_arke_sscg_is_enable,
.enable = clk_arke_sscg_enable,
.disable = clk_arke_sscg_disable,
.recalc_rate = clk_arke_sscg_recalc_rate,
.set_rate = clk_arke_sscg_set_rate,
.round_rate = clk_arke_sscg_round_rate,
};
static int clk_arke_pll_is_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
return !!(reg & ARKE_PLL_ENA);
}
static int clk_arke_pll_enable(struct clk_hw *hwclk)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg |= ARKE_PLL_ENA;
writel(reg, clk->reg);
return 0;
}
static void clk_arke_pll_disable(struct clk_hw *hwclk)
{
/* struct ark_clk *clk = to_ark_clk(hwclk);
u32 reg;
reg = readl(clk->reg);
reg &= ~ARKE_PLL_ENA;
writel(reg, clk->reg); */
return;
}
static unsigned long clk_arke_pll_recalc_rate(struct clk_hw *hwclk,
unsigned long parent_rate)
{
struct ark_clk *clk = to_ark_clk(hwclk);
u32 ns, ms, ps;
u32 cfg0;
cfg0 = readl(clk->reg);
ms = (cfg0 >> ARKE_PLL_MS_OFFSET) & ARKE_PLL_MS_MASK;
ns = (cfg0 >> ARKE_PLL_NS_OFFSET) & ARKE_PLL_NS_MASK;
ps = (cfg0 >> ARKE_PLL_PS_OFFSET) & ARKE_PLL_PS_MASK;
ms = ms ? ms : 1;
ns = ns ? ns : 1;
ps = ps ? ps : 1;
pr_info("clk %s rate %lu.\n", clk_hw_get_name(hwclk), parent_rate / ms * ns / ps / 2);
return parent_rate / ms * ns / ps / 2;
}
static const struct clk_ops clk_arke_pll_ops = {
.is_enabled = clk_arke_pll_is_enable,
.enable = clk_arke_pll_enable,
.disable = clk_arke_pll_disable,
.recalc_rate = clk_arke_pll_recalc_rate,
};
static __init struct clk *ark_clk_init(struct device_node *node, enum ARK_CLK_TYPE clk_type, const struct clk_ops *ops)
{
u32 reg, reg2;
struct ark_clk *ark_clk;
const char *clk_name = node->name;
const char *parent_name;
struct clk_init_data init;
struct device_node *srnp;
int rc;
rc = of_property_read_u32(node, "reg", ®);
if (WARN_ON(rc))
return NULL;
ark_clk = kzalloc(sizeof(*ark_clk), GFP_KERNEL);
if (WARN_ON(!ark_clk))
return NULL;
/* Map system registers */
srnp = of_find_compatible_node(NULL, NULL, "arkmicro,ark-sregs");
ark_clk->reg = of_iomap(srnp, 0);
BUG_ON(!ark_clk->reg);
of_property_read_string(node, "clock-output-names", &clk_name);
init.name = clk_name;
init.ops = ops;
init.flags = 0;
if (clk_type == ARK_CLK_TYPE_PLL) {
u32 offset, mask;
int index;
rc = of_property_read_u32(node, "reg2", ®2);
if (WARN_ON(rc))
return NULL;
rc = of_property_read_u32(node, "offset", &offset);
if (WARN_ON(rc))
return NULL;
rc = of_property_read_u32(node, "mask", &mask);
if (WARN_ON(rc))
return NULL;
index = (readl(ark_clk->reg + reg2) >> offset) & mask;
parent_name = of_clk_get_parent_name(node, index);
} else if (clk_type == ARK_CLK_TYPE_DDS || clk_type == ARKE_CLK_TYPE_SSCG) {
rc = of_property_read_u32(node, "reg2", ®2);
if (WARN_ON(rc))
return NULL;
ark_clk->reg2 = ark_clk->reg + reg2;
parent_name = of_clk_get_parent_name(node, 0);
} else {
parent_name = of_clk_get_parent_name(node, 0);
}
init.parent_names = &parent_name;
init.num_parents = 1;
ark_clk->can_change = of_property_read_bool(node, "clk-can-change");
ark_clk->reg += reg;
ark_clk->hw.init = &init;
rc = clk_hw_register(NULL, &ark_clk->hw);
if (WARN_ON(rc)) {
kfree(ark_clk);
return NULL;
}
rc = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &ark_clk->hw);
return ark_clk->hw.clk;
}
static void __init of_ark_clk_dds_setup(struct device_node *np)
{
ark_clk_init(np, ARK_CLK_TYPE_DDS, &clk_dds_ops);
}
CLK_OF_DECLARE(ark_clk_dds, "arkmiro,ark-clk-dds",
of_ark_clk_dds_setup);
static void __init of_ark_clk_pll_setup(struct device_node *np)
{
ark_clk_init(np, ARK_CLK_TYPE_PLL, &clk_pll_ops);
}
CLK_OF_DECLARE(ark_clk_pll, "arkmiro,ark-clk-pll",
of_ark_clk_pll_setup);
static void __init of_arke_clk_sscg_setup(struct device_node *np)
{
ark_clk_init(np, ARKE_CLK_TYPE_SSCG, &clk_arke_sscg_ops);
}
CLK_OF_DECLARE(arke_clk_sscg, "arkmiro,arke-clk-sscg",
of_arke_clk_sscg_setup);
static void __init of_arke_clk_pll_setup(struct device_node *np)
{
ark_clk_init(np, ARKE_CLK_TYPE_PLL, &clk_arke_pll_ops);
}
CLK_OF_DECLARE(arke_clk_pll, "arkmiro,arke-clk-pll",
of_arke_clk_pll_setup);