CPU clock handling for Rockchip SoCs

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQEcBAABCAAGBQJUJt+NAAoJEPOmecmc0R2BzmEH/00GYMLZZZrhUUBzF5+O5Wvu
 2jjVwZkyDsEL7Dnhen45Z+2umoXat8TbgNoeX+BbLEj55ntfAv1aNV62gO/wLrJy
 GbUDeAMNLXnJsF3UefBHvDf1F7SOZAm7QPs9oYQMyUDQqzsorfuDjKDPPLAHoiO9
 7TfVxLYqkd4lVm0cYo4tMYTgONK2QR6iKEGHHkXIsKZXMdd2gMAxuTIjBcTqbBu1
 9Hxt9P2G9InmRaSmW4zXzMlur0it16BnFt2wnJGoawBZkkwnvkze54APQgA4GV61
 tqUhh94mQdAzJd1wMtJASK6ZY79pjv9pmGgQ5kNVlDV0SaSafZ1MhQU4k7NdemQ=
 =myeX
 -----END PGP SIGNATURE-----

Merge tag 'v3.18-rockchip-cpuclk' of git://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip into clk-next

CPU clock handling for Rockchip SoCs
This commit is contained in:
Mike Turquette 2014-09-27 12:50:40 -07:00
commit 5ad67d3e5e
9 changed files with 629 additions and 78 deletions

View File

@ -5,6 +5,7 @@
obj-y += clk-rockchip.o
obj-y += clk.o
obj-y += clk-pll.o
obj-y += clk-cpu.o
obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
obj-y += clk-rk3188.o

View File

