Charging fault diagnosis (BQ24297 register dump)


ouzle

Very Active Member
Joined
Jun 3, 2019
Messages
136
Location
England
I've written a register dump code for the BQ24297 power management chip. I wanted to see why the charging fault is flashing and to gain general understanding of the chip. The fault state is reported at the end of the output.

Note that there is a similar program for the BQ27421 fuel guage chip in /usr/share/pyra/bin

This belongs in https://dev.pyra-handheld.com/packages/pyra-misc but I'll need approval to push to the dev site.

/sys/class/power_supply/bq24297/device/registers

INPUT SOURCE CONTROL, REG00: 0x37
EN_HIZ= no
Input voltage limit, VINDPM=4.12V
Input current limit, INLIM =3000mA

POWER-ON CONFIGURATION, REG01: 0x17
Register Reset= keep
I2C Watchdog Timer Reset= normal
OTG_CONFIG= OTG_DISABLE
CHG_CONFIG= Charge enabled
Minimum system voltage= 3.3V
Boost mode current limit, BOOST_LIM= 1.5A

CHARGE CURRENT CONTROL, REG02: 0x60
Charge current= 2048mA
Boost mode temperature monitor threshold voltage=
V bcold0 (Typ. 76% of REGN or -10°C w/103AT thermistor)
FORCE_20PCT=
ICHG as Fast Charge Current (REG02[7:2]) and
IPRECH as Pre-Charge Current (REG03[7:4]) programmed

PRE-CHARGE & TERMINATION CURRENT, REG03: 0x00
Pre-charge current= 128mA
Termination current= 128mA

CHARGE VOLTAGE CONTROL REGISTER, REG04: 0x8a
Charge voltage, VREG =4.05V
Battery recharge threshold, VRECHG =3.95V
Battery low voltage, BATLOWV=3.00V

CHARGE TERMINATION / TIMER CONTROL REGISTER, REG05: 0x8c
Termination = Enabled
Watchdog timeout = 0s (disabled)
Charging safety timer = Enabled (12hr)

BOOST VOLTAGE / THERMAL REGULATION CONTROL REGISTER, REG06: 0x73
Boost voltage = 4.998
Boost mode temperature threshold = 33% of REGN or 55°C w/103AT thermistor
Thermal regulation threshold = 120°C

MISC OPERATION CONTROL REGISTER, REG07: 0x4b
Force DPDM detection = Not in input current limit detection
Safety timer setting during Input DPM and Thermal Regulation:
Safety timer slowed by 2X during input DPM or thermal regulation
Batt FET disable = Allow BATFET (Q4) turn on
Charge fault INT mask = INT on CHRG_FAULT
Batt fault INT mask = INT on BAT_FAULT

SYSTEM STATUS REGISTER, REG08: 0xa4
VBUS status = Adapter port
Charge status = Fast Charging
DPM status = Not DPM
Power good status= Power Good
Thermal status = Normal
VSYS status = Not in VSYSMIN regulation (BAT > VSYSMIN)

NEW FAULT REGISTER, REG09: 0x00
Watchdog fault = Normal
OTG fault = Normal
Charge fault = Normal
Battery fault = Normal
NTC fault (cold) = Normal
NTC fault (hot) = Normal

VENDOR / PART / REVISION STATUS REGISTER, REG0A: 0x60
Chip code = BQ24297
Revision = 0

