forked from len0rd/rockbox
I noticed a few mistakes in the old driver code and it was in need of an overhaul anyway... I decided to scale things back, simplify the code and remove most of the debug menus, netting a nice code size savings. One new feature is an advanced debug menu which is accessible by recompiling the code with AXP_EXTRA_DEBUG. It adds quite a bit of code size and isn't useful other than for development so it must be manually enabled by editing the source. Change-Id: I30e17c1194c14823decd726a574ed14451d4cb2d
810 lines
22 KiB
C
810 lines
22 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2021 Aidan MacDonald
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "axp192.h"
|
|
#include "system.h"
|
|
#include "power.h"
|
|
#include "i2c-async.h"
|
|
#include "logf.h"
|
|
|
|
/*
|
|
* Direct register access
|
|
*/
|
|
|
|
int axp_read(uint8_t reg)
|
|
{
|
|
int ret = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, reg);
|
|
if(ret < 0)
|
|
logf("axp: read reg %02x err=%d", reg, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int axp_write(uint8_t reg, uint8_t value)
|
|
{
|
|
int ret = i2c_reg_write1(AXP_PMU_BUS, AXP_PMU_ADDR, reg, value);
|
|
if(ret < 0)
|
|
logf("axp: write reg %02x err=%d", reg, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int axp_modify(uint8_t reg, uint8_t clr, uint8_t set)
|
|
{
|
|
int ret = i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, reg, clr, set, NULL);
|
|
if(ret < 0)
|
|
logf("axp: modify reg %02x err=%d", reg, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Power supplies: enable/disable, set voltage
|
|
*/
|
|
|
|
struct axp_supplydata {
|
|
uint8_t en_reg;
|
|
uint8_t en_bit;
|
|
uint8_t volt_reg;
|
|
uint8_t volt_msb: 4;
|
|
uint8_t volt_lsb: 4;
|
|
short min_mV;
|
|
short step_mV;
|
|
};
|
|
|
|
static const struct axp_supplydata supplydata[] = {
|
|
[AXP_SUPPLY_EXTEN] = {
|
|
.en_reg = AXP_REG_PWRCTL1,
|
|
.en_bit = 1 << 2,
|
|
.volt_reg = 0xff, /* undefined */
|
|
.volt_msb = 0xf,
|
|
.volt_lsb = 0xf,
|
|
.min_mV = 0,
|
|
.step_mV = 0,
|
|
},
|
|
[AXP_SUPPLY_DCDC1] = {
|
|
.en_reg = AXP_REG_PWRCTL2,
|
|
.en_bit = 1 << 0,
|
|
.volt_reg = AXP_REG_DCDC1VOLT,
|
|
.volt_msb = 6,
|
|
.volt_lsb = 0,
|
|
.min_mV = 700,
|
|
.step_mV = 25,
|
|
},
|
|
[AXP_SUPPLY_DCDC2] = {
|
|
.en_reg = AXP_REG_PWRCTL1,
|
|
.en_bit = 1 << 0,
|
|
.volt_reg = AXP_REG_DCDC2VOLT,
|
|
.volt_msb = 5,
|
|
.volt_lsb = 0,
|
|
.min_mV = 700,
|
|
.step_mV = 25,
|
|
},
|
|
[AXP_SUPPLY_DCDC3] = {
|
|
.en_reg = AXP_REG_PWRCTL2,
|
|
.en_bit = 1 << 1,
|
|
.volt_reg = AXP_REG_DCDC3VOLT,
|
|
.volt_msb = 6,
|
|
.volt_lsb = 0,
|
|
.min_mV = 700,
|
|
.step_mV = 25,
|
|
},
|
|
[AXP_SUPPLY_LDO2] = {
|
|
.en_reg = AXP_REG_PWRCTL2,
|
|
.en_bit = 1 << 2,
|
|
.volt_reg = AXP_REG_LDO2LDO3VOLT,
|
|
.volt_msb = 7,
|
|
.volt_lsb = 4,
|
|
.min_mV = 1800,
|
|
.step_mV = 100,
|
|
},
|
|
[AXP_SUPPLY_LDO3] = {
|
|
.en_reg = AXP_REG_PWRCTL2,
|
|
.en_bit = 1 << 3,
|
|
.volt_reg = AXP_REG_LDO2LDO3VOLT,
|
|
.volt_msb = 3,
|
|
.volt_lsb = 0,
|
|
.min_mV = 1800,
|
|
.step_mV = 100,
|
|
},
|
|
[AXP_SUPPLY_LDOIO0] = {
|
|
.en_reg = 0xff, /* undefined */
|
|
.en_bit = 0,
|
|
.volt_reg = AXP_REG_GPIO0LDO,
|
|
.volt_msb = 7,
|
|
.volt_lsb = 4,
|
|
.min_mV = 1800,
|
|
.step_mV = 100,
|
|
},
|
|
};
|
|
|
|
void axp_enable_supply(int supply, bool enable)
|
|
{
|
|
const struct axp_supplydata* data = &supplydata[supply];
|
|
axp_modify(data->en_reg, data->en_bit, enable ? data->en_bit : 0);
|
|
}
|
|
|
|
void axp_set_enabled_supplies(unsigned int supply_mask)
|
|
{
|
|
uint8_t xfer[3];
|
|
xfer[0] = 0;
|
|
xfer[1] = AXP_REG_PWRCTL2;
|
|
xfer[2] = 0;
|
|
|
|
for(int i = 0; i < AXP_NUM_SUPPLIES; ++i) {
|
|
if(!(supply_mask & (1 << i)))
|
|
continue;
|
|
|
|
const struct axp_supplydata* data = &supplydata[i];
|
|
if(data->en_reg == AXP_REG_PWRCTL1) {
|
|
xfer[0] |= data->en_bit;
|
|
xfer[2] |= data->en_bit << 4; /* HACK: work around AXP quirk */
|
|
} else {
|
|
xfer[2] |= data->en_bit;
|
|
}
|
|
}
|
|
|
|
i2c_reg_write(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_PWRCTL1, 3, xfer);
|
|
}
|
|
|
|
void axp_set_supply_voltage(int supply, int output_mV)
|
|
{
|
|
const struct axp_supplydata* data = &supplydata[supply];
|
|
uint8_t mask = (1 << (data->volt_msb - data->volt_lsb + 1)) - 1;
|
|
uint8_t value = (output_mV - data->min_mV) / data->step_mV;
|
|
axp_modify(data->volt_reg, mask << data->volt_lsb, value << data->volt_lsb);
|
|
}
|
|
|
|
/*
|
|
* ADC control: enable/disable, read
|
|
*/
|
|
|
|
struct axp_adcdata {
|
|
uint8_t data_reg;
|
|
uint8_t en_reg;
|
|
uint8_t en_bit;
|
|
int8_t num;
|
|
int8_t den;
|
|
};
|
|
|
|
static const struct axp_adcdata adcdata[] = {
|
|
[AXP_ADC_ACIN_VOLTAGE] = {0x56, AXP_REG_ADCEN1, 1 << 5, 17, 10},
|
|
[AXP_ADC_ACIN_CURRENT] = {0x58, AXP_REG_ADCEN1, 1 << 4, 5, 8},
|
|
[AXP_ADC_VBUS_VOLTAGE] = {0x5a, AXP_REG_ADCEN1, 1 << 3, 17, 10},
|
|
[AXP_ADC_VBUS_CURRENT] = {0x5c, AXP_REG_ADCEN1, 1 << 2, 3, 8},
|
|
[AXP_ADC_INTERNAL_TEMP] = {0x5e, AXP_REG_ADCEN2, 1 << 7, 0, 0},
|
|
[AXP_ADC_TS_INPUT] = {0x62, AXP_REG_ADCEN1, 1 << 0, 4, 5},
|
|
[AXP_ADC_GPIO0] = {0x64, AXP_REG_ADCEN2, 1 << 3, 1, 2},
|
|
[AXP_ADC_GPIO1] = {0x66, AXP_REG_ADCEN2, 1 << 2, 1, 2},
|
|
[AXP_ADC_GPIO2] = {0x68, AXP_REG_ADCEN2, 1 << 1, 1, 2},
|
|
[AXP_ADC_GPIO3] = {0x6a, AXP_REG_ADCEN2, 1 << 0, 1, 2},
|
|
[AXP_ADC_BATTERY_VOLTAGE] = {0x78, AXP_REG_ADCEN1, 1 << 7, 11, 10},
|
|
[AXP_ADC_CHARGE_CURRENT] = {0x7a, AXP_REG_ADCEN1, 1 << 6, 1, 2},
|
|
[AXP_ADC_DISCHARGE_CURRENT] = {0x7c, AXP_REG_ADCEN1, 1 << 6, 1, 2},
|
|
[AXP_ADC_APS_VOLTAGE] = {0x7e, AXP_REG_ADCEN1, 1 << 1, 7, 5},
|
|
};
|
|
|
|
void axp_enable_adc(int adc, bool enable)
|
|
{
|
|
const struct axp_adcdata* data = &adcdata[adc];
|
|
axp_modify(data->en_reg, data->en_bit, enable ? data->en_bit : 0);
|
|
}
|
|
|
|
void axp_set_enabled_adcs(unsigned int adc_mask)
|
|
{
|
|
uint8_t xfer[3];
|
|
xfer[0] = 0;
|
|
xfer[1] = AXP_REG_ADCEN2;
|
|
xfer[2] = 0;
|
|
|
|
for(int i = 0; i < AXP_NUM_ADCS; ++i) {
|
|
if(!(adc_mask & (1 << i)))
|
|
continue;
|
|
|
|
const struct axp_adcdata* data = &adcdata[i];
|
|
if(data->en_reg == AXP_REG_ADCEN1)
|
|
xfer[0] |= data->en_bit;
|
|
else
|
|
xfer[2] |= data->en_bit;
|
|
}
|
|
|
|
i2c_reg_write(AXP_PMU_BUS, AXP_PMU_ADDR, AXP_REG_ADCEN1, 3, xfer);
|
|
}
|
|
|
|
int axp_read_adc_raw(int adc)
|
|
{
|
|
uint8_t data[2];
|
|
int ret = i2c_reg_read(AXP_PMU_BUS, AXP_PMU_ADDR,
|
|
adcdata[adc].data_reg, 2, data);
|
|
if(ret < 0) {
|
|
logf("axp: ADC read failed, err=%d", ret);
|
|
return INT_MIN;
|
|
}
|
|
|
|
if(adc == AXP_ADC_CHARGE_CURRENT || adc == AXP_ADC_DISCHARGE_CURRENT)
|
|
return (data[0] << 5) | data[1];
|
|
else
|
|
return (data[0] << 4) | data[1];
|
|
}
|
|
|
|
int axp_conv_adc(int adc, int value)
|
|
{
|
|
const struct axp_adcdata* data = &adcdata[adc];
|
|
if(adc == AXP_ADC_INTERNAL_TEMP)
|
|
return value - 1447;
|
|
else
|
|
return data->num * value / data->den;
|
|
}
|
|
|
|
int axp_read_adc(int adc)
|
|
{
|
|
int ret = axp_read_adc_raw(adc);
|
|
if(ret == INT_MIN)
|
|
return ret;
|
|
|
|
return axp_conv_adc(adc, ret);
|
|
}
|
|
|
|
/*
|
|
* GPIOs: set function, pull down control, get/set pin level
|
|
*/
|
|
|
|
struct axp_gpiodata {
|
|
uint8_t func_reg;
|
|
uint8_t func_msb: 4;
|
|
uint8_t func_lsb: 4;
|
|
uint8_t level_reg;
|
|
uint8_t level_out: 4;
|
|
uint8_t level_in: 4;
|
|
};
|
|
|
|
static const struct axp_gpiodata gpiodata[] = {
|
|
{AXP_REG_GPIO0FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 0, 4},
|
|
{AXP_REG_GPIO1FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 1, 5},
|
|
{AXP_REG_GPIO2FUNC, 2, 0, AXP_REG_GPIOLEVEL1, 2, 6},
|
|
{AXP_REG_GPIO3GPIO4FUNC, 1, 0, AXP_REG_GPIOLEVEL2, 0, 4},
|
|
{AXP_REG_GPIO3GPIO4FUNC, 3, 2, AXP_REG_GPIOLEVEL2, 1, 5},
|
|
{AXP_REG_NRSTO, 7, 6, AXP_REG_NRSTO, 4, 5},
|
|
};
|
|
|
|
static const uint8_t gpio34funcmap[8] = {
|
|
[AXP_GPIO_SPECIAL] = 0x0,
|
|
[AXP_GPIO_OPEN_DRAIN_OUTPUT] = 0x1,
|
|
[AXP_GPIO_INPUT] = 0x2,
|
|
[AXP_GPIO_ADC_IN] = 0x3,
|
|
};
|
|
|
|
static const uint8_t nrstofuncmap[8] = {
|
|
[AXP_GPIO_SPECIAL] = 0x0,
|
|
[AXP_GPIO_OPEN_DRAIN_OUTPUT] = 0x2,
|
|
[AXP_GPIO_INPUT] = 0x3,
|
|
};
|
|
|
|
void axp_set_gpio_function(int gpio, int function)
|
|
{
|
|
const struct axp_gpiodata* data = &gpiodata[gpio];
|
|
int mask = (1 << (data->func_msb - data->func_lsb + 1)) - 1;
|
|
|
|
if(gpio == 5)
|
|
function = nrstofuncmap[function];
|
|
else if(gpio >= 3)
|
|
function = gpio34funcmap[function];
|
|
|
|
axp_modify(data->func_reg, mask << data->func_lsb, function << data->func_lsb);
|
|
}
|
|
|
|
void axp_set_gpio_pulldown(int gpio, bool enable)
|
|
{
|
|
int bit = 1 << gpio;
|
|
axp_modify(AXP_REG_GPIOPULL, bit, enable ? bit : 0);
|
|
}
|
|
|
|
int axp_get_gpio(int gpio)
|
|
{
|
|
const struct axp_gpiodata* data = &gpiodata[gpio];
|
|
return axp_read(data->level_reg) & (1 << data->level_in);
|
|
}
|
|
|
|
void axp_set_gpio(int gpio, bool enable)
|
|
{
|
|
const struct axp_gpiodata* data = &gpiodata[gpio];
|
|
uint8_t bit = 1 << data->level_out;
|
|
axp_modify(data->level_reg, bit, enable ? bit : 0);
|
|
}
|
|
|
|
/*
|
|
* Charging: set charging current, query charging/input status
|
|
*/
|
|
|
|
static const short chargecurrent_tbl[] = {
|
|
100, 190, 280, 360,
|
|
450, 550, 630, 700,
|
|
780, 880, 960, 1000,
|
|
1080, 1160, 1240, 1320,
|
|
};
|
|
|
|
void axp_set_charge_current(int current_mA)
|
|
{
|
|
/* find greatest charging current not exceeding requested current */
|
|
unsigned int index = 0;
|
|
while(index < ARRAYLEN(chargecurrent_tbl)-1 &&
|
|
chargecurrent_tbl[index+1] <= current_mA)
|
|
++index;
|
|
|
|
axp_modify(AXP_REG_CHGCTL1, BM_AXP_CHGCTL1_CHARGE_CURRENT,
|
|
index << BP_AXP_CHGCTL1_CHARGE_CURRENT);
|
|
}
|
|
|
|
int axp_get_charge_current(void)
|
|
{
|
|
int value = axp_read(AXP_REG_CHGCTL1);
|
|
if(value < 0)
|
|
value = 0;
|
|
|
|
value &= BM_AXP_CHGCTL1_CHARGE_CURRENT;
|
|
value >>= BP_AXP_CHGCTL1_CHARGE_CURRENT;
|
|
return chargecurrent_tbl[value];
|
|
}
|
|
|
|
void axp_set_vbus_limit(int mode)
|
|
{
|
|
const int mask = BM_AXP_VBUSIPSOUT_VHOLD_LIM |
|
|
BM_AXP_VBUSIPSOUT_VBUS_LIM |
|
|
BM_AXP_VBUSIPSOUT_LIM_100mA;
|
|
|
|
axp_modify(AXP_REG_VBUSIPSOUT, mask, mode);
|
|
}
|
|
|
|
void axp_set_vhold_level(int vhold_mV)
|
|
{
|
|
if(vhold_mV < 4000)
|
|
vhold_mV = 4000;
|
|
else if(vhold_mV > 4700)
|
|
vhold_mV = 4700;
|
|
|
|
int level = (vhold_mV - 4000) / 100;
|
|
axp_modify(AXP_REG_VBUSIPSOUT, BM_AXP_VBUSIPSOUT_VHOLD_LEV,
|
|
level << BP_AXP_VBUSIPSOUT_VHOLD_LEV);
|
|
}
|
|
|
|
bool axp_is_charging(void)
|
|
{
|
|
int value = axp_read(AXP_REG_CHGSTS);
|
|
return (value >= 0) && (value & BM_AXP_CHGSTS_CHARGING);
|
|
}
|
|
|
|
unsigned int axp_power_input_status(void)
|
|
{
|
|
unsigned int state = 0;
|
|
int value = axp_read(AXP_REG_PWRSTS);
|
|
if(value >= 0) {
|
|
/* ACIN is the main charger. Includes USB */
|
|
if(value & BM_AXP_PWRSTS_ACIN_VALID)
|
|
state |= POWER_INPUT_MAIN_CHARGER;
|
|
|
|
/* Report USB separately if discernable from ACIN */
|
|
if((value & BM_AXP_PWRSTS_VBUS_VALID) &&
|
|
!(value & BM_AXP_PWRSTS_PCB_SHORTED))
|
|
state |= POWER_INPUT_USB_CHARGER;
|
|
}
|
|
|
|
#ifdef HAVE_BATTERY_SWITCH
|
|
/* If target allows switching batteries then report if the
|
|
* battery is present or not */
|
|
value = axp_read(AXP_REG_CHGSTS);
|
|
if(value >= 0 && (value & BM_AXP_CHGSTS_BATT_PRESENT))
|
|
state |= POWER_INPUT_BATTERY;
|
|
#endif
|
|
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
* Misc. functions
|
|
*/
|
|
|
|
void axp_power_off(void)
|
|
{
|
|
axp_modify(AXP_REG_PWROFF, BM_AXP_PWROFF_SHUTDOWN, BM_AXP_PWROFF_SHUTDOWN);
|
|
}
|
|
|
|
/*
|
|
* Debug menu
|
|
*/
|
|
|
|
#ifndef BOOTLOADER
|
|
#include "action.h"
|
|
#include "list.h"
|
|
#include "splash.h"
|
|
#include <stdio.h>
|
|
|
|
/* enable extra debug menus which are only useful for development,
|
|
* allow potentially dangerous operations and increase code size
|
|
* significantly */
|
|
/*#define AXP_EXTRA_DEBUG*/
|
|
|
|
enum {
|
|
MODE_ADC,
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
MODE_SUPPLY,
|
|
MODE_REGISTER,
|
|
#endif
|
|
NUM_MODES,
|
|
};
|
|
|
|
static const char* const axp_modenames[NUM_MODES] = {
|
|
[MODE_ADC] = "ADCs",
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
[MODE_SUPPLY] = "Power supplies",
|
|
[MODE_REGISTER] = "Register viewer",
|
|
#endif
|
|
};
|
|
|
|
struct axp_adcdebuginfo {
|
|
const char* name;
|
|
const char* unit;
|
|
};
|
|
|
|
static const struct axp_adcdebuginfo adc_debuginfo[AXP_NUM_ADCS] = {
|
|
[AXP_ADC_ACIN_VOLTAGE] = {"V_acin", "mV"},
|
|
[AXP_ADC_ACIN_CURRENT] = {"I_acin", "mA"},
|
|
[AXP_ADC_VBUS_VOLTAGE] = {"V_vbus", "mV"},
|
|
[AXP_ADC_VBUS_CURRENT] = {"I_vbus", "mA"},
|
|
[AXP_ADC_INTERNAL_TEMP] = {"T_int", "C"},
|
|
[AXP_ADC_TS_INPUT] = {"V_ts", "mV"},
|
|
[AXP_ADC_GPIO0] = {"V_gpio0", "mV"},
|
|
[AXP_ADC_GPIO1] = {"V_gpio1", "mV"},
|
|
[AXP_ADC_GPIO2] = {"V_gpio2", "mV"},
|
|
[AXP_ADC_GPIO3] = {"V_gpio3", "mV"},
|
|
[AXP_ADC_BATTERY_VOLTAGE] = {"V_batt", "mV"},
|
|
[AXP_ADC_CHARGE_CURRENT] = {"I_chrg", "mA"},
|
|
[AXP_ADC_DISCHARGE_CURRENT] = {"I_dchg", "mA"},
|
|
[AXP_ADC_APS_VOLTAGE] = {"V_aps", "mV"},
|
|
};
|
|
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
static const char* supply_names[AXP_NUM_SUPPLIES] = {
|
|
[AXP_SUPPLY_EXTEN] = "EXTEN",
|
|
[AXP_SUPPLY_DCDC1] = "DCDC1",
|
|
[AXP_SUPPLY_DCDC2] = "DCDC2",
|
|
[AXP_SUPPLY_DCDC3] = "DCDC3",
|
|
[AXP_SUPPLY_LDO2] = "LDO2",
|
|
[AXP_SUPPLY_LDO3] = "LDO3",
|
|
[AXP_SUPPLY_LDOIO0] = "LDOIO0",
|
|
};
|
|
|
|
struct axp_fieldinfo {
|
|
uint8_t rnum;
|
|
uint8_t msb: 4;
|
|
uint8_t lsb: 4;
|
|
};
|
|
|
|
enum {
|
|
#define DEFREG(name, ...) AXP_RNUM_##name,
|
|
#include "axp192-defs.h"
|
|
AXP_NUM_REGS,
|
|
};
|
|
|
|
enum {
|
|
#define DEFFLD(regname, fldname, ...) AXP_FNUM_##regname##_##fldname,
|
|
#include "axp192-defs.h"
|
|
AXP_NUM_FIELDS,
|
|
};
|
|
|
|
static const uint8_t axp_regaddr[AXP_NUM_REGS] = {
|
|
#define DEFREG(name, addr) addr,
|
|
#include "axp192-defs.h"
|
|
};
|
|
|
|
static const struct axp_fieldinfo axp_fieldinfo[AXP_NUM_FIELDS] = {
|
|
#define DEFFLD(regname, fldname, _msb, _lsb, ...) \
|
|
{.rnum = AXP_RNUM_##regname, .msb = _msb, .lsb = _lsb},
|
|
#include "axp192-defs.h"
|
|
};
|
|
|
|
static const char* const axp_regnames[AXP_NUM_REGS] = {
|
|
#define DEFREG(name, ...) #name,
|
|
#include "axp192-defs.h"
|
|
};
|
|
|
|
static const char* const axp_fldnames[AXP_NUM_FIELDS] = {
|
|
#define DEFFLD(regname, fldname, ...) #fldname,
|
|
#include "axp192-defs.h"
|
|
};
|
|
#endif /* AXP_EXTRA_DEBUG */
|
|
|
|
struct axp_debug_menu_state {
|
|
int mode;
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
int reg_num;
|
|
int field_num;
|
|
int field_cnt;
|
|
uint8_t cache[AXP_NUM_REGS];
|
|
uint8_t is_cached[AXP_NUM_REGS];
|
|
#endif
|
|
};
|
|
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
static void axp_debug_clear_cache(struct axp_debug_menu_state* state)
|
|
{
|
|
memset(state->is_cached, 0, sizeof(state->is_cached));
|
|
}
|
|
|
|
static int axp_debug_get_rnum(uint8_t addr)
|
|
{
|
|
for(int i = 0; i < AXP_NUM_REGS; ++i)
|
|
if(axp_regaddr[i] == addr)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static uint8_t axp_debug_read(struct axp_debug_menu_state* state, int rnum)
|
|
{
|
|
if(state->is_cached[rnum])
|
|
return state->cache[rnum];
|
|
|
|
int value = axp_read(axp_regaddr[rnum]);
|
|
if(value < 0)
|
|
return 0;
|
|
|
|
state->is_cached[rnum] = 1;
|
|
state->cache[rnum] = value;
|
|
return value;
|
|
}
|
|
|
|
static void axp_debug_get_sel(const struct axp_debug_menu_state* state,
|
|
int item, int* rnum, int* fnum)
|
|
{
|
|
if(state->reg_num >= 0 && state->field_num >= 0) {
|
|
int i = item - state->reg_num;
|
|
if(i <= 0) {
|
|
/* preceding register is selected */
|
|
} else if(i <= state->field_cnt) {
|
|
/* field is selected */
|
|
*rnum = state->reg_num;
|
|
*fnum = i + state->field_num - 1;
|
|
return;
|
|
} else {
|
|
/* subsequent regiser is selected */
|
|
item -= state->field_cnt;
|
|
}
|
|
}
|
|
|
|
/* register is selected */
|
|
*rnum = item;
|
|
*fnum = -1;
|
|
}
|
|
|
|
static int axp_debug_set_sel(struct axp_debug_menu_state* state, int rnum)
|
|
{
|
|
state->reg_num = rnum;
|
|
state->field_num = -1;
|
|
state->field_cnt = 0;
|
|
|
|
for(int i = 0; i < AXP_NUM_FIELDS; ++i) {
|
|
if(axp_fieldinfo[i].rnum != rnum)
|
|
continue;
|
|
|
|
state->field_num = i;
|
|
do {
|
|
state->field_cnt++;
|
|
i++;
|
|
} while(axp_fieldinfo[i].rnum == rnum);
|
|
break;
|
|
}
|
|
|
|
return rnum;
|
|
}
|
|
#endif /* AXP_EXTRA_DEBUG */
|
|
|
|
static const char* axp_debug_menu_get_name(int item, void* data,
|
|
char* buf, size_t buflen)
|
|
{
|
|
struct axp_debug_menu_state* state = data;
|
|
int value;
|
|
|
|
/* for safety */
|
|
buf[0] = '\0';
|
|
|
|
if(state->mode == MODE_ADC && item < AXP_NUM_ADCS)
|
|
{
|
|
const struct axp_adcdebuginfo* info = &adc_debuginfo[item];
|
|
value = axp_read_adc(item);
|
|
if(item == AXP_ADC_INTERNAL_TEMP) {
|
|
snprintf(buf, buflen, "%s: %d.%d %s",
|
|
info->name, value/10, value%10, info->unit);
|
|
} else {
|
|
snprintf(buf, buflen, "%s: %d %s", info->name, value, info->unit);
|
|
}
|
|
}
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
else if(state->mode == MODE_SUPPLY && item < AXP_NUM_SUPPLIES)
|
|
{
|
|
const struct axp_supplydata* data = &supplydata[item];
|
|
int en_rnum = axp_debug_get_rnum(data->en_reg);
|
|
int volt_rnum = axp_debug_get_rnum(data->volt_reg);
|
|
bool enabled = false;
|
|
int voltage = -1;
|
|
|
|
if(en_rnum >= 0) {
|
|
value = axp_debug_read(state, en_rnum);
|
|
if(value & data->en_bit)
|
|
enabled = true;
|
|
else
|
|
enabled = false;
|
|
} else if(item == AXP_SUPPLY_LDOIO0) {
|
|
value = axp_debug_read(state, AXP_RNUM_GPIO0FUNC);
|
|
if((value & 0x7) == AXP_GPIO_SPECIAL)
|
|
enabled = true;
|
|
else
|
|
enabled = false;
|
|
}
|
|
|
|
if(volt_rnum >= 0) {
|
|
voltage = axp_debug_read(state, volt_rnum);
|
|
voltage >>= data->volt_lsb;
|
|
voltage &= (1 << (data->volt_msb - data->volt_lsb + 1)) - 1;
|
|
|
|
/* convert to mV */
|
|
voltage = data->min_mV + voltage * data->step_mV;
|
|
}
|
|
|
|
if(enabled && voltage >= 0) {
|
|
snprintf(buf, buflen, "%s: %d mV",
|
|
supply_names[item], voltage);
|
|
} else {
|
|
snprintf(buf, buflen, "%s: %sabled",
|
|
supply_names[item], enabled ? "en" : "dis");
|
|
}
|
|
}
|
|
else if(state->mode == MODE_REGISTER)
|
|
{
|
|
int rnum, fnum;
|
|
axp_debug_get_sel(state, item, &rnum, &fnum);
|
|
|
|
if(fnum >= 0) {
|
|
const struct axp_fieldinfo* info = &axp_fieldinfo[fnum];
|
|
value = axp_debug_read(state, info->rnum);
|
|
value >>= info->lsb;
|
|
value &= (1 << (info->msb - info->lsb + 1)) - 1;
|
|
snprintf(buf, buflen, "\t%s: %d (0x%x)",
|
|
axp_fldnames[fnum], value, value);
|
|
} else if(rnum < AXP_NUM_REGS) {
|
|
value = axp_debug_read(state, rnum);
|
|
snprintf(buf, buflen, "%s: 0x%02x", axp_regnames[rnum], value);
|
|
}
|
|
}
|
|
#endif /* AXP_EXTRA_DEBUG */
|
|
|
|
return buf;
|
|
}
|
|
|
|
static int axp_debug_menu_cb(int action, struct gui_synclist* lists)
|
|
{
|
|
struct axp_debug_menu_state* state = lists->data;
|
|
|
|
if(state->mode == MODE_ADC)
|
|
{
|
|
/* update continuously */
|
|
if(action == ACTION_NONE)
|
|
action = ACTION_REDRAW;
|
|
}
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
else if(state->mode == MODE_REGISTER)
|
|
{
|
|
if(action == ACTION_STD_OK) {
|
|
/* expand a register to show its fields */
|
|
int rnum, fnum;
|
|
int sel_pos = gui_synclist_get_sel_pos(lists);
|
|
axp_debug_get_sel(state, sel_pos, &rnum, &fnum);
|
|
if(fnum < 0 && rnum < AXP_NUM_REGS) {
|
|
int delta_items = -state->field_cnt;
|
|
if(rnum != state->reg_num) {
|
|
if(rnum > state->reg_num)
|
|
sel_pos += delta_items;
|
|
|
|
axp_debug_set_sel(state, rnum);
|
|
delta_items += state->field_cnt;
|
|
} else {
|
|
state->reg_num = -1;
|
|
state->field_num = -1;
|
|
state->field_cnt = 0;
|
|
}
|
|
|
|
gui_synclist_set_nb_items(lists, lists->nb_items + delta_items);
|
|
gui_synclist_select_item(lists, sel_pos);
|
|
action = ACTION_REDRAW;
|
|
}
|
|
}
|
|
}
|
|
else if(state->mode == MODE_SUPPLY)
|
|
{
|
|
/* disable a supply... use with caution */
|
|
if(action == ACTION_STD_CONTEXT) {
|
|
int sel_pos = gui_synclist_get_sel_pos(lists);
|
|
axp_enable_supply(sel_pos, false);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
/* clear register cache to refresh values */
|
|
if(state->mode != MODE_ADC && action == ACTION_STD_CONTEXT) {
|
|
splashf(HZ/2, "Refreshed");
|
|
axp_debug_clear_cache(state);
|
|
action = ACTION_REDRAW;
|
|
}
|
|
#endif
|
|
|
|
/* mode switching */
|
|
if(action == ACTION_STD_MENU) {
|
|
state->mode = (state->mode + 1) % NUM_MODES;
|
|
gui_synclist_set_title(lists, (char*)axp_modenames[state->mode], Icon_NOICON);
|
|
action = ACTION_REDRAW;
|
|
|
|
switch(state->mode) {
|
|
case MODE_ADC:
|
|
gui_synclist_set_nb_items(lists, AXP_NUM_ADCS);
|
|
gui_synclist_select_item(lists, 0);
|
|
break;
|
|
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
case MODE_SUPPLY:
|
|
axp_debug_clear_cache(state);
|
|
gui_synclist_set_nb_items(lists, AXP_NUM_SUPPLIES);
|
|
gui_synclist_select_item(lists, 0);
|
|
break;
|
|
|
|
case MODE_REGISTER:
|
|
state->reg_num = -1;
|
|
state->field_num = -1;
|
|
state->field_cnt = 0;
|
|
axp_debug_clear_cache(state);
|
|
gui_synclist_set_nb_items(lists, AXP_NUM_REGS);
|
|
gui_synclist_select_item(lists, 0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
bool axp_debug_menu(void)
|
|
{
|
|
struct axp_debug_menu_state state;
|
|
state.mode = MODE_ADC;
|
|
#ifdef AXP_EXTRA_DEBUG
|
|
state.reg_num = -1;
|
|
state.field_num = -1;
|
|
state.field_cnt = 0;
|
|
axp_debug_clear_cache(&state);
|
|
#endif
|
|
|
|
struct simplelist_info info;
|
|
simplelist_info_init(&info, (char*)axp_modenames[state.mode],
|
|
AXP_NUM_ADCS, &state);
|
|
info.get_name = axp_debug_menu_get_name;
|
|
info.action_callback = axp_debug_menu_cb;
|
|
return simplelist_show_list(&info);
|
|
}
|
|
#endif
|