summaryrefslogtreecommitdiff
path: root/firmware/drivers
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2022-01-09 16:03:30 +0000
committerAidan MacDonald <amachronic@protonmail.com>2022-01-09 19:58:34 +0000
commiteaee5e7339aec46b4b67f06b139b8708810a4c2e (patch)
tree28bac18b6a68caae4689c712b4b7d2ab80fed583 /firmware/drivers
parent8f063d49c2e1dffb5548e40b69782963e18b1171 (diff)
downloadrockbox-eaee5e7339aec46b4b67f06b139b8708810a4c2e.tar.gz
rockbox-eaee5e7339aec46b4b67f06b139b8708810a4c2e.zip
Revert "AXP PMU rewrite (again)"
This caused LCD problems on the ErosQ, where the screen would go white until being put through a sleep/wake cycle. The exact reason for this isn't obvious, but the problem didn't exist prior to the AXP driver rewrite. The two dependent changes, 42999913ba - x1000: Increase USB current limit to 500 mA at all times 90dd2f84a9 - x1000: Correctly limit USB charging current ended up bringing the USB charging situation back to where it was prior to the rewrite, so the cleanest option is to revert the whole lot. This reverts commit 42999913ba3a76221fceb04b1f935ed4e0e71476. This reverts commit 90dd2f84a9174c38dbfb07d582ec6ee7697b1939. This reverts commit 2d891439623bb76d38b98202ca5f3eea3c01c5f0. Change-Id: I1cff2bfdd1b189df14bcf8cce42db725caa470d7
Diffstat (limited to 'firmware/drivers')
-rw-r--r--firmware/drivers/axp-pmu.c670
-rw-r--r--firmware/drivers/axp192.c810
2 files changed, 670 insertions, 810 deletions
diff --git a/firmware/drivers/axp-pmu.c b/firmware/drivers/axp-pmu.c
new file mode 100644
index 0000000000..fd1126dbbf
--- /dev/null
+++ b/firmware/drivers/axp-pmu.c
@@ -0,0 +1,670 @@
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 "axp-pmu.h"
23#include "power.h"
24#include "system.h"
25#include "i2c-async.h"
26#include <string.h>
27
28/* Headers for the debug menu */
29#ifndef BOOTLOADER
30# include "action.h"
31# include "list.h"
32# include <stdio.h>
33#endif
34
35struct axp_adc_info {
36 uint8_t reg;
37 uint8_t en_reg;
38 uint8_t en_bit;
39};
40
41struct axp_supply_info {
42 uint8_t volt_reg;
43 uint8_t volt_reg_mask;
44 uint8_t en_reg;
45 uint8_t en_bit;
46 int min_mV;
47 int max_mV;
48 int step_mV;
49};
50
51static const struct axp_adc_info axp_adc_info[NUM_ADC_CHANNELS] = {
52 {0x56, AXP_REG_ADCENABLE1, 5}, /* ACIN_VOLTAGE */
53 {0x58, AXP_REG_ADCENABLE1, 4}, /* ACIN_CURRENT */
54 {0x5a, AXP_REG_ADCENABLE1, 3}, /* VBUS_VOLTAGE */
55 {0x5c, AXP_REG_ADCENABLE1, 2}, /* VBUS_CURRENT */
56 {0x5e, AXP_REG_ADCENABLE2, 7}, /* INTERNAL_TEMP */
57 {0x62, AXP_REG_ADCENABLE1, 1}, /* TS_INPUT */
58 {0x78, AXP_REG_ADCENABLE1, 7}, /* BATTERY_VOLTAGE */
59 {0x7a, AXP_REG_ADCENABLE1, 6}, /* CHARGE_CURRENT */
60 {0x7c, AXP_REG_ADCENABLE1, 6}, /* DISCHARGE_CURRENT */
61 {0x7e, AXP_REG_ADCENABLE1, 1}, /* APS_VOLTAGE */
62 {0x70, 0xff, 0}, /* BATTERY_POWER */
63};
64
65static const struct axp_supply_info axp_supply_info[AXP_NUM_SUPPLIES] = {
66#if HAVE_AXP_PMU == 192
67 [AXP_SUPPLY_DCDC1] = {
68 .volt_reg = 0x26,
69 .volt_reg_mask = 0x7f,
70 .en_reg = 0x12,
71 .en_bit = 0,
72 .min_mV = 700,
73 .max_mV = 3500,
74 .step_mV = 25,
75 },
76 [AXP_SUPPLY_DCDC2] = {
77 .volt_reg = 0x23,
78 .volt_reg_mask = 0x3f,
79 .en_reg = 0x10,
80 .en_bit = 0,
81 .min_mV = 700,
82 .max_mV = 2275,
83 .step_mV = 25,
84 },
85 [AXP_SUPPLY_DCDC3] = {
86 .volt_reg = 0x27,
87 .volt_reg_mask = 0x7f,
88 .en_reg = 0x12,
89 .en_bit = 1,
90 .min_mV = 700,
91 .max_mV = 3500,
92 .step_mV = 25,
93 },
94 /*
95 * NOTE: LDO1 is always on, and we can't query it or change voltages
96 */
97 [AXP_SUPPLY_LDO2] = {
98 .volt_reg = 0x28,
99 .volt_reg_mask = 0xf0,
100 .en_reg = 0x12,
101 .en_bit = 2,
102 .min_mV = 1800,
103 .max_mV = 3300,
104 .step_mV = 100,
105 },
106 [AXP_SUPPLY_LDO3] = {
107 .volt_reg = 0x28,
108 .volt_reg_mask = 0x0f,
109 .en_reg = 0x12,
110 .en_bit = 3,
111 .min_mV = 1800,
112 .max_mV = 3300,
113 .step_mV = 100,
114 },
115 [AXP_SUPPLY_LDO_IO0] = {
116 .volt_reg = 0x91,
117 .volt_reg_mask = 0xf0,
118 .en_reg = 0x90,
119 .en_bit = 0xff, /* this one requires special handling */
120 .min_mV = 1800,
121 .max_mV = 3300,
122 .step_mV = 100,
123 },
124#else
125# error "Untested AXP chip"
126#endif
127};
128
129static struct axp_driver {
130 int adc_enable;
131 int chargecurrent_setting;
132 int chip_id;
133} axp;
134
135static void axp_init_enabled_adcs(void)
136{
137 axp.adc_enable = 0;
138
139 /* Read chip ID, so we can display it on the debug screen.
140 * This is undocumented but there's Linux driver code floating around
141 * which suggests this should work for many AXP chips. */
142 axp.chip_id = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_CHIP_ID);
143
144 /* Read enabled ADCs from the hardware */
145 uint8_t regs[2];
146 int rc = i2c_reg_read(AXP_PMU_BUS, AXP_PMU_ADDR,
147 AXP_REG_ADCENABLE1, 2, &regs[0]);
148 if(rc != I2C_STATUS_OK)
149 return;
150
151 /* Parse registers to set ADC enable bits */
152 const struct axp_adc_info* info = axp_adc_info;
153 for(int i = 0; i < NUM_ADC_CHANNELS; ++i) {
154 if(info[i].en_reg == 0xff)
155 continue;
156
157 if(regs[info[i].en_reg - AXP_REG_ADCENABLE1] & info[i].en_bit)
158 axp.adc_enable |= 1 << i;
159 }
160
161 /* Handle battery power ADC */
162 if((axp.adc_enable & (1 << ADC_BATTERY_VOLTAGE)) &&
163 (axp.adc_enable & (1 << ADC_DISCHARGE_CURRENT))) {
164 axp.adc_enable |= (1 << ADC_BATTERY_POWER);
165 }
166}
167
168void axp_init(void)
169{
170 axp_init_enabled_adcs();
171
172 /* We need discharge current ADC to reliably poll for a full battery */
173 int bits = axp.adc_enable;
174 bits |= (1 << ADC_DISCHARGE_CURRENT);
175 axp_adc_set_enabled(bits);
176
177 /* Read the maximum charging current */
178 int value = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_CHARGECONTROL1);
179 axp.chargecurrent_setting = (value < 0) ? -1 : (value & 0xf);
180}
181
182void axp_supply_set_voltage(int supply, int voltage)
183{
184 const struct axp_supply_info* info = &axp_supply_info[supply];
185 if(info->volt_reg == 0 || info->volt_reg_mask == 0)
186 return;
187
188 if(voltage > 0 && info->step_mV != 0) {
189 if(voltage < info->min_mV || voltage > info->max_mV)
190 return;
191
192 int regval = (voltage - info->min_mV) / info->step_mV;
193 i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, info->volt_reg,
194 info->volt_reg_mask, regval, NULL);
195 }
196
197 if(info->en_bit != 0xff) {
198 i2c_reg_setbit1(AXP_PMU_BUS, AXP_PMU_ADDR,
199 info->en_reg, info->en_bit,
200 voltage > 0 ? 1 : 0, NULL);
201 }
202}
203
204int axp_supply_get_voltage(int supply)
205{
206 const struct axp_supply_info* info = &axp_supply_info[supply];
207 if(info->volt_reg == 0)
208 return AXP_SUPPLY_NOT_PRESENT;
209
210 if(info->en_reg != 0) {
211 int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, info->en_reg);
212 if(r < 0)
213 return AXP_SUPPLY_DISABLED;
214
215#if HAVE_AXP_PMU == 192
216 if(supply == AXP_SUPPLY_LDO_IO0) {
217 if((r & 7) != 2)
218 return AXP_SUPPLY_DISABLED;
219 } else
220#endif
221 {
222 if(r & (1 << info->en_bit) == 0)
223 return AXP_SUPPLY_DISABLED;
224 }
225 }
226
227 /* Hack, avoid undefined shift below. Can be useful too... */
228 if(info->volt_reg_mask == 0)
229 return info->min_mV;
230
231 int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, info->volt_reg);
232 if(r < 0)
233 return 0;
234
235 int bit = find_first_set_bit(info->volt_reg_mask);
236 int val = (r & info->volt_reg_mask) >> bit;
237 return info->min_mV + (val * info->step_mV);
238}
239
240/* TODO: this can STILL indicate some false positives! */
241int axp_battery_status(void)
242{
243 int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_POWERSTATUS);
244 if(r >= 0) {
245 /* Charging bit indicates we're currently charging */
246 if((r & 0x04) != 0)
247 return AXP_BATT_CHARGING;
248
249 /* Not plugged in means we're discharging */
250 if((r & 0xf0) == 0)
251 return AXP_BATT_DISCHARGING;
252 } else {
253 /* Report discharging if we can't find out power status */
254 return AXP_BATT_DISCHARGING;
255 }
256
257 /* If the battery is full and not in use, the charging bit will be 0,
258 * there will be an external power source, AND the discharge current
259 * will be zero. Seems to rule out all false positives. */
260 int d = axp_adc_read_raw(ADC_DISCHARGE_CURRENT);
261 if(d == 0)
262 return AXP_BATT_FULL;
263
264 return AXP_BATT_DISCHARGING;
265}
266
267int axp_input_status(void)
268{
269#ifdef HAVE_BATTERY_SWITCH
270 int input_status = 0;
271#else
272 int input_status = AXP_INPUT_BATTERY;
273#endif
274
275 int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_POWERSTATUS);
276 if(r < 0)
277 return input_status;
278
279 /* Check for AC input */
280 if(r & 0x80)
281 input_status |= AXP_INPUT_AC;
282
283 /* Only report USB if ACIN and VBUS are not shorted */
284 if((r & 0x20) != 0 && (r & 0x02) == 0)
285 input_status |= AXP_INPUT_USB;
286
287#ifdef HAVE_BATTERY_SWITCH
288 /* Check for battery presence if target defines it as removable */
289 r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_CHARGESTATUS);
290 if(r >= 0 && (r & 0x20) != 0)
291 input_status |= AXP_INPUT_BATTERY;
292#endif
293
294 return input_status;
295}
296
297int axp_adc_read(int adc)
298{
299 int value = axp_adc_read_raw(adc);
300 if(value == INT_MIN)
301 return INT_MIN;
302
303 return axp_adc_conv_raw(adc, value);
304}
305
306int axp_adc_read_raw(int adc)
307{
308 /* Don't give a reading if the ADC is not enabled */
309 if((axp.adc_enable & (1 << adc)) == 0)
310 return INT_MIN;
311
312 /* Read the ADC */
313 uint8_t buf[3];
314 int count = (adc == ADC_BATTERY_POWER) ? 3 : 2;
315 uint8_t reg = axp_adc_info[adc].reg;
316 int rc = i2c_reg_read(AXP_PMU_BUS, AXP_PMU_ADDR, reg, count, &buf[0]);
317 if(rc != I2C_STATUS_OK)
318 return INT_MIN;
319
320 /* Parse the value */
321 if(adc == ADC_BATTERY_POWER)
322 return (buf[0] << 16) | (buf[1] << 8) | buf[2];
323 else if(adc == ADC_CHARGE_CURRENT || adc == ADC_DISCHARGE_CURRENT)
324 return (buf[0] << 5) | (buf[1] & 0x1f);
325 else
326 return (buf[0] << 4) | (buf[1] & 0xf);
327}
328
329int axp_adc_conv_raw(int adc, int value)
330{
331 switch(adc) {
332 case ADC_ACIN_VOLTAGE:
333 case ADC_VBUS_VOLTAGE:
334 /* 0 mV ... 6.9615 mV, step 1.7 mV */
335 return value * 17 / 10;
336 case ADC_ACIN_CURRENT:
337 /* 0 mA ... 2.5594 A, step 0.625 mA */
338 return value * 5 / 8;
339 case ADC_VBUS_CURRENT:
340 /* 0 mA ... 1.5356 A, step 0.375 mA */
341 return value * 3 / 8;
342 case ADC_INTERNAL_TEMP:
343 /* -144.7 C ... 264.8 C, step 0.1 C */
344 return value - 1447;
345 case ADC_TS_INPUT:
346 /* 0 mV ... 3.276 V, step 0.8 mV */
347 return value * 4 / 5;
348 case ADC_BATTERY_VOLTAGE:
349 /* 0 mV ... 4.5045 V, step 1.1 mV */
350 return value * 11 / 10;
351 case ADC_CHARGE_CURRENT:
352 case ADC_DISCHARGE_CURRENT:
353 /* 0 mA to 4.095 A, step 0.5 mA */
354 return value / 2;
355 case ADC_APS_VOLTAGE:
356 /* 0 mV to 5.733 V, step 1.4 mV */
357 return value * 7 / 5;
358 case ADC_BATTERY_POWER:
359 /* 0 uW to 23.6404 W, step 0.55 uW */
360 return value * 11 / 20;
361 default:
362 /* Shouldn't happen */
363 return INT_MIN;
364 }
365}
366
367int axp_adc_get_enabled(void)
368{
369 return axp.adc_enable;
370}
371
372void axp_adc_set_enabled(int adc_bits)
373{
374 /* Ignore no-op */
375 if(adc_bits == axp.adc_enable)
376 return;
377
378 /* Compute the new register values */
379 const struct axp_adc_info* info = axp_adc_info;
380 uint8_t regs[2] = {0, 0};
381 for(int i = 0; i < NUM_ADC_CHANNELS; ++i) {
382 if(info[i].en_reg == 0xff)
383 continue;
384
385 if(adc_bits & (1 << i))
386 regs[info[i].en_reg - 0x82] |= 1 << info[i].en_bit;
387 }
388
389 /* These ADCs share an enable bit */
390 if(adc_bits & ((1 << ADC_CHARGE_CURRENT)|(1 << ADC_DISCHARGE_CURRENT))) {
391 adc_bits |= (1 << ADC_CHARGE_CURRENT);
392 adc_bits |= (1 << ADC_DISCHARGE_CURRENT);
393 }
394
395 /* Enable required bits for battery power ADC */
396 if(adc_bits & (1 << ADC_BATTERY_POWER)) {
397 regs[0] |= 1 << info[ADC_DISCHARGE_CURRENT].en_bit;
398 regs[0] |= 1 << info[ADC_BATTERY_VOLTAGE].en_bit;
399 }
400
401 /* Update the configuration */
402 i2c_reg_write(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_ADCENABLE1, 2, &regs[0]);
403 axp.adc_enable = adc_bits;
404}
405
406int axp_adc_get_rate(void)
407{
408 int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_ADCSAMPLERATE);
409 if(r < 0)
410 return AXP_ADC_RATE_100HZ; /* an arbitrary value */
411
412 return (r >> 6) & 3;
413}
414
415void axp_adc_set_rate(int rate)
416{
417 i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_ADCSAMPLERATE,
418 0xc0, (rate & 3) << 6, NULL);
419}
420
421static uint32_t axp_cc_parse(const uint8_t* buf)
422{
423 return ((uint32_t)buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
424}
425
426void axp_cc_read(uint32_t* charge, uint32_t* discharge)
427{
428 uint8_t buf[8];
429 int rc = i2c_reg_read(AXP_PMU_BUS, AXP_PMU_ADDR,
430 AXP_REG_COULOMBCOUNTERBASE, 8, &buf[0]);
431 if(rc != I2C_STATUS_OK) {
432 if(charge)
433 *charge = 0;
434 if(discharge)
435 *discharge = 0;
436 return;
437 }
438
439 if(charge)
440 *charge = axp_cc_parse(&buf[0]);
441 if(discharge)
442 *discharge = axp_cc_parse(&buf[4]);
443}
444
445void axp_cc_clear(void)
446{
447 i2c_reg_setbit1(AXP_PMU_BUS, AXP_PMU_ADDR,
448 AXP_REG_COULOMBCOUNTERCTRL, 5, 1, NULL);
449}
450
451void axp_cc_enable(bool en)
452{
453 i2c_reg_setbit1(AXP_PMU_BUS, AXP_PMU_ADDR,
454 AXP_REG_COULOMBCOUNTERCTRL, 7, en ? 1 : 0, NULL);
455}
456
457bool axp_cc_is_enabled(void)
458{
459 int reg = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR,
460 AXP_REG_COULOMBCOUNTERCTRL);
461 return reg >= 0 && (reg & 0x40) != 0;
462}
463
464static const int chargecurrent_tbl[] = {
465 100, 190, 280, 360,
466 450, 550, 630, 700,
467 780, 880, 960, 1000,
468 1080, 1160, 1240, 1320,
469};
470
471static const int chargecurrent_tblsz = sizeof(chargecurrent_tbl)/sizeof(int);
472
473void axp_set_charge_current(int maxcurrent)
474{
475 /* Find the charge current just higher than maxcurrent */
476 int value = 0;
477 while(value < chargecurrent_tblsz &&
478 chargecurrent_tbl[value] <= maxcurrent)
479 ++value;
480
481 /* Select the next lower current, the greatest current <= maxcurrent */
482 if(value >= chargecurrent_tblsz)
483 value = chargecurrent_tblsz - 1;
484 else if(value > 0)
485 --value;
486
487 /* Don't issue i2c write if desired setting is already in use */
488 if(value == axp.chargecurrent_setting)
489 return;
490
491 /* Update register */
492 i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR,
493 AXP_REG_CHARGECONTROL1, 0x0f, value, NULL);
494 axp.chargecurrent_setting = value;
495}
496
497int axp_get_charge_current(void)
498{
499 if(axp.chargecurrent_setting < 0)
500 return chargecurrent_tbl[0];
501 else
502 return chargecurrent_tbl[axp.chargecurrent_setting];
503}
504
505void axp_power_off(void)
506{
507 /* Set the shutdown bit */
508 i2c_reg_setbit1(AXP_PMU_BUS, AXP_PMU_ADDR,
509 AXP_REG_SHUTDOWNLEDCTRL, 7, 1, NULL);
510}
511
512#ifndef BOOTLOADER
513enum {
514 AXP_DEBUG_CHIP_ID,
515 AXP_DEBUG_BATTERY_STATUS,
516 AXP_DEBUG_INPUT_STATUS,
517 AXP_DEBUG_CHARGE_CURRENT,
518 AXP_DEBUG_COULOMB_COUNTERS,
519 AXP_DEBUG_ADC_RATE,
520 AXP_DEBUG_FIRST_ADC,
521 AXP_DEBUG_FIRST_SUPPLY = AXP_DEBUG_FIRST_ADC + NUM_ADC_CHANNELS,
522 AXP_DEBUG_NUM_ENTRIES = AXP_DEBUG_FIRST_SUPPLY + AXP_NUM_SUPPLIES,
523};
524
525static int axp_debug_menu_cb(int action, struct gui_synclist* lists)
526{
527 (void)lists;
528
529 if(action == ACTION_NONE)
530 action = ACTION_REDRAW;
531
532 return action;
533}
534
535static const char* axp_debug_menu_get_name(int item, void* data,
536 char* buf, size_t buflen)
537{
538 (void)data;
539
540 static const char* const adc_names[] = {
541 "V_acin", "I_acin", "V_vbus", "I_vbus", "T_int",
542 "V_ts", "V_batt", "I_chrg", "I_dchg", "V_aps", "P_batt"
543 };
544
545 static const char* const adc_units[] = {
546 "mV", "mA", "mV", "mA", "C", "mV", "mV", "mA", "mA", "mV", "uW",
547 };
548
549 static const char* const supply_names[] = {
550 "DCDC1", "DCDC2", "DCDC3",
551 "LDO1", "LDO2", "LDO3", "LDO_IO0",
552 };
553
554 int adc = item - AXP_DEBUG_FIRST_ADC;
555 if(item >= AXP_DEBUG_FIRST_ADC && adc < NUM_ADC_CHANNELS) {
556 int raw_value = axp_adc_read_raw(adc);
557 if(raw_value == INT_MIN) {
558 snprintf(buf, buflen, "%s: [Disabled]", adc_names[adc]);
559 return buf;
560 }
561
562 int value = axp_adc_conv_raw(adc, raw_value);
563 if(adc == ADC_INTERNAL_TEMP) {
564 snprintf(buf, buflen, "%s: %d.%d %s", adc_names[adc],
565 value/10, value%10, adc_units[adc]);
566 } else {
567 snprintf(buf, buflen, "%s: %d %s", adc_names[adc],
568 value, adc_units[adc]);
569 }
570
571 return buf;
572 }
573
574 int supply = item - AXP_DEBUG_FIRST_SUPPLY;
575 if(item >= AXP_DEBUG_FIRST_SUPPLY && supply < AXP_NUM_SUPPLIES) {
576 int voltage = axp_supply_get_voltage(supply);
577 if(voltage == AXP_SUPPLY_NOT_PRESENT)
578 snprintf(buf, buflen, "%s: [Not Present]", supply_names[supply]);
579 else if(voltage == AXP_SUPPLY_DISABLED)
580 snprintf(buf, buflen, "%s: [Disabled]", supply_names[supply]);
581 else
582 snprintf(buf, buflen, "%s: %d mV", supply_names[supply], voltage);
583
584 return buf;
585 }
586
587 switch(item) {
588 case AXP_DEBUG_CHIP_ID: {
589 snprintf(buf, buflen, "Chip ID: %d (%02x) [Driver: AXP%d]",
590 axp.chip_id, axp.chip_id, HAVE_AXP_PMU);
591 return buf;
592 } break;
593
594 case AXP_DEBUG_BATTERY_STATUS: {
595 switch(axp_battery_status()) {
596 case AXP_BATT_FULL:
597 return "Battery: Full";
598 case AXP_BATT_CHARGING:
599 return "Battery: Charging";
600 case AXP_BATT_DISCHARGING:
601 return "Battery: Discharging";
602 default:
603 return "Battery: Unknown";
604 }
605 } break;
606
607 case AXP_DEBUG_INPUT_STATUS: {
608 int s = axp_input_status();
609 const char* ac = (s & AXP_INPUT_AC) ? " AC" : "";
610 const char* usb = (s & AXP_INPUT_USB) ? " USB" : "";
611 const char* batt = (s & AXP_INPUT_BATTERY) ? " Battery" : "";
612 snprintf(buf, buflen, "Inputs:%s%s%s", ac, usb, batt);
613 return buf;
614 } break;
615
616 case AXP_DEBUG_CHARGE_CURRENT: {
617 int current = axp_get_charge_current();
618 snprintf(buf, buflen, "Max charge current: %d mA", current);
619 return buf;
620 } break;
621
622 case AXP_DEBUG_COULOMB_COUNTERS: {
623 uint32_t charge, discharge;
624 axp_cc_read(&charge, &discharge);
625
626 snprintf(buf, buflen, "Coulomb counters: +%lu / -%lu",
627 (unsigned long)charge, (unsigned long)discharge);
628 return buf;
629 } break;
630
631 case AXP_DEBUG_ADC_RATE: {
632 int rate = 25 << axp_adc_get_rate();
633 snprintf(buf, buflen, "ADC sample rate: %d Hz", rate);
634 return buf;
635 } break;
636
637 default:
638 return "---";
639 }
640}
641
642bool axp_debug_menu(void)
643{
644 struct simplelist_info info;
645 simplelist_info_init(&info, "AXP debug", AXP_DEBUG_NUM_ENTRIES, NULL);
646 info.action_callback = axp_debug_menu_cb;
647 info.get_name = axp_debug_menu_get_name;
648 return simplelist_show_list(&info);
649}
650#endif /* !BOOTLOADER */
651
652/* This is basically the only valid implementation, so define it here */
653unsigned int power_input_status(void)
654{
655 unsigned int state = 0;
656 int input_status = axp_input_status();
657
658 if(input_status & AXP_INPUT_AC)
659 state |= POWER_INPUT_MAIN_CHARGER;
660
661 if(input_status & AXP_INPUT_USB)
662 state |= POWER_INPUT_USB_CHARGER;
663
664#ifdef HAVE_BATTERY_SWITCH
665 if(input_status & AXP_INPUT_BATTERY)
666 state |= POWER_INPUT_BATTERY;
667#endif
668
669 return state;
670}
diff --git a/firmware/drivers/axp192.c b/firmware/drivers/axp192.c
deleted file mode 100644
index 3c61d8c533..0000000000
--- a/firmware/drivers/axp192.c
+++ /dev/null
@@ -1,810 +0,0 @@
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
32int 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
41int 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
50int 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
63struct 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
73static 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
139void 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
145void 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
168void 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
180struct 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
188static 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
205void 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
211void 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
232int 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
248int 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
257int 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
270struct 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
279static 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
288static 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
295static const uint8_t nrstofuncmap[8] = {
296 [AXP_GPIO_SPECIAL] = 0x0,
297 [AXP_GPIO_OPEN_DRAIN_OUTPUT] = 0x2,
298 [AXP_GPIO_INPUT] = 0x3,
299};
300
301void 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
314void 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
320int 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
326void 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
337static 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
344void 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
356int 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
367void 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
376void 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
388bool axp_is_charging(void)
389{
390 int value = axp_read(AXP_REG_CHGSTS);
391 return (value >= 0) && (value & BM_AXP_CHGSTS_CHARGING);
392}
393
394unsigned 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
424void 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
444enum {
445 MODE_ADC,
446#ifdef AXP_EXTRA_DEBUG
447 MODE_SUPPLY,
448 MODE_REGISTER,
449#endif
450 NUM_MODES,
451};
452
453static 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
461struct axp_adcdebuginfo {
462 const char* name;
463 const char* unit;
464};
465
466static 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
484static 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
494struct axp_fieldinfo {
495 uint8_t rnum;
496 uint8_t msb: 4;
497 uint8_t lsb: 4;
498};
499
500enum {
501#define DEFREG(name, ...) AXP_RNUM_##name,
502#include "axp192-defs.h"
503 AXP_NUM_REGS,
504};
505
506enum {
507#define DEFFLD(regname, fldname, ...) AXP_FNUM_##regname##_##fldname,
508#include "axp192-defs.h"
509 AXP_NUM_FIELDS,
510};
511
512static const uint8_t axp_regaddr[AXP_NUM_REGS] = {
513#define DEFREG(name, addr) addr,
514#include "axp192-defs.h"
515};
516
517static 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
523static const char* const axp_regnames[AXP_NUM_REGS] = {
524#define DEFREG(name, ...) #name,
525#include "axp192-defs.h"
526};
527
528static 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
534struct 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
546static void axp_debug_clear_cache(struct axp_debug_menu_state* state)
547{
548 memset(state->is_cached, 0, sizeof(state->is_cached));
549}
550
551static 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
560static 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
574static 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
597static 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
619static 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
701static 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
792bool 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