/*************************************************************************** * __________ __ ___. * 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 "axp173.h" #include "power.h" #include "i2c-async.h" /* Headers for the debug menu */ #ifndef BOOTLOADER # include "action.h" # include "list.h" # include #endif static const struct axp173_adc_info { uint8_t reg; uint8_t en_reg; uint8_t en_bit; } axp173_adc_info[NUM_ADC_CHANNELS] = { {0x56, AXP173_REG_ADCENABLE1, 5}, /* ACIN_VOLTAGE */ {0x58, AXP173_REG_ADCENABLE1, 4}, /* ACIN_CURRENT */ {0x5a, AXP173_REG_ADCENABLE1, 3}, /* VBUS_VOLTAGE */ {0x5c, AXP173_REG_ADCENABLE1, 2}, /* VBUS_CURRENT */ {0x5e, AXP173_REG_ADCENABLE2, 7}, /* INTERNAL_TEMP */ {0x62, AXP173_REG_ADCENABLE1, 1}, /* TS_INPUT */ {0x78, AXP173_REG_ADCENABLE1, 7}, /* BATTERY_VOLTAGE */ {0x7a, AXP173_REG_ADCENABLE1, 6}, /* CHARGE_CURRENT */ {0x7c, AXP173_REG_ADCENABLE1, 6}, /* DISCHARGE_CURRENT */ {0x7e, AXP173_REG_ADCENABLE1, 1}, /* APS_VOLTAGE */ {0x70, 0xff, 0}, /* BATTERY_POWER */ }; static struct axp173 { int adc_enable; int chargecurrent_setting; } axp173; static void axp173_init_enabled_adcs(void) { axp173.adc_enable = 0; /* Read enabled ADCs from the hardware */ uint8_t regs[2]; int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, AXP173_REG_ADCENABLE1, 2, ®s[0]); if(rc != I2C_STATUS_OK) return; /* Parse registers to set ADC enable bits */ const struct axp173_adc_info* info = axp173_adc_info; for(int i = 0; i < NUM_ADC_CHANNELS; ++i) { if(info[i].en_reg == 0xff) continue; if(regs[info[i].en_reg - AXP173_REG_ADCENABLE1] & info[i].en_bit) axp173.adc_enable |= 1 << i; } /* Handle battery power ADC */ if((axp173.adc_enable & (1 << ADC_BATTERY_VOLTAGE)) && (axp173.adc_enable & (1 << ADC_DISCHARGE_CURRENT))) { axp173.adc_enable |= (1 << ADC_BATTERY_POWER); } } void axp173_init(void) { axp173_init_enabled_adcs(); /* We need discharge current ADC to reliably poll for a full battery */ int bits = axp173.adc_enable; bits |= (1 << ADC_DISCHARGE_CURRENT); axp173_adc_set_enabled(bits); /* Read the maximum charging current */ int value = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, AXP173_REG_CHARGECONTROL1); axp173.chargecurrent_setting = (value < 0) ? -1 : value; } /* TODO: this can STILL indicate some false positives! */ int axp173_battery_status(void) { int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, AXP173_REG_POWERSTATUS); if(r >= 0) { /* Charging bit indicates we're currently charging */ if((r & 0x04) != 0) return AXP173_BATT_CHARGING; /* Not plugged in means we're discharging */ if((r & 0xf0) == 0) return AXP173_BATT_DISCHARGING; } else { /* Report discharging if we can't find out power status */ return AXP173_BATT_DISCHARGING; } /* If the battery is full and not in use, the charging bit will be 0, * there will be an external power source, AND the discharge current * will be zero. Seems to rule out all false positives. */ int d = axp173_adc_read_raw(ADC_DISCHARGE_CURRENT); if(d == 0) return AXP173_BATT_FULL; return AXP173_BATT_DISCHARGING; } int axp173_input_status(void) { #ifdef HAVE_BATTERY_SWITCH int input_status = 0; #else int input_status = AXP173_INPUT_BATTERY; #endif int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, AXP173_REG_POWERSTATUS); if(r < 0) return input_status; /* Check for AC input */ if(r & 0x80) input_status |= AXP173_INPUT_AC; /* Only report USB if ACIN and VBUS are not shorted */ if((r & 0x20) != 0 && (r & 0x02) == 0) input_status |= AXP173_INPUT_USB; #ifdef HAVE_BATTERY_SWITCH /* Check for battery presence if target defines it as removable */ r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, AXP173_REG_CHARGESTATUS); if(r >= 0 && (r & 0x20) != 0) input_status |= AXP173_INPUT_BATTERY; #endif return input_status; } int axp173_adc_read(int adc) { int value = axp173_adc_read_raw(adc); if(value == INT_MIN) return INT_MIN; return axp173_adc_conv_raw(adc, value); } int axp173_adc_read_raw(int adc) { /* Don't give a reading if the ADC is not enabled */ if((axp173.adc_enable & (1 << adc)) == 0) return INT_MIN; /* Read the ADC */ uint8_t buf[3]; int count = (adc == ADC_BATTERY_POWER) ? 3 : 2; uint8_t reg = axp173_adc_info[adc].reg; int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, reg, count, &buf[0]); if(rc != I2C_STATUS_OK) return INT_MIN; /* Parse the value */ if(adc == ADC_BATTERY_POWER) return (buf[0] << 16) | (buf[1] << 8) | buf[2]; else if(adc == ADC_CHARGE_CURRENT || adc == ADC_DISCHARGE_CURRENT) return (buf[0] << 5) | (buf[1] & 0x1f); else return (buf[0] << 4) | (buf[1] & 0xf); } int axp173_adc_conv_raw(int adc, int value) { switch(adc) { case ADC_ACIN_VOLTAGE: case ADC_VBUS_VOLTAGE: /* 0 mV ... 6.9615 mV, step 1.7 mV */ return value * 17 / 10; case ADC_ACIN_CURRENT: /* 0 mA ... 2.5594 A, step 0.625 mA */ return value * 5 / 8; case ADC_VBUS_CURRENT: /* 0 mA ... 1.5356 A, step 0.375 mA */ return value * 3 / 8; case ADC_INTERNAL_TEMP: /* -144.7 C ... 264.8 C, step 0.1 C */ return value - 1447; case ADC_TS_INPUT: /* 0 mV ... 3.276 V, step 0.8 mV */ return value * 4 / 5; case ADC_BATTERY_VOLTAGE: /* 0 mV ... 4.5045 V, step 1.1 mV */ return value * 11 / 10; case ADC_CHARGE_CURRENT: case ADC_DISCHARGE_CURRENT: /* 0 mA to 4.095 A, step 0.5 mA */ return value / 2; case ADC_APS_VOLTAGE: /* 0 mV to 5.733 V, step 1.4 mV */ return value * 7 / 5; case ADC_BATTERY_POWER: /* 0 uW to 23.6404 W, step 0.55 uW */ return value * 11 / 20; default: /* Shouldn't happen */ return INT_MIN; } } int axp173_adc_get_enabled(void) { return axp173.adc_enable; } void axp173_adc_set_enabled(int adc_bits) { /* Ignore no-op */ if(adc_bits == axp173.adc_enable) return; /* Compute the new register values */ const struct axp173_adc_info* info = axp173_adc_info; uint8_t regs[2] = {0, 0}; for(int i = 0; i < NUM_ADC_CHANNELS; ++i) { if(info[i].en_reg == 0xff) continue; if(adc_bits & (1 << i)) regs[info[i].en_reg - 0x82] |= 1 << info[i].en_bit; } /* These ADCs share an enable bit */ if(adc_bits & ((1 << ADC_CHARGE_CURRENT)|(1 << ADC_DISCHARGE_CURRENT))) { adc_bits |= (1 << ADC_CHARGE_CURRENT); adc_bits |= (1 << ADC_DISCHARGE_CURRENT); } /* Enable required bits for battery power ADC */ if(adc_bits & (1 << ADC_BATTERY_POWER)) { regs[0] |= 1 << info[ADC_DISCHARGE_CURRENT].en_bit; regs[0] |= 1 << info[ADC_BATTERY_VOLTAGE].en_bit; } /* Update the configuration */ i2c_reg_write(AXP173_BUS, AXP173_ADDR, AXP173_REG_ADCENABLE1, 2, ®s[0]); axp173.adc_enable = adc_bits; } int axp173_adc_get_rate(void) { int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, AXP173_REG_ADCSAMPLERATE); if(r < 0) return AXP173_ADC_RATE_100HZ; /* an arbitrary value */ return (r >> 6) & 3; } void axp173_adc_set_rate(int rate) { i2c_reg_modify1(AXP173_BUS, AXP173_ADDR, AXP173_REG_ADCSAMPLERATE, 0xc0, (rate & 3) << 6, NULL); } static uint32_t axp173_cc_parse(const uint8_t* buf) { return ((uint32_t)buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } void axp173_cc_read(uint32_t* charge, uint32_t* discharge) { uint8_t buf[8]; int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, AXP173_REG_COULOMBCOUNTERBASE, 8, &buf[0]); if(rc != I2C_STATUS_OK) { if(charge) *charge = 0; if(discharge) *discharge = 0; return; } if(charge) *charge = axp173_cc_parse(&buf[0]); if(discharge) *discharge = axp173_cc_parse(&buf[4]); } void axp173_cc_clear(void) { i2c_reg_setbit1(AXP173_BUS, AXP173_ADDR, AXP173_REG_COULOMBCOUNTERCTRL, 5, 1, NULL); } void axp173_cc_enable(bool en) { i2c_reg_setbit1(AXP173_BUS, AXP173_ADDR, AXP173_REG_COULOMBCOUNTERCTRL, 7, en ? 1 : 0, NULL); } static const int chargecurrent_tbl[] = { 100, 190, 280, 360, 450, 550, 630, 700, 780, 880, 960, 1000, 1080, 1160, 1240, 1320, }; static const int chargecurrent_tblsz = sizeof(chargecurrent_tbl)/sizeof(int); void axp173_set_charge_current(int maxcurrent) { /* Find the charge current just higher than maxcurrent */ int value = 0; while(value < chargecurrent_tblsz && chargecurrent_tbl[value] <= maxcurrent) ++value; /* Select the next lower current, the greatest current <= maxcurrent */ if(value >= chargecurrent_tblsz) value = chargecurrent_tblsz - 1; else if(value > 0) --value; /* Don't issue i2c write if desired setting is already in use */ if(value == axp173.chargecurrent_setting) return; /* Update register */ i2c_reg_modify1(AXP173_BUS, AXP173_ADDR, AXP173_REG_CHARGECONTROL1, 0x0f, value, NULL); axp173.chargecurrent_setting = value; } int axp173_get_charge_current(void) { if(axp173.chargecurrent_setting < 0) return chargecurrent_tbl[0]; else return chargecurrent_tbl[axp173.chargecurrent_setting]; } #ifndef BOOTLOADER #define AXP173_DEBUG_BATTERY_STATUS 0 #define AXP173_DEBUG_INPUT_STATUS 1 #define AXP173_DEBUG_CHARGE_CURRENT 2 #define AXP173_DEBUG_ADC_RATE 3 #define AXP173_DEBUG_FIRST_ADC 4 #define AXP173_DEBUG_ENTRIES (AXP173_DEBUG_FIRST_ADC + NUM_ADC_CHANNELS) static int axp173_debug_menu_cb(int action, struct gui_synclist* lists) { (void)lists; if(action == ACTION_NONE) action = ACTION_REDRAW; return action; } static const char* axp173_debug_menu_get_name(int item, void* data, char* buf, size_t buflen) { (void)data; static const char* const adc_names[] = { "V_acin", "I_acin", "V_vbus", "I_vbus", "T_int", "V_ts", "V_batt", "I_chrg", "I_dchg", "V_aps", "P_batt" }; static const char* const adc_units[] = { "mV", "mA", "mV", "mA", "C", "mV", "mV", "mA", "mA", "mV", "uW", }; int adc = item - AXP173_DEBUG_FIRST_ADC; if(item >= AXP173_DEBUG_FIRST_ADC && adc < NUM_ADC_CHANNELS) { int raw_value = axp173_adc_read_raw(adc); if(raw_value == INT_MIN) { snprintf(buf, buflen, "%s: [Disabled]", adc_names[adc]); return buf; } int value = axp173_adc_conv_raw(adc, raw_value); if(adc == ADC_INTERNAL_TEMP) { snprintf(buf, buflen, "%s: %d.%d %s", adc_names[adc], value/10, value%10, adc_units[adc]); } else { snprintf(buf, buflen, "%s: %d %s", adc_names[adc], value, adc_units[adc]); } return buf; } switch(item) { case AXP173_DEBUG_BATTERY_STATUS: { switch(axp173_battery_status()) { case AXP173_BATT_FULL: return "Battery: Full"; case AXP173_BATT_CHARGING: return "Battery: Charging"; case AXP173_BATT_DISCHARGING: return "Battery: Discharging"; default: return "Battery: Unknown"; } } break; case AXP173_DEBUG_INPUT_STATUS: { int s = axp173_input_status(); const char* ac = (s & AXP173_INPUT_AC) ? " AC" : ""; const char* usb = (s & AXP173_INPUT_USB) ? " USB" : ""; const char* batt = (s & AXP173_INPUT_BATTERY) ? " Battery" : ""; snprintf(buf, buflen, "Inputs:%s%s%s", ac, usb, batt); return buf; } break; case AXP173_DEBUG_CHARGE_CURRENT: { int current = axp173_get_charge_current(); snprintf(buf, buflen, "Max charge current: %d mA", current); return buf; } break; case AXP173_DEBUG_ADC_RATE: { int rate = 25 << axp173_adc_get_rate(); snprintf(buf, buflen, "ADC sample rate: %d Hz", rate); return buf; } break; default: return "---"; } } bool axp173_debug_menu(void) { struct simplelist_info info; simplelist_info_init(&info, "AXP173 debug", AXP173_DEBUG_ENTRIES, NULL); info.action_callback = axp173_debug_menu_cb; info.get_name = axp173_debug_menu_get_name; return simplelist_show_list(&info); } #endif /* !BOOTLOADER */ /* This is basically the only valid implementation, so define it here */ unsigned int power_input_status(void) { unsigned int state = 0; int input_status = axp173_input_status(); if(input_status & AXP173_INPUT_AC) state |= POWER_INPUT_MAIN_CHARGER; if(input_status & AXP173_INPUT_USB) state |= POWER_INPUT_USB_CHARGER; #ifdef HAVE_BATTERY_SWITCH if(input_status & AXP173_INPUT_BATTERY) state |= POWER_INPUT_BATTERY; #endif return state; }