@ -0,0 +1,329 @@
/*
* Copyright (c) 2014 MundoReader S.L.
* Author: Heiko Stuebner <heiko@sntech.de>
*
* based on clk/samsung/clk-cpu.c
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* Author: Thomas Abraham <thomas.ab@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* A CPU clock is defined as a clock supplied to a CPU or a group of CPUs.
* The CPU clock is typically derived from a hierarchy of clock
* blocks which includes mux and divider blocks. There are a number of other
* auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
* clock for CPU domain. The rates of these auxiliary clocks are related to the
* CPU clock rate and this relation is usually specified in the hardware manual
* of the SoC or supplied after the SoC characterization.
*
* The below implementation of the CPU clock allows the rate changes of the CPU
* clock and the corresponding rate changes of the auxillary clocks of the CPU
* domain. The platform clock driver provides a clock register configuration
* for each configurable rate which is then used to program the clock hardware
* registers to acheive a fast co-oridinated rate change for all the CPU domain
* clocks.
*
* On a rate change request for the CPU clock, the rate change is propagated
* upto the PLL supplying the clock to the CPU domain clock blocks. While the
* CPU domain PLL is reconfigured, the CPU domain clocks are driven using an
* alternate clock source. If required, the alternate clock source is divided
* down in order to keep the output clock rate within the previous OPP limits.
*/
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include "clk.h"
/**
* struct rockchip_cpuclk: information about clock supplied to a CPU core.
* @hw: handle between ccf and cpu clock.
* @alt_parent: alternate parent clock to use when switching the speed
* of the primary parent clock.
* @reg_base: base register for cpu-clock values.
* @clk_nb: clock notifier registered for changes in clock speed of the
* primary parent clock.
* @rate_count: number of rates in the rate_table
* @rate_table: pll-rates and their associated dividers
* @reg_data: cpu-specific register settings
* @lock: clock lock
*/
struct rockchip_cpuclk {
struct clk_hw hw;
struct clk_mux cpu_mux;
const struct clk_ops *cpu_mux_ops;
struct clk *alt_parent;
void __iomem *reg_base;
struct notifier_block clk_nb;
unsigned int rate_count;
struct rockchip_cpuclk_rate_table *rate_table;
const struct rockchip_cpuclk_reg_data *reg_data;
spinlock_t *lock;
};
#define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)
#define to_rockchip_cpuclk_nb(nb) \
container_of(nb, struct rockchip_cpuclk, clk_nb)
static const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings(
struct rockchip_cpuclk *cpuclk, unsigned long rate)
{
const struct rockchip_cpuclk_rate_table *rate_table =
cpuclk->rate_table;
int i;
for (i = 0; i < cpuclk->rate_count; i++) {
if (rate == rate_table[i].prate)
return &rate_table[i];
}
return NULL;
}
static unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw);
const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg);
clksel0 >>= reg_data->div_core_shift;
clksel0 &= reg_data->div_core_mask;
return parent_rate / (clksel0 + 1);
}
static const struct clk_ops rockchip_cpuclk_ops = {
.recalc_rate = rockchip_cpuclk_recalc_rate,
};
static void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk,
const struct rockchip_cpuclk_rate_table *rate)
{
int i;
/* alternate parent is active now. set the dividers */
for (i = 0; i < ARRAY_SIZE(rate->divs); i++) {
const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i];
if (!clksel->reg)
continue;
pr_debug("%s: setting reg 0x%x to 0x%x\n",
__func__, clksel->reg, clksel->val);
writel(clksel->val , cpuclk->reg_base + clksel->reg);
}
}
static int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk,
struct clk_notifier_data *ndata)
{
const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
unsigned long alt_prate, alt_div;
alt_prate = clk_get_rate(cpuclk->alt_parent);
spin_lock(cpuclk->lock);
/*
* If the old parent clock speed is less than the clock speed
* of the alternate parent, then it should be ensured that at no point
* the armclk speed is more than the old_rate until the dividers are
* set.
*/
if (alt_prate > ndata->old_rate) {
/* calculate dividers */
alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1;
if (alt_div > reg_data->div_core_mask) {
pr_warn("%s: limiting alt-divider %lu to %d\n",
__func__, alt_div, reg_data->div_core_mask);
alt_div = reg_data->div_core_mask;
}
/*
* Change parents and add dividers in a single transaction.
*
* NOTE: we do this in a single transaction so we're never
* dividing the primary parent by the extra dividers that were
* needed for the alt.
*/
pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n",
__func__, alt_div, alt_prate, ndata->old_rate);
writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask,
reg_data->div_core_shift) |
HIWORD_UPDATE(1, 1, reg_data->mux_core_shift),
cpuclk->reg_base + reg_data->core_reg);
} else {
/* select alternate parent */
writel(HIWORD_UPDATE(1, 1, reg_data->mux_core_shift),
cpuclk->reg_base + reg_data->core_reg);
}
spin_unlock(cpuclk->lock);
return 0;
}
static int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk,
struct clk_notifier_data *ndata)
{
const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
const struct rockchip_cpuclk_rate_table *rate;
rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
if (!rate) {
pr_err("%s: Invalid rate : %lu for cpuclk\n",
__func__, ndata->new_rate);
return -EINVAL;
}
spin_lock(cpuclk->lock);
if (ndata->old_rate < ndata->new_rate)
rockchip_cpuclk_set_dividers(cpuclk, rate);
/*
* post-rate change event, re-mux to primary parent and remove dividers.
*
* NOTE: we do this in a single transaction so we're never dividing the
* primary parent by the extra dividers that were needed for the alt.
*/
writel(HIWORD_UPDATE(0, reg_data->div_core_mask,
reg_data->div_core_shift) |
HIWORD_UPDATE(0, 1, reg_data->mux_core_shift),
cpuclk->reg_base + reg_data->core_reg);
if (ndata->old_rate > ndata->new_rate)
rockchip_cpuclk_set_dividers(cpuclk, rate);
spin_unlock(cpuclk->lock);
return 0;
}
/*
* This clock notifier is called when the frequency of the parent clock
* of cpuclk is to be changed. This notifier handles the setting up all
* the divider clocks, remux to temporary parent and handling the safe
* frequency levels when using temporary parent.
*/
static int rockchip_cpuclk_notifier_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct clk_notifier_data *ndata = data;
struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb);
int ret = 0;
pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
__func__, event, ndata->old_rate, ndata->new_rate);
if (event == PRE_RATE_CHANGE)
ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata);
else if (event == POST_RATE_CHANGE)
ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata);
return notifier_from_errno(ret);
}
struct clk *rockchip_clk_register_cpuclk(const char *name,
const char **parent_names, u8 num_parents,
const struct rockchip_cpuclk_reg_data *reg_data,
const struct rockchip_cpuclk_rate_table *rates,
int nrates, void __iomem *reg_base, spinlock_t *lock)
{
struct rockchip_cpuclk *cpuclk;
struct clk_init_data init;
struct clk *clk, *cclk;
int ret;
if (num_parents != 2) {
pr_err("%s: needs two parent clocks\n", __func__);
return ERR_PTR(-EINVAL);
}
cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
if (!cpuclk)
return ERR_PTR(-ENOMEM);
init.name = name;
init.parent_names = &parent_names[0];
init.num_parents = 1;
init.ops = &rockchip_cpuclk_ops;
/* only allow rate changes when we have a rate table */
init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0;
/* disallow automatic parent changes by ccf */
init.flags |= CLK_SET_RATE_NO_REPARENT;
init.flags |= CLK_GET_RATE_NOCACHE;
cpuclk->reg_base = reg_base;
cpuclk->lock = lock;
cpuclk->reg_data = reg_data;
cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb;
cpuclk->hw.init = &init;
cpuclk->alt_parent = __clk_lookup(parent_names[1]);
if (!cpuclk->alt_parent) {
pr_err("%s: could not lookup alternate parent\n",
__func__);
ret = -EINVAL;
goto free_cpuclk;
}
ret = clk_prepare_enable(cpuclk->alt_parent);
if (ret) {
pr_err("%s: could not enable alternate parent\n",
__func__);
goto free_cpuclk;
}
clk = __clk_lookup(parent_names[0]);
if (!clk) {
pr_err("%s: could not lookup parent clock %s\n",
__func__, parent_names[0]);
ret = -EINVAL;
goto free_cpuclk;
}
ret = clk_notifier_register(clk, &cpuclk->clk_nb);
if (ret) {
pr_err("%s: failed to register clock notifier for %s\n",
__func__, name);
goto free_cpuclk;
}
if (nrates > 0) {
cpuclk->rate_count = nrates;
cpuclk->rate_table = kmemdup(rates,
sizeof(*rates) * nrates,
GFP_KERNEL);
if (!cpuclk->rate_table) {
pr_err("%s: could not allocate memory for cpuclk rates\n",
__func__);
ret = -ENOMEM;
goto unregister_notifier;
}
}
cclk = clk_register(NULL, &cpuclk->hw);
if (IS_ERR(clk)) {
pr_err("%s: could not register cpuclk %s\n", __func__, name);
ret = PTR_ERR(clk);
goto free_rate_table;
}
return cclk;
free_rate_table:
kfree(cpuclk->rate_table);
unregister_notifier:
clk_notifier_unregister(clk, &cpuclk->clk_nb);
free_cpuclk:
kfree(cpuclk);
return ERR_PTR(ret);
}