C:
/*
* bqdump - bq24297 register dumper
*
* Copyright (c) 2021, Ian Williams
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the organization nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#define SYS_REGISTERS "/sys/class/power_supply/bq24297/device/registers"
#define I2C_ADDRESS 0x6b

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

static int bq_read_i2c(int fd, __u8 reg, int len)
{
    struct i2c_rdwr_ioctl_data rdwr;
    struct i2c_msg msg[2];
    unsigned char data[2];
    int ret;

    assert(len == 1 || len == 2);

    msg[0].addr = I2C_ADDRESS;
    msg[0].flags = 0;
    msg[0].buf = &reg;
    msg[0].len = sizeof(reg);
    msg[1].addr = I2C_ADDRESS;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = data;
    msg[1].len = len;

    rdwr.msgs = msg;
    rdwr.nmsgs = ARRAY_SIZE(msg);

    // I2C_SMBUS also works, but reads sometimes return 0xff?
    ret = ioctl(fd, I2C_RDWR, &rdwr);
    if (ret != ARRAY_SIZE(msg)) {
        fprintf(stderr, "read at %02x failed: ", reg);
        perror(NULL);
        exit(1);
    }
    ret = data[0];
    if (len > 1)
        ret |= data[1] << 8;
    return ret;
}

const int inlim_val[] = { 100, 150, 500, 900, 1000, 1500, 2000, 3000 };

const char *vbus_status_val[] = {
    "Unknown (no input or DPDM inspection incomplete)",
    "USB host",
    "Adapter port",
    "OTG"
    };

const char *charge_status_val[] = {
    "Not Charging",
    "Pre-charge (<V_BATLOWV)",
    "Fast Charging",
    "Charge Termination Done"
    };

const char *charge_fault_val[] = {
    "Normal",
    "Input fault (OVP or bad source)",
    "Thermal shutdown",
    "Charge timer expiration"
    };

const int charge_timer_duration_val[] = { 5, 8, 12, 20 };
   
const char *boosthot_val[] = {
    "33\% of REGN or 55°C w/103AT thermistor",
    "36\% of REGN or 60°C w/103AT thermistor",
    "30\% of REGN or 65°C w/103AT thermistor",
    "Disable boost mode thermal protection."
    };
     
int main(int argc, char *argv[])
{
    /* If false, read registers using sysfs, otherwise use i2c */
    int use_i2c = 0;

    /* Parse command line arguments to determine whether to read from sysfs or direct from i2c */
    if (argc > 1)
    {
        int opt;
        while ((opt = getopt(argc, argv, "ih")) != -1) {
            switch (opt) {
            case 'i': use_i2c = 1; break;   
       
            default:
                printf("Usage: %s [-i]\n", argv[0]);
                printf("   Dumps registers from BQ24297 power management chip at i2c:1 addr %02x\n\n", I2C_ADDRESS);
                printf("   Default is to read from \n   %s\n\n", SYS_REGISTERS);
                printf("   -i\tRegisters are read directly from /dev/i2c-1 (not recommended)\n");
                exit(EXIT_FAILURE);
            }
        }
    }

    const int nreg = 0x0A + 1;
    int reg[nreg];
    if (use_i2c)
    {
        printf("Bus i2c-1, address 0x%02x\n\n", I2C_ADDRESS);

        int fd = open("/dev/i2c-1", O_RDWR);
        if (fd < 0) {
            perror("i2c open");
            return 1;
        }
           
        for (int i=0; i<nreg; i++)
            reg[i] = bq_read_i2c(fd, i, 1);
    }
    else
    {
        printf("%s\n\n", SYS_REGISTERS);   
        FILE* fp = fopen( SYS_REGISTERS, "r");   
        if (fp == NULL)
            exit(EXIT_FAILURE);
           
        char * line = NULL;
        size_t len = 0;
        ssize_t read;
        while ((read = getline(&line, &len, fp)) != -1) {       
            char * pch;       
            pch = strtok (line," \t"); /* "reg" */
            if (pch == NULL) break;
            pch = strtok (NULL," \t");
            if (pch == NULL) break;
            int regaddr = (int)strtol(pch, NULL, 16);
            /*printf ("REG%02x = ",regaddr);*/
            pch = strtok (NULL," \t"); /* "value" */
            if (pch == NULL) break;
            pch = strtok (NULL," \t");
            if (pch == NULL) break;
            if (regaddr >= 0 && regaddr < nreg)
            {
                reg[regaddr] = (int)strtol(pch, NULL, 16);
                /*printf ("%02x\n",reg[regaddr]);*/
            }
        }

        fclose(fp);
        if (line)
            free(line);
    }

    /* Input source control register, 0x00 */
    float vindpm = 3.88 + 0.08*(float)((reg[0] & 0x70)>>4);
    int inlim = inlim_val[reg[0] & 0x0F];
    printf("INPUT SOURCE CONTROL, REG00: 0x%02x\n", reg[0]);
    printf("   EN_HIZ= %s\n", reg[0] & (1 << 7) ? "yes" : "no");
    printf("   Input voltage limit, VINDPM=%.2fV\n", vindpm);
    printf("   Input current limit, INLIM =%dmA\n", inlim);
    printf("\n");

    /* Power-On Configuration Register, 0x01 */
    float sysminv = 3.0 + 0.1*(float)((reg[1] & 0x0E) >> 1);
    float boostlim = reg[1] & 1 ? 1.5 : 1.0;
    printf("POWER-ON CONFIGURATION, REG01: 0x%02x\n", reg[1]);
    printf("   Register Reset= %s\n",           reg[1] & (1 << 7) ? "reset to default" : "keep");
    printf("   I2C Watchdog Timer Reset= %s\n", reg[1] & (1 << 6) ? "reset" : "normal");
    printf("   OTG_CONFIG= %s\n",               reg[1] & (1 << 5) ? "OTG_ENABLE" : "OTG_DISABLE");
    printf("   CHG_CONFIG= %s\n",               reg[1] & (1 << 4) ? "Charge enabled" : "Charge disable");
    printf("   Minimum system voltage= %gV\n", sysminv);
    printf("   Boost mode current limit, BOOST_LIM= %.1fA\n", boostlim);
    printf("\n");

    /* Charge Current Control Register, 0x02 */
    int chargecur_mA = 512 + 64*(reg[2] >> 2);
    printf("CHARGE CURRENT CONTROL, REG02: 0x%02x\n", reg[2]);
    printf("   Charge current= %dmA\n", chargecur_mA);
    printf("   Boost mode temperature monitor threshold voltage=\n%s\n",
        reg[2] & (1 << 1) ?
        "      V_bcold1 (Typ. 79% of REGN or -20°C w/103AT thermistor)" :
        "      V bcold0 (Typ. 76% of REGN or -10°C w/103AT thermistor)");
    printf("   FORCE_20PCT=\n%s\n",
        reg[2] & (1 << 0) ?
        "      ICHG as 20% Fast Charge Current (REG02[7:2]) and \n      IPRECH as 50% Pre-Charge Current(REG03[7:4]) programmed\n" :
        "      ICHG as Fast Charge Current (REG02[7:2]) and \n      IPRECH as Pre-Charge Current (REG03[7:4]) programmed");
    printf("\n");

    /* Pre-Charge/Termination Current Control Register, 0x03 */
    int iprechg_mA = 128 + 128*((reg[3] & 0xF0) >> 4);
    int iterm_mA   = 128 + 128* (reg[3] & 0x0F);
    printf("PRE-CHARGE & TERMINATION CURRENT, REG03: 0x%02x\n", reg[3]);
    printf("    Pre-charge current= %dmA\n", iprechg_mA);
    printf("   Termination current= %dmA\n", iterm_mA);
    printf("\n");

    /* Charge Voltage Control Register, 0x04 */
    float vreg = 3.504 + 0.016*(float)(reg[4] >> 2);
    printf("CHARGE VOLTAGE CONTROL REGISTER, REG04: 0x%02x\n", reg[4]);
    printf("               Charge voltage, VREG   =%.2fV\n", vreg);
    printf("   Battery recharge threshold, VRECHG =%.2fV\n", reg[4] & 1 ? vreg - 0.3 : vreg - 0.1);
    printf("          Battery low voltage, BATLOWV=%.2fV\n", reg[4] & 2 ? 3.0 : 2.8 );
    printf("\n");

    /* Charge Termination/Timer Control Register */
    int watchdog_timeout = (reg[5] & 0x30) >> 4;
    int charge_safety_duration = charge_timer_duration_val[(reg[5] & 0x06) >> 1];
    printf("CHARGE TERMINATION / TIMER CONTROL REGISTER, REG05: 0x%02x\n", reg[5]);
    printf("   Termination = %s\n", reg[5] & (1<<7) ? "Enabled" : "Disabled");
    printf("   Watchdog timeout = %ds", watchdog_timeout );
    if (watchdog_timeout == 0) printf(" (disabled)");
    printf("\n");
    printf("   Charging safety timer = %s", reg[5] & (1<<3) ? "Enabled" : "Disabled");
    printf(" (%dhr)\n", charge_safety_duration);
    printf("\n");
   
    /* Boost Voltage/Thermal Regulation Control Register, 0x06 */
    float boostv = 4.55 + 0.064*(float)(reg[6] >> 4);
    const char* boosthot = boosthot_val[(reg[6] & 0x0C) >> 2];
    int thermreg = 60 + 20*(reg[6] & 3);
    printf("BOOST VOLTAGE / THERMAL REGULATION CONTROL REGISTER, REG06: 0x%02x\n", reg[6]);
    printf("   Boost voltage = %g\n", boostv);
    printf("   Boost mode temperature threshold = %s\n", boosthot );
    printf("   Thermal regulation threshold = %d°C\n", thermreg );
    printf("\n");

    /* Misc Operation Control Register, 0x07 */
    printf("MISC OPERATION CONTROL REGISTER, REG07: 0x%02x\n", reg[7]);
    printf("   Force DPDM detection = %s\n", reg[7] & (1<<7) ? "Force input current limit detection when VBUS power is present"
                                                          : "Not in input current limit detection");
    printf("   Safety timer setting during Input DPM and Thermal Regulation:\n");
    printf("       %s\n", reg[7] & (1<<6) ? "Safety timer slowed by 2X during input DPM or thermal regulation"
                                       : "Safety timer not slowed by 2X during input DPM or thermal regulation");
    printf("   Batt FET disable = %s\n", reg[7] & (1<<5) ? "Turn off BATFET (Q4)" : "Allow BATFET (Q4) turn on");                                   
    printf("   Charge fault INT mask = %s\n", reg[7] & (1<<1) ? "INT on CHRG_FAULT" : "No INT on CHRG_FAULT");
    printf("   Batt fault INT mask = %s\n", reg[7] & (1<<0) ? "INT on BAT_FAULT" : "No INT on BAT_FAULT");
    printf("\n");

    /* System Status Register, 0x08 */
    printf("SYSTEM STATUS REGISTER, REG08: 0x%02x\n", reg[8]);
    printf("   VBUS status      = %s\n", vbus_status_val[reg[8] >> 6]);
    printf("   Charge status    = %s\n", charge_status_val[(reg[8] & 0x30) >> 4]);
    printf("   DPM status       = %s\n", reg[8] & (1<<3) ? "VINDPM or IINDPM" : "Not DPM");
    printf("   Power good status= %s\n", reg[8] & (1<<2) ? "Power Good" : "Not Power Good");
    printf("   Thermal status   = %s\n", reg[8] & (1<<1) ? "In Thermal Regulation" : "Normal" );
    printf("   VSYS status      = %s\n", reg[8] & (1<<0) ? "In VSYSMIN regulation (BAT < VSYSMIN)" : "Not in VSYSMIN regulation (BAT > VSYSMIN)");
    printf("\n");

    /* New Fault Register, 0x09 */
    printf("NEW FAULT REGISTER, REG09: 0x%02x\n", reg[9]);
    printf("   Watchdog fault   = %s\n", reg[9] & (1<<7) ? "Watchdog timer expiration" : "Normal");
    printf("   OTG fault        = %s\n", reg[9] & (1<<6) ? "VBUS overloaded in OTG, or VBUS OVP, or battery is too low (any conditions that cannot start boost function)" : "Normal");
    printf("   Charge fault     = %s\n", charge_fault_val[(reg[9] & 0x30) >> 4]);
    printf("   Battery fault    = %s\n", reg[9] & (1<<3) ? "Battery OVP" : "Normal");
    printf("   NTC fault (cold) = %s\n", reg[9] & (1<<1) ? "Cold" : "Normal");
    printf("   NTC fault (hot)  = %s\n", reg[9] & (1<<0) ? "Hot"  : "Normal");  
    printf("\n");

    /* Vender / Part / Revision Status Register, 0x0A */
    printf("VENDOR / PART / REVISION STATUS REGISTER, REG0A: 0x%02x\n", reg[10]);
    int code = (reg[10] & 0xE0) >> 5;
    printf("   Chip code = %s\n", code==1 ? "BQ24296" : code==3 ? "BQ24297" : "Unrecognised");
    printf("   Revision = %d\n", reg[10] & 0x07);

    return 0;
}

