diff options
author | Colin Cross <ccross@android.com> | 2010-01-28 19:40:29 -0500 |
---|---|---|
committer | Erik Gilling <konkers@android.com> | 2010-08-05 17:51:42 -0400 |
commit | d861196163e30c07add471562b45dce38517c9b2 (patch) | |
tree | 33c3577854f2e600e0f4dc25620f4932a31d3138 /arch/arm/mach-tegra/clock.c | |
parent | 5ad36c5f0ece31552a195f2f9e29357a2747536e (diff) |
[ARM] tegra: Add clock support
v2: fixes from Russell King:
- include linux/io.h instead of asm/io.h
- fix whitespace in Kconfig
- Use spin_lock_init to initialize lock
- Return -ENOSYS instead of BUG for unimplemented clock ops
- Use proper return values in tegra2 clock ops
additional changes:
- Rename some clocks to match dev_ids
- add rate propagation
- add debugfs entries
- add support for clock listed in clk_lookup under multiple dev_ids
v3:
- Replace per-clock locking with global clock lock
- Autodetect clock state on init
- Let clock dividers pick next lower possible frequency
- Add support for clock init tables
- Minor bug fixes
- Fix checkpatch issues
Signed-off-by: Colin Cross <ccross@android.com>
Diffstat (limited to 'arch/arm/mach-tegra/clock.c')
-rw-r--r-- | arch/arm/mach-tegra/clock.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c new file mode 100644 index 000000000000..03ad578349b9 --- /dev/null +++ b/arch/arm/mach-tegra/clock.c | |||
@@ -0,0 +1,488 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Copyright (C) 2010 Google, Inc. | ||
4 | * | ||
5 | * Author: | ||
6 | * Colin Cross <ccross@google.com> | ||
7 | * | ||
8 | * This software is licensed under the terms of the GNU General Public | ||
9 | * License version 2, as published by the Free Software Foundation, and | ||
10 | * may be copied, distributed, and modified under those terms. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | */ | ||
18 | |||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/clk.h> | ||
21 | #include <linux/list.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/debugfs.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/seq_file.h> | ||
27 | #include <asm/clkdev.h> | ||
28 | |||
29 | #include "clock.h" | ||
30 | |||
31 | static LIST_HEAD(clocks); | ||
32 | |||
33 | static DEFINE_SPINLOCK(clock_lock); | ||
34 | |||
35 | struct clk *tegra_get_clock_by_name(const char *name) | ||
36 | { | ||
37 | struct clk *c; | ||
38 | struct clk *ret = NULL; | ||
39 | unsigned long flags; | ||
40 | spin_lock_irqsave(&clock_lock, flags); | ||
41 | list_for_each_entry(c, &clocks, node) { | ||
42 | if (strcmp(c->name, name) == 0) { | ||
43 | ret = c; | ||
44 | break; | ||
45 | } | ||
46 | } | ||
47 | spin_unlock_irqrestore(&clock_lock, flags); | ||
48 | return ret; | ||
49 | } | ||
50 | |||
51 | int clk_reparent(struct clk *c, struct clk *parent) | ||
52 | { | ||
53 | pr_debug("%s: %s\n", __func__, c->name); | ||
54 | if (c->refcnt && c->parent) | ||
55 | clk_disable_locked(c->parent); | ||
56 | c->parent = parent; | ||
57 | if (c->refcnt && c->parent) | ||
58 | clk_enable_locked(c->parent); | ||
59 | list_del(&c->sibling); | ||
60 | list_add_tail(&c->sibling, &parent->children); | ||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | static void propagate_rate(struct clk *c) | ||
65 | { | ||
66 | struct clk *clkp; | ||
67 | pr_debug("%s: %s\n", __func__, c->name); | ||
68 | list_for_each_entry(clkp, &c->children, sibling) { | ||
69 | pr_debug(" %s\n", clkp->name); | ||
70 | if (clkp->ops->recalculate_rate) | ||
71 | clkp->ops->recalculate_rate(clkp); | ||
72 | propagate_rate(clkp); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | void clk_init(struct clk *c) | ||
77 | { | ||
78 | unsigned long flags; | ||
79 | |||
80 | spin_lock_irqsave(&clock_lock, flags); | ||
81 | |||
82 | INIT_LIST_HEAD(&c->children); | ||
83 | INIT_LIST_HEAD(&c->sibling); | ||
84 | |||
85 | if (c->ops && c->ops->init) | ||
86 | c->ops->init(c); | ||
87 | |||
88 | list_add(&c->node, &clocks); | ||
89 | |||
90 | if (c->parent) | ||
91 | list_add_tail(&c->sibling, &c->parent->children); | ||
92 | |||
93 | spin_unlock_irqrestore(&clock_lock, flags); | ||
94 | } | ||
95 | |||
96 | int clk_enable_locked(struct clk *c) | ||
97 | { | ||
98 | int ret; | ||
99 | pr_debug("%s: %s\n", __func__, c->name); | ||
100 | if (c->refcnt == 0) { | ||
101 | if (c->parent) { | ||
102 | ret = clk_enable_locked(c->parent); | ||
103 | if (ret) | ||
104 | return ret; | ||
105 | } | ||
106 | |||
107 | if (c->ops && c->ops->enable) { | ||
108 | ret = c->ops->enable(c); | ||
109 | if (ret) { | ||
110 | if (c->parent) | ||
111 | clk_disable_locked(c->parent); | ||
112 | return ret; | ||
113 | } | ||
114 | c->state = ON; | ||
115 | #ifdef CONFIG_DEBUG_FS | ||
116 | c->set = 1; | ||
117 | #endif | ||
118 | } | ||
119 | } | ||
120 | c->refcnt++; | ||
121 | |||
122 | return 0; | ||
123 | } | ||
124 | |||
125 | int clk_enable(struct clk *c) | ||
126 | { | ||
127 | int ret; | ||
128 | unsigned long flags; | ||
129 | spin_lock_irqsave(&clock_lock, flags); | ||
130 | ret = clk_enable_locked(c); | ||
131 | spin_unlock_irqrestore(&clock_lock, flags); | ||
132 | return ret; | ||
133 | } | ||
134 | EXPORT_SYMBOL(clk_enable); | ||
135 | |||
136 | void clk_disable_locked(struct clk *c) | ||
137 | { | ||
138 | pr_debug("%s: %s\n", __func__, c->name); | ||
139 | if (c->refcnt == 0) { | ||
140 | WARN(1, "Attempting to disable clock %s with refcnt 0", c->name); | ||
141 | return; | ||
142 | } | ||
143 | if (c->refcnt == 1) { | ||
144 | if (c->ops && c->ops->disable) | ||
145 | c->ops->disable(c); | ||
146 | |||
147 | if (c->parent) | ||
148 | clk_disable_locked(c->parent); | ||
149 | |||
150 | c->state = OFF; | ||
151 | } | ||
152 | c->refcnt--; | ||
153 | } | ||
154 | |||
155 | void clk_disable(struct clk *c) | ||
156 | { | ||
157 | unsigned long flags; | ||
158 | spin_lock_irqsave(&clock_lock, flags); | ||
159 | clk_disable_locked(c); | ||
160 | spin_unlock_irqrestore(&clock_lock, flags); | ||
161 | } | ||
162 | EXPORT_SYMBOL(clk_disable); | ||
163 | |||
164 | int clk_set_parent_locked(struct clk *c, struct clk *parent) | ||
165 | { | ||
166 | int ret; | ||
167 | |||
168 | pr_debug("%s: %s\n", __func__, c->name); | ||
169 | |||
170 | if (!c->ops || !c->ops->set_parent) | ||
171 | return -ENOSYS; | ||
172 | |||
173 | ret = c->ops->set_parent(c, parent); | ||
174 | |||
175 | if (ret) | ||
176 | return ret; | ||
177 | |||
178 | propagate_rate(c); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | int clk_set_parent(struct clk *c, struct clk *parent) | ||
184 | { | ||
185 | int ret; | ||
186 | unsigned long flags; | ||
187 | spin_lock_irqsave(&clock_lock, flags); | ||
188 | ret = clk_set_parent_locked(c, parent); | ||
189 | spin_unlock_irqrestore(&clock_lock, flags); | ||
190 | return ret; | ||
191 | } | ||
192 | EXPORT_SYMBOL(clk_set_parent); | ||
193 | |||
194 | struct clk *clk_get_parent(struct clk *c) | ||
195 | { | ||
196 | return c->parent; | ||
197 | } | ||
198 | EXPORT_SYMBOL(clk_get_parent); | ||
199 | |||
200 | int clk_set_rate(struct clk *c, unsigned long rate) | ||
201 | { | ||
202 | int ret = 0; | ||
203 | unsigned long flags; | ||
204 | |||
205 | spin_lock_irqsave(&clock_lock, flags); | ||
206 | |||
207 | pr_debug("%s: %s\n", __func__, c->name); | ||
208 | |||
209 | if (c->ops && c->ops->set_rate) | ||
210 | ret = c->ops->set_rate(c, rate); | ||
211 | else | ||
212 | ret = -ENOSYS; | ||
213 | |||
214 | propagate_rate(c); | ||
215 | |||
216 | spin_unlock_irqrestore(&clock_lock, flags); | ||
217 | |||
218 | return ret; | ||
219 | } | ||
220 | EXPORT_SYMBOL(clk_set_rate); | ||
221 | |||
222 | unsigned long clk_get_rate(struct clk *c) | ||
223 | { | ||
224 | unsigned long flags; | ||
225 | unsigned long ret; | ||
226 | |||
227 | spin_lock_irqsave(&clock_lock, flags); | ||
228 | |||
229 | pr_debug("%s: %s\n", __func__, c->name); | ||
230 | |||
231 | ret = c->rate; | ||
232 | |||
233 | spin_unlock_irqrestore(&clock_lock, flags); | ||
234 | return ret; | ||
235 | } | ||
236 | EXPORT_SYMBOL(clk_get_rate); | ||
237 | |||
238 | static int tegra_clk_init_one_from_table(struct tegra_clk_init_table *table) | ||
239 | { | ||
240 | struct clk *c; | ||
241 | struct clk *p; | ||
242 | |||
243 | int ret = 0; | ||
244 | |||
245 | c = tegra_get_clock_by_name(table->name); | ||
246 | |||
247 | if (!c) { | ||
248 | pr_warning("Unable to initialize clock %s\n", | ||
249 | table->name); | ||
250 | return -ENODEV; | ||
251 | } | ||
252 | |||
253 | if (table->parent) { | ||
254 | p = tegra_get_clock_by_name(table->parent); | ||
255 | if (!p) { | ||
256 | pr_warning("Unable to find parent %s of clock %s\n", | ||
257 | table->parent, table->name); | ||
258 | return -ENODEV; | ||
259 | } | ||
260 | |||
261 | if (c->parent != p) { | ||
262 | ret = clk_set_parent(c, p); | ||
263 | if (ret) { | ||
264 | pr_warning("Unable to set parent %s of clock %s: %d\n", | ||
265 | table->parent, table->name, ret); | ||
266 | return -EINVAL; | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | |||
271 | if (table->rate && table->rate != clk_get_rate(c)) { | ||
272 | ret = clk_set_rate(c, table->rate); | ||
273 | if (ret) { | ||
274 | pr_warning("Unable to set clock %s to rate %lu: %d\n", | ||
275 | table->name, table->rate, ret); | ||
276 | return -EINVAL; | ||
277 | } | ||
278 | } | ||
279 | |||
280 | if (table->enabled) { | ||
281 | ret = clk_enable(c); | ||
282 | if (ret) { | ||
283 | pr_warning("Unable to enable clock %s: %d\n", | ||
284 | table->name, ret); | ||
285 | return -EINVAL; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | void tegra_clk_init_from_table(struct tegra_clk_init_table *table) | ||
293 | { | ||
294 | for (; table->name; table++) | ||
295 | tegra_clk_init_one_from_table(table); | ||
296 | } | ||
297 | EXPORT_SYMBOL(tegra_clk_init_from_table); | ||
298 | |||
299 | void tegra_periph_reset_deassert(struct clk *c) | ||
300 | { | ||
301 | tegra2_periph_reset_deassert(c); | ||
302 | } | ||
303 | EXPORT_SYMBOL(tegra_periph_reset_deassert); | ||
304 | |||
305 | void tegra_periph_reset_assert(struct clk *c) | ||
306 | { | ||
307 | tegra2_periph_reset_assert(c); | ||
308 | } | ||
309 | EXPORT_SYMBOL(tegra_periph_reset_assert); | ||
310 | |||
311 | int __init tegra_init_clock(void) | ||
312 | { | ||
313 | tegra2_init_clocks(); | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | #ifdef CONFIG_DEBUG_FS | ||
319 | static struct dentry *clk_debugfs_root; | ||
320 | |||
321 | |||
322 | static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) | ||
323 | { | ||
324 | struct clk *child; | ||
325 | struct clk *safe; | ||
326 | const char *state = "uninit"; | ||
327 | char div[5] = {0}; | ||
328 | |||
329 | if (c->state == ON) | ||
330 | state = "on"; | ||
331 | else if (c->state == OFF) | ||
332 | state = "off"; | ||
333 | |||
334 | if (c->mul != 0 && c->div != 0) { | ||
335 | BUG_ON(c->mul > 2); | ||
336 | if (c->mul > c->div) | ||
337 | snprintf(div, sizeof(div), "x%d", c->mul / c->div); | ||
338 | else | ||
339 | snprintf(div, sizeof(div), "%d%s", c->div / c->mul, | ||
340 | (c->div % c->mul) ? ".5" : ""); | ||
341 | } | ||
342 | |||
343 | seq_printf(s, "%*s%-*s %-6s %-3d %-5s %-10lu\n", | ||
344 | level * 3 + 1, c->set ? "" : "*", | ||
345 | 30 - level * 3, c->name, | ||
346 | state, c->refcnt, div, c->rate); | ||
347 | list_for_each_entry_safe(child, safe, &c->children, sibling) { | ||
348 | clock_tree_show_one(s, child, level + 1); | ||
349 | } | ||
350 | } | ||
351 | |||
352 | static int clock_tree_show(struct seq_file *s, void *data) | ||
353 | { | ||
354 | struct clk *c; | ||
355 | unsigned long flags; | ||
356 | seq_printf(s, " clock state ref div rate \n"); | ||
357 | seq_printf(s, "-----------------------------------------------------------\n"); | ||
358 | spin_lock_irqsave(&clock_lock, flags); | ||
359 | list_for_each_entry(c, &clocks, node) | ||
360 | if (c->parent == NULL) | ||
361 | clock_tree_show_one(s, c, 0); | ||
362 | spin_unlock_irqrestore(&clock_lock, flags); | ||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | static int clock_tree_open(struct inode *inode, struct file *file) | ||
367 | { | ||
368 | return single_open(file, clock_tree_show, inode->i_private); | ||
369 | } | ||
370 | |||
371 | static const struct file_operations clock_tree_fops = { | ||
372 | .open = clock_tree_open, | ||
373 | .read = seq_read, | ||
374 | .llseek = seq_lseek, | ||
375 | .release = single_release, | ||
376 | }; | ||
377 | |||
378 | static int possible_parents_show(struct seq_file *s, void *data) | ||
379 | { | ||
380 | struct clk *c = s->private; | ||
381 | int i; | ||
382 | |||
383 | for (i = 0; c->inputs[i].input; i++) { | ||
384 | char *first = (i == 0) ? "" : " "; | ||
385 | seq_printf(s, "%s%s", first, c->inputs[i].input->name); | ||
386 | } | ||
387 | seq_printf(s, "\n"); | ||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | static int possible_parents_open(struct inode *inode, struct file *file) | ||
392 | { | ||
393 | return single_open(file, possible_parents_show, inode->i_private); | ||
394 | } | ||
395 | |||
396 | static const struct file_operations possible_parents_fops = { | ||
397 | .open = possible_parents_open, | ||
398 | .read = seq_read, | ||
399 | .llseek = seq_lseek, | ||
400 | .release = single_release, | ||
401 | }; | ||
402 | |||
403 | static int clk_debugfs_register_one(struct clk *c) | ||
404 | { | ||
405 | struct dentry *d, *child, *child_tmp; | ||
406 | |||
407 | d = debugfs_create_dir(c->name, clk_debugfs_root); | ||
408 | if (!d) | ||
409 | return -ENOMEM; | ||
410 | c->dent = d; | ||
411 | |||
412 | d = debugfs_create_u8("refcnt", S_IRUGO, c->dent, (u8 *)&c->refcnt); | ||
413 | if (!d) | ||
414 | goto err_out; | ||
415 | |||
416 | d = debugfs_create_u32("rate", S_IRUGO, c->dent, (u32 *)&c->rate); | ||
417 | if (!d) | ||
418 | goto err_out; | ||
419 | |||
420 | d = debugfs_create_x32("flags", S_IRUGO, c->dent, (u32 *)&c->flags); | ||
421 | if (!d) | ||
422 | goto err_out; | ||
423 | |||
424 | if (c->inputs) { | ||
425 | d = debugfs_create_file("possible_parents", S_IRUGO, c->dent, | ||
426 | c, &possible_parents_fops); | ||
427 | if (!d) | ||
428 | goto err_out; | ||
429 | } | ||
430 | |||
431 | return 0; | ||
432 | |||
433 | err_out: | ||
434 | d = c->dent; | ||
435 | list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) | ||
436 | debugfs_remove(child); | ||
437 | debugfs_remove(c->dent); | ||
438 | return -ENOMEM; | ||
439 | } | ||
440 | |||
441 | static int clk_debugfs_register(struct clk *c) | ||
442 | { | ||
443 | int err; | ||
444 | struct clk *pa = c->parent; | ||
445 | |||
446 | if (pa && !pa->dent) { | ||
447 | err = clk_debugfs_register(pa); | ||
448 | if (err) | ||
449 | return err; | ||
450 | } | ||
451 | |||
452 | if (!c->dent) { | ||
453 | err = clk_debugfs_register_one(c); | ||
454 | if (err) | ||
455 | return err; | ||
456 | } | ||
457 | return 0; | ||
458 | } | ||
459 | |||
460 | static int __init clk_debugfs_init(void) | ||
461 | { | ||
462 | struct clk *c; | ||
463 | struct dentry *d; | ||
464 | int err = -ENOMEM; | ||
465 | |||
466 | d = debugfs_create_dir("clock", NULL); | ||
467 | if (!d) | ||
468 | return -ENOMEM; | ||
469 | clk_debugfs_root = d; | ||
470 | |||
471 | d = debugfs_create_file("clock_tree", S_IRUGO, clk_debugfs_root, NULL, | ||
472 | &clock_tree_fops); | ||
473 | if (!d) | ||
474 | goto err_out; | ||
475 | |||
476 | list_for_each_entry(c, &clocks, node) { | ||
477 | err = clk_debugfs_register(c); | ||
478 | if (err) | ||
479 | goto err_out; | ||
480 | } | ||
481 | return 0; | ||
482 | err_out: | ||
483 | debugfs_remove_recursive(clk_debugfs_root); | ||
484 | return err; | ||
485 | } | ||
486 | |||
487 | late_initcall(clk_debugfs_init); | ||
488 | #endif | ||