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 | ||
14 | #include <linux/syscore_ops.h> | |
15 | #include "clk.h" | |
16 | ||
3ccefbd2 TF |
17 | void samsung_clk_save(void __iomem *base, |
18 | struct samsung_clk_reg_dump *rd, | |
19 | unsigned int num_regs) | |
20 | { | |
21 | for (; num_regs > 0; --num_regs, ++rd) | |
22 | rd->value = readl(base + rd->offset); | |
23 | } | |
24 | ||
25 | void samsung_clk_restore(void __iomem *base, | |
26 | const struct samsung_clk_reg_dump *rd, | |
27 | unsigned int num_regs) | |
28 | { | |
29 | for (; num_regs > 0; --num_regs, ++rd) | |
30 | writel(rd->value, base + rd->offset); | |
31 | } | |
32 | ||
c3b6c1d7 TF |
33 | struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump( |
34 | const unsigned long *rdump, | |
35 | unsigned long nr_rdump) | |
3ccefbd2 TF |
36 | { |
37 | struct samsung_clk_reg_dump *rd; | |
38 | unsigned int i; | |
39 | ||
40 | rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL); | |
41 | if (!rd) | |
42 | return NULL; | |
43 | ||
44 | for (i = 0; i < nr_rdump; ++i) | |
45 | rd[i].offset = rdump[i]; | |
46 | ||
47 | return rd; | |
48 | } | |
49 | ||
721c42a3 | 50 | /* setup the essentials required to support clock lookup using ccf */ |
976face4 RS |
51 | struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np, |
52 | void __iomem *base, unsigned long nr_clks) | |
721c42a3 | 53 | { |
976face4 RS |
54 | struct samsung_clk_provider *ctx; |
55 | struct clk **clk_table; | |
91a1263f TF |
56 | int i; |
57 | ||
976face4 RS |
58 | ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); |
59 | if (!ctx) | |
60 | panic("could not allocate clock provider context.\n"); | |
721c42a3 | 61 | |
91a1263f | 62 | clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); |
2466196d HS |
63 | if (!clk_table) |
64 | panic("could not allocate clock lookup table\n"); | |
65 | ||
91a1263f TF |
66 | for (i = 0; i < nr_clks; ++i) |
67 | clk_table[i] = ERR_PTR(-ENOENT); | |
68 | ||
976face4 RS |
69 | ctx->reg_base = base; |
70 | ctx->clk_data.clks = clk_table; | |
71 | ctx->clk_data.clk_num = nr_clks; | |
72 | spin_lock_init(&ctx->lock); | |
73 | ||
976face4 | 74 | return ctx; |
d5e136a2 SN |
75 | } |
76 | ||
77 | void __init samsung_clk_of_add_provider(struct device_node *np, | |
78 | struct samsung_clk_provider *ctx) | |
79 | { | |
80 | if (np) { | |
81 | if (of_clk_add_provider(np, of_clk_src_onecell_get, | |
82 | &ctx->clk_data)) | |
83 | panic("could not register clk provider\n"); | |
84 | } | |
721c42a3 TA |
85 | } |
86 | ||
87 | /* add a clock instance to the clock lookup table used for dt based lookup */ | |
976face4 RS |
88 | void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk, |
89 | unsigned int id) | |
721c42a3 | 90 | { |
976face4 RS |
91 | if (ctx->clk_data.clks && id) |
92 | ctx->clk_data.clks[id] = clk; | |
721c42a3 TA |
93 | } |
94 | ||
5e2e0195 | 95 | /* register a list of aliases */ |
976face4 RS |
96 | void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx, |
97 | struct samsung_clock_alias *list, | |
98 | unsigned int nr_clk) | |
5e2e0195 HS |
99 | { |
100 | struct clk *clk; | |
101 | unsigned int idx, ret; | |
102 | ||
976face4 | 103 | if (!ctx->clk_data.clks) { |
5e2e0195 HS |
104 | pr_err("%s: clock table missing\n", __func__); |
105 | return; | |
106 | } | |
107 | ||
108 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
109 | if (!list->id) { | |
110 | pr_err("%s: clock id missing for index %d\n", __func__, | |
111 | idx); | |
112 | continue; | |
113 | } | |
114 | ||
976face4 | 115 | clk = ctx->clk_data.clks[list->id]; |
5e2e0195 HS |
116 | if (!clk) { |
117 | pr_err("%s: failed to find clock %d\n", __func__, | |
118 | list->id); | |
119 | continue; | |
120 | } | |
121 | ||
122 | ret = clk_register_clkdev(clk, list->alias, list->dev_name); | |
123 | if (ret) | |
124 | pr_err("%s: failed to register lookup %s\n", | |
125 | __func__, list->alias); | |
126 | } | |
127 | } | |
128 | ||
721c42a3 | 129 | /* register a list of fixed clocks */ |
976face4 | 130 | void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx, |
721c42a3 TA |
131 | struct samsung_fixed_rate_clock *list, unsigned int nr_clk) |
132 | { | |
133 | struct clk *clk; | |
134 | unsigned int idx, ret; | |
135 | ||
136 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
137 | clk = clk_register_fixed_rate(NULL, list->name, | |
138 | list->parent_name, list->flags, list->fixed_rate); | |
139 | if (IS_ERR(clk)) { | |
140 | pr_err("%s: failed to register clock %s\n", __func__, | |
141 | list->name); | |
142 | continue; | |
143 | } | |
144 | ||
976face4 | 145 | samsung_clk_add_lookup(ctx, clk, 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 | */ | |
151 | ret = clk_register_clkdev(clk, list->name, NULL); | |
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, |
721c42a3 TA |
160 | struct samsung_fixed_factor_clock *list, unsigned int nr_clk) |
161 | { | |
162 | struct clk *clk; | |
163 | unsigned int idx; | |
164 | ||
165 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
166 | clk = clk_register_fixed_factor(NULL, list->name, | |
167 | list->parent_name, list->flags, list->mult, list->div); | |
168 | if (IS_ERR(clk)) { | |
169 | pr_err("%s: failed to register clock %s\n", __func__, | |
170 | list->name); | |
171 | continue; | |
172 | } | |
173 | ||
976face4 | 174 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
175 | } |
176 | } | |
177 | ||
178 | /* register a list of mux clocks */ | |
976face4 RS |
179 | void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx, |
180 | struct samsung_mux_clock *list, | |
181 | unsigned int nr_clk) | |
721c42a3 TA |
182 | { |
183 | struct clk *clk; | |
184 | unsigned int idx, ret; | |
185 | ||
186 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
187 | clk = clk_register_mux(NULL, list->name, list->parent_names, | |
976face4 RS |
188 | list->num_parents, list->flags, |
189 | ctx->reg_base + list->offset, | |
190 | list->shift, list->width, list->mux_flags, &ctx->lock); | |
721c42a3 TA |
191 | if (IS_ERR(clk)) { |
192 | pr_err("%s: failed to register clock %s\n", __func__, | |
193 | list->name); | |
194 | continue; | |
195 | } | |
196 | ||
976face4 | 197 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
198 | |
199 | /* register a clock lookup only if a clock alias is specified */ | |
200 | if (list->alias) { | |
201 | ret = clk_register_clkdev(clk, list->alias, | |
202 | list->dev_name); | |
203 | if (ret) | |
204 | pr_err("%s: failed to register lookup %s\n", | |
205 | __func__, list->alias); | |
206 | } | |
207 | } | |
208 | } | |
209 | ||
210 | /* register a list of div clocks */ | |
976face4 RS |
211 | void __init samsung_clk_register_div(struct samsung_clk_provider *ctx, |
212 | struct samsung_div_clock *list, | |
213 | unsigned int nr_clk) | |
721c42a3 TA |
214 | { |
215 | struct clk *clk; | |
216 | unsigned int idx, ret; | |
217 | ||
218 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
798ed613 HS |
219 | if (list->table) |
220 | clk = clk_register_divider_table(NULL, list->name, | |
976face4 RS |
221 | list->parent_name, list->flags, |
222 | ctx->reg_base + list->offset, | |
223 | list->shift, list->width, list->div_flags, | |
224 | list->table, &ctx->lock); | |
798ed613 HS |
225 | else |
226 | clk = clk_register_divider(NULL, list->name, | |
976face4 RS |
227 | list->parent_name, list->flags, |
228 | ctx->reg_base + list->offset, list->shift, | |
229 | list->width, list->div_flags, &ctx->lock); | |
721c42a3 TA |
230 | if (IS_ERR(clk)) { |
231 | pr_err("%s: failed to register clock %s\n", __func__, | |
232 | list->name); | |
233 | continue; | |
234 | } | |
235 | ||
976face4 | 236 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
237 | |
238 | /* register a clock lookup only if a clock alias is specified */ | |
239 | if (list->alias) { | |
240 | ret = clk_register_clkdev(clk, list->alias, | |
241 | list->dev_name); | |
242 | if (ret) | |
243 | pr_err("%s: failed to register lookup %s\n", | |
244 | __func__, list->alias); | |
245 | } | |
246 | } | |
247 | } | |
248 | ||
249 | /* register a list of gate clocks */ | |
976face4 RS |
250 | void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx, |
251 | struct samsung_gate_clock *list, | |
252 | unsigned int nr_clk) | |
721c42a3 TA |
253 | { |
254 | struct clk *clk; | |
255 | unsigned int idx, ret; | |
256 | ||
257 | for (idx = 0; idx < nr_clk; idx++, list++) { | |
258 | clk = clk_register_gate(NULL, list->name, list->parent_name, | |
976face4 RS |
259 | list->flags, ctx->reg_base + list->offset, |
260 | list->bit_idx, list->gate_flags, &ctx->lock); | |
721c42a3 TA |
261 | if (IS_ERR(clk)) { |
262 | pr_err("%s: failed to register clock %s\n", __func__, | |
263 | list->name); | |
264 | continue; | |
265 | } | |
266 | ||
267 | /* register a clock lookup only if a clock alias is specified */ | |
268 | if (list->alias) { | |
269 | ret = clk_register_clkdev(clk, list->alias, | |
270 | list->dev_name); | |
271 | if (ret) | |
272 | pr_err("%s: failed to register lookup %s\n", | |
273 | __func__, list->alias); | |
274 | } | |
275 | ||
976face4 | 276 | samsung_clk_add_lookup(ctx, clk, list->id); |
721c42a3 TA |
277 | } |
278 | } | |
279 | ||
280 | /* | |
281 | * obtain the clock speed of all external fixed clock sources from device | |
282 | * tree and register it | |
283 | */ | |
6cec9082 | 284 | #ifdef CONFIG_OF |
976face4 | 285 | void __init samsung_clk_of_register_fixed_ext(struct samsung_clk_provider *ctx, |
721c42a3 TA |
286 | struct samsung_fixed_rate_clock *fixed_rate_clk, |
287 | unsigned int nr_fixed_rate_clk, | |
305cfab0 | 288 | const struct of_device_id *clk_matches) |
721c42a3 TA |
289 | { |
290 | const struct of_device_id *match; | |
976face4 | 291 | struct device_node *clk_np; |
721c42a3 TA |
292 | u32 freq; |
293 | ||
976face4 RS |
294 | for_each_matching_node_and_match(clk_np, clk_matches, &match) { |
295 | if (of_property_read_u32(clk_np, "clock-frequency", &freq)) | |
721c42a3 | 296 | continue; |
42fb57c0 | 297 | fixed_rate_clk[(unsigned long)match->data].fixed_rate = freq; |
721c42a3 | 298 | } |
976face4 | 299 | samsung_clk_register_fixed_rate(ctx, fixed_rate_clk, nr_fixed_rate_clk); |
721c42a3 | 300 | } |
6cec9082 | 301 | #endif |
721c42a3 TA |
302 | |
303 | /* utility function to get the rate of a specified clock */ | |
304 | unsigned long _get_rate(const char *clk_name) | |
305 | { | |
306 | struct clk *clk; | |
721c42a3 | 307 | |
3a647895 TF |
308 | clk = __clk_lookup(clk_name); |
309 | if (!clk) { | |
721c42a3 TA |
310 | pr_err("%s: could not find clock %s\n", __func__, clk_name); |
311 | return 0; | |
312 | } | |
3a647895 TF |
313 | |
314 | return clk_get_rate(clk); | |
721c42a3 | 315 | } |