View File

@ -34,7 +34,6 @@ struct rockchip_clk_pll {
const struct clk_ops *pll_mux_ops;
struct notifier_block clk_nb;
bool rate_change_remuxed;
void __iomem *reg_base;
int lock_offset;
@ -108,38 +107,6 @@ static int rockchip_pll_wait_lock(struct rockchip_clk_pll *pll)
return -ETIMEDOUT;
}
/**
* Set pll mux when changing the pll rate.
* This makes sure to move the pll mux away from the actual pll before
* changing its rate and back to the original parent after the change.
*/
static int rockchip_pll_notifier_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct rockchip_clk_pll *pll = to_rockchip_clk_pll_nb(nb);
struct clk_mux *pll_mux = &pll->pll_mux;
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
int cur_parent;
switch (event) {
case PRE_RATE_CHANGE:
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
pll->rate_change_remuxed = 1;
}
break;
case POST_RATE_CHANGE:
if (pll->rate_change_remuxed) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
pll->rate_change_remuxed = 0;
}
break;
}
return NOTIFY_OK;
}
/**
* PLL used in RK3066, RK3188 and RK3288
*/
@ -194,6 +161,10 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate,
const struct rockchip_pll_rate_table *rate;
unsigned long old_rate = rockchip_rk3066_pll_recalc_rate(hw, prate);
struct regmap *grf = rockchip_clk_get_grf();
struct clk_mux *pll_mux = &pll->pll_mux;
const struct clk_ops *pll_mux_ops = pll->pll_mux_ops;
int rate_change_remuxed = 0;
int cur_parent;
int ret;
if (IS_ERR(grf)) {
@ -216,6 +187,12 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate,
pr_debug("%s: rate settings for %lu (nr, no, nf): (%d, %d, %d)\n",
__func__, rate->rate, rate->nr, rate->no, rate->nf);
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
/* enter reset mode */
writel(HIWORD_UPDATE(RK3066_PLLCON3_RESET, RK3066_PLLCON3_RESET, 0),
pll->reg_base + RK3066_PLLCON(3));
@ -247,6 +224,9 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate,
rockchip_rk3066_pll_set_rate(hw, old_rate, prate);
}
if (rate_change_remuxed)
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM);
return ret;
}
@ -310,7 +290,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
struct clk_mux *pll_mux;
struct clk *pll_clk, *mux_clk;
char pll_name[20];
int ret;
if (num_parents != 2) {
pr_err("%s: needs two parent clocks\n", __func__);
@ -367,7 +346,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
pll->lock_offset = grf_lock_offset;
pll->lock_shift = lock_shift;
pll->lock = lock;
pll->clk_nb.notifier_call = rockchip_pll_notifier_cb;
pll_clk = clk_register(NULL, &pll->hw);
if (IS_ERR(pll_clk)) {
@ -377,14 +355,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
goto err_pll;
}
ret = clk_notifier_register(pll_clk, &pll->clk_nb);
if (ret) {
pr_err("%s: failed to register clock notifier for %s : %d\n",
__func__, name, ret);
mux_clk = ERR_PTR(ret);
goto err_pll_notifier;
}
/* create the mux on top of the real pll */
pll->pll_mux_ops = &clk_mux_ops;
pll_mux = &pll->pll_mux;
@ -417,13 +387,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
return mux_clk;
err_mux:
ret = clk_notifier_unregister(pll_clk, &pll->clk_nb);
if (ret) {
pr_err("%s: could not unregister clock notifier in error path : %d\n",
__func__, ret);
return mux_clk;
}
err_pll_notifier:
clk_unregister(pll_clk);
err_pll:
kfree(pll);

View File

@ -19,6 +19,7 @@
#include <dt-bindings/clock/rk3188-cru-common.h>
#include "clk.h"
#define RK3066_GRF_SOC_STATUS 0x15c
#define RK3188_GRF_SOC_STATUS 0xac
enum rk3188_plls {
@ -100,6 +101,98 @@ struct rockchip_pll_rate_table rk3188_pll_rates[] = {
{ /* sentinel */ },
};
#define RK3066_DIV_CORE_PERIPH_MASK 0x3
#define RK3066_DIV_CORE_PERIPH_SHIFT 6
#define RK3066_DIV_ACLK_CORE_MASK 0x7
#define RK3066_DIV_ACLK_CORE_SHIFT 0
#define RK3066_DIV_ACLK_HCLK_MASK 0x3
#define RK3066_DIV_ACLK_HCLK_SHIFT 8
#define RK3066_DIV_ACLK_PCLK_MASK 0x3
#define RK3066_DIV_ACLK_PCLK_SHIFT 12
#define RK3066_DIV_AHB2APB_MASK 0x3
#define RK3066_DIV_AHB2APB_SHIFT 14
#define RK3066_CLKSEL0(_core_peri) \
{ \
.reg = RK2928_CLKSEL_CON(0), \
.val = HIWORD_UPDATE(_core_peri, RK3066_DIV_CORE_PERIPH_MASK, \
RK3066_DIV_CORE_PERIPH_SHIFT) \
}
#define RK3066_CLKSEL1(_aclk_core, _aclk_hclk, _aclk_pclk, _ahb2apb) \
{ \
.reg = RK2928_CLKSEL_CON(1), \
.val = HIWORD_UPDATE(_aclk_core, RK3066_DIV_ACLK_CORE_MASK, \
RK3066_DIV_ACLK_CORE_SHIFT) | \
HIWORD_UPDATE(_aclk_hclk, RK3066_DIV_ACLK_HCLK_MASK, \
RK3066_DIV_ACLK_HCLK_SHIFT) | \
HIWORD_UPDATE(_aclk_pclk, RK3066_DIV_ACLK_PCLK_MASK, \
RK3066_DIV_ACLK_PCLK_SHIFT) | \
HIWORD_UPDATE(_ahb2apb, RK3066_DIV_AHB2APB_MASK, \
RK3066_DIV_AHB2APB_SHIFT), \
}
#define RK3066_CPUCLK_RATE(_prate, _core_peri, _acore, _ahclk, _apclk, _h2p) \
{ \
.prate = _prate, \
.divs = { \
RK3066_CLKSEL0(_core_peri), \
RK3066_CLKSEL1(_acore, _ahclk, _apclk, _h2p), \
}, \
}
static struct rockchip_cpuclk_rate_table rk3066_cpuclk_rates[] __initdata = {
RK3066_CPUCLK_RATE(1416000000, 2, 3, 1, 2, 1),
RK3066_CPUCLK_RATE(1200000000, 2, 3, 1, 2, 1),
RK3066_CPUCLK_RATE(1008000000, 2, 2, 1, 2, 1),
RK3066_CPUCLK_RATE( 816000000, 2, 2, 1, 2, 1),
RK3066_CPUCLK_RATE( 600000000, 1, 2, 1, 2, 1),
RK3066_CPUCLK_RATE( 504000000, 1, 1, 1, 2, 1),
RK3066_CPUCLK_RATE( 312000000, 0, 1, 1, 1, 0),
};
static const struct rockchip_cpuclk_reg_data rk3066_cpuclk_data = {
.core_reg = RK2928_CLKSEL_CON(0),
.div_core_shift = 0,
.div_core_mask = 0x1f,
.mux_core_shift = 8,
};
#define RK3188_DIV_ACLK_CORE_MASK 0x7
#define RK3188_DIV_ACLK_CORE_SHIFT 3
#define RK3188_CLKSEL1(_aclk_core) \
{ \
.reg = RK2928_CLKSEL_CON(1), \
.val = HIWORD_UPDATE(_aclk_core, RK3188_DIV_ACLK_CORE_MASK,\
RK3188_DIV_ACLK_CORE_SHIFT) \
}
#define RK3188_CPUCLK_RATE(_prate, _core_peri, _aclk_core) \
{ \
.prate = _prate, \
.divs = { \
RK3066_CLKSEL0(_core_peri), \
RK3188_CLKSEL1(_aclk_core), \
}, \
}
static struct rockchip_cpuclk_rate_table rk3188_cpuclk_rates[] __initdata = {
RK3188_CPUCLK_RATE(1608000000, 2, 3),
RK3188_CPUCLK_RATE(1416000000, 2, 3),
RK3188_CPUCLK_RATE(1200000000, 2, 3),
RK3188_CPUCLK_RATE(1008000000, 2, 3),
RK3188_CPUCLK_RATE( 816000000, 2, 3),
RK3188_CPUCLK_RATE( 600000000, 1, 3),
RK3188_CPUCLK_RATE( 504000000, 1, 3),
RK3188_CPUCLK_RATE( 312000000, 0, 1),
};
static const struct rockchip_cpuclk_reg_data rk3188_cpuclk_data = {
.core_reg = RK2928_CLKSEL_CON(0),
.div_core_shift = 9,
.div_core_mask = 0x1f,
.mux_core_shift = 8,
};
PNAME(mux_pll_p) = { "xin24m", "xin32k" };
PNAME(mux_armclk_p) = { "apll", "gpll_armclk" };
PNAME(mux_ddrphy_p) = { "dpll", "gpll_ddr" };
@ -173,17 +266,10 @@ static struct rockchip_clk_branch common_clk_branches[] __initdata = {
GATE(0, "aclk_cpu", "aclk_cpu_pre", 0,
RK2928_CLKGATE_CON(0), 3, GFLAGS),
DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO),
GATE(0, "atclk_cpu", "pclk_cpu_pre", 0,
RK2928_CLKGATE_CON(0), 6, GFLAGS),
GATE(0, "pclk_cpu", "pclk_cpu_pre", 0,
RK2928_CLKGATE_CON(0), 5, GFLAGS),
DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO),
COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO,
RK2928_CLKGATE_CON(4), 9, GFLAGS),
GATE(0, "hclk_cpu", "hclk_cpu_pre", 0,
RK2928_CLKGATE_CON(0), 4, GFLAGS),
@ -412,10 +498,18 @@ static struct clk_div_table div_aclk_cpu_t[] = {
};
static struct rockchip_clk_branch rk3066a_clk_branches[] __initdata = {
COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0,
RK2928_CLKSEL_CON(0), 8, 1, MFLAGS, 0, 5, DFLAGS),
DIVTBL(0, "aclk_cpu_pre", "armclk", 0,
RK2928_CLKSEL_CON(1), 0, 3, DFLAGS, div_aclk_cpu_t),
RK2928_CLKSEL_CON(1), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, div_aclk_cpu_t),
DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO
| CLK_DIVIDER_READ_ONLY),
DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO
| CLK_DIVIDER_READ_ONLY),
COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO
| CLK_DIVIDER_READ_ONLY,
RK2928_CLKGATE_CON(4), 9, GFLAGS),
GATE(CORE_L2C, "core_l2c", "aclk_cpu", 0,
RK2928_CLKGATE_CON(9), 4, GFLAGS),
@ -524,8 +618,6 @@ PNAME(mux_hsicphy_p) = { "sclk_otgphy0", "sclk_otgphy1",
"gpll", "cpll" };
static struct rockchip_clk_branch rk3188_clk_branches[] __initdata = {
COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0,
RK2928_CLKSEL_CON(0), 8, 1, MFLAGS, 9, 5, DFLAGS),
COMPOSITE_NOMUX_DIVTBL(0, "aclk_core", "armclk", 0,
RK2928_CLKSEL_CON(1), 3, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
div_rk3188_aclk_core_t, RK2928_CLKGATE_CON(0), 7, GFLAGS),
@ -533,6 +625,13 @@ static struct rockchip_clk_branch rk3188_clk_branches[] __initdata = {
/* do not source aclk_cpu_pre from the apll, to keep complexity down */
COMPOSITE_NOGATE(0, "aclk_cpu_pre", mux_aclk_cpu_p, CLK_SET_RATE_NO_REPARENT,
RK2928_CLKSEL_CON(0), 5, 1, MFLAGS, 0, 5, DFLAGS),
DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO),
DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO),
COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0,
RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO,
RK2928_CLKGATE_CON(4), 9, GFLAGS),
GATE(CORE_L2C, "core_l2c", "armclk", 0,
RK2928_CLKGATE_CON(9), 4, GFLAGS),
@ -629,9 +728,6 @@ static void __init rk3188_common_clk_init(struct device_node *np)
pr_warn("%s: could not register clock usb480m: %ld\n",
__func__, PTR_ERR(clk));
rockchip_clk_register_plls(rk3188_pll_clks,
ARRAY_SIZE(rk3188_pll_clks),
RK3188_GRF_SOC_STATUS);
rockchip_clk_register_branches(common_clk_branches,
ARRAY_SIZE(common_clk_branches));
rockchip_clk_protect_critical(rk3188_critical_clocks,
@ -644,16 +740,51 @@ static void __init rk3188_common_clk_init(struct device_node *np)
static void __init rk3066a_clk_init(struct device_node *np)
{
rk3188_common_clk_init(np);
rockchip_clk_register_plls(rk3188_pll_clks,
ARRAY_SIZE(rk3188_pll_clks),
RK3066_GRF_SOC_STATUS);
rockchip_clk_register_branches(rk3066a_clk_branches,
ARRAY_SIZE(rk3066a_clk_branches));
rockchip_clk_register_armclk(ARMCLK, "armclk",
mux_armclk_p, ARRAY_SIZE(mux_armclk_p),
&rk3066_cpuclk_data, rk3066_cpuclk_rates,
ARRAY_SIZE(rk3066_cpuclk_rates));
}
CLK_OF_DECLARE(rk3066a_cru, "rockchip,rk3066a-cru", rk3066a_clk_init);
static void __init rk3188a_clk_init(struct device_node *np)
{
struct clk *clk1, *clk2;
unsigned long rate;
int ret;
rk3188_common_clk_init(np);
rockchip_clk_register_plls(rk3188_pll_clks,
ARRAY_SIZE(rk3188_pll_clks),
RK3188_GRF_SOC_STATUS);
rockchip_clk_register_branches(rk3188_clk_branches,
ARRAY_SIZE(rk3188_clk_branches));
rockchip_clk_register_armclk(ARMCLK, "armclk",
mux_armclk_p, ARRAY_SIZE(mux_armclk_p),
&rk3188_cpuclk_data, rk3188_cpuclk_rates,
ARRAY_SIZE(rk3188_cpuclk_rates));
/* reparent aclk_cpu_pre from apll */
clk1 = __clk_lookup("aclk_cpu_pre");
clk2 = __clk_lookup("gpll");
if (clk1 && clk2) {
rate = clk_get_rate(clk1);
ret = clk_set_parent(clk1, clk2);
if (ret < 0)
pr_warn("%s: could not reparent aclk_cpu_pre to gpll\n",
__func__);
clk_set_rate(clk1, rate);
} else {
pr_warn("%s: missing clocks to reparent aclk_cpu_pre to gpll\n",
__func__);
}
}
CLK_OF_DECLARE(rk3188a_cru, "rockchip,rk3188a-cru", rk3188a_clk_init);

View File

@ -20,7 +20,7 @@
#include "clk.h"
#define RK3288_GRF_SOC_CON(x) (0x244 + x * 4)
#define RK3288_GRF_SOC_STATUS 0x280
#define RK3288_GRF_SOC_STATUS1 0x284
enum rk3288_plls {
apll, dpll, cpll, gpll, npll,
@ -101,6 +101,70 @@ struct rockchip_pll_rate_table rk3288_pll_rates[] = {
{ /* sentinel */ },
};
#define RK3288_DIV_ACLK_CORE_M0_MASK 0xf
#define RK3288_DIV_ACLK_CORE_M0_SHIFT 0
#define RK3288_DIV_ACLK_CORE_MP_MASK 0xf
#define RK3288_DIV_ACLK_CORE_MP_SHIFT 4
#define RK3288_DIV_L2RAM_MASK 0x7
#define RK3288_DIV_L2RAM_SHIFT 0
#define RK3288_DIV_ATCLK_MASK 0x1f
#define RK3288_DIV_ATCLK_SHIFT 4
#define RK3288_DIV_PCLK_DBGPRE_MASK 0x1f
#define RK3288_DIV_PCLK_DBGPRE_SHIFT 9
#define RK3288_CLKSEL0(_core_m0, _core_mp) \
{ \
.reg = RK3288_CLKSEL_CON(0), \
.val = HIWORD_UPDATE(_core_m0, RK3288_DIV_ACLK_CORE_M0_MASK, \
RK3288_DIV_ACLK_CORE_M0_SHIFT) | \
HIWORD_UPDATE(_core_mp, RK3288_DIV_ACLK_CORE_MP_MASK, \
RK3288_DIV_ACLK_CORE_MP_SHIFT), \
}
#define RK3288_CLKSEL37(_l2ram, _atclk, _pclk_dbg_pre) \
{ \
.reg = RK3288_CLKSEL_CON(37), \
.val = HIWORD_UPDATE(_l2ram, RK3288_DIV_L2RAM_MASK, \
RK3288_DIV_L2RAM_SHIFT) | \
HIWORD_UPDATE(_atclk, RK3288_DIV_ATCLK_MASK, \
RK3288_DIV_ATCLK_SHIFT) | \
HIWORD_UPDATE(_pclk_dbg_pre, \
RK3288_DIV_PCLK_DBGPRE_MASK, \
RK3288_DIV_PCLK_DBGPRE_SHIFT), \
}
#define RK3288_CPUCLK_RATE(_prate, _core_m0, _core_mp, _l2ram, _atclk, _pdbg) \
{ \
.prate = _prate, \
.divs = { \
RK3288_CLKSEL0(_core_m0, _core_mp), \
RK3288_CLKSEL37(_l2ram, _atclk, _pdbg), \
}, \
}
static struct rockchip_cpuclk_rate_table rk3288_cpuclk_rates[] __initdata = {
RK3288_CPUCLK_RATE(1800000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1704000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1608000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1512000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1416000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1200000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE(1008000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 816000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 696000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 600000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 408000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 312000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 216000000, 2, 4, 2, 4, 4),
RK3288_CPUCLK_RATE( 126000000, 2, 4, 2, 4, 4),
};
static const struct rockchip_cpuclk_reg_data rk3288_cpuclk_data = {
.core_reg = RK3288_CLKSEL_CON(0),
.div_core_shift = 8,
.div_core_mask = 0x1f,
.mux_core_shift = 15,
};
PNAME(mux_pll_p) = { "xin24m", "xin32k" };
PNAME(mux_armclk_p) = { "apll_core", "gpll_core" };
PNAME(mux_ddrphy_p) = { "dpll_ddr", "gpll_ddr" };
@ -166,35 +230,33 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = {
RK3288_CLKGATE_CON(0), 1, GFLAGS),
GATE(0, "gpll_core", "gpll", 0,
RK3288_CLKGATE_CON(0), 2, GFLAGS),
COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0,
RK3288_CLKSEL_CON(0), 15, 1, MFLAGS, 8, 5, DFLAGS),
COMPOSITE_NOMUX(0, "armcore0", "armclk", 0,
RK3288_CLKSEL_CON(36), 0, 3, DFLAGS,
RK3288_CLKSEL_CON(36), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 0, GFLAGS),
COMPOSITE_NOMUX(0, "armcore1", "armclk", 0,
RK3288_CLKSEL_CON(36), 4, 3, DFLAGS,
RK3288_CLKSEL_CON(36), 4, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 1, GFLAGS),
COMPOSITE_NOMUX(0, "armcore2", "armclk", 0,
RK3288_CLKSEL_CON(36), 8, 3, DFLAGS,
RK3288_CLKSEL_CON(36), 8, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 2, GFLAGS),
COMPOSITE_NOMUX(0, "armcore3", "armclk", 0,
RK3288_CLKSEL_CON(36), 12, 3, DFLAGS,
RK3288_CLKSEL_CON(36), 12, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 3, GFLAGS),
COMPOSITE_NOMUX(0, "l2ram", "armclk", 0,
RK3288_CLKSEL_CON(37), 0, 3, DFLAGS,
RK3288_CLKSEL_CON(37), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 4, GFLAGS),
COMPOSITE_NOMUX(0, "aclk_core_m0", "armclk", 0,
RK3288_CLKSEL_CON(0), 0, 4, DFLAGS,
RK3288_CLKSEL_CON(0), 0, 4, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 5, GFLAGS),
COMPOSITE_NOMUX(0, "aclk_core_mp", "armclk", 0,
RK3288_CLKSEL_CON(0), 4, 4, DFLAGS,
RK3288_CLKSEL_CON(0), 4, 4, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 6, GFLAGS),
COMPOSITE_NOMUX(0, "atclk", "armclk", 0,
RK3288_CLKSEL_CON(37), 4, 5, DFLAGS,
RK3288_CLKSEL_CON(37), 4, 5, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 7, GFLAGS),
COMPOSITE_NOMUX(0, "pclk_dbg_pre", "armclk", 0,
RK3288_CLKSEL_CON(37), 9, 5, DFLAGS,
RK3288_CLKSEL_CON(37), 9, 5, DFLAGS | CLK_DIVIDER_READ_ONLY,
RK3288_CLKGATE_CON(12), 8, GFLAGS),
GATE(0, "pclk_dbg", "pclk_dbg_pre", 0,
RK3288_CLKGATE_CON(12), 9, GFLAGS),
@ -733,12 +795,17 @@ static void __init rk3288_clk_init(struct device_node *np)
rockchip_clk_register_plls(rk3288_pll_clks,
ARRAY_SIZE(rk3288_pll_clks),
RK3288_GRF_SOC_STATUS);
RK3288_GRF_SOC_STATUS1);
rockchip_clk_register_branches(rk3288_clk_branches,
ARRAY_SIZE(rk3288_clk_branches));
rockchip_clk_protect_critical(rk3288_critical_clocks,
ARRAY_SIZE(rk3288_critical_clocks));
rockchip_clk_register_armclk(ARMCLK, "armclk",
mux_armclk_p, ARRAY_SIZE(mux_armclk_p),
&rk3288_cpuclk_data, rk3288_cpuclk_rates,
ARRAY_SIZE(rk3288_cpuclk_rates));
rockchip_register_softrst(np, 12, reg_base + RK3288_SOFTRST_CON(0),
ROCKCHIP_SOFTRST_HIWORD_MASK);
}

