diff options
Diffstat (limited to 'arch/arm/mach-omap2/clockdomain.c')
-rw-r--r-- | arch/arm/mach-omap2/clockdomain.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c new file mode 100644 index 000000000000..4c3ce9cfd948 --- /dev/null +++ b/arch/arm/mach-omap2/clockdomain.c | |||
@@ -0,0 +1,623 @@ | |||
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 | pwrdm_add_clkdm(pwrdm, clkdm); | ||
223 | |||
224 | pr_debug("clockdomain: registered %s\n", clkdm->name); | ||
225 | ret = 0; | ||
226 | |||
227 | cr_unlock: | ||
228 | mutex_unlock(&clkdm_mutex); | ||
229 | |||
230 | return ret; | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * clkdm_unregister - unregister a clockdomain | ||
235 | * @clkdm: struct clockdomain * to unregister | ||
236 | * | ||
237 | * Removes a clockdomain from the internal clockdomain list. Returns | ||
238 | * -EINVAL if clkdm argument is NULL. | ||
239 | */ | ||
240 | int clkdm_unregister(struct clockdomain *clkdm) | ||
241 | { | ||
242 | if (!clkdm) | ||
243 | return -EINVAL; | ||
244 | |||
245 | pwrdm_del_clkdm(clkdm->pwrdm, clkdm); | ||
246 | |||
247 | mutex_lock(&clkdm_mutex); | ||
248 | list_del(&clkdm->node); | ||
249 | mutex_unlock(&clkdm_mutex); | ||
250 | |||
251 | pr_debug("clockdomain: unregistered %s\n", clkdm->name); | ||
252 | |||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | /** | ||
257 | * clkdm_lookup - look up a clockdomain by name, return a pointer | ||
258 | * @name: name of clockdomain | ||
259 | * | ||
260 | * Find a registered clockdomain by its name. Returns a pointer to the | ||
261 | * struct clockdomain if found, or NULL otherwise. | ||
262 | */ | ||
263 | struct clockdomain *clkdm_lookup(const char *name) | ||
264 | { | ||
265 | struct clockdomain *clkdm, *temp_clkdm; | ||
266 | |||
267 | if (!name) | ||
268 | return NULL; | ||
269 | |||
270 | clkdm = NULL; | ||
271 | |||
272 | mutex_lock(&clkdm_mutex); | ||
273 | list_for_each_entry(temp_clkdm, &clkdm_list, node) { | ||
274 | if (!strcmp(name, temp_clkdm->name)) { | ||
275 | clkdm = temp_clkdm; | ||
276 | break; | ||
277 | } | ||
278 | } | ||
279 | mutex_unlock(&clkdm_mutex); | ||
280 | |||
281 | return clkdm; | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * clkdm_for_each - call function on each registered clockdomain | ||
286 | * @fn: callback function * | ||
287 | * | ||
288 | * Call the supplied function for each registered clockdomain. | ||
289 | * The callback function can return anything but 0 to bail | ||
290 | * out early from the iterator. The callback function is called with | ||
291 | * the clkdm_mutex held, so no clockdomain structure manipulation | ||
292 | * functions should be called from the callback, although hardware | ||
293 | * clockdomain control functions are fine. Returns the last return | ||
294 | * value of the callback function, which should be 0 for success or | ||
295 | * anything else to indicate failure; or -EINVAL if the function pointer | ||
296 | * is null. | ||
297 | */ | ||
298 | int clkdm_for_each(int (*fn)(struct clockdomain *clkdm)) | ||
299 | { | ||
300 | struct clockdomain *clkdm; | ||
301 | int ret = 0; | ||
302 | |||
303 | if (!fn) | ||
304 | return -EINVAL; | ||
305 | |||
306 | mutex_lock(&clkdm_mutex); | ||
307 | list_for_each_entry(clkdm, &clkdm_list, node) { | ||
308 | ret = (*fn)(clkdm); | ||
309 | if (ret) | ||
310 | break; | ||
311 | } | ||
312 | mutex_unlock(&clkdm_mutex); | ||
313 | |||
314 | return ret; | ||
315 | } | ||
316 | |||
317 | |||
318 | /** | ||
319 | * clkdm_get_pwrdm - return a ptr to the pwrdm that this clkdm resides in | ||
320 | * @clkdm: struct clockdomain * | ||
321 | * | ||
322 | * Return a pointer to the struct powerdomain that the specified clockdomain | ||
323 | * 'clkdm' exists in, or returns NULL if clkdm argument is NULL. | ||
324 | */ | ||
325 | struct powerdomain *clkdm_get_pwrdm(struct clockdomain *clkdm) | ||
326 | { | ||
327 | if (!clkdm) | ||
328 | return NULL; | ||
329 | |||
330 | return clkdm->pwrdm; | ||
331 | } | ||
332 | |||
333 | |||
334 | /* Hardware clockdomain control */ | ||
335 | |||
336 | /** | ||
337 | * omap2_clkdm_clktrctrl_read - read the clkdm's current state transition mode | ||
338 | * @clk: struct clk * of a clockdomain | ||
339 | * | ||
340 | * Return the clockdomain's current state transition mode from the | ||
341 | * corresponding domain CM_CLKSTCTRL register. Returns -EINVAL if clk | ||
342 | * is NULL or the current mode upon success. | ||
343 | */ | ||
344 | static int omap2_clkdm_clktrctrl_read(struct clockdomain *clkdm) | ||
345 | { | ||
346 | u32 v; | ||
347 | |||
348 | if (!clkdm) | ||
349 | return -EINVAL; | ||
350 | |||
351 | v = cm_read_mod_reg(clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
352 | v &= clkdm->clktrctrl_mask; | ||
353 | v >>= __ffs(clkdm->clktrctrl_mask); | ||
354 | |||
355 | return v; | ||
356 | } | ||
357 | |||
358 | /** | ||
359 | * omap2_clkdm_sleep - force clockdomain sleep transition | ||
360 | * @clkdm: struct clockdomain * | ||
361 | * | ||
362 | * Instruct the CM to force a sleep transition on the specified | ||
363 | * clockdomain 'clkdm'. Returns -EINVAL if clk is NULL or if | ||
364 | * clockdomain does not support software-initiated sleep; 0 upon | ||
365 | * success. | ||
366 | */ | ||
367 | int omap2_clkdm_sleep(struct clockdomain *clkdm) | ||
368 | { | ||
369 | if (!clkdm) | ||
370 | return -EINVAL; | ||
371 | |||
372 | if (!(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) { | ||
373 | pr_debug("clockdomain: %s does not support forcing " | ||
374 | "sleep via software\n", clkdm->name); | ||
375 | return -EINVAL; | ||
376 | } | ||
377 | |||
378 | pr_debug("clockdomain: forcing sleep on %s\n", clkdm->name); | ||
379 | |||
380 | if (cpu_is_omap24xx()) { | ||
381 | |||
382 | cm_set_mod_reg_bits(OMAP24XX_FORCESTATE, | ||
383 | clkdm->pwrdm->prcm_offs, PM_PWSTCTRL); | ||
384 | |||
385 | } else if (cpu_is_omap34xx()) { | ||
386 | |||
387 | u32 v = (OMAP34XX_CLKSTCTRL_FORCE_SLEEP << | ||
388 | __ffs(clkdm->clktrctrl_mask)); | ||
389 | |||
390 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, | ||
391 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
392 | |||
393 | } else { | ||
394 | BUG(); | ||
395 | }; | ||
396 | |||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | /** | ||
401 | * omap2_clkdm_wakeup - force clockdomain wakeup transition | ||
402 | * @clkdm: struct clockdomain * | ||
403 | * | ||
404 | * Instruct the CM to force a wakeup transition on the specified | ||
405 | * clockdomain 'clkdm'. Returns -EINVAL if clkdm is NULL or if the | ||
406 | * clockdomain does not support software-controlled wakeup; 0 upon | ||
407 | * success. | ||
408 | */ | ||
409 | int omap2_clkdm_wakeup(struct clockdomain *clkdm) | ||
410 | { | ||
411 | if (!clkdm) | ||
412 | return -EINVAL; | ||
413 | |||
414 | if (!(clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)) { | ||
415 | pr_debug("clockdomain: %s does not support forcing " | ||
416 | "wakeup via software\n", clkdm->name); | ||
417 | return -EINVAL; | ||
418 | } | ||
419 | |||
420 | pr_debug("clockdomain: forcing wakeup on %s\n", clkdm->name); | ||
421 | |||
422 | if (cpu_is_omap24xx()) { | ||
423 | |||
424 | cm_clear_mod_reg_bits(OMAP24XX_FORCESTATE, | ||
425 | clkdm->pwrdm->prcm_offs, PM_PWSTCTRL); | ||
426 | |||
427 | } else if (cpu_is_omap34xx()) { | ||
428 | |||
429 | u32 v = (OMAP34XX_CLKSTCTRL_FORCE_WAKEUP << | ||
430 | __ffs(clkdm->clktrctrl_mask)); | ||
431 | |||
432 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, v, | ||
433 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
434 | |||
435 | } else { | ||
436 | BUG(); | ||
437 | }; | ||
438 | |||
439 | return 0; | ||
440 | } | ||
441 | |||
442 | /** | ||
443 | * omap2_clkdm_allow_idle - enable hwsup idle transitions for clkdm | ||
444 | * @clkdm: struct clockdomain * | ||
445 | * | ||
446 | * Allow the hardware to automatically switch the clockdomain into | ||
447 | * active or idle states, as needed by downstream clocks. If the | ||
448 | * clockdomain has any downstream clocks enabled in the clock | ||
449 | * framework, wkdep/sleepdep autodependencies are added; this is so | ||
450 | * device drivers can read and write to the device. No return value. | ||
451 | */ | ||
452 | void omap2_clkdm_allow_idle(struct clockdomain *clkdm) | ||
453 | { | ||
454 | u32 v; | ||
455 | |||
456 | if (!clkdm) | ||
457 | return; | ||
458 | |||
459 | if (!(clkdm->flags & CLKDM_CAN_ENABLE_AUTO)) { | ||
460 | pr_debug("clock: automatic idle transitions cannot be enabled " | ||
461 | "on clockdomain %s\n", clkdm->name); | ||
462 | return; | ||
463 | } | ||
464 | |||
465 | pr_debug("clockdomain: enabling automatic idle transitions for %s\n", | ||
466 | clkdm->name); | ||
467 | |||
468 | if (atomic_read(&clkdm->usecount) > 0) | ||
469 | _clkdm_add_autodeps(clkdm); | ||
470 | |||
471 | if (cpu_is_omap24xx()) | ||
472 | v = OMAP24XX_CLKSTCTRL_ENABLE_AUTO; | ||
473 | else if (cpu_is_omap34xx()) | ||
474 | v = OMAP34XX_CLKSTCTRL_ENABLE_AUTO; | ||
475 | else | ||
476 | BUG(); | ||
477 | |||
478 | |||
479 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, | ||
480 | v << __ffs(clkdm->clktrctrl_mask), | ||
481 | clkdm->pwrdm->prcm_offs, | ||
482 | CM_CLKSTCTRL); | ||
483 | } | ||
484 | |||
485 | /** | ||
486 | * omap2_clkdm_deny_idle - disable hwsup idle transitions for clkdm | ||
487 | * @clkdm: struct clockdomain * | ||
488 | * | ||
489 | * Prevent the hardware from automatically switching the clockdomain | ||
490 | * into inactive or idle states. If the clockdomain has downstream | ||
491 | * clocks enabled in the clock framework, wkdep/sleepdep | ||
492 | * autodependencies are removed. No return value. | ||
493 | */ | ||
494 | void omap2_clkdm_deny_idle(struct clockdomain *clkdm) | ||
495 | { | ||
496 | u32 v; | ||
497 | |||
498 | if (!clkdm) | ||
499 | return; | ||
500 | |||
501 | if (!(clkdm->flags & CLKDM_CAN_DISABLE_AUTO)) { | ||
502 | pr_debug("clockdomain: automatic idle transitions cannot be " | ||
503 | "disabled on %s\n", clkdm->name); | ||
504 | return; | ||
505 | } | ||
506 | |||
507 | pr_debug("clockdomain: disabling automatic idle transitions for %s\n", | ||
508 | clkdm->name); | ||
509 | |||
510 | if (cpu_is_omap24xx()) | ||
511 | v = OMAP24XX_CLKSTCTRL_DISABLE_AUTO; | ||
512 | else if (cpu_is_omap34xx()) | ||
513 | v = OMAP34XX_CLKSTCTRL_DISABLE_AUTO; | ||
514 | else | ||
515 | BUG(); | ||
516 | |||
517 | cm_rmw_mod_reg_bits(clkdm->clktrctrl_mask, | ||
518 | v << __ffs(clkdm->clktrctrl_mask), | ||
519 | clkdm->pwrdm->prcm_offs, CM_CLKSTCTRL); | ||
520 | |||
521 | if (atomic_read(&clkdm->usecount) > 0) | ||
522 | _clkdm_del_autodeps(clkdm); | ||
523 | } | ||
524 | |||
525 | |||
526 | /* Clockdomain-to-clock framework interface code */ | ||
527 | |||
528 | /** | ||
529 | * omap2_clkdm_clk_enable - add an enabled downstream clock to this clkdm | ||
530 | * @clkdm: struct clockdomain * | ||
531 | * @clk: struct clk * of the enabled downstream clock | ||
532 | * | ||
533 | * Increment the usecount of this clockdomain 'clkdm' and ensure that | ||
534 | * it is awake. Intended to be called by clk_enable() code. If the | ||
535 | * clockdomain is in software-supervised idle mode, force the | ||
536 | * clockdomain to wake. If the clockdomain is in hardware-supervised | ||
537 | * idle mode, add clkdm-pwrdm autodependencies, to ensure that devices | ||
538 | * in the clockdomain can be read from/written to by on-chip processors. | ||
539 | * Returns -EINVAL if passed null pointers; returns 0 upon success or | ||
540 | * if the clockdomain is in hwsup idle mode. | ||
541 | */ | ||
542 | int omap2_clkdm_clk_enable(struct clockdomain *clkdm, struct clk *clk) | ||
543 | { | ||
544 | int v; | ||
545 | |||
546 | /* | ||
547 | * XXX Rewrite this code to maintain a list of enabled | ||
548 | * downstream clocks for debugging purposes? | ||
549 | */ | ||
550 | |||
551 | if (!clkdm || !clk) | ||
552 | return -EINVAL; | ||
553 | |||
554 | if (atomic_inc_return(&clkdm->usecount) > 1) | ||
555 | return 0; | ||
556 | |||
557 | /* Clockdomain now has one enabled downstream clock */ | ||
558 | |||
559 | pr_debug("clockdomain: clkdm %s: clk %s now enabled\n", clkdm->name, | ||
560 | clk->name); | ||
561 | |||
562 | v = omap2_clkdm_clktrctrl_read(clkdm); | ||
563 | |||
564 | if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || | ||
565 | (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) | ||
566 | _clkdm_add_autodeps(clkdm); | ||
567 | else | ||
568 | omap2_clkdm_wakeup(clkdm); | ||
569 | |||
570 | return 0; | ||
571 | } | ||
572 | |||
573 | /** | ||
574 | * omap2_clkdm_clk_disable - remove an enabled downstream clock from this clkdm | ||
575 | * @clkdm: struct clockdomain * | ||
576 | * @clk: struct clk * of the disabled downstream clock | ||
577 | * | ||
578 | * Decrement the usecount of this clockdomain 'clkdm'. Intended to be | ||
579 | * called by clk_disable() code. If the usecount goes to 0, put the | ||
580 | * clockdomain to sleep (software-supervised mode) or remove the | ||
581 | * clkdm-pwrdm autodependencies (hardware-supervised mode). Returns | ||
582 | * -EINVAL if passed null pointers; -ERANGE if the clkdm usecount | ||
583 | * underflows and debugging is enabled; or returns 0 upon success or | ||
584 | * if the clockdomain is in hwsup idle mode. | ||
585 | */ | ||
586 | int omap2_clkdm_clk_disable(struct clockdomain *clkdm, struct clk *clk) | ||
587 | { | ||
588 | int v; | ||
589 | |||
590 | /* | ||
591 | * XXX Rewrite this code to maintain a list of enabled | ||
592 | * downstream clocks for debugging purposes? | ||
593 | */ | ||
594 | |||
595 | if (!clkdm || !clk) | ||
596 | return -EINVAL; | ||
597 | |||
598 | #ifdef DEBUG | ||
599 | if (atomic_read(&clkdm->usecount) == 0) { | ||
600 | WARN_ON(1); /* underflow */ | ||
601 | return -ERANGE; | ||
602 | } | ||
603 | #endif | ||
604 | |||
605 | if (atomic_dec_return(&clkdm->usecount) > 0) | ||
606 | return 0; | ||
607 | |||
608 | /* All downstream clocks of this clockdomain are now disabled */ | ||
609 | |||
610 | pr_debug("clockdomain: clkdm %s: clk %s now disabled\n", clkdm->name, | ||
611 | clk->name); | ||
612 | |||
613 | v = omap2_clkdm_clktrctrl_read(clkdm); | ||
614 | |||
615 | if ((cpu_is_omap34xx() && v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) || | ||
616 | (cpu_is_omap24xx() && v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO)) | ||
617 | _clkdm_del_autodeps(clkdm); | ||
618 | else | ||
619 | omap2_clkdm_sleep(clkdm); | ||
620 | |||
621 | return 0; | ||
622 | } | ||
623 | |||