mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-23 19:43:33 +00:00
c7554574ff
Add clock controller driver for NPCM750 Signed-off-by: Jim Liu <JJLIU0@nuvoton.com> Signed-off-by: Stanley Chu <yschu@nuvoton.com> Reviewed-by: Sean Anderson <seanga2@gmail.com>
299 lines
6.3 KiB
C
299 lines
6.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2022 Nuvoton Technology Corp.
|
|
*
|
|
* Formula for calculating clock rate:
|
|
* Fout = ((Fin / PRE_DIV) / div) / POST_DIV
|
|
*/
|
|
|
|
#include <div64.h>
|
|
#include <dm.h>
|
|
#include <asm/io.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/log2.h>
|
|
#include "clk_npcm.h"
|
|
|
|
static int clkid_to_clksel(struct npcm_clk_select *selector, int id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < selector->num_parents; i++) {
|
|
if (selector->parents[i].id == id)
|
|
return selector->parents[i].clksel;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int clksel_to_clkid(struct npcm_clk_select *selector, int clksel)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < selector->num_parents; i++) {
|
|
if (selector->parents[i].clksel == clksel)
|
|
return selector->parents[i].id;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static struct npcm_clk_pll *npcm_clk_pll_get(struct npcm_clk_data *clk_data, int id)
|
|
{
|
|
struct npcm_clk_pll *pll = clk_data->clk_plls;
|
|
int i;
|
|
|
|
for (i = 0; i < clk_data->num_plls; i++) {
|
|
if (pll->id == id)
|
|
return pll;
|
|
pll++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct npcm_clk_select *npcm_clk_selector_get(struct npcm_clk_data *clk_data,
|
|
int id)
|
|
{
|
|
struct npcm_clk_select *selector = clk_data->clk_selectors;
|
|
int i;
|
|
|
|
for (i = 0; i < clk_data->num_selectors; i++) {
|
|
if (selector->id == id)
|
|
return selector;
|
|
selector++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct npcm_clk_div *npcm_clk_divider_get(struct npcm_clk_data *clk_data,
|
|
int id)
|
|
{
|
|
struct npcm_clk_div *divider = clk_data->clk_dividers;
|
|
int i;
|
|
|
|
for (i = 0; i < clk_data->num_dividers; i++) {
|
|
if (divider->id == id)
|
|
return divider;
|
|
divider++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ulong npcm_clk_get_fin(struct clk *clk)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_select *selector;
|
|
struct clk parent;
|
|
ulong parent_rate;
|
|
u32 val, clksel;
|
|
int ret;
|
|
|
|
selector = npcm_clk_selector_get(priv->clk_data, clk->id);
|
|
if (!selector)
|
|
return 0;
|
|
|
|
if (selector->flags & FIXED_PARENT) {
|
|
clksel = 0;
|
|
} else {
|
|
val = readl(priv->base + selector->reg);
|
|
clksel = (val & selector->mask) >> (ffs(selector->mask) - 1);
|
|
}
|
|
parent.id = clksel_to_clkid(selector, clksel);
|
|
|
|
ret = clk_request(clk->dev, &parent);
|
|
if (ret)
|
|
return 0;
|
|
|
|
parent_rate = clk_get_rate(&parent);
|
|
|
|
debug("fin of clk%lu = %lu\n", clk->id, parent_rate);
|
|
return parent_rate;
|
|
}
|
|
|
|
static u32 npcm_clk_get_div(struct clk *clk)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_div *divider;
|
|
u32 val, div;
|
|
|
|
divider = npcm_clk_divider_get(priv->clk_data, clk->id);
|
|
if (!divider)
|
|
return 0;
|
|
|
|
val = readl(priv->base + divider->reg);
|
|
div = (val & divider->mask) >> (ffs(divider->mask) - 1);
|
|
if (divider->flags & DIV_TYPE1)
|
|
div = div + 1;
|
|
else
|
|
div = 1 << div;
|
|
|
|
if (divider->flags & PRE_DIV2)
|
|
div = div << 1;
|
|
|
|
return div;
|
|
}
|
|
|
|
static u32 npcm_clk_set_div(struct clk *clk, u32 div)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_div *divider;
|
|
u32 val, clkdiv;
|
|
|
|
divider = npcm_clk_divider_get(priv->clk_data, clk->id);
|
|
if (!divider)
|
|
return -EINVAL;
|
|
|
|
if (divider->flags & PRE_DIV2)
|
|
div = div >> 1;
|
|
|
|
if (divider->flags & DIV_TYPE1)
|
|
clkdiv = div - 1;
|
|
else
|
|
clkdiv = ilog2(div);
|
|
|
|
val = readl(priv->base + divider->reg);
|
|
val &= ~divider->mask;
|
|
val |= (clkdiv << (ffs(divider->mask) - 1)) & divider->mask;
|
|
writel(val, priv->base + divider->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong npcm_clk_get_fout(struct clk *clk)
|
|
{
|
|
ulong parent_rate;
|
|
u32 div;
|
|
|
|
parent_rate = npcm_clk_get_fin(clk);
|
|
if (!parent_rate)
|
|
return -EINVAL;
|
|
|
|
div = npcm_clk_get_div(clk);
|
|
if (!div)
|
|
return -EINVAL;
|
|
|
|
debug("fout of clk%lu = (%lu / %u)\n", clk->id, parent_rate, div);
|
|
return (parent_rate / div);
|
|
}
|
|
|
|
static ulong npcm_clk_get_pll_fout(struct clk *clk)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_pll *pll;
|
|
struct clk parent;
|
|
ulong parent_rate;
|
|
ulong fbdv, indv, otdv1, otdv2;
|
|
u32 val;
|
|
u64 ret;
|
|
|
|
pll = npcm_clk_pll_get(priv->clk_data, clk->id);
|
|
if (!pll)
|
|
return -ENODEV;
|
|
|
|
parent.id = pll->parent_id;
|
|
ret = clk_request(clk->dev, &parent);
|
|
if (ret)
|
|
return ret;
|
|
|
|
parent_rate = clk_get_rate(&parent);
|
|
|
|
val = readl(priv->base + pll->reg);
|
|
indv = FIELD_GET(PLLCON_INDV, val);
|
|
fbdv = FIELD_GET(PLLCON_FBDV, val);
|
|
otdv1 = FIELD_GET(PLLCON_OTDV1, val);
|
|
otdv2 = FIELD_GET(PLLCON_OTDV2, val);
|
|
|
|
ret = (u64)parent_rate * fbdv;
|
|
do_div(ret, indv * otdv1 * otdv2);
|
|
if (pll->flags & POST_DIV2)
|
|
do_div(ret, 2);
|
|
|
|
debug("fout of pll(id %lu) = %llu\n", clk->id, ret);
|
|
return ret;
|
|
}
|
|
|
|
static ulong npcm_clk_get_rate(struct clk *clk)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_data *clk_data = priv->clk_data;
|
|
struct clk refclk;
|
|
int ret;
|
|
|
|
debug("%s: id %lu\n", __func__, clk->id);
|
|
if (clk->id == clk_data->refclk_id) {
|
|
ret = clk_get_by_name(clk->dev, "refclk", &refclk);
|
|
if (!ret)
|
|
return clk_get_rate(&refclk);
|
|
else
|
|
return ret;
|
|
}
|
|
|
|
if (clk->id >= clk_data->pll0_id &&
|
|
clk->id < clk_data->pll0_id + clk_data->num_plls)
|
|
return npcm_clk_get_pll_fout(clk);
|
|
else
|
|
return npcm_clk_get_fout(clk);
|
|
}
|
|
|
|
static ulong npcm_clk_set_rate(struct clk *clk, ulong rate)
|
|
{
|
|
ulong parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
debug("%s: id %lu, rate %lu\n", __func__, clk->id, rate);
|
|
parent_rate = npcm_clk_get_fin(clk);
|
|
if (!parent_rate)
|
|
return -EINVAL;
|
|
|
|
div = DIV_ROUND_UP(parent_rate, rate);
|
|
ret = npcm_clk_set_div(clk, div);
|
|
if (ret)
|
|
return ret;
|
|
|
|
debug("%s: rate %lu, new rate (%lu / %u)\n", __func__, rate, parent_rate, div);
|
|
return (parent_rate / div);
|
|
}
|
|
|
|
static int npcm_clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct npcm_clk_select *selector;
|
|
int clksel;
|
|
u32 val;
|
|
|
|
debug("%s: id %lu, parent %lu\n", __func__, clk->id, parent->id);
|
|
selector = npcm_clk_selector_get(priv->clk_data, clk->id);
|
|
if (!selector)
|
|
return -EINVAL;
|
|
|
|
clksel = clkid_to_clksel(selector, parent->id);
|
|
if (clksel < 0)
|
|
return -EINVAL;
|
|
|
|
val = readl(priv->base + selector->reg);
|
|
val &= ~selector->mask;
|
|
val |= clksel << (ffs(selector->mask) - 1);
|
|
writel(val, priv->base + selector->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int npcm_clk_request(struct clk *clk)
|
|
{
|
|
struct npcm_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
if (clk->id >= priv->num_clks)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct clk_ops npcm_clk_ops = {
|
|
.get_rate = npcm_clk_get_rate,
|
|
.set_rate = npcm_clk_set_rate,
|
|
.set_parent = npcm_clk_set_parent,
|
|
.request = npcm_clk_request,
|
|
};
|