diff options
author | Moritz Fischer <moritz.fischer@ettus.com> | 2015-07-13 14:03:42 -0700 |
---|---|---|
committer | Moritz Fischer <moritz.fischer@ettus.com> | 2015-07-13 14:03:42 -0700 |
commit | c65e0ea6e387b39e115b6af28e79ba1418f605b2 (patch) | |
tree | 85bc5673ca01f22b6b9362b0aba8c80d13ba1eab /firmware/e300/battery/bq2419x.c | |
parent | 69bcfba936e49c2825a6d9be677c3150a5c6b70c (diff) | |
download | uhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.tar.gz uhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.tar.bz2 uhd-c65e0ea6e387b39e115b6af28e79ba1418f605b2.zip |
e3xx: Added firmware for battery based devices.
Note: This firmware does *not* support Rev B units.
Signed-off-by: Moritz Fischer <moritz.fischer@ettus.com>
Diffstat (limited to 'firmware/e300/battery/bq2419x.c')
-rw-r--r-- | firmware/e300/battery/bq2419x.c | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/firmware/e300/battery/bq2419x.c b/firmware/e300/battery/bq2419x.c new file mode 100644 index 000000000..11b393622 --- /dev/null +++ b/firmware/e300/battery/bq2419x.c @@ -0,0 +1,588 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include "bq2419x.h" +#include "io.h" +#include "i2c_twi.h" +#include "interrupt.h" +#include "pmu.h" +#include "mcu_settings.h" +#include "utils.h" + +#include <stdbool.h> +#include <string.h> + +#include <util/atomic.h> +#include <util/delay.h> + +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; +} |