imx233: fix regulator voltage setting + always enable DCDC mode
[maemo-rb.git] / firmware / target / arm / imx233 / power-imx233.c
blob186256f7b87df94ba601e455d1c1f95630bb6887
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2011 by Amaury Pouly
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include "config.h"
23 #include "system.h"
24 #include "power.h"
25 #include "string.h"
26 #include "usb.h"
27 #include "system-target.h"
28 #include "power-imx233.h"
29 #include "pinctrl-imx233.h"
30 #include "fmradio_i2c.h"
32 struct current_step_bit_t
34 unsigned current;
35 uint32_t bit;
38 /* in decreasing order */
39 static struct current_step_bit_t g_charger_current_bits[] =
41 { 400, HW_POWER_CHARGE__BATTCHRG_I__400mA },
42 { 200, HW_POWER_CHARGE__BATTCHRG_I__200mA },
43 { 100, HW_POWER_CHARGE__BATTCHRG_I__100mA },
44 { 50, HW_POWER_CHARGE__BATTCHRG_I__50mA },
45 { 20, HW_POWER_CHARGE__BATTCHRG_I__20mA },
46 { 10, HW_POWER_CHARGE__BATTCHRG_I__10mA }
49 /* in decreasing order */
50 static struct current_step_bit_t g_charger_stop_current_bits[] =
52 { 100, HW_POWER_CHARGE__STOP_ILIMIT__100mA },
53 { 50, HW_POWER_CHARGE__STOP_ILIMIT__50mA },
54 { 20, HW_POWER_CHARGE__STOP_ILIMIT__20mA },
55 { 10, HW_POWER_CHARGE__STOP_ILIMIT__10mA }
58 /* in decreasing order */
59 static struct current_step_bit_t g_4p2_charge_limit_bits[] =
61 { 400, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__400mA },
62 { 200, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__200mA },
63 { 100, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__100mA },
64 { 50, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__50mA },
65 { 20, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__20mA },
66 { 10, HW_POWER_5VCTRL__CHARGE_4P2_ILIMIT__10mA }
69 void INT_VDD5V(void)
71 if(HW_POWER_CTRL & HW_POWER_CTRL__VBUSVALID_IRQ)
73 if(HW_POWER_STS & HW_POWER_STS__VBUSVALID)
74 usb_insert_int();
75 else
76 usb_remove_int();
77 /* reverse polarity */
78 __REG_TOG(HW_POWER_CTRL) = HW_POWER_CTRL__POLARITY_VBUSVALID;
79 /* enable int */
80 __REG_CLR(HW_POWER_CTRL) = HW_POWER_CTRL__VBUSVALID_IRQ;
84 void power_init(void)
86 /* setup vbusvalid parameters: set threshold to 4v and power up comparators */
87 __REG_CLR(HW_POWER_5VCTRL) = HW_POWER_5VCTRL__VBUSVALID_TRSH_BM;
88 __REG_SET(HW_POWER_5VCTRL) = HW_POWER_5VCTRL__VBUSVALID_TRSH_4V |
89 HW_POWER_5VCTRL__PWRUP_VBUS_CMPS;
90 /* enable vbusvalid detection method for the dcdc (improves efficiency) */
91 __REG_SET(HW_POWER_5VCTRL) = HW_POWER_5VCTRL__VBUSVALID_5VDETECT;
92 /* clear vbusvalid irq and set correct polarity */
93 __REG_CLR(HW_POWER_CTRL) = HW_POWER_CTRL__VBUSVALID_IRQ;
94 if(HW_POWER_STS & HW_POWER_STS__VBUSVALID)
95 __REG_CLR(HW_POWER_CTRL) = HW_POWER_CTRL__POLARITY_VBUSVALID;
96 else
97 __REG_SET(HW_POWER_CTRL) = HW_POWER_CTRL__POLARITY_VBUSVALID;
98 __REG_SET(HW_POWER_CTRL) = HW_POWER_CTRL__ENIRQ_VBUS_VALID;
99 imx233_icoll_enable_interrupt(INT_SRC_VDD5V, true);
100 /* setup linear regulator offsets to 25 mV below to prevent contention between
101 * linear regulators and DCDC */
102 __FIELD_SET(HW_POWER_VDDDCTRL, LINREG_OFFSET, 2);
103 __FIELD_SET(HW_POWER_VDDACTRL, LINREG_OFFSET, 2);
104 __FIELD_SET(HW_POWER_VDDIOCTRL, LINREG_OFFSET, 2);
105 /* enable DCDC (more efficient) */
106 __REG_SET(HW_POWER_5VCTRL) = HW_POWER_5VCTRL__ENABLE_DCDC;
107 /* enable a few bits controlling the DC-DC as recommended by Freescale */
108 __REG_SET(HW_POWER_LOOPCTRL) = HW_POWER_LOOPCTRL__TOGGLE_DIF |
109 HW_POWER_LOOPCTRL__EN_CM_HYST;
110 __FIELD_SET(HW_POWER_LOOPCTRL, EN_RCSCALE, HW_POWER_LOOPCTRL__EN_RCSCALE__2X);
113 void power_off(void)
115 /* wait a bit, useful for the user to stop touching anything */
116 sleep(HZ / 2);
117 #ifdef SANSA_FUZEPLUS
118 /* This pin seems to be important to shutdown the hardware properly */
119 imx233_pinctrl_acquire_pin(0, 9, "power off");
120 imx233_set_pin_function(0, 9, PINCTRL_FUNCTION_GPIO);
121 imx233_enable_gpio_output(0, 9, true);
122 imx233_set_gpio_output(0, 9, true);
123 #endif
124 /* power down */
125 HW_POWER_RESET = HW_POWER_RESET__UNLOCK | HW_POWER_RESET__PWD;
126 while(1);
129 unsigned int power_input_status(void)
131 return (usb_detect() == USB_INSERTED)
132 ? POWER_INPUT_MAIN_CHARGER : POWER_INPUT_NONE;
135 bool charging_state(void)
137 return HW_POWER_STS & HW_POWER_STS__CHRGSTS;
140 void imx233_power_set_charge_current(unsigned current)
142 __REG_CLR(HW_POWER_CHARGE) = HW_POWER_CHARGE__BATTCHRG_I_BM;
143 /* find closest current LOWER THAN OR EQUAL TO the expected current */
144 for(unsigned i = 0; i < ARRAYLEN(g_charger_current_bits); i++)
145 if(current >= g_charger_current_bits[i].current)
147 current -= g_charger_current_bits[i].current;
148 __REG_SET(HW_POWER_CHARGE) = g_charger_current_bits[i].bit;
152 void imx233_power_set_stop_current(unsigned current)
154 __REG_CLR(HW_POWER_CHARGE) = HW_POWER_CHARGE__STOP_ILIMIT_BM;
155 /* find closest current GREATHER THAN OR EQUAL TO the expected current */
156 unsigned sum = 0;
157 for(unsigned i = 0; i < ARRAYLEN(g_charger_stop_current_bits); i++)
158 sum += g_charger_stop_current_bits[i].current;
159 for(unsigned i = 0; i < ARRAYLEN(g_charger_stop_current_bits); i++)
161 sum -= g_charger_stop_current_bits[i].current;
162 if(current > sum)
164 current -= g_charger_stop_current_bits[i].current;
165 __REG_SET(HW_POWER_CHARGE) = g_charger_stop_current_bits[i].bit;
170 /* regulator info */
171 #define HAS_BO (1 << 0)
172 #define HAS_LINREG (1 << 1)
173 #define HAS_LINREG_OFFSET (1 << 2)
175 static struct
177 unsigned min, step;
178 volatile uint32_t *reg;
179 uint32_t trg_bm, trg_bp; // bitmask and bitpos
180 unsigned flags;
181 uint32_t bo_bm, bo_bp; // bitmask and bitpos
182 uint32_t linreg_bm;
183 uint32_t linreg_offset_bm, linreg_offset_bp; // bitmask and bitpos
184 } regulator_info[] =
186 #define ADD_REGULATOR(name, mask) \
187 .min = HW_POWER_##name##CTRL__TRG_MIN, \
188 .step = HW_POWER_##name##CTRL__TRG_STEP, \
189 .reg = &HW_POWER_##name##CTRL, \
190 .trg_bm = HW_POWER_##name##CTRL__TRG_BM, \
191 .trg_bp = HW_POWER_##name##CTRL__TRG_BP, \
192 .flags = mask
193 #define ADD_REGULATOR_BO(name) \
194 .bo_bm = HW_POWER_##name##CTRL__BO_OFFSET_BM, \
195 .bo_bp = HW_POWER_##name##CTRL__BO_OFFSET_BP
196 #define ADD_REGULATOR_LINREG(name) \
197 .linreg_bm = HW_POWER_##name##CTRL__ENABLE_LINREG
198 #define ADD_REGULATOR_LINREG_OFFSET(name) \
199 .linreg_offset_bm = HW_POWER_##name##CTRL__LINREG_OFFSET_BM, \
200 .linreg_offset_bp = HW_POWER_##name##CTRL__LINREG_OFFSET_BP
201 [REGULATOR_VDDD] =
203 ADD_REGULATOR(VDDD, HAS_BO|HAS_LINREG|HAS_LINREG_OFFSET),
204 ADD_REGULATOR_BO(VDDD),
205 ADD_REGULATOR_LINREG(VDDD),
206 ADD_REGULATOR_LINREG_OFFSET(VDDD)
208 [REGULATOR_VDDA] =
210 ADD_REGULATOR(VDDA, HAS_BO|HAS_LINREG|HAS_LINREG_OFFSET),
211 ADD_REGULATOR_BO(VDDA),
212 ADD_REGULATOR_LINREG(VDDA),
213 ADD_REGULATOR_LINREG_OFFSET(VDDA)
215 [REGULATOR_VDDIO] =
217 ADD_REGULATOR(VDDIO, HAS_BO|HAS_LINREG_OFFSET),
218 ADD_REGULATOR_BO(VDDIO),
219 ADD_REGULATOR_LINREG_OFFSET(VDDIO)
221 [REGULATOR_VDDMEM] =
223 ADD_REGULATOR(VDDMEM, HAS_LINREG),
224 ADD_REGULATOR_LINREG(VDDMEM),
228 void imx233_power_get_regulator(enum imx233_regulator_t reg, unsigned *value_mv,
229 unsigned *brownout_mv)
231 uint32_t reg_val = *regulator_info[reg].reg;
232 /* read target value */
233 unsigned raw_val = (reg_val & regulator_info[reg].trg_bm) >> regulator_info[reg].trg_bp;
234 /* convert it to mv */
235 if(value_mv)
236 *value_mv = regulator_info[reg].min + regulator_info[reg].step * raw_val;
237 if(regulator_info[reg].flags & HAS_BO)
239 /* read brownout offset */
240 unsigned raw_bo = (reg_val & regulator_info[reg].bo_bm) >> regulator_info[reg].bo_bp;
241 /* convert it to mv */
242 if(brownout_mv)
243 *brownout_mv = regulator_info[reg].min + regulator_info[reg].step * (raw_val - raw_bo);
245 else if(brownout_mv)
246 *brownout_mv = 0;
249 void imx233_power_set_regulator(enum imx233_regulator_t reg, unsigned value_mv,
250 unsigned brownout_mv)
252 // compute raw values
253 unsigned raw_val = (value_mv - regulator_info[reg].min) / regulator_info[reg].step;
254 unsigned raw_bo_offset = (value_mv - brownout_mv) / regulator_info[reg].step;
255 // clear dc-dc ok flag
256 __REG_SET(HW_POWER_CTRL) = HW_POWER_CTRL__DC_OK_IRQ;
257 // update
258 uint32_t reg_val = (*regulator_info[reg].reg) & ~regulator_info[reg].trg_bm;
259 reg_val |= raw_val << regulator_info[reg].trg_bp;
260 if(regulator_info[reg].flags & HAS_BO)
262 reg_val &= ~regulator_info[reg].bo_bm;
263 reg_val |= raw_bo_offset << regulator_info[reg].bo_bp;
265 *regulator_info[reg].reg = reg_val;
266 /* Wait until regulator is stable (ie brownout condition is gone)
267 * If DC-DC is used, we can use the DCDC_OK irq
268 * Otherwise it is unreliable (doesn't work when lowering voltage on linregs)
269 * It usually takes between 0.5ms and 2.5ms */
270 if(!(HW_POWER_5VCTRL & HW_POWER_5VCTRL__ENABLE_DCDC))
271 panicf("regulator %d: wait for voltage stabilize in linreg mode !", reg);
272 unsigned timeout = current_tick + (HZ * 20) / 1000;
273 while(!(HW_POWER_CTRL & HW_POWER_CTRL__DC_OK_IRQ) || !TIME_AFTER(current_tick, timeout))
274 yield();
275 if(!(HW_POWER_CTRL & HW_POWER_CTRL__DC_OK_IRQ))
276 panicf("regulator %d: failed to stabilize", reg);
279 // offset is -1,0 or 1
280 void imx233_power_get_regulator_linreg(enum imx233_regulator_t reg,
281 bool *enabled, int *linreg_offset)
283 if(enabled && regulator_info[reg].flags & HAS_LINREG)
284 *enabled = !!(*regulator_info[reg].reg & regulator_info[reg].linreg_bm);
285 else if(enabled)
286 *enabled = true;
287 if(regulator_info[reg].flags & HAS_LINREG_OFFSET)
289 unsigned v = (*regulator_info[reg].reg & regulator_info[reg].linreg_offset_bm);
290 v >>= regulator_info[reg].linreg_offset_bp;
291 if(linreg_offset)
292 *linreg_offset = (v == 0) ? 0 : (v == 1) ? 1 : -1;
294 else if(linreg_offset)
295 *linreg_offset = 0;
298 // offset is -1,0 or 1
300 void imx233_power_set_regulator_linreg(enum imx233_regulator_t reg,
301 bool enabled, int linreg_offset)
307 struct imx233_power_info_t imx233_power_get_info(unsigned flags)
309 static int dcdc_freqsel[8] = {
310 [HW_POWER_MISC__FREQSEL__RES] = 0,
311 [HW_POWER_MISC__FREQSEL__20MHz] = 20000,
312 [HW_POWER_MISC__FREQSEL__24MHz] = 24000,
313 [HW_POWER_MISC__FREQSEL__19p2MHz] = 19200,
314 [HW_POWER_MISC__FREQSEL__14p4MHz] = 14200,
315 [HW_POWER_MISC__FREQSEL__18MHz] = 18000,
316 [HW_POWER_MISC__FREQSEL__21p6MHz] = 21600,
317 [HW_POWER_MISC__FREQSEL__17p28MHz] = 17280,
320 struct imx233_power_info_t s;
321 memset(&s, 0, sizeof(s));
322 if(flags & POWER_INFO_DCDC)
324 s.dcdc_sel_pllclk = HW_POWER_MISC & HW_POWER_MISC__SEL_PLLCLK;
325 s.dcdc_freqsel = dcdc_freqsel[__XTRACT(HW_POWER_MISC, FREQSEL)];
327 if(flags & POWER_INFO_CHARGE)
329 for(unsigned i = 0; i < ARRAYLEN(g_charger_current_bits); i++)
330 if(HW_POWER_CHARGE & g_charger_current_bits[i].bit)
331 s.charge_current += g_charger_current_bits[i].current;
332 for(unsigned i = 0; i < ARRAYLEN(g_charger_stop_current_bits); i++)
333 if(HW_POWER_CHARGE & g_charger_stop_current_bits[i].bit)
334 s.stop_current += g_charger_stop_current_bits[i].current;
335 s.charging = HW_POWER_STS & HW_POWER_STS__CHRGSTS;
336 s.batt_adj = HW_POWER_BATTMONITOR & HW_POWER_BATTMONITOR__ENBATADJ;
338 if(flags & POWER_INFO_4P2)
340 s._4p2_enable = HW_POWER_DCDC4P2 & HW_POWER_DCDC4P2__ENABLE_4P2;
341 s._4p2_dcdc = HW_POWER_DCDC4P2 & HW_POWER_DCDC4P2__ENABLE_DCDC;
342 s._4p2_cmptrip = __XTRACT(HW_POWER_DCDC4P2, CMPTRIP);
343 s._4p2_dropout = __XTRACT(HW_POWER_DCDC4P2, DROPOUT_CTRL);
345 if(flags & POWER_INFO_5V)
347 s._5v_pwd_charge_4p2 = HW_POWER_5VCTRL & HW_POWER_5VCTRL__PWD_CHARGE_4P2;
348 s._5v_dcdc_xfer = HW_POWER_5VCTRL & HW_POWER_5VCTRL__DCDC_XFER;
349 s._5v_enable_dcdc = HW_POWER_5VCTRL & HW_POWER_5VCTRL__ENABLE_DCDC;
350 for(unsigned i = 0; i < ARRAYLEN(g_4p2_charge_limit_bits); i++)
351 if(HW_POWER_5VCTRL & g_4p2_charge_limit_bits[i].bit)
352 s._5v_charge_4p2_limit += g_4p2_charge_limit_bits[i].current;
353 s._5v_vbusvalid_detect = HW_POWER_5VCTRL & HW_POWER_5VCTRL__VBUSVALID_5VDETECT;
354 s._5v_vbus_cmps = HW_POWER_5VCTRL & HW_POWER_5VCTRL__PWRUP_VBUS_CMPS;
355 s._5v_vbusvalid_thr =
356 __XTRACT(HW_POWER_5VCTRL, VBUSVALID_TRSH) == 0 ?
357 2900
358 : 3900 + __XTRACT(HW_POWER_5VCTRL, VBUSVALID_TRSH) * 100;
360 return s;