Commit | Line | Data |
---|---|---|
ff4ae5d9 PW |
1 | /* |
2 | * OMAP2xxx CM module functions | |
3 | * | |
4 | * Copyright (C) 2009 Nokia Corporation | |
4bd5259e | 5 | * Copyright (C) 2008-2010, 2012 Texas Instruments, Inc. |
ff4ae5d9 | 6 | * Paul Walmsley |
4bd5259e | 7 | * Rajendra Nayak <rnayak@ti.com> |
ff4ae5d9 PW |
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 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/types.h> | |
16 | #include <linux/delay.h> | |
17 | #include <linux/errno.h> | |
18 | #include <linux/err.h> | |
19 | #include <linux/io.h> | |
20 | ||
21 | #include "soc.h" | |
22 | #include "iomap.h" | |
23 | #include "common.h" | |
4bd5259e | 24 | #include "prm2xxx.h" |
ff4ae5d9 PW |
25 | #include "cm.h" |
26 | #include "cm2xxx.h" | |
27 | #include "cm-regbits-24xx.h" | |
4bd5259e | 28 | #include "clockdomain.h" |
ff4ae5d9 PW |
29 | |
30 | /* CM_AUTOIDLE_PLL.AUTO_* bit values for DPLLs */ | |
31 | #define DPLL_AUTOIDLE_DISABLE 0x0 | |
32 | #define OMAP2XXX_DPLL_AUTOIDLE_LOW_POWER_STOP 0x3 | |
33 | ||
34 | /* CM_AUTOIDLE_PLL.AUTO_* bit values for APLLs (OMAP2xxx only) */ | |
35 | #define OMAP2XXX_APLL_AUTOIDLE_DISABLE 0x0 | |
36 | #define OMAP2XXX_APLL_AUTOIDLE_LOW_POWER_STOP 0x3 | |
37 | ||
b6ffa050 PW |
38 | /* CM_IDLEST_PLL bit value offset for APLLs (OMAP2xxx only) */ |
39 | #define EN_APLL_LOCKED 3 | |
40 | ||
ff4ae5d9 PW |
41 | static const u8 omap2xxx_cm_idlest_offs[] = { |
42 | CM_IDLEST1, CM_IDLEST2, OMAP2430_CM_IDLEST3, OMAP24XX_CM_IDLEST4 | |
43 | }; | |
44 | ||
45 | /* | |
46 | * | |
47 | */ | |
48 | ||
49 | static void _write_clktrctrl(u8 c, s16 module, u32 mask) | |
50 | { | |
51 | u32 v; | |
52 | ||
53 | v = omap2_cm_read_mod_reg(module, OMAP2_CM_CLKSTCTRL); | |
54 | v &= ~mask; | |
55 | v |= c << __ffs(mask); | |
56 | omap2_cm_write_mod_reg(v, module, OMAP2_CM_CLKSTCTRL); | |
57 | } | |
58 | ||
59 | bool omap2xxx_cm_is_clkdm_in_hwsup(s16 module, u32 mask) | |
60 | { | |
61 | u32 v; | |
62 | ||
63 | v = omap2_cm_read_mod_reg(module, OMAP2_CM_CLKSTCTRL); | |
64 | v &= mask; | |
65 | v >>= __ffs(mask); | |
66 | ||
67 | return (v == OMAP24XX_CLKSTCTRL_ENABLE_AUTO) ? 1 : 0; | |
68 | } | |
69 | ||
70 | void omap2xxx_cm_clkdm_enable_hwsup(s16 module, u32 mask) | |
71 | { | |
72 | _write_clktrctrl(OMAP24XX_CLKSTCTRL_ENABLE_AUTO, module, mask); | |
73 | } | |
74 | ||
75 | void omap2xxx_cm_clkdm_disable_hwsup(s16 module, u32 mask) | |
76 | { | |
77 | _write_clktrctrl(OMAP24XX_CLKSTCTRL_DISABLE_AUTO, module, mask); | |
78 | } | |
79 | ||
80 | /* | |
81 | * DPLL autoidle control | |
82 | */ | |
83 | ||
84 | static void _omap2xxx_set_dpll_autoidle(u8 m) | |
85 | { | |
86 | u32 v; | |
87 | ||
88 | v = omap2_cm_read_mod_reg(PLL_MOD, CM_AUTOIDLE); | |
89 | v &= ~OMAP24XX_AUTO_DPLL_MASK; | |
90 | v |= m << OMAP24XX_AUTO_DPLL_SHIFT; | |
91 | omap2_cm_write_mod_reg(v, PLL_MOD, CM_AUTOIDLE); | |
92 | } | |
93 | ||
94 | void omap2xxx_cm_set_dpll_disable_autoidle(void) | |
95 | { | |
96 | _omap2xxx_set_dpll_autoidle(OMAP2XXX_DPLL_AUTOIDLE_LOW_POWER_STOP); | |
97 | } | |
98 | ||
99 | void omap2xxx_cm_set_dpll_auto_low_power_stop(void) | |
100 | { | |
101 | _omap2xxx_set_dpll_autoidle(DPLL_AUTOIDLE_DISABLE); | |
102 | } | |
103 | ||
104 | /* | |
b6ffa050 | 105 | * APLL control |
ff4ae5d9 PW |
106 | */ |
107 | ||
108 | static void _omap2xxx_set_apll_autoidle(u8 m, u32 mask) | |
109 | { | |
110 | u32 v; | |
111 | ||
112 | v = omap2_cm_read_mod_reg(PLL_MOD, CM_AUTOIDLE); | |
113 | v &= ~mask; | |
114 | v |= m << __ffs(mask); | |
115 | omap2_cm_write_mod_reg(v, PLL_MOD, CM_AUTOIDLE); | |
116 | } | |
117 | ||
118 | void omap2xxx_cm_set_apll54_disable_autoidle(void) | |
119 | { | |
120 | _omap2xxx_set_apll_autoidle(OMAP2XXX_APLL_AUTOIDLE_LOW_POWER_STOP, | |
121 | OMAP24XX_AUTO_54M_MASK); | |
122 | } | |
123 | ||
124 | void omap2xxx_cm_set_apll54_auto_low_power_stop(void) | |
125 | { | |
126 | _omap2xxx_set_apll_autoidle(OMAP2XXX_APLL_AUTOIDLE_DISABLE, | |
127 | OMAP24XX_AUTO_54M_MASK); | |
128 | } | |
129 | ||
130 | void omap2xxx_cm_set_apll96_disable_autoidle(void) | |
131 | { | |
132 | _omap2xxx_set_apll_autoidle(OMAP2XXX_APLL_AUTOIDLE_LOW_POWER_STOP, | |
133 | OMAP24XX_AUTO_96M_MASK); | |
134 | } | |
135 | ||
136 | void omap2xxx_cm_set_apll96_auto_low_power_stop(void) | |
137 | { | |
138 | _omap2xxx_set_apll_autoidle(OMAP2XXX_APLL_AUTOIDLE_DISABLE, | |
139 | OMAP24XX_AUTO_96M_MASK); | |
140 | } | |
141 | ||
b6ffa050 PW |
142 | /* Enable an APLL if off */ |
143 | static int _omap2xxx_apll_enable(u8 enable_bit, u8 status_bit) | |
144 | { | |
145 | u32 v, m; | |
146 | ||
147 | m = EN_APLL_LOCKED << enable_bit; | |
148 | ||
149 | v = omap2_cm_read_mod_reg(PLL_MOD, CM_CLKEN); | |
150 | if (v & m) | |
151 | return 0; /* apll already enabled */ | |
152 | ||
153 | v |= m; | |
154 | omap2_cm_write_mod_reg(v, PLL_MOD, CM_CLKEN); | |
155 | ||
156 | omap2xxx_cm_wait_module_ready(PLL_MOD, 1, status_bit); | |
157 | ||
158 | /* | |
159 | * REVISIT: Should we return an error code if | |
160 | * omap2xxx_cm_wait_module_ready() fails? | |
161 | */ | |
162 | return 0; | |
163 | } | |
164 | ||
165 | /* Stop APLL */ | |
166 | static void _omap2xxx_apll_disable(u8 enable_bit) | |
167 | { | |
168 | u32 v; | |
169 | ||
170 | v = omap2_cm_read_mod_reg(PLL_MOD, CM_CLKEN); | |
171 | v &= ~(EN_APLL_LOCKED << enable_bit); | |
172 | omap2_cm_write_mod_reg(v, PLL_MOD, CM_CLKEN); | |
173 | } | |
174 | ||
175 | /* Enable an APLL if off */ | |
176 | int omap2xxx_cm_apll54_enable(void) | |
177 | { | |
178 | return _omap2xxx_apll_enable(OMAP24XX_EN_54M_PLL_SHIFT, | |
179 | OMAP24XX_ST_54M_APLL_SHIFT); | |
180 | } | |
181 | ||
182 | /* Enable an APLL if off */ | |
183 | int omap2xxx_cm_apll96_enable(void) | |
184 | { | |
185 | return _omap2xxx_apll_enable(OMAP24XX_EN_96M_PLL_SHIFT, | |
186 | OMAP24XX_ST_96M_APLL_SHIFT); | |
187 | } | |
188 | ||
189 | /* Stop APLL */ | |
190 | void omap2xxx_cm_apll54_disable(void) | |
191 | { | |
192 | _omap2xxx_apll_disable(OMAP24XX_EN_54M_PLL_SHIFT); | |
193 | } | |
194 | ||
195 | /* Stop APLL */ | |
196 | void omap2xxx_cm_apll96_disable(void) | |
197 | { | |
198 | _omap2xxx_apll_disable(OMAP24XX_EN_96M_PLL_SHIFT); | |
199 | } | |
200 | ||
c4ceedcb PW |
201 | /** |
202 | * omap2xxx_cm_split_idlest_reg - split CM_IDLEST reg addr into its components | |
203 | * @idlest_reg: CM_IDLEST* virtual address | |
204 | * @prcm_inst: pointer to an s16 to return the PRCM instance offset | |
205 | * @idlest_reg_id: pointer to a u8 to return the CM_IDLESTx register ID | |
206 | * | |
207 | * XXX This function is only needed until absolute register addresses are | |
208 | * removed from the OMAP struct clk records. | |
209 | */ | |
210 | int omap2xxx_cm_split_idlest_reg(void __iomem *idlest_reg, s16 *prcm_inst, | |
211 | u8 *idlest_reg_id) | |
212 | { | |
213 | unsigned long offs; | |
214 | u8 idlest_offs; | |
215 | int i; | |
216 | ||
217 | if (idlest_reg < cm_base || idlest_reg > (cm_base + 0x0fff)) | |
218 | return -EINVAL; | |
219 | ||
220 | idlest_offs = (unsigned long)idlest_reg & 0xff; | |
221 | for (i = 0; i < ARRAY_SIZE(omap2xxx_cm_idlest_offs); i++) { | |
222 | if (idlest_offs == omap2xxx_cm_idlest_offs[i]) { | |
223 | *idlest_reg_id = i + 1; | |
224 | break; | |
225 | } | |
226 | } | |
227 | ||
228 | if (i == ARRAY_SIZE(omap2xxx_cm_idlest_offs)) | |
229 | return -EINVAL; | |
230 | ||
231 | offs = idlest_reg - cm_base; | |
232 | offs &= 0xff00; | |
233 | *prcm_inst = offs; | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
ff4ae5d9 PW |
238 | /* |
239 | * | |
240 | */ | |
241 | ||
242 | /** | |
243 | * omap2xxx_cm_wait_module_ready - wait for a module to leave idle or standby | |
244 | * @prcm_mod: PRCM module offset | |
245 | * @idlest_id: CM_IDLESTx register ID (i.e., x = 1, 2, 3) | |
246 | * @idlest_shift: shift of the bit in the CM_IDLEST* register to check | |
247 | * | |
248 | * Wait for the PRCM to indicate that the module identified by | |
249 | * (@prcm_mod, @idlest_id, @idlest_shift) is clocked. Return 0 upon | |
250 | * success or -EBUSY if the module doesn't enable in time. | |
251 | */ | |
252 | int omap2xxx_cm_wait_module_ready(s16 prcm_mod, u8 idlest_id, u8 idlest_shift) | |
253 | { | |
254 | int ena = 0, i = 0; | |
255 | u8 cm_idlest_reg; | |
256 | u32 mask; | |
257 | ||
258 | if (!idlest_id || (idlest_id > ARRAY_SIZE(omap2xxx_cm_idlest_offs))) | |
259 | return -EINVAL; | |
260 | ||
261 | cm_idlest_reg = omap2xxx_cm_idlest_offs[idlest_id - 1]; | |
262 | ||
263 | mask = 1 << idlest_shift; | |
264 | ena = mask; | |
265 | ||
266 | omap_test_timeout(((omap2_cm_read_mod_reg(prcm_mod, cm_idlest_reg) & | |
267 | mask) == ena), MAX_MODULE_READY_TIME, i); | |
268 | ||
269 | return (i < MAX_MODULE_READY_TIME) ? 0 : -EBUSY; | |
270 | } | |
4bd5259e PW |
271 | |
272 | /* Clockdomain low-level functions */ | |
273 | ||
274 | static void omap2xxx_clkdm_allow_idle(struct clockdomain *clkdm) | |
275 | { | |
276 | if (atomic_read(&clkdm->usecount) > 0) | |
277 | _clkdm_add_autodeps(clkdm); | |
278 | ||
279 | omap2xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
280 | clkdm->clktrctrl_mask); | |
281 | } | |
282 | ||
283 | static void omap2xxx_clkdm_deny_idle(struct clockdomain *clkdm) | |
284 | { | |
285 | omap2xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
286 | clkdm->clktrctrl_mask); | |
287 | ||
288 | if (atomic_read(&clkdm->usecount) > 0) | |
289 | _clkdm_del_autodeps(clkdm); | |
290 | } | |
291 | ||
292 | static int omap2xxx_clkdm_clk_enable(struct clockdomain *clkdm) | |
293 | { | |
294 | bool hwsup = false; | |
295 | ||
296 | if (!clkdm->clktrctrl_mask) | |
297 | return 0; | |
298 | ||
299 | hwsup = omap2xxx_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
300 | clkdm->clktrctrl_mask); | |
301 | ||
302 | if (hwsup) { | |
303 | /* Disable HW transitions when we are changing deps */ | |
304 | omap2xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
305 | clkdm->clktrctrl_mask); | |
306 | _clkdm_add_autodeps(clkdm); | |
307 | omap2xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
308 | clkdm->clktrctrl_mask); | |
309 | } else { | |
310 | if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP) | |
311 | omap2xxx_clkdm_wakeup(clkdm); | |
312 | } | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
317 | static int omap2xxx_clkdm_clk_disable(struct clockdomain *clkdm) | |
318 | { | |
319 | bool hwsup = false; | |
320 | ||
321 | if (!clkdm->clktrctrl_mask) | |
322 | return 0; | |
323 | ||
324 | hwsup = omap2xxx_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
325 | clkdm->clktrctrl_mask); | |
326 | ||
327 | if (hwsup) { | |
328 | /* Disable HW transitions when we are changing deps */ | |
329 | omap2xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
330 | clkdm->clktrctrl_mask); | |
331 | _clkdm_del_autodeps(clkdm); | |
332 | omap2xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs, | |
333 | clkdm->clktrctrl_mask); | |
334 | } else { | |
335 | if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP) | |
336 | omap2xxx_clkdm_sleep(clkdm); | |
337 | } | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
342 | struct clkdm_ops omap2_clkdm_operations = { | |
343 | .clkdm_add_wkdep = omap2_clkdm_add_wkdep, | |
344 | .clkdm_del_wkdep = omap2_clkdm_del_wkdep, | |
345 | .clkdm_read_wkdep = omap2_clkdm_read_wkdep, | |
346 | .clkdm_clear_all_wkdeps = omap2_clkdm_clear_all_wkdeps, | |
347 | .clkdm_sleep = omap2xxx_clkdm_sleep, | |
348 | .clkdm_wakeup = omap2xxx_clkdm_wakeup, | |
349 | .clkdm_allow_idle = omap2xxx_clkdm_allow_idle, | |
350 | .clkdm_deny_idle = omap2xxx_clkdm_deny_idle, | |
351 | .clkdm_clk_enable = omap2xxx_clkdm_clk_enable, | |
352 | .clkdm_clk_disable = omap2xxx_clkdm_clk_disable, | |
353 | }; | |
c4ceedcb PW |
354 | |
355 | /* | |
356 | * | |
357 | */ | |
358 | ||
359 | static struct cm_ll_data omap2xxx_cm_ll_data = { | |
360 | .split_idlest_reg = &omap2xxx_cm_split_idlest_reg, | |
361 | .wait_module_ready = &omap2xxx_cm_wait_module_ready, | |
362 | }; | |
363 | ||
364 | int __init omap2xxx_cm_init(void) | |
365 | { | |
366 | if (!cpu_is_omap24xx()) | |
367 | return 0; | |
368 | ||
369 | return cm_register(&omap2xxx_cm_ll_data); | |
370 | } | |
371 | ||
372 | static void __exit omap2xxx_cm_exit(void) | |
373 | { | |
374 | if (!cpu_is_omap24xx()) | |
375 | return; | |
376 | ||
377 | /* Should never happen */ | |
378 | WARN(cm_unregister(&omap2xxx_cm_ll_data), | |
379 | "%s: cm_ll_data function pointer mismatch\n", __func__); | |
380 | } | |
381 | __exitcall(omap2xxx_cm_exit); |