Makefile:
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -O2 -ggdb

TARGETS = bqdump274 bqdump24297 neon_loop2 pyra_test_inputs

all: $(TARGETS)

clean:
    $(RM) $(TARGETS)

pyra_test_inputs: LDLIBS += -lpthread
neon_loop2: ASFLAGS += -mcpu=cortex-a15 -mfpu=neon

.PHONY: all clean

EDIT:
Updated to read registers from/sys/class/power_supply/bq24297/device/registers
or /dev/i2c-1 if the -i argument is given

EDIT2: Updated example output for good fast charging state
 
Last edited:
Nothing stop you to create an account on that gitlab instance and create your own fork there
That would be more usefull for packaging later on :)
 
Ok, makes sense. I'll do that once the account approval goes through:
" Your account is pending approval from your GitLab administrator and hence blocked. Please contact your GitLab administrator if you think this is an error."
 
Ok, makes sense. I'll do that once the account approval goes through:
" Your account is pending approval from your GitLab administrator and hence blocked. Please contact your GitLab administrator if you think this is an error."
I believe @ED is seeking PMs
Yeah, I wanted to post a sticky here on the boards as well.
The problem is that I receive 30 approval requests per day and it's hard to find out which user is legit and who isn't. Well, for most it's obvious - but not for all.
Therefore, registering and then PM'in me the username / email address so I can approve it would be useful.
Also, Thank you very much for throwing the source up here in spoiler tags. It really adds some sex appeal.
 
Most of the information is available through
Code:
cat /sys/class/power_supply/bq24297/uevent
cat /sys/class/power_supply/bq24297/device/registers
so that you do not have to bypass i2c of the chip driver.
Ok, I'll update bqdump24297 and bqdump274 to take the values from /sys/.../registers. That way it won't need sudo to work.
 
Last edited:
Back
Top