| /* |
| * Copyright 2023 Cirrus Logic, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_bq24190 |
| |
| #include <errno.h> |
| |
| #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 <zephyr/drivers/gpio.h> |
| |
| 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) |