blob: 9b313136fefe9987f751552b0a120c96c5f42835 [file] [log] [blame]
/*
* 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)