Files

369 lines
8.7 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#define LOG_CATEGORY UCLASS_CLK
#include <common.h>
#include <log.h>
#include <clk-uclass.h>
#include <malloc.h>
#include <asm/io.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/math64.h>
#include <amlogic/clk-pll.h>
#define UBOOT_DM_CLK_PLL_AML "clk_pll_aml"
#define to_clk_pll(_clk) container_of(_clk, struct aml_pll, clk)
static bool clk_pll_is_enabled(struct clk *clk)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct pll_parm *pen, *pl;
pen = &pll->en;
pl = &pll->l;
/* Enable and lock bit equal 1, it locks */
if (readl(aml_pll->addr + pen->reg_off) & BIT(pen->shift) &&
readl(aml_pll->addr + pl->reg_off) & BIT(pl->shift))
return true;
return false;
}
static int clk_pll_init(struct clk *clk)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct reg_sequence *init_regs;
int i;
if (!clk)
return -EFAULT;
if (pll->init_count == 0)
return 0;
if ((pll->flags & CLK_AML_PLL_NOINIT_ENABLED) &&
clk_pll_is_enabled(clk)) {
log_notice("ignore %s clock init\n", clk->dev->name);
return 0;
}
if (pll->flags & CLK_AML_PLL_RSTN)
clrbits_32(aml_pll->addr + pll->rst.reg_off, BIT(pll->rst.shift));
else
setbits_32(aml_pll->addr + pll->rst.reg_off, BIT(pll->rst.shift));
init_regs = pll->init_regs;
for (i = 0; i < pll->init_count; i++, init_regs++) {
writel(init_regs->def, aml_pll->addr + init_regs->reg_off);
if (init_regs->delay_us)
udelay(init_regs->delay_us);
}
if (pll->flags & CLK_AML_PLL_RSTN)
setbits_32(aml_pll->addr + pll->rst.reg_off, BIT(pll->rst.shift));
else
clrbits_32(aml_pll->addr + pll->rst.reg_off, BIT(pll->rst.shift));
return 0;
}
#define PLL_WAIT_LOCK_US_MAX 200
#define PLL_LOCK_RETRY_CNT_MAX 10
static int clk_pll_wait_lock(void __iomem *addr, const struct pll_parm *pl)
{
unsigned int val;
int delay = PLL_WAIT_LOCK_US_MAX;
do {
/* Is the clock locked now ? */
val = readl(addr + pl->reg_off);
if (val & BIT(pl->shift))
return 0;
udelay(1);
} while (delay--);
return -ETIMEDOUT;
}
static int clk_pll_enable(struct clk *clk)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct pll_parm *pen = &pll->en;
const struct pll_parm *prst = &pll->rst;
const struct pll_parm *plrst = &pll->l_rst;
const struct pll_parm *pl = &pll->l;
int retry = 0;
if (!clk)
return -EFAULT;
/* do nothing if the PLL is already enabled */
if (clk_pll_is_enabled(clk))
return 0;
do {
/* Make sure the pll is in reset */
if (pll->flags & CLK_AML_PLL_RSTN)
clrbits_32(aml_pll->addr + prst->reg_off,
BIT(prst->shift));
else
setbits_32(aml_pll->addr + prst->reg_off,
BIT(prst->shift));
/* Enable the pll */
setbits_32(aml_pll->addr + pen->reg_off, BIT(pen->shift));
/* Make sure the pll is in lock reset */
if (plrst->width) {
if (pll->flags & CLK_AML_PLL_RSTN)
clrbits_32(aml_pll->addr + plrst->reg_off,
BIT(plrst->shift));
else
setbits_32(aml_pll->addr + plrst->reg_off,
BIT(plrst->shift));
}
udelay(20);
/* Take the pll out reset */
if (pll->flags & CLK_AML_PLL_RSTN)
setbits_32(aml_pll->addr + prst->reg_off,
BIT(prst->shift));
else
clrbits_32(aml_pll->addr + prst->reg_off,
BIT(prst->shift));
/* Take the pll out lock reset */
if (plrst->width) {
udelay(20);
if (pll->flags & CLK_AML_PLL_RSTN)
setbits_32(aml_pll->addr + plrst->reg_off,
BIT(plrst->shift));
else
clrbits_32(aml_pll->addr + plrst->reg_off,
BIT(plrst->shift));
}
if (!clk_pll_wait_lock(aml_pll->addr, pl))
return 0;
retry++;
} while (retry < PLL_LOCK_RETRY_CNT_MAX);
log_err("%s: pll lock failed!!!\n", clk->dev->name);
return -EIO;
}
static int clk_pll_disable(struct clk *clk)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct pll_parm *pen = &pll->en;
const struct pll_parm *prst = &pll->rst;
if (!clk)
return -EINVAL;
/* Put the pll is in reset */
if (pll->flags & CLK_AML_PLL_RSTN)
clrbits_32(aml_pll->addr + prst->reg_off, BIT(prst->shift));
else
setbits_32(aml_pll->addr + prst->reg_off, BIT(prst->shift));
/* Disable the pll */
clrbits_32(aml_pll->addr + pen->reg_off, BIT(pen->shift));
return 0;
}
static unsigned long __pll_params_to_rate(unsigned long parent_rate,
unsigned int m, unsigned int n,
unsigned int frac,
const struct aml_pll_data *pll)
{
u64 rate, frac_rate, frac_base;
if (pll->flags & CLK_AML_PLL_FIXED_EN0P5)
parent_rate = parent_rate >> 1;
rate = (u64)parent_rate * m;
/*
* FIXME: frac is currently only available on fixpll, and other frac
* features are not yet implemented.
*/
if (frac && pll->frac.width) {
frac_rate = (u64)parent_rate * frac;
if (pll->flags & CLK_AML_PLL_FIXED_FRAC_WEIGHT_PRECISION)
frac_base = 100000;
else
frac_base = (uint32_t)BIT(pll->frac.width);
rate += DIV_ROUND_UP_ULL(frac_rate, frac_base);
}
if (pll->flags & CLK_AML_PLL_POWER_OF_TWO)
n = 1 << n;
if (n == 0)
return 0;
return DIV_ROUND_UP_ULL(rate, n);
}
static const struct pll_params_table *clk_get_pll_settings(unsigned long rate,
const struct aml_pll_data *data)
{
int i;
if (!data || !data->table)
return NULL;
for (i = 0; i < data->table_count; i++) {
if (rate == data->table[i].rate ||
((i + 1 < data->table_count) && rate < data->table[i + 1].rate))
return &data->table[i];
}
return &data->table[data->table_count - 1];
}
#define SETPMASK(width, shift) GENMASK((shift) + (width) - 1, (shift))
#define PARM_GET(width, shift, reg) \
(((reg) & SETPMASK(width, shift)) >> (shift))
static unsigned long clk_pll_set_rate(struct clk *clk, unsigned long rate)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct pll_params_table *table;
const struct pll_parm *pm, *pn, *pod, *pfrac;
bool enabled;
if (!clk)
return -EFAULT;
table = clk_get_pll_settings(rate, pll);
if (!table)
return -EFAULT;
pm = &pll->m;
pn = &pll->n;
pod = &pll->od;
pfrac = &pll->frac;
enabled = clk_pll_is_enabled(clk);
if (enabled)
clk_pll_disable(clk);
clrsetbits_32(aml_pll->addr + pm->reg_off, SETPMASK(pm->width, pm->shift),
table->m << pm->shift);
clrsetbits_32(aml_pll->addr + pn->reg_off, SETPMASK(pn->width, pn->shift),
table->n << pn->shift);
if (pod->width)
clrsetbits_32(aml_pll->addr + pod->reg_off, SETPMASK(pod->width,
pod->shift),
table->od << pod->shift);
if (pfrac->width)
clrsetbits_32(aml_pll->addr + pfrac->reg_off, SETPMASK(pfrac->width,
pfrac->shift),
table->frac << pfrac->shift);
/* If the pll is stopped, bail out now */
if (!enabled)
return 0;
if (!clk_pll_enable(clk))
return 0;
return -EIO;
}
static unsigned long clk_pll_get_rate(struct clk *clk)
{
const struct aml_pll *aml_pll = to_clk_pll(clk);
const struct aml_pll_data *pll = aml_pll->data;
const struct pll_parm *pm, *pn, *pod, *pfrac;
unsigned int m, n, frac, od;
unsigned long prate = clk_get_parent_rate(clk);
if (!clk)
return -EFAULT;
pm = &pll->m;
pn = &pll->n;
pod = &pll->od;
pfrac = &pll->frac;
m = PARM_GET(pm->width, pm->shift, readl(aml_pll->addr + pm->reg_off));
n = PARM_GET(pn->width, pn->shift, readl(aml_pll->addr + pn->reg_off));
frac = pfrac->width ?
PARM_GET(pfrac->width, pfrac->shift,
readl(aml_pll->addr + pfrac->reg_off)) : 0;
od = pod->width ?
PARM_GET(pod->width, pod->shift,
readl(aml_pll->addr + pod->reg_off)) : 0;
return __pll_params_to_rate(prate, m, n, frac, pll) >> od;
}
const struct clk_ops clk_pll_ops = {
.enable = clk_pll_enable,
.disable = clk_pll_disable,
.get_rate = clk_pll_get_rate,
.set_rate = clk_pll_set_rate,
};
struct clk *aml_clk_register_pll(const char *name, const char *parent_name,
unsigned long flags, void __iomem *addr,
const struct aml_pll_data *data)
{
struct aml_pll *pll;
struct clk *clk;
int ret;
/* allocate the gate */
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
/* struct clk_gate assignments */
pll->data = data;
pll->addr = addr;
clk = &pll->clk;
clk->flags = flags;
ret = clk_register(clk, UBOOT_DM_CLK_PLL_AML, name, parent_name);
if (ret) {
kfree(pll);
return ERR_PTR(ret);
}
clk_pll_init(clk);
return clk;
}
U_BOOT_DRIVER(clk_pll_aml) = {
.name = UBOOT_DM_CLK_PLL_AML,
.id = UCLASS_CLK,
.ops = &clk_pll_ops,
.flags = DM_FLAG_PRE_RELOC,
};