View File

@ -297,6 +297,27 @@ void __init rockchip_clk_register_branches(
}
}
void __init rockchip_clk_register_armclk(unsigned int lookup_id,
const char *name, const char **parent_names,
u8 num_parents,
const struct rockchip_cpuclk_reg_data *reg_data,
const struct rockchip_cpuclk_rate_table *rates,
int nrates)
{
struct clk *clk;
clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents,
reg_data, rates, nrates, reg_base,
&clk_lock);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s: %ld\n",
__func__, name, PTR_ERR(clk));
return;
}
rockchip_clk_add_lookup(clk, lookup_id);
}
void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks)
{
int i;

View File

@ -120,6 +120,38 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
struct rockchip_pll_rate_table *rate_table,
spinlock_t *lock);
struct rockchip_cpuclk_clksel {
int reg;
u32 val;
};
#define ROCKCHIP_CPUCLK_NUM_DIVIDERS 2
struct rockchip_cpuclk_rate_table {
unsigned long prate;
struct rockchip_cpuclk_clksel divs[ROCKCHIP_CPUCLK_NUM_DIVIDERS];
};
/**
* struct rockchip_cpuclk_reg_data: describes register offsets and masks of the cpuclock
* @core_reg: register offset of the core settings register
* @div_core_shift: core divider offset used to divide the pll value
* @div_core_mask: core divider mask
* @mux_core_shift: offset of the core multiplexer
*/
struct rockchip_cpuclk_reg_data {
int core_reg;
u8 div_core_shift;
u32 div_core_mask;
int mux_core_reg;
u8 mux_core_shift;
};
struct clk *rockchip_clk_register_cpuclk(const char *name,
const char **parent_names, u8 num_parents,
const struct rockchip_cpuclk_reg_data *reg_data,
const struct rockchip_cpuclk_rate_table *rates,
int nrates, void __iomem *reg_base, spinlock_t *lock);
#define PNAME(x) static const char *x[] __initconst
enum rockchip_clk_branch_type {
@ -329,6 +361,11 @@ void rockchip_clk_register_branches(struct rockchip_clk_branch *clk_list,
unsigned int nr_clk);
void rockchip_clk_register_plls(struct rockchip_pll_clock *pll_list,
unsigned int nr_pll, int grf_lock_offset);
void rockchip_clk_register_armclk(unsigned int lookup_id, const char *name,
const char **parent_names, u8 num_parents,
const struct rockchip_cpuclk_reg_data *reg_data,
const struct rockchip_cpuclk_rate_table *rates,
int nrates);
void rockchip_clk_protect_critical(const char *clocks[], int nclocks);
#define ROCKCHIP_SOFTRST_HIWORD_MASK BIT(0)

View File

@ -20,6 +20,7 @@
#define PLL_GPLL 4
#define CORE_PERI 5
#define CORE_L2C 6
#define ARMCLK 7
/* sclk gates (special clocks) */
#define SCLK_UART0 64

View File

@ -19,6 +19,7 @@
#define PLL_CPLL 3
#define PLL_GPLL 4
#define PLL_NPLL 5
#define ARMCLK 6
/* sclk gates (special clocks) */
#define SCLK_GPU 64