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