summaryrefslogtreecommitdiff
path: root/firmware/target/arm/imx233/emi-imx233.c
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2013-01-10 00:43:08 +0000
committerAmaury Pouly <amaury.pouly@gmail.com>2013-01-10 00:51:35 +0000
commit5aa19f3eeb4e401b2d1ac57131a87b87b6ce55d5 (patch)
treeae54a62a5a4420dff8ec5198f6f5c9418c34151f /firmware/target/arm/imx233/emi-imx233.c
parentb9923df170104320e55bff05ecc2a0105067f9cb (diff)
downloadrockbox-5aa19f3eeb4e401b2d1ac57131a87b87b6ce55d5.tar.gz
rockbox-5aa19f3eeb4e401b2d1ac57131a87b87b6ce55d5.zip
imx233: implement emi frequency scaling (disabled by default)
CPU frequency scaling is basically useless without scaling the memory frequency. On the i.MX233, the EMI (external memory interface) and DRAM blocks are responsable for the DDR settings. This commits implements emi frequency scaling. Only some settings are implemented and the timings values only apply to mDDR (extracted from Sigmatel linux port) and have been checked to work on the Fuze+ and Zen X-Fi2/3. This feature is still disabled by default but I expected some battery life savings by boosting higher to 454MHz and unboosting lower to 64MHz. Note that changing the emi frequency is particularly tricky and to avoid writing it entirely in assembly we rely on the compiler to not use the stack except in the prolog and epilog (because it's in dram which is disabled when doing the change) and to put constant pools in iram which should always be true if the compiler isn't completely dumb and since the code itself is put in iram. If this proves to be insufficient, one can always switch the stack to the irq stack since interrupts are disabled during the change. Change-Id: If6ef5357f7ff091130ca1063e48536c6028f23ba
Diffstat (limited to 'firmware/target/arm/imx233/emi-imx233.c')
-rw-r--r--firmware/target/arm/imx233/emi-imx233.c186
1 files changed, 186 insertions, 0 deletions
diff --git a/firmware/target/arm/imx233/emi-imx233.c b/firmware/target/arm/imx233/emi-imx233.c
new file mode 100644
index 0000000000..b10d08134d
--- /dev/null
+++ b/firmware/target/arm/imx233/emi-imx233.c
@@ -0,0 +1,186 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2013 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "emi-imx233.h"
22#include "clkctrl-imx233.h"
23
24struct emi_reg_t
25{
26 int index;
27 uint32_t value;
28};
29
30/* hardcode all the register values for the different settings. This is ugly
31 * but I don't understand what they mean and it's faster this way so...
32 * Recall that everything should be put in iram !
33 * Make sure the last value is written to register 40. */
34
35/* Values extracted from Sigmatel linux port (GPL) */
36
37/** mDDR value */
38static struct emi_reg_t settings_24M[15] ICONST_ATTR =
39{
40 {4, 0x01000101}, {7, 0x01000101}, {12, 0x02010002}, {13, 0x06060a02},
41 {15, 0x01030000}, {17, 0x2d000102}, {18, 0x20200000}, {19, 0x027f1414},
42 {20, 0x01021608}, {21, 0x00000002}, {26, 0x000000b3}, {32, 0x00030687},
43 {33, 0x00000003}, {34, 0x000012c1}, {40, 0x00010000}
44};
45
46static struct emi_reg_t settings_48M[15] ICONST_ATTR =
47{
48 {4, 0x01000101}, {7, 0x01000101}, {13, 0x06060a02}, {12, 0x02010002},
49 {15, 0x02040000}, {17, 0x2d000104}, {18, 0x1f1f0000}, {19, 0x027f0a0a},
50 {20, 0x01021608}, {21, 0x00000004}, {26, 0x0000016f}, {32, 0x00060d17},
51 {33, 0x00000006}, {34, 0x00002582}, {40, 0x00020000}
52};
53
54static struct emi_reg_t settings_60M[15] ICONST_ATTR =
55{
56 {4, 0x01000101}, {7, 0x01000101}, {12, 0x02020002}, {13, 0x06060a02},
57 {15, 0x02040000}, {17, 0x2d000005}, {18, 0x1f1f0000}, {19, 0x027f0a0a},
58 {20, 0x02040a10}, {21, 0x00000006}, {26, 0x000001cc}, {32, 0x00081060},
59 {33, 0x00000008}, {34, 0x00002ee5}, {40, 0x00020000}
60};
61
62static struct emi_reg_t settings_80M[15] ICONST_ATTR __attribute__((alias("settings_60M")));
63
64static struct emi_reg_t settings_96M[15] ICONST_ATTR =
65{
66 {4, 0x00000101}, {7, 0x01000001}, {12, 0x02020002}, {13, 0x06070a02},
67 {15, 0x03050000}, {17, 0x2d000808}, {18, 0x1f1f0000}, {19, 0x020c1010},
68 {20, 0x0305101c}, {21, 0x00000007}, {26, 0x000002e6}, {32, 0x000c1a3b},
69 {33, 0x0000000c}, {34, 0x00004b0d}, {40, 0x00030000}
70};
71
72static struct emi_reg_t settings_120M[15] ICONST_ATTR =
73{
74 {4, 0x00000101}, {7, 0x01000001}, {12, 0x02020002}, {13, 0x06070a02},
75 {15, 0x03050000}, {17, 0x2300080a}, {18, 0x1f1f0000}, {19, 0x020c1010},
76 {20, 0x0306101c}, {21, 0x00000009}, {26, 0x000003a1}, {32, 0x000f20ca},
77 {33, 0x0000000f}, {34, 0x00005dca}, {40, 0x00040000}
78};
79
80static struct emi_reg_t settings_133M[15] ICONST_ATTR =
81{
82 {4, 0x00000101}, {7, 0x01000001}, {12, 0x02020002}, {13, 0x06070a02},
83 {15, 0x03050000}, {17, 0x2000080a}, {18, 0x1f1f0000}, {19, 0x020c1010},
84 {20, 0x0306101c}, {21, 0x0000000a}, {26, 0x00000408}, {32, 0x0010245f},
85 {33, 0x00000010}, {34, 0x00006808}, {40, 0x00040000}
86};
87
88static struct emi_reg_t settings_155M[15] ICONST_ATTR __attribute__((alias("settings_133M")));
89
90static void set_frequency(unsigned long freq) ICODE_ATTR;
91
92static void set_frequency(unsigned long freq)
93{
94 /* Set divider and clear clkgate. Do byte access to register to avoid bothering
95 * with other PFDs */
96 switch(freq)
97 {
98 case IMX233_EMIFREQ_151_MHz:
99 /* clk_emi@ref_emi/3*18/19 */
100 HW_CLKCTRL_FRAC_EMI = 19;
101 __FIELD_SET(HW_CLKCTRL_EMI, DIV_EMI, 3);
102 /* ref_emi@480 MHz
103 * clk_emi@151.58 MHz */
104 break;
105 case IMX233_EMIFREQ_130_MHz:
106 /* clk_emi@ref_emi/2*18/33 */
107 HW_CLKCTRL_FRAC_EMI = 33;
108 __FIELD_SET(HW_CLKCTRL_EMI, DIV_EMI, 2);
109 /* ref_emi@480 MHz
110 * clk_emi@130.91 MHz */
111 break;
112 case IMX233_EMIFREQ_64_MHz:
113 default:
114 /* clk_emi@ref_emi/5*18/27 */
115 HW_CLKCTRL_FRAC_EMI = 27;
116 __FIELD_SET(HW_CLKCTRL_EMI, DIV_EMI, 5);
117 /* ref_emi@480 MHz
118 * clk_emi@64 MHz */
119 break;
120 }
121}
122
123void imx233_emi_set_frequency(unsigned long freq) ICODE_ATTR;
124
125void imx233_emi_set_frequency(unsigned long freq)
126{
127 /** FIXME we rely on the compiler to NOT use the stack here because it's
128 * in iram ! If it's not smart enough, one can switch the switch to use
129 * the irq stack since we are running interrupts disable here ! */
130 /** BUG for freq<=24 MHz we must keep bypass mode since we run on xtal
131 * we this setting is unused by our code so ignore this bug for now */
132 /** WARNING DANGER
133 * Changing the EMI frequency is complicated because it requires to
134 * completely shutdown the external memory interface. We must make sure
135 * that this code and all the data it uses in in iram and that no access to
136 * the sdram will be made during the change. Care must be taken w.r.t to
137 * the cache also. */
138 /** FIXME assume that auto-slow is disabled here since that could put some
139 * clock below the minimum value and we want to spend as less time as
140 * possible in this state anyway. */
141
142 /* first disable all interrupts */
143 int oldstatus = disable_interrupt_save(IRQ_FIQ_STATUS);
144 /* flush the cache */
145 commit_discard_idcache();
146 /* put DRAM into self-refresh mode */
147 HW_DRAM_CTL08 |= HW_DRAM_CTL08__SREFRESH;
148 /* wait for DRAM to be halted */
149 while(!(HW_EMI_STAT & HW_EMI_STAT__DRAM_HALTED));
150 /* load timings */
151 struct emi_reg_t *regs;
152 if(freq <= 24000) regs = settings_24M;
153 else if(freq <= 48000) regs = settings_48M;
154 else if(freq <= 60000) regs = settings_60M;
155 else if(freq <= 80000) regs = settings_80M;
156 else if(freq <= 96000) regs = settings_96M;
157 else if(freq <= 120000) regs = settings_120M;
158 else if(freq <= 133000) regs = settings_133M;
159 else regs = settings_155M;
160
161 do
162 HW_DRAM_CTLxx(regs->index) = regs->value;
163 while((regs++)->index != 40);
164 /* switch emi to xtal */
165 __REG_SET(HW_CLKCTRL_CLKSEQ) = HW_CLKCTRL_CLKSEQ__BYPASS_EMI;
166 /* wait for transition */
167 while(HW_CLKCTRL_EMI & HW_CLKCTRL_EMI__BUSY_REF_XTAL);
168 /* put emi dll into reset mode */
169 __REG_SET(HW_EMI_CTRL) = HW_EMI_CTRL__DLL_RESET | HW_EMI_CTRL__DLL_SHIFT_RESET;
170 /* load the new frequency dividers */
171 set_frequency(freq);
172 /* switch emi back to pll */
173 __REG_CLR(HW_CLKCTRL_CLKSEQ) = HW_CLKCTRL_CLKSEQ__BYPASS_EMI;
174 /* wait for transition */
175 while(HW_CLKCTRL_EMI & HW_CLKCTRL_EMI__BUSY_REF_EMI);
176 /* allow emi dll to lock again */
177 __REG_CLR(HW_EMI_CTRL) = HW_EMI_CTRL__DLL_RESET | HW_EMI_CTRL__DLL_SHIFT_RESET;
178 /* wait for lock */
179 while(!(HW_DRAM_CTL04 & HW_DRAM_CTL04__DLLLOCKREG));
180 /* get DRAM out of self-refresh mode */
181 HW_DRAM_CTL08 &= ~HW_DRAM_CTL08__SREFRESH;
182 /* wait for DRAM to be to run again */
183 while(HW_EMI_STAT & HW_EMI_STAT__DRAM_HALTED);
184
185 restore_interrupt(oldstatus);
186}