diff options
author | Paul Walmsley <paul@pwsan.com> | 2008-08-19 04:08:43 -0400 |
---|---|---|
committer | Tony Lindgren <tony@atomide.com> | 2008-08-19 04:08:43 -0400 |
commit | d459bfe01f523983a822de8c2d3fe0bd2f2c194e (patch) | |
tree | 9f583480f3c0940778bb0f7b4fee5c68eb5b5bfc /arch/arm | |
parent | ecb24aa129c6d4b2152571f856320aa7dea41676 (diff) |
ARM: OMAP2: Clockdomain: Add base OMAP2/3 clockdomain code
This patch creates an interface to the clockdomain registers in the
PRM/CM modules on OMAP2/3. This interface is intended to be used by
PM code, e.g., pm.c; not by device drivers directly.
The patch also adds clockdomain usecount tracking. This is intended
to be called whenever the first clock in a clockdomain is enabled, or
when the last enabled clock in a clockdomain is disabled. If the
clockdomain is in software-supervised mode, the code will force-wakeup
or force-sleep the clockdomain. If the clockdomain is in
hardware-supervised mode, the first clock enable will add sleep and
wakeup dependencies on a user-selectable set of parent domains (usually
MPU & IVA2), and the disable will remove them.
Each clockdomain will be defined in later patches as static
structures. The clockdomain structures are linked into a list at boot
by clkdm_register(), similar to the OMAP clock code.
The patch adds a Kconfig option, CONFIG_OMAP_DEBUG_CLOCKDOMAIN, which
when enabled will emit verbose debug messages via pr_debug().
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/mach-omap2/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-omap2/clockdomain.c | 603 | ||||
-rw-r--r-- | arch/arm/plat-omap/Kconfig | 12 | ||||
-rw-r--r-- | arch/arm/plat-omap/include/mach/clockdomain.h | 106 |
4 files changed, 723 insertions, 1 deletions
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 1001d42048e9..e7cf1b4357ce 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile | |||
@@ -4,7 +4,8 @@ | |||
4 | 4 | ||
5 | # Common support | 5 | # Common support |
6 | obj-y := irq.o id.o io.o memory.o control.o prcm.o clock.o mux.o \ | 6 | obj-y := irq.o id.o io.o memory.o control.o prcm.o clock.o mux.o \ |
7 | devices.o serial.o gpmc.o timer-gp.o powerdomain.o | 7 | devices.o serial.o gpmc.o timer-gp.o powerdomain.o \ |
8 | clockdomain.o | ||
8 | 9 | ||
9 | obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o | 10 | obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o |
10 | 11 | ||
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c new file mode 100644 index 000000000000..f867d8f1d0e9 --- /dev/null +++ b/arch/arm/mach-omap2/clockdomain.c | |||
@@ -0,0 +1,603 @@ | |||
1 | /* | ||
2 | * OMAP2/3 clockdomain framework functions | ||
3 | * | ||
4 | * Copyright (C) 2008 Texas Instruments, Inc. | ||
5 | * Copyright (C) 2008 Nokia Corporation | ||
6 | * | ||
7 | * Written by Paul Walmsley and Jouni Högander | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | #ifdef CONFIG_OMAP_DEBUG_CLOCKDOMAIN | ||
14 | # define DEBUG | ||
15 | #endif | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/list.h> | ||
21 | #include <linux/errno.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/clk.h> | ||
24 | #include <linux/limits.h> | ||
25 | |||
26 | #include <linux/io.h> | ||
27 | |||
28 | #include <linux/bitops.h> | ||
29 | |||
30 | #include <mach/clock.h> | ||
31 | |||
32 | #include "prm.h" | ||
33 | #include "prm-regbits-24xx.h" | ||
34 | #include "cm.h" | ||
35 | |||
36 | #include <mach/powerdomain.h> | ||
37 | #include <mach/clockdomain.h> | ||
38 | |||
39 | /* clkdm_list contains all registered struct clockdomains */ | ||
40 | static LIST_HEAD(clkdm_list); | ||
41 | |||
42 | /* clkdm_mutex protects clkdm_list add and del ops */ | ||
43 | static DEFINE_MUTEX(clkdm_mutex); | ||
44 | |||
45 | /* array of powerdomain deps to be added/removed when clkdm in hwsup mode */ | ||
46 | static struct clkdm_pwrdm_autodep *autodeps; | ||
47 | |||
48 | |||
49 | /* Private functions */ | ||
50 | |||
51 | /* | ||
52 | * _autodep_lookup - resolve autodep pwrdm names to pwrdm pointers; store | ||
53 | * @autodep: struct clkdm_pwrdm_autodep * to resolve | ||
54 | * | ||
55 | * Resolve autodep powerdomain names to powerdomain pointers via | ||
56 | * pwrdm_lookup() and store the pointers in the autodep structure. An | ||
57 | * "autodep" is a powerdomain sleep/wakeup dependency that is | ||
58 | * automatically added and removed whenever clocks in the associated | ||
59 | * clockdomain are enabled or disabled (respectively) when the | ||
60 | * clockdomain is in hardware-supervised mode. Meant to be called | ||
61 | * once at clockdomain layer initialization, since these should remain | ||
62 | * fixed for a particular architecture. No return value. | ||
63 | */ | ||
64 | static void _autodep_lookup(struct clkdm_pwrdm_autodep *autodep) | ||
65 | { | ||
66 | struct powerdomain *pwrdm; | ||
67 | |||
68 | if (!autodep) | ||
69 | return; | ||
70 | |||
71 | if (!omap_chip_is(autodep->omap_chip)) | ||
72 | return; | ||
73 | |||
74 | pwrdm = pwrdm_lookup(autodep->pwrdm_name); | ||
75 | if (!pwrdm) { | ||
76 | pr_debug("clockdomain: _autodep_lookup: powerdomain %s " | ||
77 | "does not exist\n", autodep->pwrdm_name); | ||
78 | WARN_ON(1); | ||
79 | return; | ||
80 | } | ||
81 | autodep->pwrdm = pwrdm; | ||
82 | |||
83 | return; | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | * _clkdm_add_autodeps - add auto sleepdeps/wkdeps to clkdm upon clock enable | ||
88 | * @clkdm: struct clockdomain * | ||
89 | * | ||
90 | * Add the "autodep" sleep & wakeup dependencies to clockdomain 'clkdm' | ||
91 | * in hardware-supervised mode. Meant to be called from clock framework | ||
92 | * when a clock inside clockdomain 'clkdm' is enabled. No return value. | ||
93 | */ | ||
94 | static void _clkdm_add_autodeps(struct clockdomain *clkdm) | ||
95 | { | ||
96 | struct clkdm_pwrdm_autodep *autodep; | ||
97 | |||
98 | for (autodep = autodeps; autodep->pwrdm_name; autodep++) { | ||
99 | if (!autodep->pwrdm) | ||
100 | continue; | ||
101 | |||
102 | pr_debug("clockdomain: adding %s sleepdep/wkdep for " | ||
103 | "pwrdm %s\n", autodep->pwrdm_name, | ||
104 | clkdm->pwrdm->name); | ||
105 | |||
106 | pwrdm_add_sleepdep(clkdm->pwrdm, autodep->pwrdm); | ||
107 | pwrdm_add_wkdep(clkdm->pwrdm, autodep->pwrdm); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * _clkdm_add_autodeps - remove auto sleepdeps/wkdeps from clkdm | ||
113 | * @clkdm: struct clockdomain * | ||
114 | * | ||
115 | * Remove the "autodep" sleep & wakeup dependencies from clockdomain 'clkdm' | ||
116 | * in hardware-supervised mode. Meant to be called from clock framework | ||
117 | * when a clock inside clockdomain 'clkdm' is disabled. No return value. | ||
118 | */ | ||
119 | static void _clkdm_del_autodeps(struct clockdomain *clkdm) | ||
120 | { | ||
121 | struct clkdm_pwrdm_autodep *autodep; | ||
122 | |||
123 | for (autodep = autodeps; autodep->pwrdm_name; autodep++) { | ||
124 | if (!autodep->pwrdm) | ||
125 | continue; | ||
126 | |||
127 | pr_debug("clockdomain: removing %s sleepdep/wkdep for " | ||
128 | "pwrdm %s\n", autodep->pwrdm_name, | ||
129 | clkdm->pwrdm->name); | ||
130 | |||
131 | pwrdm_del_sleepdep(clkdm->pwrdm, autodep->pwrdm); | ||
132 | pwrdm_del_wkdep(clkdm->pwrdm, autodep->pwrdm); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | |||
137 | static struct clockdomain *_clkdm_lookup(const char *name) | ||
138 | { | ||
139 | struct clockdomain *clkdm, *temp_clkdm; | ||
140 | |||
141 | if (!name) | ||
142 | return NULL; | ||
143 | |||
144 | clkdm = NULL; | ||
145 | |||
146 | list_for_each_entry(temp_clkdm, &clkdm_list, node) { | ||
147 | if (!strcmp(name, temp_clkdm->name)) { | ||
148 | clkdm = temp_clkdm; | ||
149 | break; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | return clkdm; | ||
154 | } | ||
155 | |||
156 | |||
157 | /* Public functions */ | ||
158 | |||
159 | /** | ||
160 | * clkdm_init - set up the clockdomain layer | ||
161 | * @clkdms: optional pointer to an array of clockdomains to register | ||
162 | * @init_autodeps: optional pointer to an array of autodeps to register | ||
163 | * | ||
164 | * Set up internal state. If a pointer to an array of clockdomains | ||
165 | * was supplied, loop through the list of clockdomains, register all | ||
166 | * that are available on the current platform. Similarly, if a | ||
167 | * pointer to an array of clockdomain-powerdomain autodependencies was | ||
168 | * provided, register those. No return value. | ||
169 | */ | ||
170 | void clkdm_init(struct clockdomain **clkdms, | ||
171 | struct clkdm_pwrdm_autodep *init_autodeps) | ||
172 | { | ||
173 | struct clockdomain **c = NULL; | ||
174 | struct clkdm_pwrdm_autodep *autodep = NULL; | ||
175 | |||
176 | if (clkdms) | ||
177 | for (c = clkdms; *c; c++) | ||
178 | clkdm_register(*c); | ||
179 | |||
180 | autodeps = init_autodeps; | ||
181 | if (autodeps) | ||
182 | for (autodep = autodeps; autodep->pwrdm_name; autodep++) | ||
183 | _autodep_lookup(autodep); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * clkdm_register - register a clockdomain | ||
188 | * @clkdm: struct clockdomain * to register | ||
189 | * | ||
190 | * Adds a clockdomain to the internal clockdomain list. | ||
191 | * Returns -EINVAL if given a null pointer, -EEXIST if a clockdomain is | ||
192 | * already registered by the provided name, or 0 upon success. | ||
193 | */ | ||
194 | int clkdm_register(struct clockdomain *clkdm) | ||
195 | { | ||
196 | int ret = -EINVAL; | ||
197 | struct powerdomain *pwrdm; | ||
198 | |||
199 | if (!clkdm || !clkdm->name) | ||
200 | return -EINVAL; | ||
201 | |||
202 | if (!omap_chip_is(clkdm->omap_chip)) | ||
203 | return -EINVAL; | ||
204 | |||
205 | pwrdm = pwrdm_lookup(clkdm->pwrdm_name); | ||
206 | if (!pwrdm) { | ||
207 | pr_debug("clockdomain: clkdm_register %s: powerdomain %s " | ||
208 | "does not exist\n", clkdm->name, clkdm->pwrdm_name); | ||
209 | return -EINVAL; | ||
210 | } | ||
211 | clkdm->pwrdm = pwrdm; | ||
212 | |||
213 | mutex_lock(&clkdm_mutex); | ||
214 | /* Verify that the clockdomain is not already registered */ | ||
215 | if (_clkdm_lookup(clkdm->name)) { | ||
216 | ret = -EEXIST; | ||
217 | goto cr_unlock; | ||
218 | }; | ||
219 | |||
220 | list_add(&clkdm->node, &clkdm_list); | ||
221 | |||
222 | pr_debug("clockdomain: registered %s\n", clkdm->name); | ||
223 | ret = 0; | ||
224 | |||
225 | cr_unlock: | ||
226 | mutex_unlock(&clkdm_mutex); | ||
227 | |||
228 | return ret; | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * clkdm_unregister - unregister a clockdomain | ||
233 | * @clkdm: struct clockdomain * to unregister | ||
234 | * | ||
235 | * Removes a clockdomain from the internal clockdomain list. Returns | ||
236 | * -EINVAL if clkdm argument is NULL. | ||
237 | */ | ||
238 | int clkdm_unregister(struct clockdomain *clkdm) | ||
239 | { | ||
240 | if (!clkdm) | ||
241 | return -EINVAL; | ||
242 | |||
243 | mutex_lock(&clkdm_mutex); | ||
244 | list_del(&clkdm->node); | ||
245 | mutex_unlock(&clkdm_mutex); | ||
246 | |||
247 | pr_debug("clockdomain: unregistered %s\n", clkdm->name); | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * clkdm_lookup - look up a clockdomain by name, return a pointer | ||
254 | * @name: name of clockdomain | ||
255 | * | ||
256 | * Find a registered clockdomain by its name. Returns a pointer to the | ||
257 | * struct clockdomain if found, or NULL otherwise. | ||
258 | */ | ||
259 | struct clockdomain *clkdm_lookup(const char *name) | ||
260 | { | ||
261 | struct clockdomain *clkdm, *temp_clkdm; | ||
262 | |||
263 | if (!name) | ||
264 | return NULL; | ||
265 | |||
266 | clkdm = NULL; | ||
267 | |||
268 | mutex_lock(&clkdm_mutex); | ||
269 | list_for_each_entry(temp_clkdm, &clkdm_list, node) { | ||
270 | if (!strcmp(name, temp_clkdm->name)) { | ||
271 | clkdm = temp_clkdm; | ||
272 | break; | ||
273 | } | ||
274 | } | ||
275 | mutex_unlock(&clkdm_mutex); | ||
276 | |||
277 | return clkdm; | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * clkdm_for_each - call function on each registered clockdomain | ||
282 | * @fn: callback function * | ||
283 | * | ||
284 | * Call the supplied function for each registered clockdomain. | ||
285 | * The callback function can return anything but 0 to bail | ||
286 | * out early from the iterator. The callback function is called with | ||
287 | * the clkdm_mutex held, so no clockdomain structure manipulation | ||
288 | * functions should be called from the callback, although hardware | ||
289 | * clockdomain control functions are fine. Returns the last return | ||
290 | * value of the callback function, which should be 0 for success or | ||
291 | * anything else to indicate failure; or -EINVAL if the function pointer | ||
292 | * is null. | ||
293 | */ | ||
294 | int clkdm_for_each(int (*fn)(struct clockdomain *clkdm)) | ||
295 | { | ||
296 | struct clockdomain *clkdm; | ||
297 | int ret = 0; | ||
298 | |||
299 | if (!fn) | ||
300 | return -EINVAL; | ||
301 | |||
302 | mutex_lock(&clkdm_mutex); | ||
303 | list_for_each_entry(clkdm, &clkdm_list, node) { | ||
304 | ret = (*fn)(clkdm); | ||
305 | if (ret) | ||
306 | break; | ||
307 | } | ||
308 | mutex_unlock(&clkdm_mutex); | ||
309 | |||
310 | return ret; | ||
311 | } | ||
312 | |||
313 | |||
314 | /* Hardware clockdomain control */ | ||
315 | |||
316 | /** | ||
317 | * omap2_clkdm_clktrctrl_read - read the clkdm's current state transition mode | ||
318 | * @clk: struct clk * of a clockdomain | ||
319 | * | ||
320 | * Return the clockdomain's current state transition mode from the | ||
321 | * corresponding domain CM_CLKSTCTRL register. Returns -EINVAL if clk | ||
322 | * is NULL or the current mode upon success. | ||
323 | */ | ||
324 | static int omap2_clkdm_clktrctrl_read(struct clockdomain *clkdm) | ||
325 | { | ||
326 | u32 v; | ||
327 | |||
328 | if (!clkdm) | ||
329 | return -EINVAL; | ||
330 | |||
331 | v = cm_read_mod_reg(clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
332 | v &= clkdm->clktrctrl_mask; | ||
333 | v >>= __ffs(clkdm->clktrctrl_mask); | ||
334 | |||
335 | return v; | ||
336 | } | ||
337 | |||
338 | /** | ||
339 | * omap2_clkdm_sleep - force clockdomain sleep transition | ||
340 | * @clkdm: struct clockdomain * | ||
341 | * | ||
342 | * Instruct the CM to force a sleep transition on the specified | ||
343 | * clockdomain 'clkdm'. Returns -EINVAL if clk is NULL or if | ||
344 | * clockdomain does not support software-initiated sleep; 0 upon | ||
345 | * success. | ||
346 | */ | ||
347 | int omap2_clkdm_sleep(struct clockdomain *clkdm) | ||
348 | { | ||
349 | if (!clkdm) | ||
350 | return -EINVAL; | ||
351 | |||
352 | if (!(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) { | ||
353 | pr_debug("clockdomain: %s does not support forcing " | ||
354 | "sleep via software\n", clkdm->name); | ||
355 | return -EINVAL; | ||
356 | } | ||
357 | |||
358 | pr_debug("clockdomain: forcing sleep on %s\n", clkdm->name); | ||
359 | |||
360 | if (cpu_is_omap24xx()) { | ||
361 | |||
362 | cm_set_mod_reg_bits(OMAP24XX_FORCESTATE, | ||
363 | clkdm->pwrdm->prcm_offs, PM_PWSTCTRL); | ||
364 | |||
365 | } else if (cpu_is_omap34xx()) { | ||
366 | |||
367 | u32 v = (OMAP34XX_CLKSTCTRL_FORCE_SLEEP << | ||
368 | __ffs(clkdm->clktrctrl_mask)); | ||
369 | |||
370 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, | ||
371 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
372 | |||
373 | } else { | ||
374 | BUG(); | ||
375 | }; | ||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | /** | ||
381 | * omap2_clkdm_wakeup - force clockdomain wakeup transition | ||
382 | * @clkdm: struct clockdomain * | ||
383 | * | ||
384 | * Instruct the CM to force a wakeup transition on the specified | ||
385 | * clockdomain 'clkdm'. Returns -EINVAL if clkdm is NULL or if the | ||
386 | * clockdomain does not support software-controlled wakeup; 0 upon | ||
387 | * success. | ||
388 | */ | ||
389 | int omap2_clkdm_wakeup(struct clockdomain *clkdm) | ||
390 | { | ||
391 | if (!clkdm) | ||
392 | return -EINVAL; | ||
393 | |||
394 | if (!(clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)) { | ||
395 | pr_debug("clockdomain: %s does not support forcing " | ||
396 | "wakeup via software\n", clkdm->name); | ||
397 | return -EINVAL; | ||
398 | } | ||
399 | |||
400 | pr_debug("clockdomain: forcing wakeup on %s\n", clkdm->name); | ||
401 | |||
402 | if (cpu_is_omap24xx()) { | ||
403 | |||
404 | cm_clear_mod_reg_bits(OMAP24XX_FORCESTATE, | ||
405 | clkdm->pwrdm->prcm_offs, PM_PWSTCTRL); | ||
406 | |||
407 | } else if (cpu_is_omap34xx()) { | ||
408 | |||
409 | u32 v = (OMAP34XX_CLKSTCTRL_FORCE_WAKEUP << | ||
410 | __ffs(clkdm->clktrctrl_mask)); | ||
411 | |||
412 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, | ||
413 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
414 | |||
415 | } else { | ||
416 | BUG(); | ||
417 | }; | ||
418 | |||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | /** | ||
423 | * omap2_clkdm_allow_idle - enable hwsup idle transitions for clkdm | ||
424 | * @clkdm: struct clockdomain * | ||
425 | * | ||
426 | * Allow the hardware to automatically switch the clockdomain into | ||
427 | * active or idle states, as needed by downstream clocks. If the | ||
428 | * clockdomain has any downstream clocks enabled in the clock | ||
429 | * framework, wkdep/sleepdep autodependencies are added; this is so | ||
430 | * device drivers can read and write to the device. No return value. | ||
431 | */ | ||
432 | void omap2_clkdm_allow_idle(struct clockdomain *clkdm) | ||
433 | { | ||
434 | u32 v; | ||
435 | |||
436 | if (!clkdm) | ||
437 | return; | ||
438 | |||
439 | if (!(clkdm->flags & CLKDM_CAN_ENABLE_AUTO)) { | ||
440 | pr_debug("clock: automatic idle transitions cannot be enabled " | ||
441 | "on clockdomain %s\n", clkdm->name); | ||
442 | return; | ||
443 | } | ||
444 | |||
445 | pr_debug("clockdomain: enabling automatic idle transitions for %s\n", | ||
446 | clkdm->name); | ||
447 | |||
448 | if (atomic_read(&clkdm->usecount) > 0) | ||
449 | _clkdm_add_autodeps(clkdm); | ||
450 | |||
451 | if (cpu_is_omap24xx()) | ||
452 | v = OMAP24XX_CLKSTCTRL_ENABLE_AUTO; | ||
453 | else if (cpu_is_omap34xx()) | ||
454 | v = OMAP34XX_CLKSTCTRL_ENABLE_AUTO; | ||
455 | else | ||
456 | BUG(); | ||
457 | |||
458 | |||
459 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, | ||
460 | v << __ffs(clkdm->clktrctrl_mask), | ||
461 | clkdm->pwrdm->prcm_offs, | ||
462 | CM_CLKSTCTRL); | ||
463 | } | ||
464 | |||
465 | /** | ||
466 | * omap2_clkdm_deny_idle - disable hwsup idle transitions for clkdm | ||
467 | * @clkdm: struct clockdomain * | ||
468 | * | ||
469 | * Prevent the hardware from automatically switching the clockdomain | ||
470 | * into inactive or idle states. If the clockdomain has downstream | ||
471 | * clocks enabled in the clock framework, wkdep/sleepdep | ||
472 | * autodependencies are removed. No return value. | ||
473 | */ | ||
474 | void omap2_clkdm_deny_idle(struct clockdomain *clkdm) | ||
475 | { | ||
476 | u32 v; | ||
477 | |||
478 | if (!clkdm) | ||
479 | return; | ||
480 | |||
481 | if (!(clkdm->flags & CLKDM_CAN_DISABLE_AUTO)) { | ||
482 | pr_debug("clockdomain: automatic idle transitions cannot be " | ||
483 | "disabled on %s\n", clkdm->name); | ||
484 | return; | ||
485 | } | ||
486 | |||
487 | pr_debug("clockdomain: disabling automatic idle transitions for %s\n", | ||
488 | clkdm->name); | ||
489 | |||
490 | if (cpu_is_omap24xx()) | ||
491 | v = OMAP24XX_CLKSTCTRL_DISABLE_AUTO; | ||
492 | else if (cpu_is_omap34xx()) | ||
493 | v = OMAP34XX_CLKSTCTRL_DISABLE_AUTO; | ||
494 | else | ||
495 | BUG(); | ||
496 | |||
497 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, | ||
498 | v << __ffs(clkdm->clktrctrl_mask), | ||
499 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
500 | |||
501 | if (atomic_read(&clkdm->usecount) > 0) | ||
502 | _clkdm_del_autodeps(clkdm); | ||
503 | } | ||
504 | |||
505 | |||
506 | /* Clockdomain-to-clock framework interface code */ | ||
507 | |||
508 | /** | ||
509 | * omap2_clkdm_clk_enable - add an enabled downstream clock to this clkdm | ||
510 | * @clkdm: struct clockdomain * | ||
511 | * @clk: struct clk * of the enabled downstream clock | ||
512 | * | ||
513 | * Increment the usecount of this clockdomain 'clkdm' and ensure that | ||
514 | * it is awake. Intended to be called by clk_enable() code. If the | ||
515 | * clockdomain is in software-supervised idle mode, force the | ||
516 | * clockdomain to wake. If the clockdomain is in hardware-supervised | ||
517 | * idle mode, add clkdm-pwrdm autodependencies, to ensure that devices | ||
518 | * in the clockdomain can be read from/written to by on-chip processors. | ||
519 | * Returns -EINVAL if passed null pointers; returns 0 upon success or | ||
520 | * if the clockdomain is in hwsup idle mode. | ||
521 | */ | ||
522 | int omap2_clkdm_clk_enable(struct clockdomain *clkdm, struct clk *clk) | ||
523 | { | ||
524 | int v; | ||
525 | |||
526 | /* | ||
527 | * XXX Rewrite this code to maintain a list of enabled | ||
528 | * downstream clocks for debugging purposes? | ||
529 | */ | ||
530 | |||
531 | if (!clkdm || !clk) | ||
532 | return -EINVAL; | ||
533 | |||
534 | if (atomic_inc_return(&clkdm->usecount) > 1) | ||
535 | return 0; | ||
536 | |||
537 | /* Clockdomain now has one enabled downstream clock */ | ||
538 | |||
539 | pr_debug("clockdomain: clkdm %s: clk %s now enabled\n", clkdm->name, | ||
540 | clk->name); | ||
541 | |||
542 | v = omap2_clkdm_clktrctrl_read(clkdm); | ||
543 | |||
544 | if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || | ||
545 | (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) | ||
546 | _clkdm_add_autodeps(clkdm); | ||
547 | else | ||
548 | omap2_clkdm_wakeup(clkdm); | ||
549 | |||
550 | return 0; | ||
551 | } | ||
552 | |||
553 | /** | ||
554 | * omap2_clkdm_clk_disable - remove an enabled downstream clock from this clkdm | ||
555 | * @clkdm: struct clockdomain * | ||
556 | * @clk: struct clk * of the disabled downstream clock | ||
557 | * | ||
558 | * Decrement the usecount of this clockdomain 'clkdm'. Intended to be | ||
559 | * called by clk_disable() code. If the usecount goes to 0, put the | ||
560 | * clockdomain to sleep (software-supervised mode) or remove the | ||
561 | * clkdm-pwrdm autodependencies (hardware-supervised mode). Returns | ||
562 | * -EINVAL if passed null pointers; -ERANGE if the clkdm usecount | ||
563 | * underflows and debugging is enabled; or returns 0 upon success or | ||
564 | * if the clockdomain is in hwsup idle mode. | ||
565 | */ | ||
566 | int omap2_clkdm_clk_disable(struct clockdomain *clkdm, struct clk *clk) | ||
567 | { | ||
568 | int v; | ||
569 | |||
570 | /* | ||
571 | * XXX Rewrite this code to maintain a list of enabled | ||
572 | * downstream clocks for debugging purposes? | ||
573 | */ | ||
574 | |||
575 | if (!clkdm || !clk) | ||
576 | return -EINVAL; | ||
577 | |||
578 | #ifdef DEBUG | ||
579 | if (atomic_read(&clkdm->usecount) == 0) { | ||
580 | WARN_ON(1); /* underflow */ | ||
581 | return -ERANGE; | ||
582 | } | ||
583 | #endif | ||
584 | |||
585 | if (atomic_dec_return(&clkdm->usecount) > 0) | ||
586 | return 0; | ||
587 | |||
588 | /* All downstream clocks of this clockdomain are now disabled */ | ||
589 | |||
590 | pr_debug("clockdomain: clkdm %s: clk %s now disabled\n", clkdm->name, | ||
591 | clk->name); | ||
592 | |||
593 | v = omap2_clkdm_clktrctrl_read(clkdm); | ||
594 | |||
595 | if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || | ||
596 | (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) | ||
597 | _clkdm_del_autodeps(clkdm); | ||
598 | else | ||
599 | omap2_clkdm_sleep(clkdm); | ||
600 | |||
601 | return 0; | ||
602 | } | ||
603 | |||
diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index e815fa35f7f4..ef62bf21e179 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig | |||
@@ -41,6 +41,18 @@ config OMAP_DEBUG_POWERDOMAIN | |||
41 | for every powerdomain register write. However, the | 41 | for every powerdomain register write. However, the |
42 | extra detail costs some memory. | 42 | extra detail costs some memory. |
43 | 43 | ||
44 | config OMAP_DEBUG_CLOCKDOMAIN | ||
45 | bool "Emit debug messages from clockdomain layer" | ||
46 | depends on ARCH_OMAP2 || ARCH_OMAP3 | ||
47 | default n | ||
48 | help | ||
49 | Say Y here if you want to compile in clockdomain layer | ||
50 | debugging messages for OMAP2/3. These messages can | ||
51 | provide more detail as to why some clockdomain calls | ||
52 | may be failing, and will also emit a descriptive message | ||
53 | for every clockdomain register write. However, the | ||
54 | extra detail costs some memory. | ||
55 | |||
44 | config OMAP_RESET_CLOCKS | 56 | config OMAP_RESET_CLOCKS |
45 | bool "Reset unused clocks during boot" | 57 | bool "Reset unused clocks during boot" |
46 | depends on ARCH_OMAP | 58 | depends on ARCH_OMAP |
diff --git a/arch/arm/plat-omap/include/mach/clockdomain.h b/arch/arm/plat-omap/include/mach/clockdomain.h new file mode 100644 index 000000000000..1f51f0173784 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/clockdomain.h | |||
@@ -0,0 +1,106 @@ | |||
1 | /* | ||
2 | * linux/include/asm-arm/arch-omap/clockdomain.h | ||
3 | * | ||
4 | * OMAP2/3 clockdomain framework functions | ||
5 | * | ||
6 | * Copyright (C) 2008 Texas Instruments, Inc. | ||
7 | * Copyright (C) 2008 Nokia Corporation | ||
8 | * | ||
9 | * Written by 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 | |||
16 | #ifndef __ASM_ARM_ARCH_OMAP_CLOCKDOMAIN_H | ||
17 | #define __ASM_ARM_ARCH_OMAP_CLOCKDOMAIN_H | ||
18 | |||
19 | #include <mach/powerdomain.h> | ||
20 | #include <mach/clock.h> | ||
21 | #include <mach/cpu.h> | ||
22 | |||
23 | /* Clockdomain capability flags */ | ||
24 | #define CLKDM_CAN_FORCE_SLEEP (1 << 0) | ||
25 | #define CLKDM_CAN_FORCE_WAKEUP (1 << 1) | ||
26 | #define CLKDM_CAN_ENABLE_AUTO (1 << 2) | ||
27 | #define CLKDM_CAN_DISABLE_AUTO (1 << 3) | ||
28 | |||
29 | #define CLKDM_CAN_HWSUP (CLKDM_CAN_ENABLE_AUTO | CLKDM_CAN_DISABLE_AUTO) | ||
30 | #define CLKDM_CAN_SWSUP (CLKDM_CAN_FORCE_SLEEP | CLKDM_CAN_FORCE_WAKEUP) | ||
31 | #define CLKDM_CAN_HWSUP_SWSUP (CLKDM_CAN_SWSUP | CLKDM_CAN_HWSUP) | ||
32 | |||
33 | /* OMAP24XX CM_CLKSTCTRL_*.AUTOSTATE_* register bit values */ | ||
34 | #define OMAP24XX_CLKSTCTRL_DISABLE_AUTO 0x0 | ||
35 | #define OMAP24XX_CLKSTCTRL_ENABLE_AUTO 0x1 | ||
36 | |||
37 | /* OMAP3XXX CM_CLKSTCTRL_*.CLKTRCTRL_* register bit values */ | ||
38 | #define OMAP34XX_CLKSTCTRL_DISABLE_AUTO 0x0 | ||
39 | #define OMAP34XX_CLKSTCTRL_FORCE_SLEEP 0x1 | ||
40 | #define OMAP34XX_CLKSTCTRL_FORCE_WAKEUP 0x2 | ||
41 | #define OMAP34XX_CLKSTCTRL_ENABLE_AUTO 0x3 | ||
42 | |||
43 | /* | ||
44 | * struct clkdm_pwrdm_autodep - a powerdomain that should have wkdeps | ||
45 | * and sleepdeps added when a powerdomain should stay active in hwsup mode; | ||
46 | * and conversely, removed when the powerdomain should be allowed to go | ||
47 | * inactive in hwsup mode. | ||
48 | */ | ||
49 | struct clkdm_pwrdm_autodep { | ||
50 | |||
51 | /* Name of the powerdomain to add a wkdep/sleepdep on */ | ||
52 | const char *pwrdm_name; | ||
53 | |||
54 | /* Powerdomain pointer (looked up at clkdm_init() time) */ | ||
55 | struct powerdomain *pwrdm; | ||
56 | |||
57 | /* OMAP chip types that this clockdomain dep is valid on */ | ||
58 | const struct omap_chip_id omap_chip; | ||
59 | |||
60 | }; | ||
61 | |||
62 | struct clockdomain { | ||
63 | |||
64 | /* Clockdomain name */ | ||
65 | const char *name; | ||
66 | |||
67 | /* Powerdomain enclosing this clockdomain */ | ||
68 | const char *pwrdm_name; | ||
69 | |||
70 | /* CLKTRCTRL/AUTOSTATE field mask in CM_CLKSTCTRL reg */ | ||
71 | const u16 clktrctrl_mask; | ||
72 | |||
73 | /* Clockdomain capability flags */ | ||
74 | const u8 flags; | ||
75 | |||
76 | /* OMAP chip types that this clockdomain is valid on */ | ||
77 | const struct omap_chip_id omap_chip; | ||
78 | |||
79 | /* Usecount tracking */ | ||
80 | atomic_t usecount; | ||
81 | |||
82 | /* Powerdomain pointer assigned at clkdm_register() */ | ||
83 | struct powerdomain *pwrdm; | ||
84 | |||
85 | struct list_head node; | ||
86 | |||
87 | }; | ||
88 | |||
89 | void clkdm_init(struct clockdomain **clkdms, struct clkdm_pwrdm_autodep *autodeps); | ||
90 | int clkdm_register(struct clockdomain *clkdm); | ||
91 | int clkdm_unregister(struct clockdomain *clkdm); | ||
92 | struct clockdomain *clkdm_lookup(const char *name); | ||
93 | |||
94 | int clkdm_for_each(int (*fn)(struct clockdomain *clkdm)); | ||
95 | struct powerdomain *clkdm_get_pwrdm(struct clockdomain *clkdm); | ||
96 | |||
97 | void omap2_clkdm_allow_idle(struct clockdomain *clkdm); | ||
98 | void omap2_clkdm_deny_idle(struct clockdomain *clkdm); | ||
99 | |||
100 | int omap2_clkdm_wakeup(struct clockdomain *clkdm); | ||
101 | int omap2_clkdm_sleep(struct clockdomain *clkdm); | ||
102 | |||
103 | int omap2_clkdm_clk_enable(struct clockdomain *clkdm, struct clk *clk); | ||
104 | int omap2_clkdm_clk_disable(struct clockdomain *clkdm, struct clk *clk); | ||
105 | |||
106 | #endif | ||