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