diff options
Diffstat (limited to 'firmware/drivers/axp192.c')
-rw-r--r-- | firmware/drivers/axp192.c | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/firmware/drivers/axp192.c b/firmware/drivers/axp192.c new file mode 100644 index 0000000000..3c61d8c533 --- /dev/null +++ b/firmware/drivers/axp192.c | |||
@@ -0,0 +1,810 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
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 | |||
22 | #include "axp192.h" | ||
23 | #include "system.h" | ||
24 | #include "power.h" | ||
25 | #include "i2c-async.h" | ||
26 | #include "logf.h" | ||
27 | |||
28 | /* | ||
29 | * Direct register access | ||
30 | */ | ||
31 | |||
32 | int axp_read(uint8_t reg) | ||
33 | { | ||
34 | int ret = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, reg); | ||
35 | if(ret < 0) | ||
36 | logf("axp: read reg %02x err=%d", reg, ret); | ||
37 | |||
38 | return ret; | ||
39 | } | ||
40 | |||
41 | int axp_write(uint8_t reg, uint8_t value) | ||
42 | { | ||
43 | int ret = i2c_reg_write1(AXP_PMU_BUS, AXP_PMU_ADDR, reg, value); | ||
44 | if(ret < 0) | ||
45 | logf("axp: write reg %02x err=%d", reg, ret); | ||
46 | |||
47 | return ret; | ||
48 | } | ||
49 | |||
50 | int axp_modify(uint8_t reg, uint8_t clr, uint8_t set) | ||
51 | { | ||
52 | int ret = i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, reg, clr, set, NULL); | ||
53 | if(ret < 0) | ||
54 | logf("axp: modify reg %02x err=%d", reg, ret); | ||
55 | |||
56 | return ret; | ||
57 | } | ||
58 | |||
59 | /* | ||
60 | * Power supplies: enable/disable, set voltage | ||
61 | */ | ||
62 | |||
63 | struct axp_supplydata { | ||
64 | uint8_t en_reg; | ||
65 | uint8_t en_bit; | ||
66 | uint8_t volt_reg; | ||
67 | uint8_t volt_msb: 4; | ||
68 | uint8_t volt_lsb: 4; | ||
69 | short min_mV; | ||
70 | short step_mV; | ||
71 | }; | ||
72 | |||
73 | static const struct axp_supplydata supplydata[] = { | ||
74 | [AXP_SUPPLY_EXTEN] = { | ||
75 | .en_reg = AXP_REG_PWRCTL1, | ||
76 | .en_bit = 1 << 2, | ||
77 | .volt_reg = 0xff, /* undefined */ | ||
78 | .volt_msb = 0xf, | ||
79 | .volt_lsb = 0xf, | ||
80 | .min_mV = 0, | ||
81 | .step_mV = 0, | ||
82 | }, | ||
83 | [AXP_SUPPLY_DCDC1] = { | ||
84 | .en_reg = AXP_REG_PWRCTL2, | ||
85 | .en_bit = 1 << 0, | ||
86 | .volt_reg = AXP_REG_DCDC1VOLT, | ||
87 | .volt_msb = 6, | ||
88 | .volt_lsb = 0, | ||
89 | .min_mV = 700, | ||
90 | .step_mV = 25, | ||
91 | }, | ||
92 | [AXP_SUPPLY_DCDC2] = { | ||
93 | .en_reg = AXP_REG_PWRCTL1, | ||
94 | .en_bit = 1 << 0, | ||
95 | .volt_reg = AXP_REG_DCDC2VOLT, | ||
96 | .volt_msb = 5, | ||
97 | .volt_lsb = 0, | ||
98 | .min_mV = 700, | ||
99 | .step_mV = 25, | ||
100 | }, | ||
101 | [AXP_SUPPLY_DCDC3] = { | ||
102 | .en_reg = AXP_REG_PWRCTL2, | ||
103 | .en_bit = 1 << 1, | ||
104 | .volt_reg = AXP_REG_DCDC3VOLT, | ||
105 | .volt_msb = 6, | ||
106 | .volt_lsb = 0, | ||
107 | .min_mV = 700, | ||
108 | .step_mV = 25, | ||
109 | }, | ||
110 | [AXP_SUPPLY_LDO2] = { | ||
111 | .en_reg = AXP_REG_PWRCTL2, | ||
112 | .en_bit = 1 << 2, | ||
113 | .volt_reg = AXP_REG_LDO2LDO3VOLT, | ||
114 | .volt_msb = 7, | ||
115 | .volt_lsb = 4, | ||
116 | .min_mV = 1800, | ||
117 | .step_mV = 100, | ||
118 | }, | ||
119 | [AXP_SUPPLY_LDO3] = { | ||
120 | .en_reg = AXP_REG_PWRCTL2, | ||
121 | .en_bit = 1 << 3, | ||
122 | .volt_reg = AXP_REG_LDO2LDO3VOLT, | ||
123 | .volt_msb = 3, | ||
124 | .volt_lsb = 0, | ||
125 | .min_mV = 1800, | ||
126 | .step_mV = 100, | ||
127 | }, | ||
128 | [AXP_SUPPLY_LDOIO0] = { | ||
129 | .en_reg = 0xff, /* undefined */ | ||
130 | .en_bit = 0, | ||
131 | .volt_reg = AXP_REG_GPIO0LDO, | ||
132 | .volt_msb = 7, | ||
133 | .volt_lsb = 4, | ||
134 | .min_mV = 1800, | ||
135 | .step_mV = 100, | ||
136 | }, | ||
137 | }; | ||
138 | |||
139 | void axp_enable_supply(int supply, bool enable) | ||
140 | { | ||
141 | const struct axp_supplydata* data = &supplydata[supply]; | ||
142 | axp_modify(data->en_reg, data->en_bit, enable ? data->en_bit : 0); | ||
143 | } | ||
144 | |||
145 | void axp_set_enabled_supplies(unsigned int supply_mask) | ||
146 | { | ||
147 | uint8_t xfer[3]; | ||
148 | xfer[0] = 0; | ||
149 | xfer[1] = AXP_REG_PWRCTL2; | ||
150 | xfer[2] = 0; | ||
151 | |||
152 | for(int i = 0; i < AXP_NUM_SUPPLIES; ++i) { | ||
153 | if(!(supply_mask & (1 << i))) | ||
154 | continue; | ||
155 | |||
156 | const struct axp_supplydata* data = &supplydata[i]; | ||
157 | if(data->en_reg == AXP_REG_PWRCTL1) { | ||
158 | xfer[0] |= data->en_bit; | ||
159 | xfer[2] |= data->en_bit << 4; /* HACK: work around AXP quirk */ | ||
160 | } else { | ||
161 | xfer[2] |= data->en_bit; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | i2c_reg_write(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_PWRCTL1, 3, xfer); | ||
166 | } | ||
167 | |||
168 | void axp_set_supply_voltage(int supply, int output_mV) | ||
169 | { | ||
170 | const struct axp_supplydata* data = &supplydata[supply]; | ||
171 | uint8_t mask = (1 << (data->volt_msb - data->volt_lsb + 1)) - 1; | ||
172 | uint8_t value = (output_mV - data->min_mV) / data->step_mV; | ||
173 | axp_modify(data->volt_reg, mask << data->volt_lsb, value << data->volt_lsb); | ||
174 | } | ||
175 | |||
176 | /* | ||
177 | * ADC control: enable/disable, read | ||
178 | */ | ||
179 | |||
180 | struct axp_adcdata { | ||
181 | uint8_t data_reg; | ||
182 | uint8_t en_reg; | ||
183 | uint8_t en_bit; | ||
184 | int8_t num; | ||
185 | int8_t den; | ||
186 | }; | ||
187 | |||
188 | static const struct axp_adcdata adcdata[] = { | ||
189 | [AXP_ADC_ACIN_VOLTAGE] = {0x56, AXP_REG_ADCEN1, 1 << 5, 17, 10}, | ||
190 | [AXP_ADC_ACIN_CURRENT] = {0x58, AXP_REG_ADCEN1, 1 << 4, 5, 8}, | ||
191 | [AXP_ADC_VBUS_VOLTAGE] = {0x5a, AXP_REG_ADCEN1, 1 << 3, 17, 10}, | ||
192 | [AXP_ADC_VBUS_CURRENT] = {0x5c, AXP_REG_ADCEN1, 1 << 2, 3, 8}, | ||
193 | [AXP_ADC_INTERNAL_TEMP] = {0x5e, AXP_REG_ADCEN2, 1 << 7, 0, 0}, | ||
194 | [AXP_ADC_TS_INPUT] = {0x62, AXP_REG_ADCEN1, 1 << 0, 4, 5}, | ||
195 | [AXP_ADC_GPIO0] = {0x64, AXP_REG_ADCEN2, 1 << 3, 1, 2}, | ||
196 | [AXP_ADC_GPIO1] = {0x66, AXP_REG_ADCEN2, 1 << 2, 1, 2}, | ||
197 | [AXP_ADC_GPIO2] = {0x68, AXP_REG_ADCEN2, 1 << 1, 1, 2}, | ||
198 | [AXP_ADC_GPIO3] = {0x6a, AXP_REG_ADCEN2, 1 << 0, 1, 2}, | ||
199 | [AXP_ADC_BATTERY_VOLTAGE] = {0x78, AXP_REG_ADCEN1, 1 << 7, 11, 10}, | ||
200 | [AXP_ADC_CHARGE_CURRENT] = {0x7a, AXP_REG_ADCEN1, 1 << 6, 1, 2}, | ||
201 | [AXP_ADC_DISCHARGE_CURRENT] = {0x7c, AXP_REG_ADCEN1, 1 << 6, 1, 2}, | ||
202 | [AXP_ADC_APS_VOLTAGE] = {0x7e, AXP_REG_ADCEN1, 1 << 1, 7, 5}, | ||
203 | }; | ||
204 | |||
205 | void axp_enable_adc(int adc, bool enable) | ||
206 | { | ||
207 | const struct axp_adcdata* data = &adcdata[adc]; | ||
208 | axp_modify(data->en_reg, data->en_bit, enable ? data->en_bit : 0); | ||
209 | } | ||
210 | |||
211 | void axp_set_enabled_adcs(unsigned int adc_mask) | ||
212 | { | ||
213 | uint8_t xfer[3]; | ||
214 | xfer[0] = 0; | ||
215 | xfer[1] = AXP_REG_ADCEN2; | ||
216 | xfer[2] = 0; | ||
217 | |||
218 | for(int i = 0; i < AXP_NUM_ADCS; ++i) { | ||
219 | if(!(adc_mask & (1 << i))) | ||
220 | continue; | ||
221 | |||
222 | const struct axp_adcdata* data = &adcdata[i]; | ||
223 | if(data->en_reg == AXP_REG_ADCEN1) | ||
224 | xfer[0] |= data->en_bit; | ||
225 | else | ||
226 | xfer[2] |= data->en_bit; | ||
227 | } | ||
228 | |||
229 | i2c_reg_write(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_ADCEN1, 3, xfer); | ||
230 | } | ||
231 | |||
232 | int axp_read_adc_raw(int adc) | ||
233 | { | ||
234 | uint8_t data[2]; | ||
235 | int ret = i2c_reg_read(AXP_PMU_BUS, AXP_PMU_ADDR, | ||
236 | adcdata[adc].data_reg, 2, data); | ||
237 | if(ret < 0) { | ||
238 | logf("axp: ADC read failed, err=%d", ret); | ||
239 | return INT_MIN; | ||
240 | } | ||
241 | |||
242 | if(adc == AXP_ADC_CHARGE_CURRENT || adc == AXP_ADC_DISCHARGE_CURRENT) | ||
243 | return (data[0] << 5) | data[1]; | ||
244 | else | ||
245 | return (data[0] << 4) | data[1]; | ||
246 | } | ||
247 | |||
248 | int axp_conv_adc(int adc, int value) | ||
249 | { | ||
250 | const struct axp_adcdata* data = &adcdata[adc]; | ||
251 | if(adc == AXP_ADC_INTERNAL_TEMP) | ||
252 | return value - 1447; | ||
253 | else | ||
254 | return data->num * value / data->den; | ||
255 | } | ||
256 | |||
257 | int axp_read_adc(int adc) | ||
258 | { | ||
259 | int ret = axp_read_adc_raw(adc); | ||
260 | if(ret == INT_MIN) | ||
261 | return ret; | ||
262 | |||
263 | return axp_conv_adc(adc, ret); | ||
264 | } | ||
265 | |||
266 | /* | ||
267 | * GPIOs: set function, pull down control, get/set pin level | ||
268 | */ | ||
269 | |||
270 | struct axp_gpiodata { | ||
271 | uint8_t func_reg; | ||
272 | uint8_t func_msb: 4; | ||
273 | uint8_t func_lsb: 4; | ||
274 | uint8_t level_reg; | ||
275 | uint8_t level_out: 4; | ||
276 | uint8_t level_in: 4; | ||
277 | }; | ||
278 | |||
279 | static const struct axp_gpiodata gpiodata[] = { | ||
280 | {AXP_REG_GPIO0FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 0, 4}, | ||
281 | {AXP_REG_GPIO1FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 1, 5}, | ||
282 | {AXP_REG_GPIO2FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 2, 6}, | ||
283 | {AXP_REG_GPIO3GPIO4FUNC, 1, 0, AXP_REG_GPIOLEVEL2, 0, 4}, | ||
284 | {AXP_REG_GPIO3GPIO4FUNC, 3, 2, AXP_REG_GPIOLEVEL2, 1, 5}, | ||
285 | {AXP_REG_NRSTO, 7, 6, AXP_REG_NRSTO, 4, 5}, | ||
286 | }; | ||
287 | |||
288 | static const uint8_t gpio34funcmap[8] = { | ||
289 | [AXP_GPIO_SPECIAL] = 0x0, | ||
290 | [AXP_GPIO_OPEN_DRAIN_OUTPUT] = 0x1, | ||
291 | [AXP_GPIO_INPUT] = 0x2, | ||
292 | [AXP_GPIO_ADC_IN] = 0x3, | ||
293 | }; | ||
294 | |||
295 | static const uint8_t nrstofuncmap[8] = { | ||
296 | [AXP_GPIO_SPECIAL] = 0x0, | ||
297 | [AXP_GPIO_OPEN_DRAIN_OUTPUT] = 0x2, | ||
298 | [AXP_GPIO_INPUT] = 0x3, | ||
299 | }; | ||
300 | |||
301 | void axp_set_gpio_function(int gpio, int function) | ||
302 | { | ||
303 | const struct axp_gpiodata* data = &gpiodata[gpio]; | ||
304 | int mask = (1 << (data->func_msb - data->func_lsb + 1)) - 1; | ||
305 | |||
306 | if(gpio == 5) | ||
307 | function = nrstofuncmap[function]; | ||
308 | else if(gpio >= 3) | ||
309 | function = gpio34funcmap[function]; | ||
310 | |||
311 | axp_modify(data->func_reg, mask << data->func_lsb, function << data->func_lsb); | ||
312 | } | ||
313 | |||
314 | void axp_set_gpio_pulldown(int gpio, bool enable) | ||
315 | { | ||
316 | int bit = 1 << gpio; | ||
317 | axp_modify(AXP_REG_GPIOPULL, bit, enable ? bit : 0); | ||
318 | } | ||
319 | |||
320 | int axp_get_gpio(int gpio) | ||
321 | { | ||
322 | const struct axp_gpiodata* data = &gpiodata[gpio]; | ||
323 | return axp_read(data->level_reg) & (1 << data->level_in); | ||
324 | } | ||
325 | |||
326 | void axp_set_gpio(int gpio, bool enable) | ||
327 | { | ||
328 | const struct axp_gpiodata* data = &gpiodata[gpio]; | ||
329 | uint8_t bit = 1 << data->level_out; | ||
330 | axp_modify(data->level_reg, bit, enable ? bit : 0); | ||
331 | } | ||
332 | |||
333 | /* | ||
334 | * Charging: set charging current, query charging/input status | ||
335 | */ | ||
336 | |||
337 | static const short chargecurrent_tbl[] = { | ||
338 | 100, 190, 280, 360, | ||
339 | 450, 550, 630, 700, | ||
340 | 780, 880, 960, 1000, | ||
341 | 1080, 1160, 1240, 1320, | ||
342 | }; | ||
343 | |||
344 | void axp_set_charge_current(int current_mA) | ||
345 | { | ||
346 | /* find greatest charging current not exceeding requested current */ | ||
347 | unsigned int index = 0; | ||
348 | while(index < ARRAYLEN(chargecurrent_tbl)-1 && | ||
349 | chargecurrent_tbl[index+1] <= current_mA) | ||
350 | ++index; | ||
351 | |||
352 | axp_modify(AXP_REG_CHGCTL1, BM_AXP_CHGCTL1_CHARGE_CURRENT, | ||
353 | index << BP_AXP_CHGCTL1_CHARGE_CURRENT); | ||
354 | } | ||
355 | |||
356 | int axp_get_charge_current(void) | ||
357 | { | ||
358 | int value = axp_read(AXP_REG_CHGCTL1); | ||
359 | if(value < 0) | ||
360 | value = 0; | ||
361 | |||
362 | value &= BM_AXP_CHGCTL1_CHARGE_CURRENT; | ||
363 | value >>= BP_AXP_CHGCTL1_CHARGE_CURRENT; | ||
364 | return chargecurrent_tbl[value]; | ||
365 | } | ||
366 | |||
367 | void axp_set_vbus_limit(int mode) | ||
368 | { | ||
369 | const int mask = BM_AXP_VBUSIPSOUT_VHOLD_LIM | | ||
370 | BM_AXP_VBUSIPSOUT_VBUS_LIM | | ||
371 | BM_AXP_VBUSIPSOUT_LIM_100mA; | ||
372 | |||
373 | axp_modify(AXP_REG_VBUSIPSOUT, mask, mode); | ||
374 | } | ||
375 | |||
376 | void axp_set_vhold_level(int vhold_mV) | ||
377 | { | ||
378 | if(vhold_mV < 4000) | ||
379 | vhold_mV = 4000; | ||
380 | else if(vhold_mV > 4700) | ||
381 | vhold_mV = 4700; | ||
382 | |||
383 | int level = (vhold_mV - 4000) / 100; | ||
384 | axp_modify(AXP_REG_VBUSIPSOUT, BM_AXP_VBUSIPSOUT_VHOLD_LEV, | ||
385 | level << BP_AXP_VBUSIPSOUT_VHOLD_LEV); | ||
386 | } | ||
387 | |||
388 | bool axp_is_charging(void) | ||
389 | { | ||
390 | int value = axp_read(AXP_REG_CHGSTS); | ||
391 | return (value >= 0) && (value & BM_AXP_CHGSTS_CHARGING); | ||
392 | } | ||
393 | |||
394 | unsigned int axp_power_input_status(void) | ||
395 | { | ||
396 | unsigned int state = 0; | ||
397 | int value = axp_read(AXP_REG_PWRSTS); | ||
398 | if(value >= 0) { | ||
399 | /* ACIN is the main charger. Includes USB */ | ||
400 | if(value & BM_AXP_PWRSTS_ACIN_VALID) | ||
401 | state |= POWER_INPUT_MAIN_CHARGER; | ||
402 | |||
403 | /* Report USB separately if discernable from ACIN */ | ||
404 | if((value & BM_AXP_PWRSTS_VBUS_VALID) && | ||
405 | !(value & BM_AXP_PWRSTS_PCB_SHORTED)) | ||
406 | state |= POWER_INPUT_USB_CHARGER; | ||
407 | } | ||
408 | |||
409 | #ifdef HAVE_BATTERY_SWITCH | ||
410 | /* If target allows switching batteries then report if the | ||
411 | * battery is present or not */ | ||
412 | value = axp_read(AXP_REG_CHGSTS); | ||
413 | if(value >= 0 && (value & BM_AXP_CHGSTS_BATT_PRESENT)) | ||
414 | state |= POWER_INPUT_BATTERY; | ||
415 | #endif | ||
416 | |||
417 | return state; | ||
418 | } | ||
419 | |||
420 | /* | ||
421 | * Misc. functions | ||
422 | */ | ||
423 | |||
424 | void axp_power_off(void) | ||
425 | { | ||
426 | axp_modify(AXP_REG_PWROFF, BM_AXP_PWROFF_SHUTDOWN, BM_AXP_PWROFF_SHUTDOWN); | ||
427 | } | ||
428 | |||
429 | /* | ||
430 | * Debug menu | ||
431 | */ | ||
432 | |||
433 | #ifndef BOOTLOADER | ||
434 | #include "action.h" | ||
435 | #include "list.h" | ||
436 | #include "splash.h" | ||
437 | #include <stdio.h> | ||
438 | |||
439 | /* enable extra debug menus which are only useful for development, | ||
440 | * allow potentially dangerous operations and increase code size | ||
441 | * significantly */ | ||
442 | /*#define AXP_EXTRA_DEBUG*/ | ||
443 | |||
444 | enum { | ||
445 | MODE_ADC, | ||
446 | #ifdef AXP_EXTRA_DEBUG | ||
447 | MODE_SUPPLY, | ||
448 | MODE_REGISTER, | ||
449 | #endif | ||
450 | NUM_MODES, | ||
451 | }; | ||
452 | |||
453 | static const char* const axp_modenames[NUM_MODES] = { | ||
454 | [MODE_ADC] = "ADCs", | ||
455 | #ifdef AXP_EXTRA_DEBUG | ||
456 | [MODE_SUPPLY] = "Power supplies", | ||
457 | [MODE_REGISTER] = "Register viewer", | ||
458 | #endif | ||
459 | }; | ||
460 | |||
461 | struct axp_adcdebuginfo { | ||
462 | const char* name; | ||
463 | const char* unit; | ||
464 | }; | ||
465 | |||
466 | static const struct axp_adcdebuginfo adc_debuginfo[AXP_NUM_ADCS] = { | ||
467 | [AXP_ADC_ACIN_VOLTAGE] = {"V_acin", "mV"}, | ||
468 | [AXP_ADC_ACIN_CURRENT] = {"I_acin", "mA"}, | ||
469 | [AXP_ADC_VBUS_VOLTAGE] = {"V_vbus", "mV"}, | ||
470 | [AXP_ADC_VBUS_CURRENT] = {"I_vbus", "mA"}, | ||
471 | [AXP_ADC_INTERNAL_TEMP] = {"T_int", "C"}, | ||
472 | [AXP_ADC_TS_INPUT] = {"V_ts", "mV"}, | ||
473 | [AXP_ADC_GPIO0] = {"V_gpio0", "mV"}, | ||
474 | [AXP_ADC_GPIO1] = {"V_gpio1", "mV"}, | ||
475 | [AXP_ADC_GPIO2] = {"V_gpio2", "mV"}, | ||
476 | [AXP_ADC_GPIO3] = {"V_gpio3", "mV"}, | ||
477 | [AXP_ADC_BATTERY_VOLTAGE] = {"V_batt", "mV"}, | ||
478 | [AXP_ADC_CHARGE_CURRENT] = {"I_chrg", "mA"}, | ||
479 | [AXP_ADC_DISCHARGE_CURRENT] = {"I_dchg", "mA"}, | ||
480 | [AXP_ADC_APS_VOLTAGE] = {"V_aps", "mV"}, | ||
481 | }; | ||
482 | |||
483 | #ifdef AXP_EXTRA_DEBUG | ||
484 | static const char* supply_names[AXP_NUM_SUPPLIES] = { | ||
485 | [AXP_SUPPLY_EXTEN] = "EXTEN", | ||
486 | [AXP_SUPPLY_DCDC1] = "DCDC1", | ||
487 | [AXP_SUPPLY_DCDC2] = "DCDC2", | ||
488 | [AXP_SUPPLY_DCDC3] = "DCDC3", | ||
489 | [AXP_SUPPLY_LDO2] = "LDO2", | ||
490 | [AXP_SUPPLY_LDO3] = "LDO3", | ||
491 | [AXP_SUPPLY_LDOIO0] = "LDOIO0", | ||
492 | }; | ||
493 | |||
494 | struct axp_fieldinfo { | ||
495 | uint8_t rnum; | ||
496 | uint8_t msb: 4; | ||
497 | uint8_t lsb: 4; | ||
498 | }; | ||
499 | |||
500 | enum { | ||
501 | #define DEFREG(name, ...) AXP_RNUM_##name, | ||
502 | #include "axp192-defs.h" | ||
503 | AXP_NUM_REGS, | ||
504 | }; | ||
505 | |||
506 | enum { | ||
507 | #define DEFFLD(regname, fldname, ...) AXP_FNUM_##regname##_##fldname, | ||
508 | #include "axp192-defs.h" | ||
509 | AXP_NUM_FIELDS, | ||
510 | }; | ||
511 | |||
512 | static const uint8_t axp_regaddr[AXP_NUM_REGS] = { | ||
513 | #define DEFREG(name, addr) addr, | ||
514 | #include "axp192-defs.h" | ||
515 | }; | ||
516 | |||
517 | static const struct axp_fieldinfo axp_fieldinfo[AXP_NUM_FIELDS] = { | ||
518 | #define DEFFLD(regname, fldname, _msb, _lsb, ...) \ | ||
519 | {.rnum = AXP_RNUM_##regname, .msb = _msb, .lsb = _lsb}, | ||
520 | #include "axp192-defs.h" | ||
521 | }; | ||
522 | |||
523 | static const char* const axp_regnames[AXP_NUM_REGS] = { | ||
524 | #define DEFREG(name, ...) #name, | ||
525 | #include "axp192-defs.h" | ||
526 | }; | ||
527 | |||
528 | static const char* const axp_fldnames[AXP_NUM_FIELDS] = { | ||
529 | #define DEFFLD(regname, fldname, ...) #fldname, | ||
530 | #include "axp192-defs.h" | ||
531 | }; | ||
532 | #endif /* AXP_EXTRA_DEBUG */ | ||
533 | |||
534 | struct axp_debug_menu_state { | ||
535 | int mode; | ||
536 | #ifdef AXP_EXTRA_DEBUG | ||
537 | int reg_num; | ||
538 | int field_num; | ||
539 | int field_cnt; | ||
540 | uint8_t cache[AXP_NUM_REGS]; | ||
541 | uint8_t is_cached[AXP_NUM_REGS]; | ||
542 | #endif | ||
543 | }; | ||
544 | |||
545 | #ifdef AXP_EXTRA_DEBUG | ||
546 | static void axp_debug_clear_cache(struct axp_debug_menu_state* state) | ||
547 | { | ||
548 | memset(state->is_cached, 0, sizeof(state->is_cached)); | ||
549 | } | ||
550 | |||
551 | static int axp_debug_get_rnum(uint8_t addr) | ||
552 | { | ||
553 | for(int i = 0; i < AXP_NUM_REGS; ++i) | ||
554 | if(axp_regaddr[i] == addr) | ||
555 | return i; | ||
556 | |||
557 | return -1; | ||
558 | } | ||
559 | |||
560 | static uint8_t axp_debug_read(struct axp_debug_menu_state* state, int rnum) | ||
561 | { | ||
562 | if(state->is_cached[rnum]) | ||
563 | return state->cache[rnum]; | ||
564 | |||
565 | int value = axp_read(axp_regaddr[rnum]); | ||
566 | if(value < 0) | ||
567 | return 0; | ||
568 | |||
569 | state->is_cached[rnum] = 1; | ||
570 | state->cache[rnum] = value; | ||
571 | return value; | ||
572 | } | ||
573 | |||
574 | static void axp_debug_get_sel(const struct axp_debug_menu_state* state, | ||
575 | int item, int* rnum, int* fnum) | ||
576 | { | ||
577 | if(state->reg_num >= 0 && state->field_num >= 0) { | ||
578 | int i = item - state->reg_num; | ||
579 | if(i <= 0) { | ||
580 | /* preceding register is selected */ | ||
581 | } else if(i <= state->field_cnt) { | ||
582 | /* field is selected */ | ||
583 | *rnum = state->reg_num; | ||
584 | *fnum = i + state->field_num - 1; | ||
585 | return; | ||
586 | } else { | ||
587 | /* subsequent regiser is selected */ | ||
588 | item -= state->field_cnt; | ||
589 | } | ||
590 | } | ||
591 | |||
592 | /* register is selected */ | ||
593 | *rnum = item; | ||
594 | *fnum = -1; | ||
595 | } | ||
596 | |||
597 | static int axp_debug_set_sel(struct axp_debug_menu_state* state, int rnum) | ||
598 | { | ||
599 | state->reg_num = rnum; | ||
600 | state->field_num = -1; | ||
601 | state->field_cnt = 0; | ||
602 | |||
603 | for(int i = 0; i < AXP_NUM_FIELDS; ++i) { | ||
604 | if(axp_fieldinfo[i].rnum != rnum) | ||
605 | continue; | ||
606 | |||
607 | state->field_num = i; | ||
608 | do { | ||
609 | state->field_cnt++; | ||
610 | i++; | ||
611 | } while(axp_fieldinfo[i].rnum == rnum); | ||
612 | break; | ||
613 | } | ||
614 | |||
615 | return rnum; | ||
616 | } | ||
617 | #endif /* AXP_EXTRA_DEBUG */ | ||
618 | |||
619 | static const char* axp_debug_menu_get_name(int item, void* data, | ||
620 | char* buf, size_t buflen) | ||
621 | { | ||
622 | struct axp_debug_menu_state* state = data; | ||
623 | int value; | ||
624 | |||
625 | /* for safety */ | ||
626 | buf[0] = '\0'; | ||
627 | |||
628 | if(state->mode == MODE_ADC && item < AXP_NUM_ADCS) | ||
629 | { | ||
630 | const struct axp_adcdebuginfo* info = &adc_debuginfo[item]; | ||
631 | value = axp_read_adc(item); | ||
632 | if(item == AXP_ADC_INTERNAL_TEMP) { | ||
633 | snprintf(buf, buflen, "%s: %d.%d %s", | ||
634 | info->name, value/10, value%10, info->unit); | ||
635 | } else { | ||
636 | snprintf(buf, buflen, "%s: %d %s", info->name, value, info->unit); | ||
637 | } | ||
638 | } | ||
639 | #ifdef AXP_EXTRA_DEBUG | ||
640 | else if(state->mode == MODE_SUPPLY && item < AXP_NUM_SUPPLIES) | ||
641 | { | ||
642 | const struct axp_supplydata* data = &supplydata[item]; | ||
643 | int en_rnum = axp_debug_get_rnum(data->en_reg); | ||
644 | int volt_rnum = axp_debug_get_rnum(data->volt_reg); | ||
645 | bool enabled = false; | ||
646 | int voltage = -1; | ||
647 | |||
648 | if(en_rnum >= 0) { | ||
649 | value = axp_debug_read(state, en_rnum); | ||
650 | if(value & data->en_bit) | ||
651 | enabled = true; | ||
652 | else | ||
653 | enabled = false; | ||
654 | } else if(item == AXP_SUPPLY_LDOIO0) { | ||
655 | value = axp_debug_read(state, AXP_RNUM_GPIO0FUNC); | ||
656 | if((value & 0x7) == AXP_GPIO_SPECIAL) | ||
657 | enabled = true; | ||
658 | else | ||
659 | enabled = false; | ||
660 | } | ||
661 | |||
662 | if(volt_rnum >= 0) { | ||
663 | voltage = axp_debug_read(state, volt_rnum); | ||
664 | voltage >>= data->volt_lsb; | ||
665 | voltage &= (1 << (data->volt_msb - data->volt_lsb + 1)) - 1; | ||
666 | |||
667 | /* convert to mV */ | ||
668 | voltage = data->min_mV + voltage * data->step_mV; | ||
669 | } | ||
670 | |||
671 | if(enabled && voltage >= 0) { | ||
672 | snprintf(buf, buflen, "%s: %d mV", | ||
673 | supply_names[item], voltage); | ||
674 | } else { | ||
675 | snprintf(buf, buflen, "%s: %sabled", | ||
676 | supply_names[item], enabled ? "en" : "dis"); | ||
677 | } | ||
678 | } | ||
679 | else if(state->mode == MODE_REGISTER) | ||
680 | { | ||
681 | int rnum, fnum; | ||
682 | axp_debug_get_sel(state, item, &rnum, &fnum); | ||
683 | |||
684 | if(fnum >= 0) { | ||
685 | const struct axp_fieldinfo* info = &axp_fieldinfo[fnum]; | ||
686 | value = axp_debug_read(state, info->rnum); | ||
687 | value >>= info->lsb; | ||
688 | value &= (1 << (info->msb - info->lsb + 1)) - 1; | ||
689 | snprintf(buf, buflen, "\t%s: %d (0x%x)", | ||
690 | axp_fldnames[fnum], value, value); | ||
691 | } else if(rnum < AXP_NUM_REGS) { | ||
692 | value = axp_debug_read(state, rnum); | ||
693 | snprintf(buf, buflen, "%s: 0x%02x", axp_regnames[rnum], value); | ||
694 | } | ||
695 | } | ||
696 | #endif /* AXP_EXTRA_DEBUG */ | ||
697 | |||
698 | return buf; | ||
699 | } | ||
700 | |||
701 | static int axp_debug_menu_cb(int action, struct gui_synclist* lists) | ||
702 | { | ||
703 | struct axp_debug_menu_state* state = lists->data; | ||
704 | |||
705 | if(state->mode == MODE_ADC) | ||
706 | { | ||
707 | /* update continuously */ | ||
708 | if(action == ACTION_NONE) | ||
709 | action = ACTION_REDRAW; | ||
710 | } | ||
711 | #ifdef AXP_EXTRA_DEBUG | ||
712 | else if(state->mode == MODE_REGISTER) | ||
713 | { | ||
714 | if(action == ACTION_STD_OK) { | ||
715 | /* expand a register to show its fields */ | ||
716 | int rnum, fnum; | ||
717 | int sel_pos = gui_synclist_get_sel_pos(lists); | ||
718 | axp_debug_get_sel(state, sel_pos, &rnum, &fnum); | ||
719 | if(fnum < 0 && rnum < AXP_NUM_REGS) { | ||
720 | int delta_items = -state->field_cnt; | ||
721 | if(rnum != state->reg_num) { | ||
722 | if(rnum > state->reg_num) | ||
723 | sel_pos += delta_items; | ||
724 | |||
725 | axp_debug_set_sel(state, rnum); | ||
726 | delta_items += state->field_cnt; | ||
727 | } else { | ||
728 | state->reg_num = -1; | ||
729 | state->field_num = -1; | ||
730 | state->field_cnt = 0; | ||
731 | } | ||
732 | |||
733 | gui_synclist_set_nb_items(lists, lists->nb_items + delta_items); | ||
734 | gui_synclist_select_item(lists, sel_pos); | ||
735 | action = ACTION_REDRAW; | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | else if(state->mode == MODE_SUPPLY) | ||
740 | { | ||
741 | /* disable a supply... use with caution */ | ||
742 | if(action == ACTION_STD_CONTEXT) { | ||
743 | int sel_pos = gui_synclist_get_sel_pos(lists); | ||
744 | axp_enable_supply(sel_pos, false); | ||
745 | } | ||
746 | } | ||
747 | #endif | ||
748 | |||
749 | #ifdef AXP_EXTRA_DEBUG | ||
750 | /* clear register cache to refresh values */ | ||
751 | if(state->mode != MODE_ADC && action == ACTION_STD_CONTEXT) { | ||
752 | splashf(HZ/2, "Refreshed"); | ||
753 | axp_debug_clear_cache(state); | ||
754 | action = ACTION_REDRAW; | ||
755 | } | ||
756 | #endif | ||
757 | |||
758 | /* mode switching */ | ||
759 | if(action == ACTION_STD_MENU) { | ||
760 | state->mode = (state->mode + 1) % NUM_MODES; | ||
761 | gui_synclist_set_title(lists, (char*)axp_modenames[state->mode], Icon_NOICON); | ||
762 | action = ACTION_REDRAW; | ||
763 | |||
764 | switch(state->mode) { | ||
765 | case MODE_ADC: | ||
766 | gui_synclist_set_nb_items(lists, AXP_NUM_ADCS); | ||
767 | gui_synclist_select_item(lists, 0); | ||
768 | break; | ||
769 | |||
770 | #ifdef AXP_EXTRA_DEBUG | ||
771 | case MODE_SUPPLY: | ||
772 | axp_debug_clear_cache(state); | ||
773 | gui_synclist_set_nb_items(lists, AXP_NUM_SUPPLIES); | ||
774 | gui_synclist_select_item(lists, 0); | ||
775 | break; | ||
776 | |||
777 | case MODE_REGISTER: | ||
778 | state->reg_num = -1; | ||
779 | state->field_num = -1; | ||
780 | state->field_cnt = 0; | ||
781 | axp_debug_clear_cache(state); | ||
782 | gui_synclist_set_nb_items(lists, AXP_NUM_REGS); | ||
783 | gui_synclist_select_item(lists, 0); | ||
784 | break; | ||
785 | #endif | ||
786 | } | ||
787 | } | ||
788 | |||
789 | return action; | ||
790 | } | ||
791 | |||
792 | bool axp_debug_menu(void) | ||
793 | { | ||
794 | struct axp_debug_menu_state state; | ||
795 | state.mode = MODE_ADC; | ||
796 | #ifdef AXP_EXTRA_DEBUG | ||
797 | state.reg_num = -1; | ||
798 | state.field_num = -1; | ||
799 | state.field_cnt = 0; | ||
800 | axp_debug_clear_cache(&state); | ||
801 | #endif | ||
802 | |||
803 | struct simplelist_info info; | ||
804 | simplelist_info_init(&info, (char*)axp_modenames[state.mode], | ||
805 | AXP_NUM_ADCS, &state); | ||
806 | info.get_name = axp_debug_menu_get_name; | ||
807 | info.action_callback = axp_debug_menu_cb; | ||
808 | return simplelist_show_list(&info); | ||
809 | } | ||
810 | #endif | ||