/* USRP E310 Firmware Texas Instruments BQ2419x driver * Copyright (C) 2014 Ettus Research * This file is part of the USRP E310 Firmware * The USRP E310 Firmware 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. * The USRP E310 Firmware is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with the USRP E310 Firmware. If not, see . */ #include "bq2419x.h" #include "io.h" #include "i2c_twi.h" #include "interrupt.h" #include "pmu.h" #include "mcu_settings.h" #include "utils.h" #include #include #include #include static const uint8_t BQ2419X_I2C_ADDR = 0x6b; #define bq2419x_read(reg, val) \ (i2c_twi_read(BQ2419X_I2C_ADDR, reg, val)) #define bq2419x_write(reg, val) \ (i2c_twi_write(BQ2419X_I2C_ADDR, reg, val)) /* register addresses */ static const uint8_t BQ2419X_REG_INPUT_SRC_CTL = 0x00; static const uint8_t BQ2419X_REG_PWR_ON_CONFIG = 0x01; static const uint8_t BQ2419X_REG_CHARGE_CURRENT = 0x02; static const uint8_t BQ2419X_REG_PRE_TERM_CURRENT = 0x03; static const uint8_t BQ2419X_REG_CHARGE_VOLTAGE = 0x04; static const uint8_t BQ2419X_REG_TIMER_CONTROL = 0x05; static const uint8_t BQ2419X_REG_THERMAL_REG_CTRL = 0x06; static const uint8_t BQ2419X_REG_MISC_OPERATION = 0x07; static const uint8_t BQ2419X_REG_SYSTEM_STATUS = 0x08; static const uint8_t BQ2419X_REG_FAULT = 0x09; static const uint8_t BQ2419X_REG_VENDOR_PART_REV = 0x0a; /* input source control register (REG00) */ static const uint8_t BQ2419X_EN_HIZ_MASK = BIT(7); static const uint8_t BQ2419X_EN_HIZ_SHIFT = 7; /* power on configuration register (REG01) */ static const uint8_t BQ2419X_REGISTER_RESET_MASK = BIT(7); static const uint8_t BQ2419X_REGISTER_RESET_SHIFT = 7; static const uint8_t BQ2419X_I2C_TIMER_RESET = BIT(6); static const uint8_t BQ2419X_I2C_TIMER_SHIFT = BIT(6); static const uint8_t BQ2419X_CHARGE_CFG_MASK = BIT(5) | BIT(4); static const uint8_t BQ2419X_CHARGE_CFG_SHIFT = 4; static const uint8_t BQ2419X_SYS_MIN_MASK = BIT(3) | BIT(2) | BIT(1); static const uint8_t BQ2419X_SYS_MIN_SHIFT = 1; /* charge current control register (REG02) */ static const uint8_t BQ2419X_ICHG_MASK = BIT(7) | BIT(6) \ | BIT(5) | BIT(4) | BIT(3) | BIT(2); static const uint8_t BQ2419X_ICHG_SHIFT = 2; /* reserved */ static const uint8_t BQ2419X_FORCE_20_PCT_MASK = BIT(0); static const uint8_t BQ2419X_FORCE_20_PCT_SHIFT = 0; static const uint8_t BQ2419X_CHARGE_CFG_DISABLED = 0x00; static const uint8_t BQ2419X_CHARGE_CFG_CHARGE = 0x01; static const uint8_t BQ2419X_CHARGE_CFG_OTG = 0x03; /* pre charge / termination current control register (REG03) */ /* charge voltage control register (REG04) */ /* charge / termination register (REG05) */ static const uint8_t BQ2419X_EN_TERM_MASK = BIT(7); static const uint8_t BQ2419X_EN_TERM_SHIFT = BIT(7); static const uint8_t BQ2419X_TERM_STAT_MASK = BIT(6); static const uint8_t BQ2419X_TERM_STAT_SHIFT = 6; static const uint8_t BQ2419X_WDT_MASK = BIT(5) | BIT(4); static const uint8_t BQ2419X_WDT_SHIFT = 4; static const uint8_t BQ2419X_EN_TIMER_MASK = BIT(3); static const uint8_t BQ2419X_EN_TIMER_SHIFT = 3; /* ir compensation / thermal regulation control register (REG06) */ static const uint8_t BQ2419X_BAT_COMP_MASK = BIT(7) | BIT(6) | BIT(5); static const uint8_t BQ2419X_BAT_COMP_SHIFT = 5; static const uint8_t BQ2419X_TREG_MASK = BIT(1) | BIT(0); static const uint8_t BQ2419X_TREG_SHIFT = 0; /* misc operation register (REG07) */ static const uint8_t BQ2419X_DPDM_EN_MASK = BIT(7); static const uint8_t BQ2419X_DPDM_EN_SHIFT = 7; static const uint8_t BQ2419X_TMR2X_EN_MASK = BIT(6); static const uint8_t BQ2419X_TMR2X_EN_SHIFT = 6; static const uint8_t BQ2419X_BATFET_DISABLE_MASK = BIT(5); static const uint8_t BQ2419X_BATFET_DISABLE_SHIFT = 5; static const uint8_t BQ2419X_JEITA_VSET_MASK = BIT(4); static const uint8_t BQ2419X_JEITA_VSET_SHIFT = BIT(4); /* reserved bits */ /* reserved bits */ static const uint8_t BQ2419X_INT_MASK_MASK = BIT(1) | BIT(0); static const uint8_t BQ2419X_INT_MASK_SHIFT = 0; /* system status register (REG08) */ static const uint8_t BQ2419X_VBUS_STAT_MASK = BIT(7) | BIT(6); static const uint8_t BQ2419X_VBUS_STAT_SHIFT = 6; static const uint8_t BQ2419X_CHG_STAT_MASK = BIT(5) | BIT(4); static const uint8_t BQ2419X_CHG_STAT_SHIFT = 4; static const uint8_t BQ2419X_DPM_STAT_MASK = BIT(3); static const uint8_t BQ2419X_DPM_STAT_SHIFT = 3; static const uint8_t BQ2419X_PG_STAT_MASK = BIT(2); static const uint8_t BQ2419X_PG_STAT_SHIFT = 2; static const uint8_t BQ2419X_THERM_STAT_MASK = BIT(1); static const uint8_t BQ2419X_THERM_STAT_SHIFT = 1; static const uint8_t BQ2419X_VSYS_STAT_MASK = BIT(0); static const uint8_t BQ2419X_VSYS_STAT_SHIFT = 0; static const uint8_t BQ2419X_CHARGE_STATUS_NOT_CHARGING = 0x00; static const uint8_t BQ2419X_CHARGE_STATUS_PRE_CHARGE = 0x01; static const uint8_t BQ2419X_CHARGE_STATUS_FAST_CHARGE = 0x02; static const uint8_t BQ2419X_CHARGE_STATUS_DONE = 0x03; /* fault register (REG09) */ static const uint8_t BQ2419X_WATCHDOG_FAULT_MASK = BIT(7); static const uint8_t BQ2419X_WATCHDOG_FAULT_SHIFT = 7; static const uint8_t BQ2419X_BOOST_FAULT_MASK = BIT(6); static const uint8_t BQ2419X_BOOST_FAULT_SHIFT = 6; static const uint8_t BQ2419X_CHG_FAULT_MASK = BIT(5) | BIT(4); static const uint8_t BQ2419X_CHG_FAULT_SHIFT = 4; static const uint8_t BQ2419X_BAT_FAULT_MASK = BIT(3); static const uint8_t BQ2419X_BAT_FAULT_SHIFT = 3; static const uint8_t BQ2419X_NTC_FAULT_MASK = BIT(2) | BIT(1) | BIT(0); static const uint8_t BQ2419X_NTC_FAULT_SHIFT = 0; static io_pin_t CHG_IRQ = IO_PB(1); typedef struct bq2419x_pmu_charger { pmu_charger_t pmu_charger; uint8_t fault; uint8_t status; bool first_time; bool battery_status_valid; bool battery_health_valid; bool charger_health_valid; volatile bool event; } bq2419x_pmu_charger_t; static bq2419x_pmu_charger_t charger; static volatile bool bq2419x_event = false; int8_t bq2419x_set_charger(bool on) { uint8_t config; int8_t ret; ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config); if (ret) return ret; config &= ~BQ2419X_CHARGE_CFG_MASK; if (on) config |= 1 << BQ2419X_CHARGE_CFG_SHIFT; ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config); if (ret) return ret; return 0; } static int8_t bq2419x_reset(void) { uint8_t config; int8_t ret; uint8_t retry = 100; ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config); if (ret) return ret; config |= BQ2419X_REGISTER_RESET_MASK; ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config); do { ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config); if (!(config & BQ2419X_REGISTER_RESET_MASK)) return 0; _delay_ms(10); } while (retry--); return ret; } static void bq2419x_set_host_mode(void) { uint8_t timer_ctrl; /* to disable watchdog, we need to clear the WDT bits */ bq2419x_read(BQ2419X_REG_TIMER_CONTROL, &timer_ctrl); timer_ctrl &= ~BQ2419X_WDT_MASK; bq2419x_write(BQ2419X_REG_TIMER_CONTROL, timer_ctrl); } static enum pmu_charge_type bq2419x_charger_get_charge_type(pmu_charger_t *pmu_charger) { uint8_t val; (void) pmu_charger; bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &val); val &= BQ2419X_CHARGE_CFG_MASK; val >>= BQ2419X_CHARGE_CFG_SHIFT; /* if check if charging is disabled */ if (!val) return PMU_CHARGE_TYPE_NONE; bq2419x_read(BQ2419X_REG_CHARGE_CURRENT, &val); val &= BQ2419X_FORCE_20_PCT_MASK; val >>= BQ2419X_FORCE_20_PCT_SHIFT; if (val) return PMU_CHARGE_TYPE_TRICKLE; else return PMU_CHARGE_TYPE_FAST; } static enum pmu_health bq2419x_charger_get_health(pmu_charger_t *pmu_charger) { uint8_t fault; int8_t ret; struct bq2419x_pmu_charger *bq2419x_charger; bq2419x_charger = container_of( pmu_charger, struct bq2419x_pmu_charger, pmu_charger); if (bq2419x_charger->charger_health_valid) { fault = bq2419x_charger->fault; bq2419x_charger->charger_health_valid = false; } else { ret = bq2419x_read(BQ2419X_REG_FAULT, &fault); if (ret) return PMU_HEALTH_UNKNOWN; } /* if BOOST_FAULT then report overvoltage */ if (fault & BQ2419X_BOOST_FAULT_MASK) { return PMU_HEALTH_OVERVOLTAGE; } else { fault &= BQ2419X_CHG_FAULT_MASK; fault >>= BQ2419X_CHG_FAULT_SHIFT; switch (fault) { case 0x0: /* all is well */ return PMU_HEALTH_GOOD; case 0x1: /* input fault, could be over- or under-voltage * and we can't tell which, so we report unspec */ return PMU_HEALTH_UNSPEC_FAIL; case 0x2: /* thermal shutdown */ return PMU_HEALTH_OVERHEAT; case 0x3: /* the charge safety timer expired */ return PMU_HEALTH_SAFETY_TIMER_EXPIRE; default: return PMU_HEALTH_UNKNOWN; } } } static enum pmu_status bq2419x_battery_get_status(pmu_charger_t *pmu_charger) { uint8_t fault, ss_reg; int8_t ret; struct bq2419x_pmu_charger *bq2419x_charger; bq2419x_charger = container_of( pmu_charger, struct bq2419x_pmu_charger, pmu_charger); if (bq2419x_charger->battery_status_valid) { fault = bq2419x_charger->fault; ss_reg = bq2419x_charger->status; bq2419x_charger->battery_status_valid = false; } else { ret = bq2419x_read(BQ2419X_REG_FAULT, &fault); if (ret) return ret; ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &ss_reg); if (ret) return ret; } fault &= BQ2419X_CHG_FAULT_MASK; fault >>= BQ2419X_CHG_FAULT_SHIFT; /* the battery is discharging if either * - we don't have a good power source * - we have a charge fault */ if (!(ss_reg & BQ2419X_PG_STAT_MASK) || fault) { return PMU_STATUS_DISCHARGING; } else { ss_reg &= BQ2419X_CHG_STAT_MASK; ss_reg >>= BQ2419X_CHG_STAT_SHIFT; switch(ss_reg) { case 0x0: /* not charging */ return PMU_STATUS_NOT_CHARGING; case 0x1: /* pre charging */ case 0x2: /* fast charging */ return PMU_STATUS_CHARGING; case 0x3: /* charge termination done */ return PMU_STATUS_FULL; } } return PMU_STATUS_NOT_CHARGING; } static bool bq2419x_battery_get_online(pmu_charger_t *pmu_charger) { uint8_t batfet_disable; int8_t ret; (void) pmu_charger; ret = bq2419x_read(BQ2419X_REG_MISC_OPERATION, &batfet_disable); if (ret) return false; batfet_disable &= BQ2419X_BATFET_DISABLE_MASK; batfet_disable >>= BQ2419X_BATFET_DISABLE_SHIFT; return !batfet_disable; } static enum pmu_health bq2419x_battery_get_health(pmu_charger_t *pmu_charger) { uint8_t fault; int8_t ret; struct bq2419x_pmu_charger *bq2419x_charger; bq2419x_charger = container_of( pmu_charger, struct bq2419x_pmu_charger, pmu_charger); if (bq2419x_charger->battery_health_valid) { fault = bq2419x_charger->fault; bq2419x_charger->battery_health_valid = false; } else { ret = bq2419x_read(BQ2419X_REG_FAULT, &fault); if (ret) return ret; } if (fault & BQ2419X_BAT_FAULT_MASK) return PMU_HEALTH_OVERVOLTAGE; else { fault &= BQ2419X_NTC_FAULT_MASK; fault >>= BQ2419X_NTC_FAULT_SHIFT; switch (fault) { case 0x0: /* all is well */ return PMU_HEALTH_GOOD; case 0x1: case 0x3: case 0x5: /* either TS1 cold, TS2 cold, or both cold */ return PMU_HEALTH_COLD; case 0x2: case 0x4: case 0x6: /* either TS1 hot, TS2 hot, or both hot */ return PMU_HEALTH_OVERHEAT; default: return PMU_HEALTH_UNKNOWN; } } } static bool bq2419x_charger_get_online(pmu_charger_t *pmu_charger) { uint8_t val; int8_t ret; (void) pmu_charger; ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &val); if (ret) return ret; /* check the power good bit */ return !!(BQ2419X_PG_STAT_MASK & val); } static inline bool bq2419x_get_irq(void) { /* the CHG_IRQ line is low active */ return !io_test_pin(CHG_IRQ); } uint8_t bq2419x_pmu_charger_check_events(pmu_charger_t *pmu_charger) { uint8_t flags; int8_t ret; uint8_t status; uint8_t fault; uint8_t isc; volatile bool event; bq2419x_pmu_charger_t *bq2419x_charger; bq2419x_charger = container_of( pmu_charger, struct bq2419x_pmu_charger, pmu_charger); event = false; flags = PMU_CHARGER_EVENT_NONE; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if (bq2419x_charger->event) { bq2419x_charger->event = false; event = true; } } if (event) { ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &status); if (ret) return ret; if (status != bq2419x_charger->status) { if ((bq2419x_charger->status & BQ2419X_PG_STAT_MASK) && !(status & BQ2419X_PG_STAT_MASK)) { /* we're in host mode and need to turn off HIZ * when PG_STAT goes 1->0, in order to have the * battery supply the juice */ ret = bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL, &isc); if (ret) return 0; isc &= ~BQ2419X_EN_HIZ_MASK; ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL, isc); if (ret) return 0; } if ((bq2419x_charger->status & BQ2419X_CHG_STAT_MASK) != (status & BQ2419X_CHG_STAT_MASK)) { if ((status & BQ2419X_CHG_STAT_MASK) >> BQ2419X_CHG_STAT_SHIFT == 0x3) flags |= PMU_CHARGER_EVENT_CHARGE_DONE; } bq2419x_charger->status = status; flags |= PMU_CHARGER_EVENT_STATUS_CHANGE; } ret = bq2419x_read(BQ2419X_REG_FAULT, &fault); if (ret) return ret; if (fault != bq2419x_charger->fault) { bq2419x_charger->fault = fault; bq2419x_charger->battery_status_valid = true; bq2419x_charger->battery_health_valid = true; bq2419x_charger->charger_health_valid = true; flags |= PMU_CHARGER_EVENT_FAULT_CHANGE; } if (!bq2419x_charger->first_time) bq2419x_charger->first_time = true; } return flags; } irqreturn_t bq2419x_irq_handler(void) { /* we check if the device indicates an event * if so we are the source of the IRQ, * so set the flag to deal with it later. * Otherwise we indicate to check the other devices, * by returning IRQ_NONE */ if (bq2419x_get_irq()) { charger.event = true; return IRQ_HANDLED; } (void) charger.event; return IRQ_NONE; } static const pmu_charger_ops_t bq2419x_pmu_charger_ops = { .set_charger_voltage = NULL, .set_charger_current = NULL, .get_charge_type = bq2419x_charger_get_charge_type, .set_charge_type = NULL, .get_charger_health = bq2419x_charger_get_health, .get_charger_online = bq2419x_charger_get_online, .get_battery_health = bq2419x_battery_get_health, .get_battery_status = bq2419x_battery_get_status, .get_battery_online = bq2419x_battery_get_online, .check_events = bq2419x_pmu_charger_check_events, }; int8_t bq2419x_init(void) { uint8_t id, input_src_ctrl, ir_comp; int8_t ret; /* initialize the state struct */ memset(&charger, 0, sizeof(charger)); charger.pmu_charger.ops = &bq2419x_pmu_charger_ops; charger.first_time = true; /* check vendor register to verify we're looking at * a TI BQ2419x chip */ ret = bq2419x_read(BQ2419X_REG_VENDOR_PART_REV, &id); if (ret) goto fail_i2c_read; /* set charge IRQ pin as input */ io_input_pin(CHG_IRQ); io_set_pin(CHG_IRQ); bq2419x_reset(); bq2419x_set_host_mode(); /* we leave the other registers at default values * BQ2419X_REG_PWR_ON_CONFIG: * - minimum system voltage limit (default) 101 3.5V * BQ2419X_REG_CHARGE_VOLTAGE: * - fast charge current limit (default) 011000 2048mA * BQ2419X_REG_PRE_TERM_CURRENT: * - pre-charge current limit (default) 0001 256mA * - termination current limit (default) 0001 256mA * BQ2419X_REG_CHARGE_VOLTAGE: * - charge voltage limit (default) 101100 4.208V */ bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL, &input_src_ctrl); /* set a 3A limit */ input_src_ctrl |= 0x7; ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL, input_src_ctrl); if (ret) return ret; /* compensate for 20m r_sense */ ret = bq2419x_read(BQ2419X_REG_THERMAL_REG_CTRL, &ir_comp); if (ret) return ret; ir_comp &= ~BQ2419X_BAT_COMP_MASK; ir_comp |= (0x02 << BQ2419X_BAT_COMP_SHIFT); /* set thermal regulation to 120 C */ ir_comp &= ~BQ2419X_TREG_MASK; ir_comp |= (0x03 << BQ2419X_TREG_SHIFT); ret = bq2419x_write(BQ2419X_REG_THERMAL_REG_CTRL, ir_comp); if (ret) return ret; pmu_register_charger(&charger.pmu_charger); return 0; fail_i2c_read: return 1; }