aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk
diff options
context:
space:
mode:
authorHeiko Stuebner <heiko@sntech.de>2014-09-04 16:10:43 -0400
committerHeiko Stuebner <heiko@sntech.de>2014-09-27 11:57:41 -0400
commitf6fba5f6967dbc062a7c138d67e2314220f5dd04 (patch)
treeb38dc7a0b7bd4d04df982645a61065d7e7661156 /drivers/clk
parent2b9bceeab70800546050f59cee4efb69c261a683 (diff)
clk: rockchip: add new clock-type for the cpuclk
When changing the armclk on Rockchip SoCs it is supposed to be reparented to an alternate parent before changing the underlying pll and back after the change. Additionally there exist clocks that are very tightly bound to the armclk whose divider values are set according to the armclk rate. Add a special clock-type to handle all that. The rate table and divider values will be supplied from the soc-specific clock controllers. Signed-off-by: Heiko Stuebner <heiko@sntech.de> Reviewed-by: Doug Anderson <dianders@chromium.org> On a rk3288-board: Tested-by: Doug Anderson <dianders@chromium.org>
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/rockchip/Makefile1
-rw-r--r--drivers/clk/rockchip/clk-cpu.c329
-rw-r--r--drivers/clk/rockchip/clk.c21
-rw-r--r--drivers/clk/rockchip/clk.h37
4 files changed, 388 insertions, 0 deletions
diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index ee6b077381e1..bd8514d63634 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -5,6 +5,7 @@
5obj-y += clk-rockchip.o 5obj-y += clk-rockchip.o
6obj-y += clk.o 6obj-y += clk.o
7obj-y += clk-pll.o 7obj-y += clk-pll.o
8obj-y += clk-cpu.o
8obj-$(CONFIG_RESET_CONTROLLER) += softrst.o 9obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
9 10
10obj-y += clk-rk3188.o 11obj-y += clk-rk3188.o
diff --git a/drivers/clk/rockchip/clk-cpu.c b/drivers/clk/rockchip/clk-cpu.c
new file mode 100644
index 000000000000..75c8c45ef728
--- /dev/null
+++ b/drivers/clk/rockchip/clk-cpu.c
@@ -0,0 +1,329 @@
1/*
2 * Copyright (c) 2014 MundoReader S.L.
3 * Author: Heiko Stuebner <heiko@sntech.de>
4 *
5 * based on clk/samsung/clk-cpu.c
6 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
7 * Author: Thomas Abraham <thomas.ab@samsung.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs.
14 * The CPU clock is typically derived from a hierarchy of clock
15 * blocks which includes mux and divider blocks. There are a number of other
16 * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
17 * clock for CPU domain. The rates of these auxiliary clocks are related to the
18 * CPU clock rate and this relation is usually specified in the hardware manual
19 * of the SoC or supplied after the SoC characterization.
20 *
21 * The below implementation of the CPU clock allows the rate changes of the CPU
22 * clock and the corresponding rate changes of the auxillary clocks of the CPU
23 * domain. The platform clock driver provides a clock register configuration
24 * for each configurable rate which is then used to program the clock hardware
25 * registers to acheive a fast co-oridinated rate change for all the CPU domain
26 * clocks.
27 *
28 * On a rate change request for the CPU clock, the rate change is propagated
29 * upto the PLL supplying the clock to the CPU domain clock blocks. While the
30 * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an
31 * alternate clock source. If required, the alternate clock source is divided
32 * down in order to keep the output clock rate within the previous OPP limits.
33 */
34
35#include <linux/of.h>
36#include <linux/slab.h>
37#include <linux/io.h>
38#include <linux/clk-provider.h>
39#include "clk.h"
40
41/**
42 * struct rockchip_cpuclk: information about clock supplied to a CPU core.
43 * @hw: handle between ccf and cpu clock.
44 * @alt_parent: alternate parent clock to use when switching the speed
45 * of the primary parent clock.
46 * @reg_base: base register for cpu-clock values.
47 * @clk_nb: clock notifier registered for changes in clock speed of the
48 * primary parent clock.
49 * @rate_count: number of rates in the rate_table
50 * @rate_table: pll-rates and their associated dividers
51 * @reg_data: cpu-specific register settings
52 * @lock: clock lock
53 */
54struct rockchip_cpuclk {
55 struct clk_hw hw;
56
57 struct clk_mux cpu_mux;
58 const struct clk_ops *cpu_mux_ops;
59
60 struct clk *alt_parent;
61 void __iomem *reg_base;
62 struct notifier_block clk_nb;
63 unsigned int rate_count;
64 struct rockchip_cpuclk_rate_table *rate_table;
65 const struct rockchip_cpuclk_reg_data *reg_data;
66 spinlock_t *lock;
67};
68
69#define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)
70#define to_rockchip_cpuclk_nb(nb) \
71 container_of(nb, struct rockchip_cpuclk, clk_nb)
72
73static const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings(
74 struct rockchip_cpuclk *cpuclk, unsigned long rate)
75{
76 const struct rockchip_cpuclk_rate_table *rate_table =
77 cpuclk->rate_table;
78 int i;
79
80 for (i = 0; i < cpuclk->rate_count; i++) {
81 if (rate == rate_table[i].prate)
82 return &rate_table[i];
83 }
84
85 return NULL;
86}
87
88static unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw,
89 unsigned long parent_rate)
90{
91 struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw);
92 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
93 u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg);
94
95 clksel0 >>= reg_data->div_core_shift;
96 clksel0 &= reg_data->div_core_mask;
97 return parent_rate / (clksel0 + 1);
98}
99
100static const struct clk_ops rockchip_cpuclk_ops = {
101 .recalc_rate = rockchip_cpuclk_recalc_rate,
102};
103
104static void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk,
105 const struct rockchip_cpuclk_rate_table *rate)
106{
107 int i;
108
109 /* alternate parent is active now. set the dividers */
110 for (i = 0; i < ARRAY_SIZE(rate->divs); i++) {
111 const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i];
112
113 if (!clksel->reg)
114 continue;
115
116 pr_debug("%s: setting reg 0x%x to 0x%x\n",
117 __func__, clksel->reg, clksel->val);
118 writel(clksel->val , cpuclk->reg_base + clksel->reg);
119 }
120}
121
122static int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk,
123 struct clk_notifier_data *ndata)
124{
125 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
126 unsigned long alt_prate, alt_div;
127
128 alt_prate = clk_get_rate(cpuclk->alt_parent);
129
130 spin_lock(cpuclk->lock);
131
132 /*
133 * If the old parent clock speed is less than the clock speed
134 * of the alternate parent, then it should be ensured that at no point
135 * the armclk speed is more than the old_rate until the dividers are
136 * set.
137 */
138 if (alt_prate > ndata->old_rate) {
139 /* calculate dividers */
140 alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1;
141 if (alt_div > reg_data->div_core_mask) {
142 pr_warn("%s: limiting alt-divider %lu to %d\n",
143 __func__, alt_div, reg_data->div_core_mask);
144 alt_div = reg_data->div_core_mask;
145 }
146
147 /*
148 * Change parents and add dividers in a single transaction.
149 *
150 * NOTE: we do this in a single transaction so we're never
151 * dividing the primary parent by the extra dividers that were
152 * needed for the alt.
153 */
154 pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n",
155 __func__, alt_div, alt_prate, ndata->old_rate);
156
157 writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask,
158 reg_data->div_core_shift) |
159 HIWORD_UPDATE(1, 1, reg_data->mux_core_shift),
160 cpuclk->reg_base + reg_data->core_reg);
161 } else {
162 /* select alternate parent */
163 writel(HIWORD_UPDATE(1, 1, reg_data->mux_core_shift),
164 cpuclk->reg_base + reg_data->core_reg);
165 }
166
167 spin_unlock(cpuclk->lock);
168 return 0;
169}
170
171static int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk,
172 struct clk_notifier_data *ndata)
173{
174 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
175 const struct rockchip_cpuclk_rate_table *rate;
176
177 rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
178 if (!rate) {
179 pr_err("%s: Invalid rate : %lu for cpuclk\n",
180 __func__, ndata->new_rate);
181 return -EINVAL;
182 }
183
184 spin_lock(cpuclk->lock);
185
186 if (ndata->old_rate < ndata->new_rate)
187 rockchip_cpuclk_set_dividers(cpuclk, rate);
188
189 /*
190 * post-rate change event, re-mux to primary parent and remove dividers.
191 *
192 * NOTE: we do this in a single transaction so we're never dividing the
193 * primary parent by the extra dividers that were needed for the alt.
194 */
195
196 writel(HIWORD_UPDATE(0, reg_data->div_core_mask,
197 reg_data->div_core_shift) |
198 HIWORD_UPDATE(0, 1, reg_data->mux_core_shift),
199 cpuclk->reg_base + reg_data->core_reg);
200
201 if (ndata->old_rate > ndata->new_rate)
202 rockchip_cpuclk_set_dividers(cpuclk, rate);
203
204 spin_unlock(cpuclk->lock);
205 return 0;
206}
207
208/*
209 * This clock notifier is called when the frequency of the parent clock
210 * of cpuclk is to be changed. This notifier handles the setting up all
211 * the divider clocks, remux to temporary parent and handling the safe
212 * frequency levels when using temporary parent.
213 */
214static int rockchip_cpuclk_notifier_cb(struct notifier_block *nb,
215 unsigned long event, void *data)
216{
217 struct clk_notifier_data *ndata = data;
218 struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb);
219 int ret = 0;
220
221 pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
222 __func__, event, ndata->old_rate, ndata->new_rate);
223 if (event == PRE_RATE_CHANGE)
224 ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata);
225 else if (event == POST_RATE_CHANGE)
226 ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata);
227
228 return notifier_from_errno(ret);
229}
230
231struct clk *rockchip_clk_register_cpuclk(const char *name,
232 const char **parent_names, u8 num_parents,
233 const struct rockchip_cpuclk_reg_data *reg_data,
234 const struct rockchip_cpuclk_rate_table *rates,
235 int nrates, void __iomem *reg_base, spinlock_t *lock)
236{
237 struct rockchip_cpuclk *cpuclk;
238 struct clk_init_data init;
239 struct clk *clk, *cclk;
240 int ret;
241
242 if (num_parents != 2) {
243 pr_err("%s: needs two parent clocks\n", __func__);
244 return ERR_PTR(-EINVAL);
245 }
246
247 cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
248 if (!cpuclk)
249 return ERR_PTR(-ENOMEM);
250
251 init.name = name;
252 init.parent_names = &parent_names[0];
253 init.num_parents = 1;
254 init.ops = &rockchip_cpuclk_ops;
255
256 /* only allow rate changes when we have a rate table */
257 init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0;
258
259 /* disallow automatic parent changes by ccf */
260 init.flags |= CLK_SET_RATE_NO_REPARENT;
261
262 init.flags |= CLK_GET_RATE_NOCACHE;
263
264 cpuclk->reg_base = reg_base;
265 cpuclk->lock = lock;
266 cpuclk->reg_data = reg_data;
267 cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb;
268 cpuclk->hw.init = &init;
269
270 cpuclk->alt_parent = __clk_lookup(parent_names[1]);
271 if (!cpuclk->alt_parent) {
272 pr_err("%s: could not lookup alternate parent\n",
273 __func__);
274 ret = -EINVAL;
275 goto free_cpuclk;
276 }
277
278 ret = clk_prepare_enable(cpuclk->alt_parent);
279 if (ret) {
280 pr_err("%s: could not enable alternate parent\n",
281 __func__);
282 goto free_cpuclk;
283 }
284
285 clk = __clk_lookup(parent_names[0]);
286 if (!clk) {
287 pr_err("%s: could not lookup parent clock %s\n",
288 __func__, parent_names[0]);
289 ret = -EINVAL;
290 goto free_cpuclk;
291 }
292
293 ret = clk_notifier_register(clk, &cpuclk->clk_nb);
294 if (ret) {
295 pr_err("%s: failed to register clock notifier for %s\n",
296 __func__, name);
297 goto free_cpuclk;
298 }
299
300 if (nrates > 0) {
301 cpuclk->rate_count = nrates;
302 cpuclk->rate_table = kmemdup(rates,
303 sizeof(*rates) * nrates,
304 GFP_KERNEL);
305 if (!cpuclk->rate_table) {
306 pr_err("%s: could not allocate memory for cpuclk rates\n",
307 __func__);
308 ret = -ENOMEM;
309 goto unregister_notifier;
310 }
311 }
312
313 cclk = clk_register(NULL, &cpuclk->hw);
314 if (IS_ERR(clk)) {
315 pr_err("%s: could not register cpuclk %s\n", __func__, name);
316 ret = PTR_ERR(clk);
317 goto free_rate_table;
318 }
319
320 return cclk;
321
322free_rate_table:
323 kfree(cpuclk->rate_table);
324unregister_notifier:
325 clk_notifier_unregister(clk, &cpuclk->clk_nb);
326free_cpuclk:
327 kfree(cpuclk);
328 return ERR_PTR(ret);
329}
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index d9c6db2151ba..fd3b5ef87e29 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -297,6 +297,27 @@ void __init rockchip_clk_register_branches(
297 } 297 }
298} 298}
299 299
300void __init rockchip_clk_register_armclk(unsigned int lookup_id,
301 const char *name, const char **parent_names,
302 u8 num_parents,
303 const struct rockchip_cpuclk_reg_data *reg_data,
304 const struct rockchip_cpuclk_rate_table *rates,
305 int nrates)
306{
307 struct clk *clk;
308
309 clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents,
310 reg_data, rates, nrates, reg_base,
311 &clk_lock);
312 if (IS_ERR(clk)) {
313 pr_err("%s: failed to register clock %s: %ld\n",
314 __func__, name, PTR_ERR(clk));
315 return;
316 }
317
318 rockchip_clk_add_lookup(clk, lookup_id);
319}
320
300void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks) 321void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks)
301{ 322{
302 int i; 323 int i;
diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h
index 2b0bca19db47..f4791fbb3da9 100644
--- a/drivers/clk/rockchip/clk.h
+++ b/drivers/clk/rockchip/clk.h
@@ -120,6 +120,38 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type,
120 struct rockchip_pll_rate_table *rate_table, 120 struct rockchip_pll_rate_table *rate_table,
121 spinlock_t *lock); 121 spinlock_t *lock);
122 122
123struct rockchip_cpuclk_clksel {
124 int reg;
125 u32 val;
126};
127
128#define ROCKCHIP_CPUCLK_NUM_DIVIDERS 2
129struct rockchip_cpuclk_rate_table {
130 unsigned long prate;
131 struct rockchip_cpuclk_clksel divs[ROCKCHIP_CPUCLK_NUM_DIVIDERS];
132};
133
134/**
135 * struct rockchip_cpuclk_reg_data: describes register offsets and masks of the cpuclock
136 * @core_reg: register offset of the core settings register
137 * @div_core_shift: core divider offset used to divide the pll value
138 * @div_core_mask: core divider mask
139 * @mux_core_shift: offset of the core multiplexer
140 */
141struct rockchip_cpuclk_reg_data {
142 int core_reg;
143 u8 div_core_shift;
144 u32 div_core_mask;
145 int mux_core_reg;
146 u8 mux_core_shift;
147};
148
149struct clk *rockchip_clk_register_cpuclk(const char *name,
150 const char **parent_names, u8 num_parents,
151 const struct rockchip_cpuclk_reg_data *reg_data,
152 const struct rockchip_cpuclk_rate_table *rates,
153 int nrates, void __iomem *reg_base, spinlock_t *lock);
154
123#define PNAME(x) static const char *x[] __initconst 155#define PNAME(x) static const char *x[] __initconst
124 156
125enum rockchip_clk_branch_type { 157enum rockchip_clk_branch_type {
@@ -329,6 +361,11 @@ void rockchip_clk_register_branches(struct rockchip_clk_branch *clk_list,
329 unsigned int nr_clk); 361 unsigned int nr_clk);
330void rockchip_clk_register_plls(struct rockchip_pll_clock *pll_list, 362void rockchip_clk_register_plls(struct rockchip_pll_clock *pll_list,
331 unsigned int nr_pll, int grf_lock_offset); 363 unsigned int nr_pll, int grf_lock_offset);
364void rockchip_clk_register_armclk(unsigned int lookup_id, const char *name,
365 const char **parent_names, u8 num_parents,
366 const struct rockchip_cpuclk_reg_data *reg_data,
367 const struct rockchip_cpuclk_rate_table *rates,
368 int nrates);
332void rockchip_clk_protect_critical(const char *clocks[], int nclocks); 369void rockchip_clk_protect_critical(const char *clocks[], int nclocks);
333 370
334#define ROCKCHIP_SOFTRST_HIWORD_MASK BIT(0) 371#define ROCKCHIP_SOFTRST_HIWORD_MASK BIT(0)