]>
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 | ||
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 | 62 | struct samsung_clk_provider *ctx; |
91a1263f TF |
63 | int i; |
64 | ||
ecb1f1f7 MS |
65 | ctx = kzalloc(sizeof(struct samsung_clk_provider) + |
66 | sizeof(*ctx->clk_data.hws) * nr_clks, GFP_KERNEL); | |
976face4 RS |
67 | if (!ctx) |
68 | panic("could not allocate clock provider context.\n"); | |
721c42a3 | 69 | |
91a1263f | 70 | for (i = 0; i < nr_clks; ++i) |
ecb1f1f7 | 71 | ctx->clk_data.hws[i] = ERR_PTR(-ENOENT); |
91a1263f | 72 | |
976face4 | 73 | ctx->reg_base = base; |
ecb1f1f7 | 74 | ctx->clk_data.num = nr_clks; |
976face4 RS |
75 | spin_lock_init(&ctx->lock); |
76 | ||
976face4 | 77 | return ctx; |
d5e136a2 SN |
78 | } |
79 | ||
80 | void __init samsung_clk_of_add_provider(struct device_node *np, | |
81 | struct samsung_clk_provider *ctx) | |
82 | { | |
83 | if (np) { | |
ecb1f1f7 | 84 | if (of_clk_add_hw_provider(np, of_clk_hw_onecell_get, |
d5e136a2 SN |
85 | &ctx->clk_data)) |
86 | panic("could not register clk provider\n"); | |
87 | } | |
721c42a3 TA |
88 | } |
89 | ||
90 | /* add a clock instance to the clock lookup table used for dt based lookup */ | |
ecb1f1f7 MS |
91 | void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, |
92 | struct clk_hw *clk_hw, unsigned int id) | |
721c42a3 | 93 | { |
ecb1f1f7 MS |
94 | if (id) |
95 | ctx->clk_data.hws[id] = clk_hw; | |
721c42a3 TA |
96 | } |
97 | ||
5e2e0195 | 98 | /* register a list of aliases */ |
976face4 | 99 | void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx, |
4a1caed3 | 100 | const struct samsung_clock_alias *list, |
976face4 | 101 | unsigned int nr_clk) |
5e2e0195 | 102 | { |
ecb1f1f7 | 103 | struct clk_hw *clk_hw; |
5e2e0195 HS |
104 | unsigned int idx, ret; |
105 | ||
5e2e0195 HS |
106 | for (idx = 0; idx < nr_clk; idx++, list++) { |
107 | if (!list->id) { | |
108 | pr_err("%s: clock id missing for index %d\n", __func__, | |
109 | idx); | |
110 | continue; | |
111 | } | |
112 | ||
ecb1f1f7 MS |
113 | clk_hw = ctx->clk_data.hws[list->id]; |
114 | if (!clk_hw) { | |
5e2e0195 HS |
115 | pr_err("%s: failed to find clock %d\n", __func__, |
116 | list->id); | |
117 | continue; | |
118 | } | |
119 | ||
ecb1f1f7 MS |
120 | ret = clk_hw_register_clkdev(clk_hw, list->alias, |
121 | list->dev_name); | |
5e2e0195 HS |
122 | if (ret) |
123 | pr_err("%s: failed to register lookup %s\n", | |
124 | __func__, list->alias); | |
125 | } | |
126 | } | |
127 | ||
721c42a3 | 128 | /* register a list of fixed clocks */ |
976face4 | 129 | void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx, |
4a1caed3 UKK |
130 | const struct samsung_fixed_rate_clock *list, |
131 | unsigned int nr_clk) | |
721c42a3 | 132 | { |
ecb1f1f7 | 133 | struct clk_hw *clk_hw; |
721c42a3 TA |
134 | unsigned int idx, ret; |
135 | ||
136 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
d2f18d7e | 137 | clk_hw = clk_hw_register_fixed_rate(ctx->dev, list->name, |
721c42a3 | 138 | list->parent_name, list->flags, list->fixed_rate); |
ecb1f1f7 | 139 | if (IS_ERR(clk_hw)) { |
721c42a3 TA |
140 | pr_err("%s: failed to register clock %s\n", __func__, |
141 | list->name); | |
142 | continue; | |
143 | } | |
144 | ||
ecb1f1f7 | 145 | samsung_clk_add_lookup(ctx, clk_hw, list->id); |
721c42a3 TA |
146 | |
147 | /* | |
148 | * Unconditionally add a clock lookup for the fixed rate clocks. | |
149 | * There are not many of these on any of Samsung platforms. | |
150 | */ | |
ecb1f1f7 | 151 | ret = clk_hw_register_clkdev(clk_hw, list->name, NULL); |
721c42a3 TA |
152 | if (ret) |
153 | pr_err("%s: failed to register clock lookup for %s", | |
154 | __func__, list->name); | |
155 | } | |
156 | } | |
157 | ||
158 | /* register a list of fixed factor clocks */ | |
976face4 | 159 | void __init samsung_clk_register_fixed_factor(struct samsung_clk_provider *ctx, |
4a1caed3 | 160 | const struct samsung_fixed_factor_clock *list, unsigned int nr_clk) |
721c42a3 | 161 | { |
ecb1f1f7 | 162 | struct clk_hw *clk_hw; |
721c42a3 TA |
163 | unsigned int idx; |
164 | ||
165 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
d2f18d7e | 166 | clk_hw = clk_hw_register_fixed_factor(ctx->dev, list->name, |
721c42a3 | 167 | list->parent_name, list->flags, list->mult, list->div); |
ecb1f1f7 | 168 | if (IS_ERR(clk_hw)) { |
721c42a3 TA |
169 | pr_err("%s: failed to register clock %s\n", __func__, |
170 | list->name); | |
171 | continue; | |
172 | } | |
173 | ||
ecb1f1f7 | 174 | samsung_clk_add_lookup(ctx, clk_hw, list->id); |
721c42a3 TA |
175 | } |
176 | } | |
177 | ||
178 | /* register a list of mux clocks */ | |
976face4 | 179 | void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx, |
4a1caed3 | 180 | const struct samsung_mux_clock *list, |
976face4 | 181 | unsigned int nr_clk) |
721c42a3 | 182 | { |
ecb1f1f7 | 183 | struct clk_hw *clk_hw; |
a4f21e9c | 184 | unsigned int idx; |
721c42a3 TA |
185 | |
186 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
d2f18d7e | 187 | clk_hw = clk_hw_register_mux(ctx->dev, list->name, |
ecb1f1f7 | 188 | list->parent_names, list->num_parents, list->flags, |
976face4 RS |
189 | ctx->reg_base + list->offset, |
190 | list->shift, list->width, list->mux_flags, &ctx->lock); | |
ecb1f1f7 | 191 | if (IS_ERR(clk_hw)) { |
721c42a3 TA |
192 | pr_err("%s: failed to register clock %s\n", __func__, |
193 | list->name); | |
194 | continue; | |
195 | } | |
196 | ||
ecb1f1f7 | 197 | samsung_clk_add_lookup(ctx, clk_hw, list->id); |
721c42a3 TA |
198 | } |
199 | } | |
200 | ||
201 | /* register a list of div clocks */ | |
976face4 | 202 | void __init samsung_clk_register_div(struct samsung_clk_provider *ctx, |
4a1caed3 | 203 | const struct samsung_div_clock *list, |
976face4 | 204 | unsigned int nr_clk) |
721c42a3 | 205 | { |
ecb1f1f7 | 206 | struct clk_hw *clk_hw; |
a4f21e9c | 207 | unsigned int idx; |
721c42a3 TA |
208 | |
209 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
798ed613 | 210 | if (list->table) |
d2f18d7e | 211 | clk_hw = clk_hw_register_divider_table(ctx->dev, |
ecb1f1f7 | 212 | list->name, list->parent_name, list->flags, |
976face4 RS |
213 | ctx->reg_base + list->offset, |
214 | list->shift, list->width, list->div_flags, | |
215 | list->table, &ctx->lock); | |
798ed613 | 216 | else |
d2f18d7e | 217 | clk_hw = clk_hw_register_divider(ctx->dev, list->name, |
976face4 RS |
218 | list->parent_name, list->flags, |
219 | ctx->reg_base + list->offset, list->shift, | |
220 | list->width, list->div_flags, &ctx->lock); | |
ecb1f1f7 | 221 | if (IS_ERR(clk_hw)) { |
721c42a3 TA |
222 | pr_err("%s: failed to register clock %s\n", __func__, |
223 | list->name); | |
224 | continue; | |
225 | } | |
226 | ||
ecb1f1f7 | 227 | samsung_clk_add_lookup(ctx, clk_hw, list->id); |
721c42a3 TA |
228 | } |
229 | } | |
230 | ||
231 | /* register a list of gate clocks */ | |
976face4 | 232 | void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx, |
4a1caed3 | 233 | const struct samsung_gate_clock *list, |
976face4 | 234 | unsigned int nr_clk) |
721c42a3 | 235 | { |
ecb1f1f7 | 236 | struct clk_hw *clk_hw; |
a4f21e9c | 237 | unsigned int idx; |
721c42a3 TA |
238 | |
239 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
d2f18d7e | 240 | clk_hw = clk_hw_register_gate(ctx->dev, list->name, list->parent_name, |
976face4 RS |
241 | list->flags, ctx->reg_base + list->offset, |
242 | list->bit_idx, list->gate_flags, &ctx->lock); | |
ecb1f1f7 | 243 | if (IS_ERR(clk_hw)) { |
721c42a3 TA |
244 | pr_err("%s: failed to register clock %s\n", __func__, |
245 | list->name); | |
246 | continue; | |
247 | } | |
248 | ||
ecb1f1f7 | 249 | samsung_clk_add_lookup(ctx, clk_hw, list->id); |
721c42a3 TA |
250 | } |
251 | } | |
252 | ||
253 | /* | |
254 | * obtain the clock speed of all external fixed clock sources from device | |
255 | * tree and register it | |
256 | */ | |
976face4 | 257 | void __init samsung_clk_of_register_fixed_ext(struct samsung_clk_provider *ctx, |
721c42a3 TA |
258 | struct samsung_fixed_rate_clock *fixed_rate_clk, |
259 | unsigned int nr_fixed_rate_clk, | |
305cfab0 | 260 | const struct of_device_id *clk_matches) |
721c42a3 TA |
261 | { |
262 | const struct of_device_id *match; | |
976face4 | 263 | struct device_node *clk_np; |
721c42a3 TA |
264 | u32 freq; |
265 | ||
976face4 RS |
266 | for_each_matching_node_and_match(clk_np, clk_matches, &match) { |
267 | if (of_property_read_u32(clk_np, "clock-frequency", &freq)) | |
721c42a3 | 268 | continue; |
42fb57c0 | 269 | fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq; |
721c42a3 | 270 | } |
976face4 | 271 | samsung_clk_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk); |
721c42a3 TA |
272 | } |
273 | ||
274 | /* utility function to get the rate of a specified clock */ | |
275 | unsigned long _get_rate(const char *clk_name) | |
276 | { | |
277 | struct clk *clk; | |
721c42a3 | 278 | |
3a647895 TF |
279 | clk = __clk_lookup(clk_name); |
280 | if (!clk) { | |
721c42a3 TA |
281 | pr_err("%s: could not find clock %s\n", __func__, clk_name); |
282 | return 0; | |
283 | } | |
3a647895 TF |
284 | |
285 | return clk_get_rate(clk); | |
721c42a3 | 286 | } |
16a9013b NKC |
287 | |
288 | #ifdef CONFIG_PM_SLEEP | |
289 | static int samsung_clk_suspend(void) | |
290 | { | |
291 | struct samsung_clock_reg_cache *reg_cache; | |
292 | ||
8bf27eaa | 293 | list_for_each_entry(reg_cache, &clock_reg_cache_list, node) { |
16a9013b NKC |
294 | samsung_clk_save(reg_cache->reg_base, reg_cache->rdump, |
295 | reg_cache->rd_num); | |
8bf27eaa MS |
296 | samsung_clk_restore(reg_cache->reg_base, reg_cache->rsuspend, |
297 | reg_cache->rsuspend_num); | |
298 | } | |
16a9013b NKC |
299 | return 0; |
300 | } | |
301 | ||
302 | static void samsung_clk_resume(void) | |
303 | { | |
304 | struct samsung_clock_reg_cache *reg_cache; | |
305 | ||
306 | list_for_each_entry(reg_cache, &clock_reg_cache_list, node) | |
307 | samsung_clk_restore(reg_cache->reg_base, reg_cache->rdump, | |
308 | reg_cache->rd_num); | |
309 | } | |
310 | ||
311 | static struct syscore_ops samsung_clk_syscore_ops = { | |
312 | .suspend = samsung_clk_suspend, | |
313 | .resume = samsung_clk_resume, | |
314 | }; | |
315 | ||
8bf27eaa | 316 | void samsung_clk_extended_sleep_init(void __iomem *reg_base, |
0c0cd59a | 317 | const unsigned long *rdump, |
8bf27eaa MS |
318 | unsigned long nr_rdump, |
319 | const struct samsung_clk_reg_dump *rsuspend, | |
320 | unsigned long nr_rsuspend) | |
16a9013b NKC |
321 | { |
322 | struct samsung_clock_reg_cache *reg_cache; | |
323 | ||
324 | reg_cache = kzalloc(sizeof(struct samsung_clock_reg_cache), | |
325 | GFP_KERNEL); | |
326 | if (!reg_cache) | |
327 | panic("could not allocate register reg_cache.\n"); | |
328 | reg_cache->rdump = samsung_clk_alloc_reg_dump(rdump, nr_rdump); | |
329 | ||
330 | if (!reg_cache->rdump) | |
331 | panic("could not allocate register dump storage.\n"); | |
332 | ||
333 | if (list_empty(&clock_reg_cache_list)) | |
334 | register_syscore_ops(&samsung_clk_syscore_ops); | |
335 | ||
336 | reg_cache->reg_base = reg_base; | |
337 | reg_cache->rd_num = nr_rdump; | |
8bf27eaa MS |
338 | reg_cache->rsuspend = rsuspend; |
339 | reg_cache->rsuspend_num = nr_rsuspend; | |
16a9013b NKC |
340 | list_add_tail(®_cache->node, &clock_reg_cache_list); |
341 | } | |
16a9013b NKC |
342 | #endif |
343 | ||
344 | /* | |
345 | * Common function which registers plls, muxes, dividers and gates | |
346 | * for each CMU. It also add CMU register list to register cache. | |
347 | */ | |
151d4d35 CC |
348 | struct samsung_clk_provider * __init samsung_cmu_register_one( |
349 | struct device_node *np, | |
9f92c0ba | 350 | const struct samsung_cmu_info *cmu) |
16a9013b NKC |
351 | { |
352 | void __iomem *reg_base; | |
353 | struct samsung_clk_provider *ctx; | |
354 | ||
355 | reg_base = of_iomap(np, 0); | |
151d4d35 | 356 | if (!reg_base) { |
16a9013b | 357 | panic("%s: failed to map registers\n", __func__); |
151d4d35 CC |
358 | return NULL; |
359 | } | |
16a9013b NKC |
360 | |
361 | ctx = samsung_clk_init(np, reg_base, cmu->nr_clk_ids); | |
151d4d35 | 362 | if (!ctx) { |
c306317a | 363 | panic("%s: unable to allocate ctx\n", __func__); |
151d4d35 CC |
364 | return ctx; |
365 | } | |
16a9013b NKC |
366 | |
367 | if (cmu->pll_clks) | |
368 | samsung_clk_register_pll(ctx, cmu->pll_clks, cmu->nr_pll_clks, | |
369 | reg_base); | |
370 | if (cmu->mux_clks) | |
371 | samsung_clk_register_mux(ctx, cmu->mux_clks, | |
372 | cmu->nr_mux_clks); | |
373 | if (cmu->div_clks) | |
374 | samsung_clk_register_div(ctx, cmu->div_clks, cmu->nr_div_clks); | |
375 | if (cmu->gate_clks) | |
376 | samsung_clk_register_gate(ctx, cmu->gate_clks, | |
377 | cmu->nr_gate_clks); | |
378 | if (cmu->fixed_clks) | |
379 | samsung_clk_register_fixed_rate(ctx, cmu->fixed_clks, | |
380 | cmu->nr_fixed_clks); | |
0e5af270 NKC |
381 | if (cmu->fixed_factor_clks) |
382 | samsung_clk_register_fixed_factor(ctx, cmu->fixed_factor_clks, | |
383 | cmu->nr_fixed_factor_clks); | |
16a9013b | 384 | if (cmu->clk_regs) |
8bf27eaa MS |
385 | samsung_clk_extended_sleep_init(reg_base, |
386 | cmu->clk_regs, cmu->nr_clk_regs, | |
387 | cmu->suspend_regs, cmu->nr_suspend_regs); | |
16a9013b NKC |
388 | |
389 | samsung_clk_of_add_provider(np, ctx); | |
151d4d35 CC |
390 | |
391 | return ctx; | |
16a9013b | 392 | } |