mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-17 04:36:58 +02:00
Add clock controller driver for sophgo cv1800b SoC Signed-off-by: Kongyang Liu <seashell11234455@gmail.com> Reviewed-by: Leo Yu-Chi Liang <ycliang@andestech.com>
276 lines
6.9 KiB
C
276 lines
6.9 KiB
C
// 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,
|
|
};
|