983 lines
25 KiB
C
983 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2020 ArtInChip Inc.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <clk-uclass.h>
|
|
#include <dm.h>
|
|
#include <dt-structs.h>
|
|
#include <errno.h>
|
|
#include <asm/io.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/lists.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include <linux/delay.h>
|
|
#include <div64.h>
|
|
#include "clk-aic.h"
|
|
|
|
/* ALL chips:
|
|
* Other vco of clock not change
|
|
* The vco of pll_fra2 range from (768M~1560M) to (360M~1584M)
|
|
*
|
|
* Different clock verison need to be distinguished
|
|
*/
|
|
static const struct pll_vco vco_arr[] = {
|
|
#ifdef CONFIG_CLK_ARTINCHIP_CMU_V1_0
|
|
{360000000, 1584000000, 8},
|
|
#endif
|
|
#ifdef CONFIG_CLK_ARTINCHIP_CMU_V2_0
|
|
{360000000, 1584000000, 17},
|
|
#endif
|
|
{768000000, 1560000000, 0},
|
|
};
|
|
|
|
static void clk_vco_select(struct aic_pll *pll,
|
|
unsigned long *min, unsigned long *max)
|
|
{
|
|
const struct pll_vco *vco;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(vco_arr); i++) {
|
|
vco = &vco_arr[i];
|
|
if (pll->id == vco->id) {
|
|
*min = vco->vco_min;
|
|
*max = vco->vco_max;
|
|
return;
|
|
}
|
|
}
|
|
*min = vco_arr[ARRAY_SIZE(vco_arr) - 1].vco_min;
|
|
*max = vco_arr[ARRAY_SIZE(vco_arr) - 1].vco_max;
|
|
}
|
|
|
|
static enum aic_clk_type aic_get_clk_info(struct aic_clk_tree *tree, u32 id,
|
|
u32 *index)
|
|
{
|
|
int i;
|
|
|
|
#ifdef CONFIG_CLK_ARTINCHIP_CMU_V2_0
|
|
if (id >= tree->cross_zone_base) {
|
|
for (i = 0; i < tree->cross_zone_cnt; i++) {
|
|
if (id == tree->clk_cz[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_CROSS_ZONE;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (id >= tree->clkout_base) {
|
|
for (i = 0; i < tree->clkout_cnt; i++) {
|
|
if (id == tree->clkout[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_OUTPUT;
|
|
}
|
|
}
|
|
} else if (id >= tree->disp_base) {
|
|
for (i = 0; i < tree->disp_cnt; i++) {
|
|
if (id == tree->disp[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_DISP;
|
|
}
|
|
}
|
|
} else if (id >= tree->periph_base) {
|
|
for (i = 0; i < tree->periph_cnt; i++) {
|
|
if (id == tree->periph[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_PERIPHERAL;
|
|
}
|
|
}
|
|
} else if (id >= tree->system_base) {
|
|
for (i = 0; i < tree->system_cnt; i++) {
|
|
if (id == tree->system[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_SYSTEM;
|
|
}
|
|
}
|
|
} else if (id >= tree->pll_base) {
|
|
for (i = 0; i < tree->pll_cnt; i++) {
|
|
if (id == tree->plls[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_PLL;
|
|
}
|
|
}
|
|
} else if (id >= tree->fixed_rate_base) {
|
|
for (i = 0; i < tree->fixed_rate_cnt; i++) {
|
|
if (id == tree->fixed_rate[i].id) {
|
|
*index = i;
|
|
return AIC_CLK_FIXED_RATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return AIC_CLK_UNKNOWN;
|
|
}
|
|
|
|
static ulong fixed_rate_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
|
|
return tree->fixed_rate[index].rate;
|
|
}
|
|
|
|
static ulong artinchip_get_parent_rate(struct clk *clk, u32 id)
|
|
{
|
|
struct clk parent = {.id = id, .dev = clk->dev };
|
|
return clk_get_rate(&parent);
|
|
}
|
|
|
|
static int pll_clk_enable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_pll *pll = &tree->plls[index];
|
|
|
|
value = readl(priv->base + pll->gen_reg);
|
|
/* Set ICP value to 8 */
|
|
value &= ~(0x1F << 24);
|
|
value |= (1UL << 31) | (8 << 24) | (1 << 20) | (1 << 18) | (1 << 16);
|
|
writel(value, priv->base + pll->gen_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pll_clk_disable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_pll *pll = &tree->plls[index];
|
|
|
|
value = readl(priv->base + pll->gen_reg);
|
|
value &= ~(1 << 16);
|
|
writel(value, priv->base + pll->gen_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong pll_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
u32 value, div_p, div_n, div_m, fra_in;
|
|
u64 rate, rate_int, rate_fra;
|
|
u32 fra_en = 0;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_pll *pll = &tree->plls[index];
|
|
|
|
value = readl(priv->base + pll->gen_reg);
|
|
if (!(value & (1 << 20)))
|
|
return 24000000;
|
|
div_p = (value & 0x01);
|
|
div_m = (value >> 4) & 0x03;
|
|
div_n = (value >> 8) & 0xff;
|
|
|
|
if (pll->type == AIC_PLL_FRA)
|
|
fra_en = readl(priv->base + pll->frac_reg) & (1 << 20);
|
|
|
|
if (pll->type != AIC_PLL_FRA || !fra_en)
|
|
rate = 24000000 / (div_p + 1) * (div_n + 1) / (div_m + 1);
|
|
else {
|
|
fra_in = readl(priv->base + pll->frac_reg) & 0x1FFFF;
|
|
rate_int = 24000000 / (div_p + 1) * (div_n + 1) / (div_m + 1);
|
|
rate_fra = (u64)24000000 / (div_p + 1) * fra_in;
|
|
do_div(rate_fra, 0x1FFFF * (div_m + 1));
|
|
rate = rate_int + rate_fra;
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
static void clk_pll_bypass(struct aic_pll *pll, void *base_addr, bool bypass)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl(base_addr + pll->gen_reg);
|
|
val &= ~(1 << 20);
|
|
val |= (!bypass << 20);
|
|
writel(val, base_addr + pll->gen_reg);
|
|
}
|
|
|
|
static ulong pll_clk_round_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 factor_n, factor_m, factor_p;
|
|
ulong rrate, vco_rate, pll_vco_min, pll_vco_max;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_pll *pll = &tree->plls[index];
|
|
|
|
if (pll->type != AIC_PLL_INT) {
|
|
if (rate < pll->min_rate)
|
|
return pll->min_rate;
|
|
else if (pll->max_rate && rate > pll->max_rate)
|
|
return pll->max_rate;
|
|
else if (pll->type == AIC_PLL_FRA)
|
|
return rate;
|
|
}
|
|
|
|
clk_vco_select(pll, &pll_vco_min, &pll_vco_max);
|
|
|
|
/* The frequency constraint of PLL_VCO is between 768M and 1560M
|
|
* But the PLL_VCO of pll_fra2 is between 360M and 1584M
|
|
*/
|
|
if (rate < pll_vco_min)
|
|
factor_m = DIV_ROUND_UP(pll_vco_min, rate) - 1;
|
|
else
|
|
factor_m = 0;
|
|
|
|
if (factor_m > 3)
|
|
factor_m = 3;
|
|
|
|
vco_rate = (factor_m + 1) * rate;
|
|
if (vco_rate > pll_vco_max)
|
|
vco_rate = pll_vco_max;
|
|
|
|
factor_p = (rate % 24000000) ? 1 : 0;
|
|
if (!factor_p)
|
|
return rate;
|
|
else if (!(rate % (24000000 / (factor_p + 1))))
|
|
return rate;
|
|
|
|
factor_n = vco_rate * (factor_p + 1) / 24000000 - 1;
|
|
|
|
rrate = 24000000 / (factor_p + 1) * (factor_n + 1) / (factor_m + 1);
|
|
|
|
return rrate;
|
|
}
|
|
|
|
|
|
static ulong pll_clk_set_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 reg_val, factor_p, factor_n, factor_m;
|
|
u64 val, fra_in = 0;
|
|
u8 fra_en, factor_m_en;
|
|
ulong vco_rate, pll_vco_min, pll_vco_max;
|
|
#ifdef CONFIG_CLK_ARTINCHIP_PLL_SDM
|
|
u32 ppm_max, sdm_amp, sdm_step;
|
|
#endif
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_pll *pll = &tree->plls[index];
|
|
|
|
if (rate == 24000000) {
|
|
reg_val = readl(priv->base + pll->gen_reg);
|
|
reg_val &= ~(1 << 20);
|
|
writel(reg_val, priv->base + pll->gen_reg);
|
|
return 0;
|
|
}
|
|
|
|
clk_vco_select(pll, &pll_vco_min, &pll_vco_max);
|
|
|
|
/* Switch the output of PLL to 24MHz */
|
|
clk_pll_bypass(pll, priv->base, true);
|
|
/* Calculate PLL parameters.
|
|
* The frequency constraint of PLL_VCO is between 768M and 1560M
|
|
* But the PLL_VCO of pll_fra2 is between 360M and 1584M
|
|
*/
|
|
if (rate < pll_vco_min)
|
|
factor_m = DIV_ROUND_UP(pll_vco_min, rate) - 1;
|
|
else
|
|
factor_m = 0;
|
|
|
|
if (factor_m) {
|
|
factor_m_en = 1;
|
|
if (factor_m > 3)
|
|
factor_m = 3;
|
|
} else
|
|
factor_m_en = 0;
|
|
|
|
|
|
vco_rate = (factor_m + 1) * rate;
|
|
if (vco_rate > pll_vco_max)
|
|
vco_rate = pll_vco_max;
|
|
|
|
factor_p = (vco_rate % 24000000) ? 1 : 0;
|
|
factor_n = vco_rate * (factor_p + 1) / 24000000 - 1;
|
|
|
|
reg_val = readl(priv->base + pll->gen_reg);
|
|
reg_val &= ~0xFFFF;
|
|
reg_val |= (factor_m_en << 19) | (factor_n << 8) | (factor_m << 4) | (factor_p << 0);
|
|
writel(reg_val, priv->base + pll->gen_reg);
|
|
|
|
if (pll->type == AIC_PLL_FRA) {
|
|
val = rate % (24000000 * (factor_n + 1) /
|
|
(factor_m + 1) / (factor_p + 1));
|
|
fra_en = val ? 1 : 0;
|
|
if (fra_en) {
|
|
fra_in = val * (factor_p + 1) *
|
|
(factor_m + 1) * 0x1FFFF;
|
|
do_div(fra_in, 24000000);
|
|
}
|
|
/* Configure fractional division */
|
|
writel(fra_en << 20 | fra_in, priv->base + pll->frac_reg);
|
|
}
|
|
|
|
#ifdef CONFIG_CLK_ARTINCHIP_PLL_SDM
|
|
if (pll->type == AIC_PLL_SDM) {
|
|
ppm_max = 1000000 / (factor_n + 1);
|
|
/* 1% spread */
|
|
if (ppm_max < PLL_SDM_SPREAD_PPM)
|
|
sdm_amp = 0;
|
|
else
|
|
sdm_amp = PLL_SDM_AMP_MAX -
|
|
PLL_SDM_SPREAD_PPM * PLL_SDM_AMP_MAX / ppm_max;
|
|
|
|
/* SDM uses triangular wave, 33KHz by default */
|
|
sdm_step = (u64)(PLL_SDM_AMP_MAX - sdm_amp) * 2 *
|
|
PLL_SDM_SPREAD_FREQ / 24000000;
|
|
if (sdm_step > 511)
|
|
sdm_step = 511;
|
|
|
|
reg_val = (1UL << 31) | (2 << PLL_SDM_MODE_BIT) |
|
|
(sdm_step << PLL_SDM_STEP_BIT) |
|
|
(3 << PLL_SDM_FREQ_BIT) |
|
|
(sdm_amp << PLL_SDM_AMP_BIT);
|
|
|
|
writel(reg_val, priv->base + pll->sdm_reg);
|
|
}
|
|
#endif
|
|
/* Switch PLL output */
|
|
clk_pll_bypass(pll, priv->base, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 cpu_clk_write_enable(struct aic_sys_clk *system, u32 val_tmp)
|
|
{
|
|
struct aic_cpu_attr *cpu = (struct aic_cpu_attr *)system->clk_attr;
|
|
u32 val = val_tmp;
|
|
|
|
if (cpu->key_bit >= 0) {
|
|
val &= ~(cpu->key_mask << cpu->key_bit);
|
|
val |= cpu->key_val << cpu->key_bit;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int system_clk_enable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_sys_clk *system = &tree->system[index];
|
|
struct aic_cpu_attr *cpu = (struct aic_cpu_attr *)system->clk_attr;
|
|
|
|
if (system->type != AIC_CPU_CLK)
|
|
return 0;
|
|
/* enable system clk */
|
|
value = readl(priv->base + system->reg);
|
|
if (cpu->mod_gate >= 0)
|
|
value |= 1 << cpu->mod_gate;
|
|
value = cpu_clk_write_enable(system, value);
|
|
|
|
/* reset system clk */
|
|
if (cpu->rst_bit >= 0)
|
|
value |= 1 << cpu->rst_bit;
|
|
value = cpu_clk_write_enable(system, value);
|
|
writel(value, priv->base + system->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int system_clk_disable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_sys_clk *system = &tree->system[index];
|
|
struct aic_cpu_attr *cpu = (struct aic_cpu_attr *)system->clk_attr;
|
|
|
|
if (system->type != AIC_CPU_CLK)
|
|
return 0;
|
|
/* disable cpu clk */
|
|
value = readl(priv->base + system->reg);
|
|
if (cpu->mod_gate >= 0)
|
|
value &= ~(1 << cpu->mod_gate);
|
|
value = cpu_clk_write_enable(system, value);
|
|
|
|
/* reset cpu clk */
|
|
if (cpu->rst_bit >= 0)
|
|
value &= ~(1 << cpu->rst_bit);
|
|
value = cpu_clk_write_enable(system, value);
|
|
writel(value, priv->base + system->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong system_clk_set_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 value, parent, parent_rate, div;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_sys_clk *system = &tree->system[index];
|
|
|
|
value = readl(priv->base + system->reg);
|
|
|
|
parent = system->parent[(value >> system->mux_shift) &
|
|
system->mux_mask];
|
|
parent_rate = artinchip_get_parent_rate(clk, parent);
|
|
|
|
if (system->div_shift >= 0) {
|
|
div = DIV_ROUND_CLOSEST(parent_rate, rate);
|
|
div -= div > 0 ? 1 : 0;
|
|
div = div > system->div_mask ? system->div_mask : div;
|
|
value = readl(priv->base + system->reg);
|
|
value &= ~(system->div_mask << system->div_shift);
|
|
value |= div << system->div_shift;
|
|
if (system->type == AIC_CPU_CLK)
|
|
value = cpu_clk_write_enable(system, value);
|
|
writel(value, priv->base + system->reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong system_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
u32 value, parent, parent_rate;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_sys_clk *system = &tree->system[index];
|
|
value = readl(priv->base + system->reg);
|
|
|
|
if (!(value & (1 << 8)))
|
|
return 24000000;
|
|
|
|
parent = system->parent[(value >> system->mux_shift) &
|
|
system->mux_mask];
|
|
parent_rate = artinchip_get_parent_rate(clk, parent);
|
|
|
|
if (system->div_shift >= 0) {
|
|
value = readl(priv->base + system->reg);
|
|
value = (value >> system->div_shift) & system->div_mask;
|
|
}
|
|
value += 1;
|
|
return parent_rate / value;
|
|
}
|
|
|
|
static int system_clk_set_parent(struct clk *clk,
|
|
struct clk *parent, int index)
|
|
{
|
|
int i;
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_sys_clk *system = &tree->system[index];
|
|
|
|
value = readl((void *)((long)priv->base + system->reg));
|
|
value &= ~(system->mux_mask << system->mux_shift);
|
|
for (i = 0; i < system->parent_cnt; i++) {
|
|
if (system->parent[i] == parent->id) {
|
|
value |= (i << system->mux_shift);
|
|
if (system->type == AIC_CPU_CLK)
|
|
value = cpu_clk_write_enable(system, value);
|
|
writel(value, (void *)((long)priv->base + system->reg));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EPERM;
|
|
}
|
|
|
|
static int periph_clk_enable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_periph_clk *periph = &tree->periph[index];
|
|
|
|
value = readl(priv->base + periph->reg);
|
|
if (periph->bus_gate >= 0)
|
|
value |= 1 << periph->bus_gate;
|
|
if (periph->mod_gate >= 0)
|
|
value |= 1 << periph->mod_gate;
|
|
writel(value, priv->base + periph->reg);
|
|
|
|
value = readl(priv->base + periph->reg);
|
|
return 0;
|
|
}
|
|
|
|
static int periph_clk_disable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_periph_clk *periph = &tree->periph[index];
|
|
|
|
value = readl(priv->base + periph->reg);
|
|
if (periph->bus_gate >= 0)
|
|
value &= ~(1 << periph->bus_gate);
|
|
if (periph->mod_gate >= 0)
|
|
value &= ~(1 << periph->mod_gate);
|
|
writel(value, priv->base + periph->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong periph_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
u32 value, parent_rate;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_periph_clk *periph = &tree->periph[index];
|
|
|
|
parent_rate = artinchip_get_parent_rate(clk, periph->parent);
|
|
|
|
value = readl(priv->base + periph->reg);
|
|
value = (value >> periph->div_shift) & periph->div_mask;
|
|
|
|
return parent_rate / (value + 1);
|
|
}
|
|
|
|
static ulong periph_clk_set_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 value, parent_rate, div;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_periph_clk *periph = &tree->periph[index];
|
|
|
|
parent_rate = artinchip_get_parent_rate(clk, periph->parent);
|
|
div = DIV_ROUND_CLOSEST(parent_rate, rate);
|
|
if (div > 0)
|
|
div = div - 1;
|
|
value = readl(priv->base + periph->reg);
|
|
value &= ~(periph->div_mask << periph->div_shift);
|
|
value |= (div & periph->div_mask) << periph->div_shift;
|
|
writel(value, priv->base + periph->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong disp_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
u32 reg_val, parent_rate, pix_divsel, divn, divm, divl, rate;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_disp_clk *disp = &tree->disp[index];
|
|
|
|
parent_rate = artinchip_get_parent_rate(clk, disp->parent);
|
|
reg_val = readl(priv->base + disp->reg);
|
|
|
|
if (disp->divm_mask) {
|
|
/* for pixclk */
|
|
pix_divsel = (reg_val >> disp->pix_divsel_shift) &
|
|
disp->pix_divsel_mask;
|
|
switch (pix_divsel) {
|
|
case 0:
|
|
divl = (reg_val >> disp->divl_shift) & disp->divl_mask;
|
|
divm = (reg_val >> disp->divm_shift) & disp->divm_mask;
|
|
rate = parent_rate / (1 << divl) / (divm + 1);
|
|
break;
|
|
case 1:
|
|
rate = parent_rate * 2 / 7;
|
|
break;
|
|
case 2:
|
|
rate = parent_rate * 2 / 9;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
/* for SCLK */
|
|
divn = (reg_val >> disp->divn_shift) & disp->divn_mask;
|
|
rate = parent_rate / (1 << divn);
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
static void disp_clk_try_best_divider_pixclk(ulong rate, ulong parent_rate,
|
|
u32 divm_max, u32 *divm,
|
|
u32 divl_max, u32 *divl,
|
|
u8 *pix_divsel)
|
|
{
|
|
u32 tmp, tmpm, tmpl, val0, val1, val2, i;
|
|
|
|
/* Calculate clock division */
|
|
tmp = DIV_ROUND_CLOSEST(parent_rate, rate);
|
|
/* Calculate the value of divl */
|
|
tmpl = DIV_ROUND_UP(tmp, divm_max);
|
|
for (i = 0; i < divl_max; i++)
|
|
if (1 << i >= tmpl)
|
|
break;
|
|
tmpm = tmp / (1 << i);
|
|
|
|
if ((1 << i) * tmpm * rate == parent_rate)
|
|
*pix_divsel = 0;
|
|
else {
|
|
val0 = abs((1 << i) * tmpm * rate - parent_rate);
|
|
val1 = abs(rate * 7 / 2 - parent_rate);
|
|
val2 = abs(rate * 9 / 2 - parent_rate);
|
|
|
|
if (val0 < val1 && val0 < val2)
|
|
*pix_divsel = 0;
|
|
else if (val1 < val0 && val1 < val2)
|
|
*pix_divsel = 1;
|
|
else if (val2 < val0 && val2 < val1)
|
|
*pix_divsel = 2;
|
|
else
|
|
*pix_divsel = 0;
|
|
}
|
|
|
|
*divm = tmpm - 1;
|
|
*divl = i;
|
|
}
|
|
|
|
static void disp_clk_try_best_divider(struct clk *clk,
|
|
struct aic_disp_clk *disp,
|
|
ulong rate, ulong *parent_rate,
|
|
u32 max_divn, u32 *divn)
|
|
{
|
|
u32 tmp, i;
|
|
ulong prrate;
|
|
|
|
struct clk parent = { .id = disp->parent, .dev = clk->dev };
|
|
|
|
for (i = 0; i < max_divn; i++) {
|
|
tmp = (1 << i) * rate;
|
|
prrate = clk_round_rate(&parent, tmp);
|
|
if (prrate <= tmp) {
|
|
*divn = i;
|
|
*parent_rate = prrate;
|
|
return;
|
|
}
|
|
}
|
|
|
|
*divn = 0;
|
|
}
|
|
|
|
static ulong disp_clk_set_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 reg_val, divm, divn, divl;
|
|
ulong parent_rate, parent_rate_old;
|
|
u8 pix_divsel = 0;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_disp_clk *disp = &tree->disp[index];
|
|
|
|
parent_rate = artinchip_get_parent_rate(clk, disp->parent);
|
|
reg_val = readl(priv->base + disp->reg);
|
|
|
|
if (disp->divm_mask) {
|
|
/* for pixclk */
|
|
disp_clk_try_best_divider_pixclk(rate, parent_rate,
|
|
disp->divm_mask + 1, &divm,
|
|
disp->divl_mask + 1, &divl,
|
|
&pix_divsel);
|
|
if (pix_divsel) {
|
|
reg_val &= ~(disp->pix_divsel_mask <<
|
|
disp->pix_divsel_shift);
|
|
reg_val |= (pix_divsel << disp->pix_divsel_shift);
|
|
} else {
|
|
reg_val &= ~((disp->pix_divsel_mask <<
|
|
disp->pix_divsel_shift) |
|
|
(disp->divm_mask << disp->divm_shift) |
|
|
(disp->divl_mask << disp->divl_shift));
|
|
reg_val |= (divm << disp->divm_shift) |
|
|
(divl << disp->divl_shift);
|
|
}
|
|
} else {
|
|
/* for SCLK */
|
|
parent_rate_old = parent_rate;
|
|
disp_clk_try_best_divider(clk, disp, rate, &parent_rate,
|
|
disp->divn_mask + 1, &divn);
|
|
/* If new parent clock rate is needed, set the parent rate */
|
|
if (parent_rate != parent_rate_old) {
|
|
struct clk parent = { .id = disp->parent,
|
|
.dev = clk->dev };
|
|
clk_set_rate(&parent, parent_rate);
|
|
}
|
|
|
|
reg_val &= ~(disp->divn_mask << disp->divn_shift);
|
|
reg_val |= (divn << disp->divn_shift);
|
|
}
|
|
|
|
writel(reg_val, priv->base + disp->reg);
|
|
return 0;
|
|
}
|
|
|
|
static int output_clk_enable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_out *clkout = &tree->clkout[index];
|
|
|
|
value = readl(priv->base + clkout->reg);
|
|
value |= 1 << 16;
|
|
writel(value, priv->base + clkout->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int output_clk_disable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_out *clkout = &tree->clkout[index];
|
|
|
|
value = readl(priv->base + clkout->reg);
|
|
value &= ~(1 << 16);
|
|
writel(value, priv->base + clkout->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong output_clk_get_rate(struct clk *clk, int index)
|
|
{
|
|
u32 value, parent, parent_rate, div_n0;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_out *clkout = &tree->clkout[index];
|
|
|
|
value = readl(priv->base + clkout->reg);
|
|
div_n0 = (value >> clkout->div0_shift) & clkout->div0_mask;
|
|
parent = clkout->parent[(value >> clkout->mux_shift) &
|
|
clkout->mux_mask];
|
|
|
|
parent_rate = artinchip_get_parent_rate(clk, parent);
|
|
|
|
return parent_rate / (div_n0 + 1);
|
|
}
|
|
|
|
static ulong output_clk_set_rate(struct clk *clk, ulong rate, int index)
|
|
{
|
|
u32 value, parent, parent_rate, div_n0;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_out *clkout = &tree->clkout[index];
|
|
|
|
value = readl(priv->base + clkout->reg);
|
|
|
|
parent = clkout->parent[(value >> clkout->mux_shift) &
|
|
clkout->mux_mask];
|
|
parent_rate = artinchip_get_parent_rate(clk, parent);
|
|
div_n0 = DIV_ROUND_CLOSEST(parent_rate, rate);
|
|
if (div_n0 > 0)
|
|
div_n0 -= 1;
|
|
|
|
value &= ~(clkout->div0_mask << clkout->div0_shift);
|
|
value |= (div_n0 << clkout->div0_shift);
|
|
writel(value, priv->base + clkout->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int output_clk_set_parent(struct clk *clk,
|
|
struct clk *parent, int index)
|
|
{
|
|
int i;
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_out *clkout = &tree->clkout[index];
|
|
|
|
value = readl(priv->base + clkout->reg);
|
|
value &= ~(clkout->mux_mask << clkout->mux_shift);
|
|
for (i = 0; i < clkout->parent_cnt; i++) {
|
|
if (clkout->parent[i] == parent->id) {
|
|
value |= (i << clkout->mux_shift);
|
|
writel(value, priv->base + clkout->reg);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EPERM;
|
|
}
|
|
#ifdef CONFIG_CLK_ARTINCHIP_CMU_V2_0
|
|
static int corsszone_clk_enable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_crosszone *cz_clk = &tree->clk_cz[index];
|
|
|
|
value = readl(priv->cz_base + cz_clk->reg);
|
|
if (cz_clk->bus_gate >= 0)
|
|
value |= 1 << cz_clk->bus_gate;
|
|
if (cz_clk->mod_gate >= 0)
|
|
value |= 1 << cz_clk->mod_gate;
|
|
writel(value, priv->cz_base + cz_clk->reg);
|
|
|
|
value = readl(priv->cz_base + cz_clk->reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int corsszone_clk_disable(struct clk *clk, int index)
|
|
{
|
|
u32 value;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
struct aic_clk_tree *tree = priv->tree;
|
|
struct aic_clk_crosszone *cz_clk = &tree->clk_cz[index];
|
|
|
|
value = readl(priv->cz_base + cz_clk->reg);
|
|
if (cz_clk->bus_gate >= 0)
|
|
value &= ~(1 << cz_clk->bus_gate);
|
|
if (cz_clk->mod_gate >= 0)
|
|
value &= ~(1 << cz_clk->mod_gate);
|
|
writel(value, priv->cz_base + cz_clk->reg);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
static struct aic_clk_ops aic_clk_type_ops[] = {
|
|
/* ops handle for fixed rate clocks */
|
|
{
|
|
.get_rate = fixed_rate_clk_get_rate,
|
|
},
|
|
|
|
/* ops handle for pll clocks */
|
|
{
|
|
.enable = pll_clk_enable,
|
|
.disable = pll_clk_disable,
|
|
.get_rate = pll_clk_get_rate,
|
|
.set_rate = pll_clk_set_rate,
|
|
.round_rate = pll_clk_round_rate,
|
|
},
|
|
|
|
/* ops handle for system clocks */
|
|
{
|
|
.enable = system_clk_enable,
|
|
.disable = system_clk_disable,
|
|
.set_rate = system_clk_set_rate,
|
|
.get_rate = system_clk_get_rate,
|
|
.set_parent = system_clk_set_parent,
|
|
},
|
|
|
|
/* ops handle for periph clocks */
|
|
{
|
|
.enable = periph_clk_enable,
|
|
.disable = periph_clk_disable,
|
|
.get_rate = periph_clk_get_rate,
|
|
.set_rate = periph_clk_set_rate,
|
|
},
|
|
|
|
/* ops handle for disp clocks */
|
|
{
|
|
.get_rate = disp_clk_get_rate,
|
|
.set_rate = disp_clk_set_rate,
|
|
},
|
|
|
|
/* ops handle for output clocks */
|
|
{
|
|
.enable = output_clk_enable,
|
|
.disable = output_clk_disable,
|
|
.get_rate = output_clk_get_rate,
|
|
.set_rate = output_clk_set_rate,
|
|
.set_parent = output_clk_set_parent,
|
|
},
|
|
#ifdef CONFIG_CLK_ARTINCHIP_CMU_V2_0
|
|
/* ops handle for corsszone clocks */
|
|
{
|
|
.enable = corsszone_clk_enable,
|
|
.disable = corsszone_clk_disable
|
|
}
|
|
#endif
|
|
};
|
|
|
|
static ulong artinchip_clk_get_rate(struct clk *clk)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].get_rate)
|
|
return aic_clk_type_ops[type].get_rate(clk, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong artinchip_clk_set_rate(struct clk *clk, ulong rate)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].set_rate)
|
|
return aic_clk_type_ops[type].set_rate(clk, rate, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int artinchip_clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].set_parent)
|
|
return aic_clk_type_ops[type].set_parent(clk, parent, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int artinchip_clk_enable(struct clk *clk)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].enable)
|
|
return aic_clk_type_ops[type].enable(clk, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int artinchip_clk_disable(struct clk *clk)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].disable)
|
|
return aic_clk_type_ops[type].disable(clk, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ulong artinchip_clk_round_rate(struct clk *clk, ulong rate)
|
|
{
|
|
u32 index;
|
|
enum aic_clk_type type;
|
|
struct aic_clk_priv *priv = dev_get_priv(clk->dev);
|
|
|
|
type = aic_get_clk_info(priv->tree, clk->id, &index);
|
|
if ((type != AIC_CLK_UNKNOWN) && aic_clk_type_ops[type].round_rate)
|
|
return aic_clk_type_ops[type].round_rate(clk, rate, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct clk_ops artinchip_clk_ops = {
|
|
.get_rate = artinchip_clk_get_rate,
|
|
.set_rate = artinchip_clk_set_rate,
|
|
.set_parent = artinchip_clk_set_parent,
|
|
.enable = artinchip_clk_enable,
|
|
.disable = artinchip_clk_disable,
|
|
.round_rate = artinchip_clk_round_rate,
|
|
};
|
|
|
|
|
|
int aic_clk_common_init(struct udevice *dev, struct aic_clk_tree *tree)
|
|
{
|
|
struct aic_clk_priv *priv = dev_get_priv(dev);
|
|
|
|
priv->base = dev_read_addr_ptr(dev);
|
|
if (!priv->base)
|
|
return -ENOENT;
|
|
|
|
priv->tree = tree;
|
|
|
|
return 0;
|
|
}
|