diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2010-04-23 15:32:50 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2010-04-23 15:32:50 +0000 |
commit | 11cca264ff57ad0b234bd1cd2c9a2366b967feb7 (patch) | |
tree | 7693e7150d5abc9687966cc248bfbd550d356964 /firmware/target/arm/imx31/dvfs_dptc-imx31.c | |
parent | 6cee7579dbdc4d41c4df08c9395cf96c952ebab1 (diff) | |
download | rockbox-11cca264ff57ad0b234bd1cd2c9a2366b967feb7.tar.gz rockbox-11cca264ff57ad0b234bd1cd2c9a2366b967feb7.zip |
i.MX31/Gigabeat S: Implement frequency and voltage scaling-- 1.6V for 528MHz, and 1.35V for 264MHz and 132MHz. Keep DPTC overdrive ( > 400MHz) voltage scaling off for now because of uncertainties. Simplify the (working) mess later.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@25699 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target/arm/imx31/dvfs_dptc-imx31.c')
-rw-r--r-- | firmware/target/arm/imx31/dvfs_dptc-imx31.c | 712 |
1 files changed, 703 insertions, 9 deletions
diff --git a/firmware/target/arm/imx31/dvfs_dptc-imx31.c b/firmware/target/arm/imx31/dvfs_dptc-imx31.c index 8f32fd0fba..6e177ba6bd 100644 --- a/firmware/target/arm/imx31/dvfs_dptc-imx31.c +++ b/firmware/target/arm/imx31/dvfs_dptc-imx31.c | |||
@@ -22,27 +22,721 @@ | |||
22 | ****************************************************************************/ | 22 | ****************************************************************************/ |
23 | #include "config.h" | 23 | #include "config.h" |
24 | #include "system.h" | 24 | #include "system.h" |
25 | #include "ccm-imx31.h" | 25 | #include "logf.h" |
26 | #include "mc13783.h" | 26 | #include "mc13783.h" |
27 | #include "iomuxc-imx31.h" | ||
28 | #include "ccm-imx31.h" | ||
29 | #include "avic-imx31.h" | ||
30 | #include "dvfs_dptc-imx31.h" | ||
31 | #include "dvfs_dptc_tables-target.h" | ||
27 | 32 | ||
28 | /* Most of the code in here is based upon the Linux BSP provided by Freescale | 33 | /* Most of the code in here is based upon the Linux BSP provided by Freescale |
29 | * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. */ | 34 | * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. */ |
30 | 35 | ||
31 | void dvfs_dptc_start(void) | 36 | /* The current DVFS index level */ |
37 | static volatile unsigned int dvfs_level = DVFS_LEVEL_DEFAULT; | ||
38 | /* The current DPTC working point */ | ||
39 | static volatile unsigned int dptc_wp = DPTC_WP_DEFAULT; | ||
40 | |||
41 | |||
42 | static void update_dptc_counts(unsigned int level, unsigned int wp) | ||
43 | { | ||
44 | int oldlevel = disable_irq_save(); | ||
45 | const struct dptc_dcvr_table_entry *entry = &dptc_dcvr_table[level][wp]; | ||
46 | |||
47 | CCM_DCVR0 = entry->dcvr0; | ||
48 | CCM_DCVR1 = entry->dcvr1; | ||
49 | CCM_DCVR2 = entry->dcvr2; | ||
50 | CCM_DCVR3 = entry->dcvr3; | ||
51 | |||
52 | restore_irq(oldlevel); | ||
53 | } | ||
54 | |||
55 | |||
56 | static inline uint32_t check_regulator_setting(uint32_t setting) | ||
57 | { | ||
58 | /* Simply a safety check *in case* table gets scrambled */ | ||
59 | if (setting < VOLTAGE_SETTING_MIN) | ||
60 | setting = VOLTAGE_SETTING_MIN; | ||
61 | else if (setting > VOLTAGE_SETTING_MAX) | ||
62 | setting = VOLTAGE_SETTING_MAX; | ||
63 | |||
64 | return setting; | ||
65 | } | ||
66 | |||
67 | |||
68 | /** DVFS **/ | ||
69 | static bool dvfs_running = false; /* Has driver enabled DVFS? */ | ||
70 | |||
71 | /* Request tracking since boot */ | ||
72 | unsigned int dvfs_nr_dn = 0; | ||
73 | unsigned int dvfs_nr_up = 0; | ||
74 | unsigned int dvfs_nr_pnc = 0; | ||
75 | |||
76 | static void dvfs_stop(void); | ||
77 | |||
78 | |||
79 | /* Wait for the UPDTEN flag to be set so that all bits may be written */ | ||
80 | static inline void wait_for_dvfs_update_en(void) | ||
81 | { | ||
82 | while (!(CCM_PMCR0 & CCM_PMCR0_UPDTEN)); | ||
83 | } | ||
84 | |||
85 | |||
86 | static void do_dvfs_update(unsigned int level) | ||
87 | { | ||
88 | const struct dvfs_clock_table_entry *setting = &dvfs_clock_table[level]; | ||
89 | unsigned long pmcr0 = CCM_PMCR0; | ||
90 | |||
91 | if (pmcr0 & CCM_PMCR0_DPTEN) | ||
92 | { | ||
93 | /* Ignore voltage change request from DPTC. Voltage is *not* valid. */ | ||
94 | pmcr0 &= ~CCM_PMCR0_DPVCR; | ||
95 | } | ||
96 | |||
97 | pmcr0 &= ~CCM_PMCR0_VSCNT; | ||
98 | |||
99 | if (level > ((pmcr0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS)) | ||
100 | { | ||
101 | pmcr0 |= CCM_PMCR0_UDSC; /* Up scaling, increase */ | ||
102 | pmcr0 |= setting->vscnt << CCM_PMCR0_VSCNT_POS; | ||
103 | } | ||
104 | else | ||
105 | { | ||
106 | pmcr0 &= ~CCM_PMCR0_UDSC; /* Down scaling, decrease */ | ||
107 | pmcr0 |= 0x1 << CCM_PMCR0_VSCNT_POS; | ||
108 | } | ||
109 | |||
110 | /* DVSUP (new frequency index) setup */ | ||
111 | pmcr0 = (pmcr0 & ~CCM_PMCR0_DVSUP) | (level << CCM_PMCR0_DVSUP_POS); | ||
112 | |||
113 | dvfs_level = level; | ||
114 | |||
115 | if ((setting->pll_num << CCM_PMCR0_DFSUP_MCUPLL_POS) ^ | ||
116 | (pmcr0 & CCM_PMCR0_DFSUP_MCUPLL)) | ||
117 | { | ||
118 | /* Update pll and post-dividers. */ | ||
119 | pmcr0 ^= CCM_PMCR0_DFSUP_MCUPLL; | ||
120 | pmcr0 &= ~CCM_PMCR0_DFSUP_POST_DIVIDERS; | ||
121 | } | ||
122 | else | ||
123 | { | ||
124 | /* Post-dividers update only */ | ||
125 | pmcr0 |= CCM_PMCR0_DFSUP_POST_DIVIDERS; | ||
126 | } | ||
127 | |||
128 | CCM_PMCR0 = pmcr0; | ||
129 | udelay(100); /* Software wait for voltage ramp-up */ | ||
130 | CCM_PDR0 = setting->pdr_val; | ||
131 | |||
132 | if (!(pmcr0 & CCM_PMCR0_DFSUP_POST_DIVIDERS)) | ||
133 | { | ||
134 | /* Update the PLL settings */ | ||
135 | if (pmcr0 & CCM_PMCR0_DFSUP_MCUPLL) | ||
136 | CCM_MPCTL = setting->pll_val; | ||
137 | else | ||
138 | CCM_SPCTL = setting->pll_val; | ||
139 | } | ||
140 | |||
141 | cpu_frequency = ccm_get_mcu_clk(); | ||
142 | |||
143 | if (pmcr0 & CCM_PMCR0_DPTEN) | ||
144 | { | ||
145 | update_dptc_counts(level, dptc_wp); | ||
146 | /* Enable DPTC to request voltage changes. Voltage is valid. */ | ||
147 | CCM_PMCR0 |= CCM_PMCR0_DPVCR; | ||
148 | udelay(2); | ||
149 | CCM_PMCR0 |= CCM_PMCR0_DPVV; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | |||
154 | /* Start DVFS, change the set point and stop it */ | ||
155 | static void set_current_dvfs_level(unsigned int level) | ||
156 | { | ||
157 | int oldlevel = disable_irq_save(); | ||
158 | |||
159 | CCM_PMCR0 |= CCM_PMCR0_DVFEN; | ||
160 | |||
161 | wait_for_dvfs_update_en(); | ||
162 | |||
163 | do_dvfs_update(level); | ||
164 | |||
165 | wait_for_dvfs_update_en(); | ||
166 | |||
167 | CCM_PMCR0 &= ~CCM_PMCR0_DVFEN; | ||
168 | |||
169 | restore_irq(oldlevel); | ||
170 | } | ||
171 | |||
172 | |||
173 | /* DVFS Interrupt handler */ | ||
174 | static void __attribute__((used)) dvfs_int(void) | ||
175 | { | ||
176 | unsigned long pmcr0 = CCM_PMCR0; | ||
177 | unsigned long fsvai = pmcr0 & CCM_PMCR0_FSVAI; | ||
178 | unsigned int level = (pmcr0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS; | ||
179 | |||
180 | if (pmcr0 & CCM_PMCR0_FSVAIM) | ||
181 | return; /* Do nothing. DVFS interrupt is masked. */ | ||
182 | |||
183 | if (!(pmcr0 & CCM_PMCR0_UPDTEN)) | ||
184 | return; /* Do nothing. DVFS didn't finish previous flow update. */ | ||
185 | |||
186 | switch (fsvai) | ||
187 | { | ||
188 | case CCM_PMCR0_FSVAI_DECREASE: | ||
189 | if (level >= DVFS_NUM_LEVELS - 1) | ||
190 | return; /* DVFS already at lowest level */ | ||
191 | |||
192 | /* Upon the DECREASE event, the frequency will be changed to the next | ||
193 | * higher state index. */ | ||
194 | level++; | ||
195 | dvfs_nr_dn++; | ||
196 | break; | ||
197 | |||
198 | /* Single-step frequency increase */ | ||
199 | case CCM_PMCR0_FSVAI_INCREASE: | ||
200 | if (level == 0) | ||
201 | return; /* DVFS already at highest level */ | ||
202 | |||
203 | /* Upon the INCREASE event, the frequency will be changed to the next | ||
204 | * lower state index. */ | ||
205 | level--; | ||
206 | dvfs_nr_up++; | ||
207 | break; | ||
208 | |||
209 | /* Right to highest if panic */ | ||
210 | case CCM_PMCR0_FSVAI_INCREASE_NOW: | ||
211 | if (level == 0) | ||
212 | return; /* DVFS already at highest level */ | ||
213 | |||
214 | /* Upon the INCREASE_NOW event, the frequency will be increased to | ||
215 | * the maximum (index 0). */ | ||
216 | level = 0; | ||
217 | dvfs_nr_pnc++; | ||
218 | break; | ||
219 | |||
220 | case CCM_PMCR0_FSVAI_NO_INT: | ||
221 | default: | ||
222 | return; /* Do nothing. Freq change is not required */ | ||
223 | } /* end switch */ | ||
224 | |||
225 | do_dvfs_update(level); | ||
226 | } | ||
227 | |||
228 | |||
229 | /* Interrupt vector for DVFS */ | ||
230 | static __attribute__((naked, interrupt("IRQ"))) void CCM_DVFS_HANDLER(void) | ||
231 | { | ||
232 | /* Audio can glitch with the long udelay if nested IRQ isn't allowed. */ | ||
233 | AVIC_NESTED_NI_CALL_PROLOGUE(); | ||
234 | asm volatile ("bl dvfs_int"); | ||
235 | AVIC_NESTED_NI_CALL_EPILOGUE(); | ||
236 | } | ||
237 | |||
238 | |||
239 | /* Initialize the DVFS hardware */ | ||
240 | static void dvfs_init(void) | ||
241 | { | ||
242 | if (CCM_PMCR0 & CCM_PMCR0_DVFEN) | ||
243 | { | ||
244 | /* Turn it off first. Really, shouldn't happen though. */ | ||
245 | dvfs_running = true; | ||
246 | dvfs_stop(); | ||
247 | } | ||
248 | |||
249 | /* Combine SW1A and SW1B DVS pins for a possible five DVS levels | ||
250 | * per working point. Four, MAXIMUM, are actually used, one for each | ||
251 | * frequency. */ | ||
252 | mc13783_set(MC13783_ARBITRATION_SWITCHERS, MC13783_SW1ABDVS); | ||
253 | |||
254 | /* Set DVS speed to 25mV every 4us. */ | ||
255 | mc13783_write_masked(MC13783_SWITCHERS4, MC13783_SW1ADVSSPEED_4US, | ||
256 | MC13783_SW1ADVSSPEED); | ||
257 | |||
258 | /* Set DVFS pins to functional outputs. Input mode and pad setting is | ||
259 | * fixed in hardware. */ | ||
260 | iomuxc_set_pin_mux(IOMUXC_DVFS0, | ||
261 | IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_NONE); | ||
262 | iomuxc_set_pin_mux(IOMUXC_DVFS1, | ||
263 | IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_NONE); | ||
264 | |||
265 | #ifndef DVFS_NO_PWRRDY | ||
266 | /* Configure PWRRDY signal pin. */ | ||
267 | imx31_regclr32(&GPIO1_GDIR, (1 << 5)); | ||
268 | iomuxc_set_pin_mux(IOMUXC_GPIO1_5, | ||
269 | IOMUXC_MUX_OUT_FUNCTIONAL | IOMUXC_MUX_IN_FUNCTIONAL); | ||
270 | #endif | ||
271 | |||
272 | /* Initialize DVFS signal weights and detection modes. */ | ||
273 | int i; | ||
274 | for (i = 0; i < 16; i++) | ||
275 | { | ||
276 | dvfs_set_lt_weight(i, lt_signals[i].weight); | ||
277 | dvfs_set_lt_detect(i, lt_signals[i].detect); | ||
278 | } | ||
279 | |||
280 | /* Set up LTR0. */ | ||
281 | imx31_regmod32(&CCM_LTR0, | ||
282 | DVFS_UPTHR << CCM_LTR0_UPTHR_POS | | ||
283 | DVFS_DNTHR << CCM_LTR0_DNTHR_POS | | ||
284 | DVFS_DIV3CK, | ||
285 | CCM_LTR0_UPTHR | CCM_LTR0_DNTHR | CCM_LTR0_DIV3CK); | ||
286 | |||
287 | /* Set up LTR1. */ | ||
288 | imx31_regmod32(&CCM_LTR1, | ||
289 | DVFS_DNCNT << CCM_LTR1_DNCNT_POS | | ||
290 | DVFS_UPCNT << CCM_LTR1_UPCNT_POS | | ||
291 | DVFS_PNCTHR << CCM_LTR1_PNCTHR_POS | | ||
292 | CCM_LTR1_LTBRSR, | ||
293 | CCM_LTR1_DNCNT | CCM_LTR1_UPCNT | | ||
294 | CCM_LTR1_PNCTHR | CCM_LTR1_LTBRSR); | ||
295 | |||
296 | /* Set up LTR2-- EMA configuration. */ | ||
297 | imx31_regmod32(&CCM_LTR2, DVFS_EMAC << CCM_LTR2_EMAC_POS, | ||
298 | CCM_LTR2_EMAC); | ||
299 | |||
300 | /* DVFS interrupt goes to MCU. Mask load buffer full interrupt. */ | ||
301 | imx31_regset32(&CCM_PMCR0, CCM_PMCR0_DVFIS | CCM_PMCR0_LBMI); | ||
302 | |||
303 | /* Initialize current core PLL and dividers for default level. Assumes | ||
304 | * clocking scheme has been set up appropriately in other init code. */ | ||
305 | ccm_set_mcupll_and_pdr(dvfs_clock_table[DVFS_LEVEL_DEFAULT].pll_val, | ||
306 | dvfs_clock_table[DVFS_LEVEL_DEFAULT].pdr_val); | ||
307 | |||
308 | /* Set initial level and working point. */ | ||
309 | set_current_dvfs_level(DVFS_LEVEL_DEFAULT); | ||
310 | |||
311 | logf("DVFS: Initialized"); | ||
312 | } | ||
313 | |||
314 | |||
315 | /* Start the DVFS hardware */ | ||
316 | static void dvfs_start(void) | ||
317 | { | ||
318 | int oldlevel; | ||
319 | |||
320 | /* Have to wait at least 3 div3 clocks before enabling after being | ||
321 | * stopped. */ | ||
322 | udelay(1500); | ||
323 | |||
324 | oldlevel = disable_irq_save(); | ||
325 | |||
326 | if (!dvfs_running) | ||
327 | { | ||
328 | dvfs_running = true; | ||
329 | |||
330 | /* Unmask DVFS interrupt source and enable DVFS. */ | ||
331 | avic_enable_int(INT_CCM_DVFS, INT_TYPE_IRQ, INT_PRIO_DVFS, | ||
332 | CCM_DVFS_HANDLER); | ||
333 | |||
334 | CCM_PMCR0 = (CCM_PMCR0 & ~CCM_PMCR0_FSVAIM) | CCM_PMCR0_DVFEN; | ||
335 | } | ||
336 | |||
337 | restore_irq(oldlevel); | ||
338 | |||
339 | logf("DVFS: started"); | ||
340 | } | ||
341 | |||
342 | |||
343 | /* Stop the DVFS hardware and return to default frequency */ | ||
344 | static void dvfs_stop(void) | ||
32 | { | 345 | { |
33 | /* For now, just set the regulator voltage off of overdrive mode */ | 346 | int oldlevel = disable_irq_save(); |
34 | /* For 264 MHz, DPTC is not needed and lower V can be used */ | 347 | |
348 | if (dvfs_running) | ||
349 | { | ||
350 | /* Mask DVFS interrupts. */ | ||
351 | CCM_PMCR0 |= CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI; | ||
352 | avic_disable_int(INT_CCM_DVFS); | ||
353 | |||
354 | if (((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS) != | ||
355 | DVFS_LEVEL_DEFAULT) | ||
356 | { | ||
357 | /* Set default frequency level */ | ||
358 | wait_for_dvfs_update_en(); | ||
359 | do_dvfs_update(DVFS_LEVEL_DEFAULT); | ||
360 | wait_for_dvfs_update_en(); | ||
361 | } | ||
362 | |||
363 | /* Disable DVFS. */ | ||
364 | CCM_PMCR0 &= ~CCM_PMCR0_DVFEN; | ||
365 | dvfs_running = false; | ||
366 | } | ||
367 | |||
368 | restore_irq(oldlevel); | ||
369 | |||
370 | logf("DVFS: stopped"); | ||
371 | } | ||
372 | |||
373 | |||
374 | /** DPTC **/ | ||
375 | |||
376 | /* Request tracking since boot */ | ||
377 | unsigned int dptc_nr_dn = 0; | ||
378 | unsigned int dptc_nr_up = 0; | ||
379 | unsigned int dptc_nr_pnc = 0; | ||
380 | |||
381 | |||
382 | /* Enable DPTC and unmask interrupt. */ | ||
383 | static void enable_dptc(void) | ||
384 | { | ||
385 | int oldlevel = disable_irq_save(); | ||
386 | |||
387 | /* Enable DPTC, assert voltage change request. */ | ||
388 | CCM_PMCR0 = (CCM_PMCR0 & ~CCM_PMCR0_PTVAIM) | CCM_PMCR0_DPTEN | | ||
389 | CCM_PMCR0_DPVCR; | ||
390 | |||
391 | udelay(2); | ||
392 | |||
393 | /* Set voltage valid *after* setting change request */ | ||
394 | CCM_PMCR0 |= CCM_PMCR0_DPVV; | ||
395 | |||
396 | restore_irq(oldlevel); | ||
397 | } | ||
398 | |||
399 | |||
400 | static void dptc_new_wp(unsigned int wp) | ||
401 | { | ||
402 | unsigned int level = dvfs_level; | ||
403 | const union dvfs_dptc_voltage_table_entry *entry = &dvfs_dptc_voltage_table[wp]; | ||
404 | |||
405 | uint32_t sw1a = check_regulator_setting(entry->sw1a); | ||
406 | uint32_t sw1advs = check_regulator_setting(entry->sw1advs); | ||
407 | uint32_t sw1bdvs = check_regulator_setting(entry->sw1bdvs); | ||
408 | uint32_t sw1bstby = check_regulator_setting(entry->sw1bstby); | ||
409 | |||
410 | dptc_wp = wp; | ||
35 | 411 | ||
36 | mc13783_write_masked(MC13783_SWITCHERS0, | 412 | mc13783_write_masked(MC13783_SWITCHERS0, |
37 | MC13783_SW_1_350 << MC13783_SW1A_POS, | 413 | sw1a << MC13783_SW1A_POS | /* SW1A */ |
38 | MC13783_SW1A); | 414 | sw1advs << MC13783_SW1ADVS_POS, /* SW1ADVS */ |
39 | imx31_regmod32(&CCM_PMCR0, CCM_PMCR0_DVS1_0_DVS0_0, | 415 | MC13783_SW1A | MC13783_SW1ADVS); |
40 | CCM_PMCR0_DVSUP_DVS); | 416 | |
417 | mc13783_write_masked(MC13783_SWITCHERS1, | ||
418 | sw1bdvs << MC13783_SW1BDVS_POS | /* SW1BDVS */ | ||
419 | sw1bstby << MC13783_SW1BSTBY_POS, /* SW1BSTBY */ | ||
420 | MC13783_SW1BDVS | MC13783_SW1BSTBY); | ||
421 | |||
422 | |||
423 | udelay(100); /* Wait to settle */ | ||
424 | |||
425 | update_dptc_counts(level, wp); | ||
426 | } | ||
427 | |||
428 | |||
429 | /* DPTC service thread */ | ||
430 | #ifdef ROCKBOX_HAS_LOGF | ||
431 | #define DPTC_STACK_SIZE DEFAULT_STACK_SIZE | ||
432 | #else | ||
433 | #define DPTC_STACK_SIZE 160 | ||
434 | #endif | ||
435 | static int dptc_thread_stack[DPTC_STACK_SIZE/sizeof(int)]; | ||
436 | static const char * const dptc_thread_name = "dptc"; | ||
437 | static struct wakeup dptc_wakeup; /* Object to signal upon DPTC event */ | ||
438 | static struct mutex dptc_mutex; /* Avoid mutually disrupting voltage updates */ | ||
439 | static unsigned long dptc_int_data; /* Data passed to thread for each event */ | ||
440 | static bool dptc_running = false; /* Has driver enabled DPTC? */ | ||
441 | |||
442 | |||
443 | static void dptc_interrupt_thread(void) | ||
444 | { | ||
445 | int wp; | ||
446 | |||
447 | mutex_lock(&dptc_mutex); | ||
448 | |||
449 | while (1) | ||
450 | { | ||
451 | mutex_unlock(&dptc_mutex); | ||
452 | |||
453 | wakeup_wait(&dptc_wakeup, TIMEOUT_BLOCK); | ||
454 | |||
455 | mutex_lock(&dptc_mutex); | ||
456 | |||
457 | if (!dptc_running) | ||
458 | continue; | ||
459 | |||
460 | wp = dptc_wp; | ||
461 | |||
462 | switch (dptc_int_data & CCM_PMCR0_PTVAI) | ||
463 | { | ||
464 | case CCM_PMCR0_PTVAI_DECREASE: | ||
465 | wp++; | ||
466 | dptc_nr_dn++; | ||
467 | break; | ||
468 | |||
469 | case CCM_PMCR0_PTVAI_INCREASE: | ||
470 | wp--; | ||
471 | dptc_nr_up++; | ||
472 | break; | ||
473 | |||
474 | case CCM_PMCR0_PTVAI_INCREASE_NOW: | ||
475 | wp = DPTC_WP_PANIC; | ||
476 | dptc_nr_pnc++; | ||
477 | break; | ||
478 | |||
479 | case CCM_PMCR0_PTVAI_NO_INT: | ||
480 | logf("DPTC: unexpected INT"); | ||
481 | continue; | ||
482 | } | ||
483 | |||
484 | if (wp < 0) | ||
485 | { | ||
486 | wp = 0; | ||
487 | logf("DPTC: already @ highest (%d)", wp); | ||
488 | } | ||
489 | else if (wp >= DPTC_NUM_WP) | ||
490 | { | ||
491 | wp = DPTC_NUM_WP - 1; | ||
492 | logf("DPTC: already @ lowest (%d)", wp); | ||
493 | } | ||
494 | else | ||
495 | { | ||
496 | logf("DPTC: new wp (%d)", wp); | ||
497 | } | ||
498 | |||
499 | dptc_new_wp(wp); | ||
500 | enable_dptc(); | ||
501 | } | ||
502 | } | ||
503 | |||
504 | |||
505 | /* Interrupt vector for DPTC */ | ||
506 | static __attribute__((interrupt("IRQ"))) void CCM_CLK_HANDLER(void) | ||
507 | { | ||
508 | /* Snapshot the interrupt cause */ | ||
509 | unsigned long pmcr0 = CCM_PMCR0; | ||
510 | dptc_int_data = pmcr0; | ||
511 | |||
512 | /* Mask DPTC interrupt and disable DPTC until the change request is | ||
513 | * serviced. */ | ||
514 | CCM_PMCR0 = (pmcr0 & ~CCM_PMCR0_DPTEN) | CCM_PMCR0_PTVAIM; | ||
515 | |||
516 | wakeup_signal(&dptc_wakeup); | ||
517 | } | ||
518 | |||
519 | |||
520 | /* Initialize the DPTC hardware */ | ||
521 | static void dptc_init(void) | ||
522 | { | ||
523 | /* Force DPTC off if running for some reason. */ | ||
524 | imx31_regmod32(&CCM_PMCR0, CCM_PMCR0_PTVAIM, | ||
525 | CCM_PMCR0_PTVAIM | CCM_PMCR0_DPTEN); | ||
526 | |||
527 | /* Set default, safe working point. */ | ||
528 | dptc_new_wp(DPTC_WP_DEFAULT); | ||
529 | |||
530 | /* Interrupt goes to MCU, specified reference circuits enabled when | ||
531 | * DPTC is active. */ | ||
532 | imx31_regset32(&CCM_PMCR0, CCM_PMCR0_PTVIS | DPTC_DRCE_MASK); | ||
533 | |||
534 | /* DPTC counting range = 256 system clocks */ | ||
535 | imx31_regclr32(&CCM_PMCR0, CCM_PMCR0_DCR); | ||
536 | |||
537 | /* Create PMIC regulator service. */ | ||
538 | wakeup_init(&dptc_wakeup); | ||
539 | mutex_init(&dptc_mutex); | ||
540 | create_thread(dptc_interrupt_thread, | ||
541 | dptc_thread_stack, sizeof(dptc_thread_stack), 0, | ||
542 | dptc_thread_name IF_PRIO(, PRIORITY_REALTIME_1) IF_COP(, CPU)); | ||
543 | |||
544 | logf("DPTC: Initialized"); | ||
41 | } | 545 | } |
42 | 546 | ||
43 | 547 | ||
548 | /* Start DPTC module */ | ||
549 | static void dptc_start(void) | ||
550 | { | ||
551 | int oldstate; | ||
552 | |||
553 | mutex_lock(&dptc_mutex); | ||
554 | |||
555 | oldstate = disable_irq_save(); | ||
556 | |||
557 | if (!dptc_running) | ||
558 | { | ||
559 | dptc_running = true; | ||
560 | |||
561 | /* Enable DPTC and unmask interrupt. */ | ||
562 | avic_enable_int(INT_CCM_CLK, INT_TYPE_IRQ, INT_PRIO_DPTC, | ||
563 | CCM_CLK_HANDLER); | ||
564 | |||
565 | update_dptc_counts(dvfs_level, dptc_wp); | ||
566 | enable_dptc(); | ||
567 | } | ||
568 | |||
569 | restore_irq(oldstate); | ||
570 | |||
571 | mutex_unlock(&dptc_mutex); | ||
572 | |||
573 | logf("DPTC: started"); | ||
574 | } | ||
575 | |||
576 | |||
577 | /* Stop the DPTC hardware if running and go back to default working point */ | ||
578 | static void dptc_stop(void) | ||
579 | { | ||
580 | int oldlevel; | ||
581 | |||
582 | mutex_lock(&dptc_mutex); | ||
583 | |||
584 | oldlevel = disable_irq_save(); | ||
585 | |||
586 | if (dptc_running) | ||
587 | { | ||
588 | /* Disable DPTC and mask interrupt. */ | ||
589 | CCM_PMCR0 = (CCM_PMCR0 & ~CCM_PMCR0_DPTEN) | CCM_PMCR0_PTVAIM; | ||
590 | avic_disable_int(INT_CCM_CLK); | ||
591 | dptc_int_data = 0; | ||
592 | |||
593 | dptc_running = false; | ||
594 | } | ||
595 | |||
596 | restore_irq(oldlevel); | ||
597 | |||
598 | /* Go back to default working point. */ | ||
599 | dptc_new_wp(DPTC_WP_DEFAULT); | ||
600 | |||
601 | mutex_unlock(&dptc_mutex); | ||
602 | |||
603 | logf("DPTC: stopped"); | ||
604 | } | ||
605 | |||
606 | |||
607 | /** Main module interface **/ | ||
608 | |||
609 | /* Initialize DVFS and DPTC */ | ||
610 | void dvfs_dptc_init(void) | ||
611 | { | ||
612 | dptc_init(); | ||
613 | dvfs_init(); | ||
614 | } | ||
615 | |||
616 | |||
617 | /* Start DVFS and DPTC */ | ||
618 | void dvfs_dptc_start(void) | ||
619 | { | ||
620 | dvfs_start(); | ||
621 | if (0) /* Hold off for now */ | ||
622 | { | ||
623 | dptc_start(); | ||
624 | } | ||
625 | } | ||
626 | |||
627 | |||
628 | /* Stop DVFS and DPTC */ | ||
44 | void dvfs_dptc_stop(void) | 629 | void dvfs_dptc_stop(void) |
45 | { | 630 | { |
46 | /* Nothing for now */ | 631 | dptc_stop(); |
632 | dvfs_stop(); | ||
47 | } | 633 | } |
48 | 634 | ||
635 | |||
636 | /* Set a signal load tracking weight */ | ||
637 | void dvfs_set_lt_weight(enum DVFS_LT_SIGS index, unsigned long value) | ||
638 | { | ||
639 | volatile unsigned long *reg_p = &CCM_LTR2; | ||
640 | unsigned int shift = 3 * index; | ||
641 | |||
642 | if (index < 9) | ||
643 | { | ||
644 | reg_p = &CCM_LTR3; | ||
645 | shift += 5; /* Bits 7:5, 10:8 ... 31:29 */ | ||
646 | } | ||
647 | else if (index < 16) | ||
648 | { | ||
649 | shift -= 16; /* Bits 13:11, 16:14 ... 31:29 */ | ||
650 | } | ||
651 | |||
652 | imx31_regmod32(reg_p, value << shift, 0x7 << shift); | ||
653 | } | ||
654 | |||
655 | |||
656 | /* Set a signal load detection mode */ | ||
657 | void dvfs_set_lt_detect(enum DVFS_LT_SIGS index, bool edge) | ||
658 | { | ||
659 | unsigned long bit = 0; | ||
660 | |||
661 | if ((unsigned)index < 13) | ||
662 | bit = 1ul << (index + 3); | ||
663 | else if ((unsigned)index < 16) | ||
664 | bit = 1ul << (index + 29); | ||
665 | |||
666 | imx31_regmod32(&CCM_LTR0, edge ? bit : 0, bit); | ||
667 | } | ||
668 | |||
669 | |||
670 | void dvfs_set_gp_bit(enum DVFS_DVGPS dvgp, bool assert) | ||
671 | { | ||
672 | if ((unsigned)dvgp <= 3) | ||
673 | { | ||
674 | unsigned long bit = 1ul << dvgp; | ||
675 | imx31_regmod32(&CCM_PMCR1, assert ? bit : 0, bit); | ||
676 | } | ||
677 | } | ||
678 | |||
679 | |||
680 | /* Turn the wait-for-interrupt monitoring on or off */ | ||
681 | void dvfs_wfi_monitor(bool on) | ||
682 | { | ||
683 | imx31_regmod32(&CCM_PMCR0, on ? 0 : CCM_PMCR0_WFIM, | ||
684 | CCM_PMCR0_WFIM); | ||
685 | } | ||
686 | |||
687 | |||
688 | /* Obtain the current core voltage setting, in millivolts 8-) */ | ||
689 | unsigned int dvfs_dptc_get_voltage(void) | ||
690 | { | ||
691 | unsigned int v; | ||
692 | |||
693 | int oldlevel = disable_irq_save(); | ||
694 | v = dvfs_dptc_voltage_table[dptc_wp].sw[dvfs_level]; | ||
695 | restore_irq(oldlevel); | ||
696 | |||
697 | /* 25mV steps from 0.900V to 1.675V */ | ||
698 | return v * 25 + 900; | ||
699 | } | ||
700 | |||
701 | |||
702 | /* Get the current DVFS level */ | ||
703 | unsigned int dvfs_get_level(void) | ||
704 | { | ||
705 | return dvfs_level; | ||
706 | } | ||
707 | |||
708 | |||
709 | /* If DVFS is disabled, set the level explicitly */ | ||
710 | void dvfs_set_level(unsigned int level) | ||
711 | { | ||
712 | int oldlevel = disable_irq_save(); | ||
713 | |||
714 | unsigned int currlevel = | ||
715 | (CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS; | ||
716 | |||
717 | if (!dvfs_running && level < DVFS_NUM_LEVELS && level != currlevel) | ||
718 | set_current_dvfs_level(level); | ||
719 | |||
720 | restore_irq(oldlevel); | ||
721 | } | ||
722 | |||
723 | |||
724 | /* Get the current DPTC working point */ | ||
725 | unsigned int dptc_get_wp(void) | ||
726 | { | ||
727 | return dptc_wp; | ||
728 | } | ||
729 | |||
730 | |||
731 | /* If DPTC is not running, set the working point explicitly */ | ||
732 | void dptc_set_wp(unsigned int wp) | ||
733 | { | ||
734 | mutex_lock(&dptc_mutex); | ||
735 | |||
736 | if (!dptc_running && wp < DPTC_NUM_WP) | ||
737 | { | ||
738 | dptc_new_wp(wp); | ||
739 | } | ||
740 | |||
741 | mutex_unlock(&dptc_mutex); | ||
742 | } | ||