aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-omap2/clkt_clksel.c
diff options
context:
space:
mode:
authorPaul Walmsley <paul@pwsan.com>2010-01-26 22:13:04 -0500
committerPaul Walmsley <paul@pwsan.com>2010-01-26 22:13:04 -0500
commitdf791b3ebf181b3eece9c770565fcf0844bbd7cb (patch)
tree63f9c589296060f84da3d8d0dd63031630aa0069 /arch/arm/mach-omap2/clkt_clksel.c
parent0b96af683026ab9ca4dd52f9005a1a4fc582e914 (diff)
OMAP2/3/4 clock: move clksel clock functions into mach-omap2/clkt_clksel.c
Move all clksel-related clock functions from mach-omap2/clock.c to mach-omap2/clkt_clksel.c. This is intended to make the clock code easier to understand, since all of the functions needed to manage clksel clocks are now located in their own file, rather than being mixed with other, unrelated functions. Clock debugging is also now more finely-grained, since the DEBUG macro can now be defined for clksel clocks alon. This should reduce unnecessary console noise when debugging. Also, if at some future point the mach-omap2/ directory is split into OMAP2/3/4 variants, this clkt file can be moved to the plat-omap/ directory to be shared. Thanks to Alexander Shishkin <virtuoso@slind.org> for his comments to improve the patch description. Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Alexander Shishkin <virtuoso@slind.org>
Diffstat (limited to 'arch/arm/mach-omap2/clkt_clksel.c')
-rw-r--r--arch/arm/mach-omap2/clkt_clksel.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/clkt_clksel.c b/arch/arm/mach-omap2/clkt_clksel.c
new file mode 100644
index 000000000000..25a2363106de
--- /dev/null
+++ b/arch/arm/mach-omap2/clkt_clksel.c
@@ -0,0 +1,417 @@
1/*
2 * clkt_clksel.c - OMAP2/3/4 clksel clock functions
3 *
4 * Copyright (C) 2005-2008 Texas Instruments, Inc.
5 * Copyright (C) 2004-2010 Nokia Corporation
6 *
7 * Contacts:
8 * Richard Woodruff <r-woodruff2@ti.com>
9 * Paul Walmsley
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
14 *
15 * XXX At some point these clksel clocks should be split into
16 * "divider" clocks and "mux" clocks to better match the hardware.
17 *
18 * XXX Currently these clocks are only used in the OMAP2/3/4 code, but
19 * many of the OMAP1 clocks should be convertible to use this
20 * mechanism.
21 */
22#undef DEBUG
23
24#include <linux/kernel.h>
25#include <linux/errno.h>
26#include <linux/clk.h>
27#include <linux/io.h>
28
29#include <plat/clock.h>
30
31#include "clock.h"
32#include "cm.h"
33#include "cm-regbits-24xx.h"
34#include "cm-regbits-34xx.h"
35
36/* Private functions */
37
38/**
39 * _omap2_get_clksel_by_parent - return clksel struct for a given clk & parent
40 * @clk: OMAP struct clk ptr to inspect
41 * @src_clk: OMAP struct clk ptr of the parent clk to search for
42 *
43 * Scan the struct clksel array associated with the clock to find
44 * the element associated with the supplied parent clock address.
45 * Returns a pointer to the struct clksel on success or NULL on error.
46 */
47static const struct clksel *_omap2_get_clksel_by_parent(struct clk *clk,
48 struct clk *src_clk)
49{
50 const struct clksel *clks;
51
52 if (!clk->clksel)
53 return NULL;
54
55 for (clks = clk->clksel; clks->parent; clks++) {
56 if (clks->parent == src_clk)
57 break; /* Found the requested parent */
58 }
59
60 if (!clks->parent) {
61 printk(KERN_ERR "clock: Could not find parent clock %s in "
62 "clksel array of clock %s\n", src_clk->name,
63 clk->name);
64 return NULL;
65 }
66
67 return clks;
68}
69
70/*
71 * Converts encoded control register address into a full address
72 * On error, the return value (parent_div) will be 0.
73 */
74static u32 _omap2_clksel_get_src_field(struct clk *src_clk, struct clk *clk,
75 u32 *field_val)
76{
77 const struct clksel *clks;
78 const struct clksel_rate *clkr;
79
80 clks = _omap2_get_clksel_by_parent(clk, src_clk);
81 if (!clks)
82 return 0;
83
84 for (clkr = clks->rates; clkr->div; clkr++) {
85 if (clkr->flags & cpu_mask && clkr->flags & DEFAULT_RATE)
86 break; /* Found the default rate for this platform */
87 }
88
89 if (!clkr->div) {
90 printk(KERN_ERR "clock: Could not find default rate for "
91 "clock %s parent %s\n", clk->name,
92 src_clk->parent->name);
93 return 0;
94 }
95
96 /* Should never happen. Add a clksel mask to the struct clk. */
97 WARN_ON(clk->clksel_mask == 0);
98
99 *field_val = clkr->val;
100
101 return clkr->div;
102}
103
104
105/* Public functions */
106
107/**
108 * omap2_init_clksel_parent - set a clksel clk's parent field from the hardware
109 * @clk: OMAP clock struct ptr to use
110 *
111 * Given a pointer to a source-selectable struct clk, read the hardware
112 * register and determine what its parent is currently set to. Update the
113 * clk->parent field with the appropriate clk ptr.
114 */
115void omap2_init_clksel_parent(struct clk *clk)
116{
117 const struct clksel *clks;
118 const struct clksel_rate *clkr;
119 u32 r, found = 0;
120
121 if (!clk->clksel)
122 return;
123
124 r = __raw_readl(clk->clksel_reg) & clk->clksel_mask;
125 r >>= __ffs(clk->clksel_mask);
126
127 for (clks = clk->clksel; clks->parent && !found; clks++) {
128 for (clkr = clks->rates; clkr->div && !found; clkr++) {
129 if ((clkr->flags & cpu_mask) && (clkr->val == r)) {
130 if (clk->parent != clks->parent) {
131 pr_debug("clock: inited %s parent "
132 "to %s (was %s)\n",
133 clk->name, clks->parent->name,
134 ((clk->parent) ?
135 clk->parent->name : "NULL"));
136 clk_reparent(clk, clks->parent);
137 };
138 found = 1;
139 }
140 }
141 }
142
143 if (!found)
144 printk(KERN_ERR "clock: init parent: could not find "
145 "regval %0x for clock %s\n", r, clk->name);
146
147 return;
148}
149
150/*
151 * Used for clocks that are part of CLKSEL_xyz governed clocks.
152 * REVISIT: Maybe change to use clk->enable() functions like on omap1?
153 */
154unsigned long omap2_clksel_recalc(struct clk *clk)
155{
156 unsigned long rate;
157 u32 div = 0;
158
159 pr_debug("clock: recalc'ing clksel clk %s\n", clk->name);
160
161 div = omap2_clksel_get_divisor(clk);
162 if (div == 0)
163 return clk->rate;
164
165 rate = clk->parent->rate / div;
166
167 pr_debug("clock: new clock rate is %ld (div %d)\n", rate, div);
168
169 return rate;
170}
171
172/**
173 * omap2_clksel_round_rate_div - find divisor for the given clock and rate
174 * @clk: OMAP struct clk to use
175 * @target_rate: desired clock rate
176 * @new_div: ptr to where we should store the divisor
177 *
178 * Finds 'best' divider value in an array based on the source and target
179 * rates. The divider array must be sorted with smallest divider first.
180 * Note that this will not work for clocks which are part of CONFIG_PARTICIPANT,
181 * they are only settable as part of virtual_prcm set.
182 *
183 * Returns the rounded clock rate or returns 0xffffffff on error.
184 */
185u32 omap2_clksel_round_rate_div(struct clk *clk, unsigned long target_rate,
186 u32 *new_div)
187{
188 unsigned long test_rate;
189 const struct clksel *clks;
190 const struct clksel_rate *clkr;
191 u32 last_div = 0;
192
193 pr_debug("clock: clksel_round_rate_div: %s target_rate %ld\n",
194 clk->name, target_rate);
195
196 *new_div = 1;
197
198 clks = _omap2_get_clksel_by_parent(clk, clk->parent);
199 if (!clks)
200 return ~0;
201
202 for (clkr = clks->rates; clkr->div; clkr++) {
203 if (!(clkr->flags & cpu_mask))
204 continue;
205
206 /* Sanity check */
207 if (clkr->div <= last_div)
208 pr_err("clock: clksel_rate table not sorted "
209 "for clock %s", clk->name);
210
211 last_div = clkr->div;
212
213 test_rate = clk->parent->rate / clkr->div;
214
215 if (test_rate <= target_rate)
216 break; /* found it */
217 }
218
219 if (!clkr->div) {
220 pr_err("clock: Could not find divisor for target "
221 "rate %ld for clock %s parent %s\n", target_rate,
222 clk->name, clk->parent->name);
223 return ~0;
224 }
225
226 *new_div = clkr->div;
227
228 pr_debug("clock: new_div = %d, new_rate = %ld\n", *new_div,
229 (clk->parent->rate / clkr->div));
230
231 return clk->parent->rate / clkr->div;
232}
233
234/**
235 * omap2_clksel_round_rate - find rounded rate for the given clock and rate
236 * @clk: OMAP struct clk to use
237 * @target_rate: desired clock rate
238 *
239 * Compatibility wrapper for OMAP clock framework
240 * Finds best target rate based on the source clock and possible dividers.
241 * rates. The divider array must be sorted with smallest divider first.
242 * Note that this will not work for clocks which are part of CONFIG_PARTICIPANT,
243 * they are only settable as part of virtual_prcm set.
244 *
245 * Returns the rounded clock rate or returns 0xffffffff on error.
246 */
247long omap2_clksel_round_rate(struct clk *clk, unsigned long target_rate)
248{
249 u32 new_div;
250
251 return omap2_clksel_round_rate_div(clk, target_rate, &new_div);
252}
253
254
255/* Given a clock and a rate apply a clock specific rounding function */
256long omap2_clk_round_rate(struct clk *clk, unsigned long rate)
257{
258 if (clk->round_rate)
259 return clk->round_rate(clk, rate);
260
261 if (clk->flags & RATE_FIXED)
262 printk(KERN_ERR "clock: generic omap2_clk_round_rate called "
263 "on fixed-rate clock %s\n", clk->name);
264
265 return clk->rate;
266}
267
268/**
269 * omap2_clksel_to_divisor() - turn clksel field value into integer divider
270 * @clk: OMAP struct clk to use
271 * @field_val: register field value to find
272 *
273 * Given a struct clk of a rate-selectable clksel clock, and a register field
274 * value to search for, find the corresponding clock divisor. The register
275 * field value should be pre-masked and shifted down so the LSB is at bit 0
276 * before calling. Returns 0 on error
277 */
278u32 omap2_clksel_to_divisor(struct clk *clk, u32 field_val)
279{
280 const struct clksel *clks;
281 const struct clksel_rate *clkr;
282
283 clks = _omap2_get_clksel_by_parent(clk, clk->parent);
284 if (!clks)
285 return 0;
286
287 for (clkr = clks->rates; clkr->div; clkr++) {
288 if ((clkr->flags & cpu_mask) && (clkr->val == field_val))
289 break;
290 }
291
292 if (!clkr->div) {
293 printk(KERN_ERR "clock: Could not find fieldval %d for "
294 "clock %s parent %s\n", field_val, clk->name,
295 clk->parent->name);
296 return 0;
297 }
298
299 return clkr->div;
300}
301
302/**
303 * omap2_divisor_to_clksel() - turn clksel integer divisor into a field value
304 * @clk: OMAP struct clk to use
305 * @div: integer divisor to search for
306 *
307 * Given a struct clk of a rate-selectable clksel clock, and a clock divisor,
308 * find the corresponding register field value. The return register value is
309 * the value before left-shifting. Returns ~0 on error
310 */
311u32 omap2_divisor_to_clksel(struct clk *clk, u32 div)
312{
313 const struct clksel *clks;
314 const struct clksel_rate *clkr;
315
316 /* should never happen */
317 WARN_ON(div == 0);
318
319 clks = _omap2_get_clksel_by_parent(clk, clk->parent);
320 if (!clks)
321 return ~0;
322
323 for (clkr = clks->rates; clkr->div; clkr++) {
324 if ((clkr->flags & cpu_mask) && (clkr->div == div))
325 break;
326 }
327
328 if (!clkr->div) {
329 printk(KERN_ERR "clock: Could not find divisor %d for "
330 "clock %s parent %s\n", div, clk->name,
331 clk->parent->name);
332 return ~0;
333 }
334
335 return clkr->val;
336}
337
338/**
339 * omap2_clksel_get_divisor - get current divider applied to parent clock.
340 * @clk: OMAP struct clk to use.
341 *
342 * Returns the integer divisor upon success or 0 on error.
343 */
344u32 omap2_clksel_get_divisor(struct clk *clk)
345{
346 u32 v;
347
348 if (!clk->clksel_mask)
349 return 0;
350
351 v = __raw_readl(clk->clksel_reg) & clk->clksel_mask;
352 v >>= __ffs(clk->clksel_mask);
353
354 return omap2_clksel_to_divisor(clk, v);
355}
356
357int omap2_clksel_set_rate(struct clk *clk, unsigned long rate)
358{
359 u32 v, field_val, validrate, new_div = 0;
360
361 if (!clk->clksel_mask)
362 return -EINVAL;
363
364 validrate = omap2_clksel_round_rate_div(clk, rate, &new_div);
365 if (validrate != rate)
366 return -EINVAL;
367
368 field_val = omap2_divisor_to_clksel(clk, new_div);
369 if (field_val == ~0)
370 return -EINVAL;
371
372 v = __raw_readl(clk->clksel_reg);
373 v &= ~clk->clksel_mask;
374 v |= field_val << __ffs(clk->clksel_mask);
375 __raw_writel(v, clk->clksel_reg);
376 v = __raw_readl(clk->clksel_reg); /* OCP barrier */
377
378 clk->rate = clk->parent->rate / new_div;
379
380 omap2xxx_clk_commit(clk);
381
382 return 0;
383}
384
385int omap2_clksel_set_parent(struct clk *clk, struct clk *new_parent)
386{
387 u32 field_val, v, parent_div;
388
389 if (!clk->clksel)
390 return -EINVAL;
391
392 parent_div = _omap2_clksel_get_src_field(new_parent, clk, &field_val);
393 if (!parent_div)
394 return -EINVAL;
395
396 /* Set new source value (previous dividers if any in effect) */
397 v = __raw_readl(clk->clksel_reg);
398 v &= ~clk->clksel_mask;
399 v |= field_val << __ffs(clk->clksel_mask);
400 __raw_writel(v, clk->clksel_reg);
401 v = __raw_readl(clk->clksel_reg); /* OCP barrier */
402
403 omap2xxx_clk_commit(clk);
404
405 clk_reparent(clk, new_parent);
406
407 /* CLKSEL clocks follow their parents' rates, divided by a divisor */
408 clk->rate = new_parent->rate;
409
410 if (parent_div > 0)
411 clk->rate /= parent_div;
412
413 pr_debug("clock: set parent of %s to %s (new rate %ld)\n",
414 clk->name, clk->parent->name, clk->rate);
415
416 return 0;
417}