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