]>
Commit | Line | Data |
---|---|---|
721c42a3 TA |
1 | /* |
2 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. | |
3 | * Copyright (c) 2013 Linaro Ltd. | |
4 | * Author: Thomas Abraham <[email protected]> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * This file includes utility functions to register clocks to common | |
11 | * clock framework for Samsung platforms. | |
12 | */ | |
13 | ||
8b2f6360 | 14 | #include <linux/of_address.h> |
721c42a3 | 15 | #include <linux/syscore_ops.h> |
8b2f6360 | 16 | |
721c42a3 TA |
17 | #include "clk.h" |
18 | ||
16a9013b NKC |
19 | static LIST_HEAD(clock_reg_cache_list); |
20 | ||
3ccefbd2 TF |
21 | void samsung_clk_save(void __iomem *base, |
22 | struct samsung_clk_reg_dump *rd, | |
23 | unsigned int num_regs) | |
24 | { | |
25 | for (; num_regs > 0; --num_regs, ++rd) | |
26 | rd->value = readl(base + rd->offset); | |
27 | } | |
28 | ||
29 | void samsung_clk_restore(void __iomem *base, | |
30 | const struct samsung_clk_reg_dump *rd, | |
31 | unsigned int num_regs) | |
32 | { | |
33 | for (; num_regs > 0; --num_regs, ++rd) | |
34 | writel(rd->value, base + rd->offset); | |
35 | } | |
36 | ||
c3b6c1d7 TF |
37 | struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump( |
38 | const unsigned long *rdump, | |
39 | unsigned long nr_rdump) | |
3ccefbd2 TF |
40 | { |
41 | struct samsung_clk_reg_dump *rd; | |
42 | unsigned int i; | |
43 | ||
44 | rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL); | |
45 | if (!rd) | |
46 | return NULL; | |
47 | ||
48 | for (i = 0; i < nr_rdump; ++i) | |
49 | rd[i].offset = rdump[i]; | |
50 | ||
51 | return rd; | |
52 | } | |
53 | ||
721c42a3 | 54 | /* setup the essentials required to support clock lookup using ccf */ |
976face4 RS |
55 | struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np, |
56 | void __iomem *base, unsigned long nr_clks) | |
721c42a3 | 57 | { |
976face4 RS |
58 | struct samsung_clk_provider *ctx; |
59 | struct clk **clk_table; | |
91a1263f TF |
60 | int i; |
61 | ||
976face4 RS |
62 | ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); |
63 | if (!ctx) | |
64 | panic("could not allocate clock provider context.\n"); | |
721c42a3 | 65 | |
91a1263f | 66 | clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); |
2466196d HS |
67 | if (!clk_table) |
68 | panic("could not allocate clock lookup table\n"); | |
69 | ||
91a1263f TF |
70 | for (i = 0; i < nr_clks; ++i) |
71 | clk_table[i] = ERR_PTR(-ENOENT); | |
72 | ||
976face4 RS |
73 | ctx->reg_base = base; |
74 | ctx->clk_data.clks = clk_table; | |
75 | ctx->clk_data.clk_num = nr_clks; | |
76 | spin_lock_init(&ctx->lock); | |
77 | ||
976face4 | 78 | return ctx; |
d5e136a2 SN |
79 | } |
80 | ||
81 | void __init samsung_clk_of_add_provider(struct device_node *np, | |
82 | struct samsung_clk_provider *ctx) | |
83 | { | |
84 | if (np) { | |
85 | if (of_clk_add_provider(np, of_clk_src_onecell_get, | |
86 | &ctx->clk_data)) | |
87 | panic("could not register clk provider\n"); | |
88 | } | |
721c42a3 TA |
89 | } |
90 | ||
91 | /* add a clock instance to the clock lookup table used for dt based lookup */ | |
976face4 RS |
92 | void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk, |
93 | unsigned int id) | |
721c42a3 | 94 | { |
976face4 RS |
95 | if (ctx->clk_data.clks && id) |
96 | ctx->clk_data.clks[id] = clk; | |
721c42a3 TA |
97 | } |
98 | ||
5e2e0195 | 99 | /* register a list of aliases */ |
976face4 | 100 | void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx, |
4a1caed3 | 101 | const struct samsung_clock_alias *list, |
976face4 | 102 | unsigned int nr_clk) |
5e2e0195 HS |
103 | { |
104 | struct clk *clk; | |
105 | unsigned int idx, ret; | |
106 | ||
976face4 | 107 | if (!ctx->clk_data.clks) { |
5e2e0195 HS |
108 | pr_err("%s: clock table missing\n", __func__); |
109 | return; | |
110 | } | |
111 | ||
112 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
113 | if (!list->id) { | |
114 | pr_err("%s: clock id missing for index %d\n", __func__, | |
115 | idx); | |
116 | continue; | |
117 | } | |
118 | ||
976face4 | 119 | clk = ctx->clk_data.clks[list->id]; |
5e2e0195 HS |
120 | if (!clk) { |
121 | pr_err("%s: failed to find clock %d\n", __func__, | |
122 | list->id); | |
123 | continue; | |
124 | } | |
125 | ||
126 | ret = clk_register_clkdev(clk, list->alias, list->dev_name); | |
127 | if (ret) | |
128 | pr_err("%s: failed to register lookup %s\n", | |
129 | __func__, list->alias); | |
130 | } | |
131 | } | |
132 | ||
721c42a3 | 133 | /* register a list of fixed clocks */ |
976face4 | 134 | void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx, |
4a1caed3 UKK |
135 | const struct samsung_fixed_rate_clock *list, |
136 | unsigned int nr_clk) | |
721c42a3 TA |
137 | { |
138 | struct clk *clk; | |
139 | unsigned int idx, ret; | |
140 | ||
141 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
142 | clk = clk_register_fixed_rate(NULL, list->name, | |
143 | list->parent_name, list->flags, list->fixed_rate); | |
144 | if (IS_ERR(clk)) { | |
145 | pr_err("%s: failed to register clock %s\n", __func__, | |
146 | list->name); | |
147 | continue; | |
148 | } | |
149 | ||
976face4 | 150 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
151 | |
152 | /* | |
153 | * Unconditionally add a clock lookup for the fixed rate clocks. | |
154 | * There are not many of these on any of Samsung platforms. | |
155 | */ | |
156 | ret = clk_register_clkdev(clk, list->name, NULL); | |
157 | if (ret) | |
158 | pr_err("%s: failed to register clock lookup for %s", | |
159 | __func__, list->name); | |
160 | } | |
161 | } | |
162 | ||
163 | /* register a list of fixed factor clocks */ | |
976face4 | 164 | void __init samsung_clk_register_fixed_factor(struct samsung_clk_provider *ctx, |
4a1caed3 | 165 | const struct samsung_fixed_factor_clock *list, unsigned int nr_clk) |
721c42a3 TA |
166 | { |
167 | struct clk *clk; | |
168 | unsigned int idx; | |
169 | ||
170 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
171 | clk = clk_register_fixed_factor(NULL, list->name, | |
172 | list->parent_name, list->flags, list->mult, list->div); | |
173 | if (IS_ERR(clk)) { | |
174 | pr_err("%s: failed to register clock %s\n", __func__, | |
175 | list->name); | |
176 | continue; | |
177 | } | |
178 | ||
976face4 | 179 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
180 | } |
181 | } | |
182 | ||
183 | /* register a list of mux clocks */ | |
976face4 | 184 | void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx, |
4a1caed3 | 185 | const struct samsung_mux_clock *list, |
976face4 | 186 | unsigned int nr_clk) |
721c42a3 TA |
187 | { |
188 | struct clk *clk; | |
189 | unsigned int idx, ret; | |
190 | ||
191 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
192 | clk = clk_register_mux(NULL, list->name, list->parent_names, | |
976face4 RS |
193 | list->num_parents, list->flags, |
194 | ctx->reg_base + list->offset, | |
195 | list->shift, list->width, list->mux_flags, &ctx->lock); | |
721c42a3 TA |
196 | if (IS_ERR(clk)) { |
197 | pr_err("%s: failed to register clock %s\n", __func__, | |
198 | list->name); | |
199 | continue; | |
200 | } | |
201 | ||
976face4 | 202 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
203 | |
204 | /* register a clock lookup only if a clock alias is specified */ | |
205 | if (list->alias) { | |
206 | ret = clk_register_clkdev(clk, list->alias, | |
207 | list->dev_name); | |
208 | if (ret) | |
209 | pr_err("%s: failed to register lookup %s\n", | |
210 | __func__, list->alias); | |
211 | } | |
212 | } | |
213 | } | |
214 | ||
215 | /* register a list of div clocks */ | |
976face4 | 216 | void __init samsung_clk_register_div(struct samsung_clk_provider *ctx, |
4a1caed3 | 217 | const struct samsung_div_clock *list, |
976face4 | 218 | unsigned int nr_clk) |
721c42a3 TA |
219 | { |
220 | struct clk *clk; | |
221 | unsigned int idx, ret; | |
222 | ||
223 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
798ed613 HS |
224 | if (list->table) |
225 | clk = clk_register_divider_table(NULL, list->name, | |
976face4 RS |
226 | list->parent_name, list->flags, |
227 | ctx->reg_base + list->offset, | |
228 | list->shift, list->width, list->div_flags, | |
229 | list->table, &ctx->lock); | |
798ed613 HS |
230 | else |
231 | clk = clk_register_divider(NULL, list->name, | |
976face4 RS |
232 | list->parent_name, list->flags, |
233 | ctx->reg_base + list->offset, list->shift, | |
234 | list->width, list->div_flags, &ctx->lock); | |
721c42a3 TA |
235 | if (IS_ERR(clk)) { |
236 | pr_err("%s: failed to register clock %s\n", __func__, | |
237 | list->name); | |
238 | continue; | |
239 | } | |
240 | ||
976face4 | 241 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
242 | |
243 | /* register a clock lookup only if a clock alias is specified */ | |
244 | if (list->alias) { | |
245 | ret = clk_register_clkdev(clk, list->alias, | |
246 | list->dev_name); | |
247 | if (ret) | |
248 | pr_err("%s: failed to register lookup %s\n", | |
249 | __func__, list->alias); | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | /* register a list of gate clocks */ | |
976face4 | 255 | void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx, |
4a1caed3 | 256 | const struct samsung_gate_clock *list, |
976face4 | 257 | unsigned int nr_clk) |
721c42a3 TA |
258 | { |
259 | struct clk *clk; | |
260 | unsigned int idx, ret; | |
261 | ||
262 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
263 | clk = clk_register_gate(NULL, list->name, list->parent_name, | |
976face4 RS |
264 | list->flags, ctx->reg_base + list->offset, |
265 | list->bit_idx, list->gate_flags, &ctx->lock); | |
721c42a3 TA |
266 | if (IS_ERR(clk)) { |
267 | pr_err("%s: failed to register clock %s\n", __func__, | |
268 | list->name); | |
269 | continue; | |
270 | } | |
271 | ||
272 | /* register a clock lookup only if a clock alias is specified */ | |
273 | if (list->alias) { | |
274 | ret = clk_register_clkdev(clk, list->alias, | |
275 | list->dev_name); | |
276 | if (ret) | |
277 | pr_err("%s: failed to register lookup %s\n", | |
278 | __func__, list->alias); | |
279 | } | |
280 | ||
976face4 | 281 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
282 | } |
283 | } | |
284 | ||
285 | /* | |
286 | * obtain the clock speed of all external fixed clock sources from device | |
287 | * tree and register it | |
288 | */ | |
976face4 | 289 | void __init samsung_clk_of_register_fixed_ext(struct samsung_clk_provider *ctx, |
721c42a3 TA |
290 | struct samsung_fixed_rate_clock *fixed_rate_clk, |
291 | unsigned int nr_fixed_rate_clk, | |
305cfab0 | 292 | const struct of_device_id *clk_matches) |
721c42a3 TA |
293 | { |
294 | const struct of_device_id *match; | |
976face4 | 295 | struct device_node *clk_np; |
721c42a3 TA |
296 | u32 freq; |
297 | ||
976face4 RS |
298 | for_each_matching_node_and_match(clk_np, clk_matches, &match) { |
299 | if (of_property_read_u32(clk_np, "clock-frequency", &freq)) | |
721c42a3 | 300 | continue; |
42fb57c0 | 301 | fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq; |
721c42a3 | 302 | } |
976face4 | 303 | samsung_clk_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk); |
721c42a3 TA |
304 | } |
305 | ||
306 | /* utility function to get the rate of a specified clock */ | |
307 | unsigned long _get_rate(const char *clk_name) | |
308 | { | |
309 | struct clk *clk; | |
721c42a3 | 310 | |
3a647895 TF |
311 | clk = __clk_lookup(clk_name); |
312 | if (!clk) { | |
721c42a3 TA |
313 | pr_err("%s: could not find clock %s\n", __func__, clk_name); |
314 | return 0; | |
315 | } | |
3a647895 TF |
316 | |
317 | return clk_get_rate(clk); | |
721c42a3 | 318 | } |
16a9013b NKC |
319 | |
320 | #ifdef CONFIG_PM_SLEEP | |
321 | static int samsung_clk_suspend(void) | |
322 | { | |
323 | struct samsung_clock_reg_cache *reg_cache; | |
324 | ||
325 | list_for_each_entry(reg_cache, &clock_reg_cache_list, node) | |
326 | samsung_clk_save(reg_cache->reg_base, reg_cache->rdump, | |
327 | reg_cache->rd_num); | |
328 | return 0; | |
329 | } | |
330 | ||
331 | static void samsung_clk_resume(void) | |
332 | { | |
333 | struct samsung_clock_reg_cache *reg_cache; | |
334 | ||
335 | list_for_each_entry(reg_cache, &clock_reg_cache_list, node) | |
336 | samsung_clk_restore(reg_cache->reg_base, reg_cache->rdump, | |
337 | reg_cache->rd_num); | |
338 | } | |
339 | ||
340 | static struct syscore_ops samsung_clk_syscore_ops = { | |
341 | .suspend = samsung_clk_suspend, | |
342 | .resume = samsung_clk_resume, | |
343 | }; | |
344 | ||
345 | static void samsung_clk_sleep_init(void __iomem *reg_base, | |
346 | const unsigned long *rdump, | |
347 | unsigned long nr_rdump) | |
348 | { | |
349 | struct samsung_clock_reg_cache *reg_cache; | |
350 | ||
351 | reg_cache = kzalloc(sizeof(struct samsung_clock_reg_cache), | |
352 | GFP_KERNEL); | |
353 | if (!reg_cache) | |
354 | panic("could not allocate register reg_cache.\n"); | |
355 | reg_cache->rdump = samsung_clk_alloc_reg_dump(rdump, nr_rdump); | |
356 | ||
357 | if (!reg_cache->rdump) | |
358 | panic("could not allocate register dump storage.\n"); | |
359 | ||
360 | if (list_empty(&clock_reg_cache_list)) | |
361 | register_syscore_ops(&samsung_clk_syscore_ops); | |
362 | ||
363 | reg_cache->reg_base = reg_base; | |
364 | reg_cache->rd_num = nr_rdump; | |
365 | list_add_tail(®_cache->node, &clock_reg_cache_list); | |
366 | } | |
367 | ||
368 | #else | |
369 | static void samsung_clk_sleep_init(void __iomem *reg_base, | |
370 | const unsigned long *rdump, | |
371 | unsigned long nr_rdump) {} | |
372 | #endif | |
373 | ||
374 | /* | |
375 | * Common function which registers plls, muxes, dividers and gates | |
376 | * for each CMU. It also add CMU register list to register cache. | |
377 | */ | |
151d4d35 CC |
378 | struct samsung_clk_provider * __init samsung_cmu_register_one( |
379 | struct device_node *np, | |
16a9013b NKC |
380 | struct samsung_cmu_info *cmu) |
381 | { | |
382 | void __iomem *reg_base; | |
383 | struct samsung_clk_provider *ctx; | |
384 | ||
385 | reg_base = of_iomap(np, 0); | |
151d4d35 | 386 | if (!reg_base) { |
16a9013b | 387 | panic("%s: failed to map registers\n", __func__); |
151d4d35 CC |
388 | return NULL; |
389 | } | |
16a9013b NKC |
390 | |
391 | ctx = samsung_clk_init(np, reg_base, cmu->nr_clk_ids); | |
151d4d35 | 392 | if (!ctx) { |
c306317a | 393 | panic("%s: unable to allocate ctx\n", __func__); |
151d4d35 CC |
394 | return ctx; |
395 | } | |
16a9013b NKC |
396 | |
397 | if (cmu->pll_clks) | |
398 | samsung_clk_register_pll(ctx, cmu->pll_clks, cmu->nr_pll_clks, | |
399 | reg_base); | |
400 | if (cmu->mux_clks) | |
401 | samsung_clk_register_mux(ctx, cmu->mux_clks, | |
402 | cmu->nr_mux_clks); | |
403 | if (cmu->div_clks) | |
404 | samsung_clk_register_div(ctx, cmu->div_clks, cmu->nr_div_clks); | |
405 | if (cmu->gate_clks) | |
406 | samsung_clk_register_gate(ctx, cmu->gate_clks, | |
407 | cmu->nr_gate_clks); | |
408 | if (cmu->fixed_clks) | |
409 | samsung_clk_register_fixed_rate(ctx, cmu->fixed_clks, | |
410 | cmu->nr_fixed_clks); | |
0e5af270 NKC |
411 | if (cmu->fixed_factor_clks) |
412 | samsung_clk_register_fixed_factor(ctx, cmu->fixed_factor_clks, | |
413 | cmu->nr_fixed_factor_clks); | |
16a9013b NKC |
414 | if (cmu->clk_regs) |
415 | samsung_clk_sleep_init(reg_base, cmu->clk_regs, | |
416 | cmu->nr_clk_regs); | |
417 | ||
418 | samsung_clk_of_add_provider(np, ctx); | |
151d4d35 CC |
419 | |
420 | return ctx; | |
16a9013b | 421 | } |