/* 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 60 C */
ir_comp &= ~BQ2419X_TREG_MASK;
ir_comp |= (0x00 << 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;
}