diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/rockchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk-cpu.c | 329 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk.c | 21 | ||||
-rw-r--r-- | drivers/clk/rockchip/clk.h | 37 |
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 @@ | |||
5 | obj-y += clk-rockchip.o | 5 | obj-y += clk-rockchip.o |
6 | obj-y += clk.o | 6 | obj-y += clk.o |
7 | obj-y += clk-pll.o | 7 | obj-y += clk-pll.o |
8 | obj-y += clk-cpu.o | ||
8 | obj-$(CONFIG_RESET_CONTROLLER) += softrst.o | 9 | obj-$(CONFIG_RESET_CONTROLLER) += softrst.o |
9 | 10 | ||
10 | obj-y += clk-rk3188.o | 11 | obj-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 | */ | ||
54 | struct 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 | |||
73 | static 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 | |||
88 | static 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 | |||
100 | static const struct clk_ops rockchip_cpuclk_ops = { | ||
101 | .recalc_rate = rockchip_cpuclk_recalc_rate, | ||
102 | }; | ||
103 | |||
104 | static 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 | |||
122 | static 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 | |||
171 | static 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 | */ | ||
214 | static 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 | |||
231 | struct 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 | |||
322 | free_rate_table: | ||
323 | kfree(cpuclk->rate_table); | ||
324 | unregister_notifier: | ||
325 | clk_notifier_unregister(clk, &cpuclk->clk_nb); | ||
326 | free_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 | ||
300 | void __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 | |||
300 | void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks) | 321 | void __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 | ||
123 | struct rockchip_cpuclk_clksel { | ||
124 | int reg; | ||
125 | u32 val; | ||
126 | }; | ||
127 | |||
128 | #define ROCKCHIP_CPUCLK_NUM_DIVIDERS 2 | ||
129 | struct 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 | */ | ||
141 | struct 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 | |||
149 | struct 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 | ||
125 | enum rockchip_clk_branch_type { | 157 | enum 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); |
330 | void rockchip_clk_register_plls(struct rockchip_pll_clock *pll_list, | 362 | void 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); |
364 | void 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); | ||
332 | void rockchip_clk_protect_critical(const char *clocks[], int nclocks); | 369 | void 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) |