/* * Copyright 2023 Cirrus Logic, Inc. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ti_bq24190 #include #include "bq24190.h" #include "zephyr/device.h" #include "zephyr/drivers/charger.h" #include "zephyr/drivers/i2c.h" #include "zephyr/kernel.h" #include "zephyr/sys/util.h" #include "zephyr/logging/log.h" #include LOG_MODULE_REGISTER(ti_bq24190); struct bq24190_config { struct i2c_dt_spec i2c; struct gpio_dt_spec ce_gpio; }; struct bq24190_data { uint8_t ss_reg; unsigned int ichg_ua; unsigned int vreg_uv; enum charger_status state; enum charger_online online; }; static int bq24190_register_reset(const struct device *dev) { const struct bq24190_config *const config = dev->config; int ret, limit = BQ24190_RESET_MAX_TRIES; uint8_t val; ret = i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_POC, BQ24190_REG_POC_RESET_MASK, BQ24190_REG_POC_RESET_MASK); if (ret) { return ret; } /* * No explicit reset timing characteristcs are provided in the datasheet. * Instead, poll every 100µs for 100 attempts to see if the reset request * bit has cleared. */ do { ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_POC, &val); if (ret) { return ret; } if (!(val & BQ24190_REG_POC_RESET_MASK)) { return 0; } k_usleep(100); } while (--limit); return -EIO; } static int bq24190_charger_get_charge_type(const struct device *dev, enum charger_charge_type *charge_type) { const struct bq24190_config *const config = dev->config; uint8_t v; int ret; *charge_type = CHARGER_CHARGE_TYPE_UNKNOWN; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_POC, &v); if (ret) { return ret; } v = FIELD_GET(BQ24190_REG_POC_CHG_CONFIG_MASK, v); if (!v) { *charge_type = CHARGER_CHARGE_TYPE_NONE; } else { ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v); if (ret) { return ret; } v = FIELD_GET(BQ24190_REG_CCC_FORCE_20PCT_MASK, v); if (v) { *charge_type = CHARGER_CHARGE_TYPE_TRICKLE; } else { *charge_type = CHARGER_CHARGE_TYPE_FAST; } } return 0; } static int bq24190_charger_get_health(const struct device *dev, enum charger_health *health) { const struct bq24190_config *const config = dev->config; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_F, &v); if (ret) { return ret; } if (v & BQ24190_REG_F_NTC_FAULT_MASK) { switch (v >> BQ24190_REG_F_NTC_FAULT_SHIFT & 0x7) { case BQ24190_NTC_FAULT_TS1_COLD: case BQ24190_NTC_FAULT_TS2_COLD: case BQ24190_NTC_FAULT_TS1_TS2_COLD: *health = CHARGER_HEALTH_COLD; break; case BQ24190_NTC_FAULT_TS1_HOT: case BQ24190_NTC_FAULT_TS2_HOT: case BQ24190_NTC_FAULT_TS1_TS2_HOT: *health = CHARGER_HEALTH_HOT; break; default: *health = CHARGER_HEALTH_UNKNOWN; } } else if (v & BQ24190_REG_F_BAT_FAULT_MASK) { *health = CHARGER_HEALTH_OVERVOLTAGE; } else if (v & BQ24190_REG_F_CHRG_FAULT_MASK) { switch (v >> BQ24190_REG_F_CHRG_FAULT_SHIFT & 0x3) { case BQ24190_CHRG_FAULT_INPUT_FAULT: /* * This could be over-voltage or under-voltage * and there's no way to tell which. Instead * of looking foolish and returning 'OVERVOLTAGE' * when its really under-voltage, just return * 'UNSPEC_FAILURE'. */ *health = CHARGER_HEALTH_UNSPEC_FAILURE; break; case BQ24190_CHRG_FAULT_TSHUT: *health = CHARGER_HEALTH_OVERHEAT; break; case BQ24190_CHRG_SAFETY_TIMER: *health = CHARGER_HEALTH_SAFETY_TIMER_EXPIRE; break; default: /* prevent compiler warning */ *health = CHARGER_HEALTH_UNKNOWN; } } else if (v & BQ24190_REG_F_BOOST_FAULT_MASK) { /* * This could be over-current or over-voltage but there's * no way to tell which. Return 'OVERVOLTAGE' since there * isn't an 'OVERCURRENT' value defined that we can return * even if it was over-current. */ *health = CHARGER_HEALTH_OVERVOLTAGE; } else { *health = CHARGER_HEALTH_GOOD; } return 0; } static int bq24190_charger_get_online(const struct device *dev, enum charger_online *online) { const struct bq24190_config *const config = dev->config; uint8_t pg_stat, batfet_disable; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &pg_stat); if (ret) { return ret; } pg_stat = FIELD_GET(BQ24190_REG_SS_PG_STAT_MASK, pg_stat); ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_MOC, &batfet_disable); if (ret) { return ret; } batfet_disable = FIELD_GET(BQ24190_REG_MOC_BATFET_DISABLE_MASK, batfet_disable); if (pg_stat && !batfet_disable) { *online = CHARGER_ONLINE_FIXED; } else { *online = CHARGER_ONLINE_OFFLINE; } return 0; } static int bq24190_charger_get_status(const struct device *dev, enum charger_status *status) { const struct bq24190_config *const config = dev->config; uint8_t ss_reg, chrg_fault; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_F, &chrg_fault); if (ret) { return ret; } chrg_fault = FIELD_GET(BQ24190_REG_F_CHRG_FAULT_MASK, chrg_fault); ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &ss_reg); if (ret) { return ret; } /* * The battery must be discharging when any of these are true: * - there is no good power source; * - there is a charge fault. * Could also be discharging when in "supplement mode" but * there is no way to tell when its in that mode. */ if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) { *status = CHARGER_STATUS_DISCHARGING; } else { ss_reg = FIELD_GET(BQ24190_REG_SS_CHRG_STAT_MASK, ss_reg); switch (ss_reg) { case BQ24190_CHRG_STAT_NOT_CHRGING: *status = CHARGER_STATUS_NOT_CHARGING; break; case BQ24190_CHRG_STAT_PRECHRG: case BQ24190_CHRG_STAT_FAST_CHRG: *status = CHARGER_STATUS_CHARGING; break; case BQ24190_CHRG_STAT_CHRG_TERM: *status = CHARGER_STATUS_FULL; break; default: return -EIO; } } return 0; } static int bq24190_charger_get_constant_charge_current(const struct device *dev, uint32_t *current_ua) { const struct bq24190_config *const config = dev->config; bool frc_20pct; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v); if (ret) { return ret; } frc_20pct = v & BQ24190_REG_CCC_FORCE_20PCT_MASK; v = FIELD_GET(BQ24190_REG_CCC_ICHG_MASK, v); *current_ua = (v * BQ24190_REG_CCC_ICHG_STEP_UA) + BQ24190_REG_CCC_ICHG_OFFSET_UA; if (frc_20pct) { *current_ua /= 5; } return 0; } static int bq24190_charger_get_precharge_current(const struct device *dev, uint32_t *current_ua) { const struct bq24190_config *const config = dev->config; bool frc_20pct; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v); if (ret) { return ret; } frc_20pct = v & BQ24190_REG_CCC_FORCE_20PCT_MASK; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_PCTCC, &v); if (ret) { return ret; } v = FIELD_GET(BQ24190_REG_PCTCC_IPRECHG_MASK, v); *current_ua = (v * BQ24190_REG_PCTCC_IPRECHG_STEP_UA) + BQ24190_REG_PCTCC_IPRECHG_OFFSET_UA; if (frc_20pct) { *current_ua /= 2; } return 0; } static int bq24190_charger_get_charge_term_current(const struct device *dev, uint32_t *current_ua) { const struct bq24190_config *const config = dev->config; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_PCTCC, &v); if (ret) { return ret; } v = FIELD_GET(BQ24190_REG_PCTCC_ITERM_MASK, v); *current_ua = (v * BQ24190_REG_PCTCC_ITERM_STEP_UA) + BQ24190_REG_PCTCC_ITERM_OFFSET_UA; return 0; } static int bq24190_get_constant_charge_voltage(const struct device *dev, uint32_t *voltage_uv) { const struct bq24190_config *const config = dev->config; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CVC, &v); if (ret < 0) { return ret; } v = FIELD_GET(BQ24190_REG_CVC_VREG_MASK, v); *voltage_uv = (v * BQ24190_REG_CVC_VREG_STEP_UV) + BQ24190_REG_CVC_VREG_OFFSET_UV; return 0; } static int bq24190_set_constant_charge_current(const struct device *dev, uint32_t current_ua) { const struct bq24190_config *const config = dev->config; uint8_t v; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v); if (ret < 0) { return ret; } v &= BQ24190_REG_CCC_FORCE_20PCT_MASK; if (v) { current_ua *= 5; } current_ua = CLAMP(current_ua, BQ24190_REG_CCC_ICHG_MIN_UA, BQ24190_REG_CCC_ICHG_MAX_UA); v = (current_ua - BQ24190_REG_CCC_ICHG_OFFSET_UA) / BQ24190_REG_CCC_ICHG_STEP_UA; v = FIELD_PREP(BQ24190_REG_CCC_ICHG_MASK, v); return i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, v); } static int bq24190_set_constant_charge_voltage(const struct device *dev, uint32_t voltage_uv) { const struct bq24190_config *const config = dev->config; uint8_t v; voltage_uv = CLAMP(voltage_uv, BQ24190_REG_CVC_VREG_MIN_UV, BQ24190_REG_CVC_VREG_MAX_UV); v = (voltage_uv - BQ24190_REG_CVC_VREG_OFFSET_UV) / BQ24190_REG_CVC_VREG_STEP_UV; v = FIELD_PREP(BQ24190_REG_CVC_VREG_MASK, v); return i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_CVC, BQ24190_REG_CVC_VREG_MASK, v); } static int bq24190_set_config(const struct device *dev) { struct bq24190_data *data = dev->data; union charger_propval val; int ret; val.const_charge_current_ua = data->ichg_ua; ret = bq24190_set_constant_charge_current(dev, val.const_charge_current_ua); if (ret < 0) { return ret; } val.const_charge_voltage_uv = data->vreg_uv; return bq24190_set_constant_charge_voltage(dev, val.const_charge_voltage_uv); } static int bq24190_get_prop(const struct device *dev, charger_prop_t prop, union charger_propval *val) { switch (prop) { case CHARGER_PROP_ONLINE: return bq24190_charger_get_online(dev, &val->online); case CHARGER_PROP_CHARGE_TYPE: return bq24190_charger_get_charge_type(dev, &val->charge_type); case CHARGER_PROP_HEALTH: return bq24190_charger_get_health(dev, &val->health); case CHARGER_PROP_STATUS: return bq24190_charger_get_status(dev, &val->status); case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: return bq24190_charger_get_constant_charge_current(dev, &val->const_charge_current_ua); case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV: return bq24190_get_constant_charge_voltage(dev, &val->const_charge_voltage_uv); case CHARGER_PROP_PRECHARGE_CURRENT_UA: return bq24190_charger_get_precharge_current(dev, &val->precharge_current_ua); case CHARGER_PROP_CHARGE_TERM_CURRENT_UA: return bq24190_charger_get_charge_term_current(dev, &val->charge_term_current_ua); default: return -ENOTSUP; } } static int bq24190_set_prop(const struct device *dev, charger_prop_t prop, const union charger_propval *val) { switch (prop) { case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: return bq24190_set_constant_charge_current(dev, val->const_charge_current_ua); case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV: return bq24190_set_constant_charge_voltage(dev, val->const_charge_voltage_uv); default: return -ENOTSUP; } } static int bq24190_init(const struct device *dev) { const struct bq24190_config *const config = dev->config; struct bq24190_data *data = dev->data; uint8_t val; int ret; ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_VPRS, &val); if (ret) { return ret; } val = FIELD_GET(BQ24190_REG_VPRS_PN_MASK, val); switch (val) { case BQ24190_REG_VPRS_PN_24190: case BQ24190_REG_VPRS_PN_24192: case BQ24190_REG_VPRS_PN_24192I: break; default: LOG_ERR("Error unknown model: 0x%02x\n", val); return -ENODEV; } ret = bq24190_register_reset(dev); if (ret) { return ret; } ret = bq24190_set_config(dev); if (ret) { return ret; } return i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &data->ss_reg); } static const struct charger_driver_api bq24190_driver_api = { .get_property = bq24190_get_prop, .set_property = bq24190_set_prop, }; #define BQ24190_INIT(inst) \ \ static const struct bq24190_config bq24190_config_##inst = { \ .i2c = I2C_DT_SPEC_INST_GET(inst), \ }; \ \ static struct bq24190_data bq24190_data_##inst = { \ .ichg_ua = DT_INST_PROP(inst, constant_charge_current_max_microamp), \ .vreg_uv = DT_INST_PROP(inst, constant_charge_voltage_max_microvolt), \ }; \ \ DEVICE_DT_INST_DEFINE(inst, bq24190_init, NULL, &bq24190_data_##inst, \ &bq24190_config_##inst, POST_KERNEL, CONFIG_CHARGER_INIT_PRIORITY, \ &bq24190_driver_api); DT_INST_FOREACH_STATUS_OKAY(BQ24190_INIT)