blob: c99aa0b4e440c9876b42fbcafbcc04af1acd7871 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2024, Kongyang Liu <seashell11234455@gmail.com>
*/
#include <clk-uclass.h>
#include <dm.h>
#include <div64.h>
#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/kernel.h>
#include "clk-common.h"
#include "clk-pll.h"
#define PLL_PRE_DIV_MIN 1
#define PLL_PRE_DIV_MAX 127
#define PLL_POST_DIV_MIN 1
#define PLL_POST_DIV_MAX 127
#define PLL_DIV_MIN 6
#define PLL_DIV_MAX 127
#define PLL_ICTRL_MIN 0
#define PLL_ICTRL_MAX 7
#define PLL_MODE_MIN 0
#define PLL_MODE_MAX 3
#define FOR_RANGE(x, RANGE) for (x = RANGE##_MIN; x <= RANGE##_MAX; x++)
#define PLL_ICTRL GENMASK(26, 24)
#define PLL_DIV_SEL GENMASK(23, 17)
#define PLL_SEL_MODE GENMASK(16, 15)
#define PLL_POST_DIV_SEL GENMASK(14, 8)
#define PLL_PRE_DIV_SEL GENMASK(6, 0)
#define PLL_MASK_ALL (PLL_ICTRL | PLL_DIV_SEL | PLL_SEL_MODE | PLL_POST_DIV_SEL | PLL_PRE_DIV_SEL)
/* IPLL */
#define to_clk_ipll(dev) container_of(dev, struct cv1800b_clk_ipll, clk)
static int cv1800b_ipll_enable(struct clk *clk)
{
struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
cv1800b_clk_clrbit(pll->base, &pll->pll_pwd);
return 0;
}
static int cv1800b_ipll_disable(struct clk *clk)
{
struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
cv1800b_clk_setbit(pll->base, &pll->pll_pwd);
return 0;
}
static ulong cv1800b_ipll_get_rate(struct clk *clk)
{
struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
ulong parent_rate = clk_get_parent_rate(clk);
u32 reg = readl(pll->base + pll->pll_reg);
u32 pre_div = FIELD_GET(PLL_PRE_DIV_SEL, reg);
u32 post_div = FIELD_GET(PLL_POST_DIV_SEL, reg);
u32 div = FIELD_GET(PLL_DIV_SEL, reg);
return DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
}
static ulong cv1800b_ipll_set_rate(struct clk *clk, ulong rate)
{
struct cv1800b_clk_ipll *pll = to_clk_ipll(clk);
ulong parent_rate = clk_get_parent_rate(clk);
u32 pre_div, post_div, div;
u32 pre_div_sel, post_div_sel, div_sel;
ulong new_rate, best_rate = 0;
u32 mode, ictrl;
u32 test, val;
FOR_RANGE(pre_div, PLL_PRE_DIV)
{
FOR_RANGE(post_div, PLL_POST_DIV)
{
FOR_RANGE(div, PLL_DIV)
{
new_rate =
DIV_ROUND_DOWN_ULL(parent_rate * div, pre_div * post_div);
if (rate - new_rate < rate - best_rate) {
best_rate = new_rate;
pre_div_sel = pre_div;
post_div_sel = post_div;
div_sel = div;
}
}
}
}
FOR_RANGE(mode, PLL_MODE)
{
FOR_RANGE(ictrl, PLL_ICTRL)
{
test = 184 * (1 + mode) * (1 + ictrl) / 2;
if (test > 20 * div_sel && test < 35 * div_sel) {
val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
FIELD_PREP(PLL_DIV_SEL, div_sel) |
FIELD_PREP(PLL_ICTRL, ictrl) |
FIELD_PREP(PLL_SEL_MODE, mode);
clrsetbits_le32(pll->base + pll->pll_reg, PLL_MASK_ALL, val);
return best_rate;
}
}
}
return -EINVAL;
}
const struct clk_ops cv1800b_ipll_ops = {
.enable = cv1800b_ipll_enable,
.disable = cv1800b_ipll_disable,
.get_rate = cv1800b_ipll_get_rate,
.set_rate = cv1800b_ipll_set_rate,
};
U_BOOT_DRIVER(cv1800b_clk_ipll) = {
.name = "cv1800b_clk_ipll",
.id = UCLASS_CLK,
.ops = &cv1800b_ipll_ops,
.flags = DM_FLAG_PRE_RELOC,
};
/* FPLL */
#define to_clk_fpll(dev) container_of(dev, struct cv1800b_clk_fpll, ipll.clk)
static ulong cv1800b_fpll_get_rate(struct clk *clk)
{
struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
u32 val, syn_set;
u32 pre_div, post_div, div;
u8 mult = 1;
ulong divisor, remainder, rate;
if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
return cv1800b_ipll_get_rate(clk);
syn_set = readl(pll->ipll.base + pll->syn.set);
if (syn_set == 0)
return 0;
val = readl(pll->ipll.base + pll->ipll.pll_reg);
pre_div = FIELD_GET(PLL_PRE_DIV_SEL, val);
post_div = FIELD_GET(PLL_POST_DIV_SEL, val);
div = FIELD_GET(PLL_DIV_SEL, val);
if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
mult = 2;
divisor = (ulong)pre_div * post_div * syn_set;
rate = (clk_get_parent_rate(clk) * div) << 25;
remainder = rate % divisor;
rate /= divisor;
return rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
}
static ulong cv1800b_find_syn(ulong rate, ulong parent_rate, ulong pre_div, ulong post_div,
ulong div, u32 *syn)
{
u32 syn_min = (4 << 26) + 1;
u32 syn_max = U32_MAX;
u32 mid;
ulong new_rate;
u32 mult = 1;
ulong divisor, remainder;
while (syn_min < syn_max) {
mid = ((ulong)syn_min + syn_max) / 2;
divisor = pre_div * post_div * mid;
new_rate = (parent_rate * div) << 25;
remainder = do_div(new_rate, divisor);
new_rate = new_rate * mult + DIV_ROUND_CLOSEST_ULL(remainder * mult, divisor);
if (new_rate > rate) {
syn_max = mid + 1;
} else if (new_rate < rate) {
syn_min = mid - 1;
} else {
syn_min = mid;
break;
}
}
*syn = syn_min;
return new_rate;
}
static ulong cv1800b_fpll_set_rate(struct clk *clk, ulong rate)
{
struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
ulong parent_rate = clk_get_parent_rate(clk);
u32 pre_div, post_div, div;
u32 pre_div_sel, post_div_sel, div_sel;
u32 syn, syn_sel;
ulong new_rate, best_rate = 0;
u32 mult = 1;
u32 mode, ictrl;
if (!cv1800b_clk_getbit(pll->ipll.base, &pll->syn.en))
return cv1800b_ipll_set_rate(clk, rate);
if (cv1800b_clk_getbit(pll->ipll.base, &pll->syn.clk_half))
mult = 2;
FOR_RANGE(pre_div, PLL_PRE_DIV)
{
FOR_RANGE(post_div, PLL_POST_DIV)
{
FOR_RANGE(div, PLL_DIV)
{
new_rate = cv1800b_find_syn(rate, parent_rate, pre_div, post_div,
div, &syn);
if (rate - new_rate < rate - best_rate) {
best_rate = new_rate;
pre_div_sel = pre_div;
post_div_sel = post_div;
div_sel = div;
syn_sel = syn;
}
}
}
}
FOR_RANGE(mode, PLL_MODE)
{
FOR_RANGE(ictrl, PLL_ICTRL)
{
u32 test = 184 * (1 + mode) * (1 + ictrl) / 2;
if (test > 10 * div_sel && test <= 24 * div_sel) {
u32 val = FIELD_PREP(PLL_PRE_DIV_SEL, pre_div_sel) |
FIELD_PREP(PLL_POST_DIV_SEL, post_div_sel) |
FIELD_PREP(PLL_DIV_SEL, div_sel) |
FIELD_PREP(PLL_ICTRL, ictrl) |
FIELD_PREP(PLL_SEL_MODE, mode);
clrsetbits_le32(pll->ipll.base + pll->ipll.pll_reg, PLL_MASK_ALL,
val);
writel(syn_sel, pll->ipll.base + pll->syn.set);
return best_rate;
}
}
}
return -EINVAL;
}
static int cv1800b_fpll_set_parent(struct clk *clk, struct clk *parent)
{
struct cv1800b_clk_fpll *pll = to_clk_fpll(clk);
if (parent->id == CV1800B_CLK_BYPASS)
cv1800b_clk_setbit(pll->ipll.base, &pll->syn.en);
else
cv1800b_clk_clrbit(pll->ipll.base, &pll->syn.en);
return 0;
}
const struct clk_ops cv1800b_fpll_ops = {
.enable = cv1800b_ipll_enable,
.disable = cv1800b_ipll_disable,
.get_rate = cv1800b_fpll_get_rate,
.set_rate = cv1800b_fpll_set_rate,
.set_parent = cv1800b_fpll_set_parent,
};
U_BOOT_DRIVER(cv1800b_clk_fpll) = {
.name = "cv1800b_clk_fpll",
.id = UCLASS_CLK,
.ops = &cv1800b_fpll_ops,
.flags = DM_FLAG_PRE_RELOC,
};