399 lines
11 KiB
C
399 lines
11 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* BQ25180 Datasheet: https://www.ti.com/lit/gpn/bq25180
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT ti_bq25180
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/charger.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
LOG_MODULE_REGISTER(bq25180, CONFIG_CHARGER_LOG_LEVEL);
|
|
|
|
#define BQ25180_STAT0 0x00
|
|
#define BQ25180_STAT1 0x01
|
|
#define BQ25180_FLAG0 0x02
|
|
#define BQ25180_VBAT_CTRL 0x03
|
|
#define BQ25180_ICHG_CTRL 0x04
|
|
#define BQ25180_IC_CTRL 0x07
|
|
#define BQ25180_SHIP_RST 0x09
|
|
#define BQ25180_MASK_ID 0x0c
|
|
|
|
#define BQ25180_STAT0_CHG_STAT_MASK GENMASK(6, 5)
|
|
#define BQ25180_STAT0_CHG_STAT_NOT_CHARGING 0x00
|
|
#define BQ25180_STAT0_CHG_STAT_CONSTANT_CURRENT 0x01
|
|
#define BQ25180_STAT0_CHG_STAT_CONSTANT_VOLTAGE 0x02
|
|
#define BQ25180_STAT0_CHG_STAT_DONE 0x03
|
|
#define BQ25180_STAT0_VIN_PGOOD_STAT BIT(0)
|
|
#define BQ25180_VBAT_MSK GENMASK(6, 0)
|
|
#define BQ25180_ICHG_CHG_DIS BIT(7)
|
|
#define BQ25180_ICHG_MSK GENMASK(6, 0)
|
|
#define BQ25180_IC_CTRL_VRCH_100 0x00
|
|
#define BQ25180_IC_CTRL_VRCH_200 BIT(5)
|
|
#define BQ25180_IC_CTRL_VRCH_MSK BIT(5)
|
|
#define BQ25180_VLOWV_SEL_2_8 BIT(6)
|
|
#define BQ25180_VLOWV_SEL_3_0 0x00
|
|
#define BQ25180_VLOWV_SEL_MSK BIT(6)
|
|
#define BQ25180_WATCHDOG_SEL_1_MSK GENMASK(1, 0)
|
|
#define BQ25180_WATCHDOG_DISABLE 0x03
|
|
#define BQ25180_DEVICE_ID_MSK GENMASK(3, 0)
|
|
#define BQ25180_DEVICE_ID 0x00
|
|
#define BQ25180_SHIP_RST_EN_RST_SHIP_MSK GENMASK(6, 5)
|
|
#define BQ25180_SHIP_RST_EN_RST_SHIP_ADAPTER 0x20
|
|
#define BQ25180_SHIP_RST_EN_RST_SHIP_BUTTON 0x40
|
|
|
|
/* Charging current limits */
|
|
#define BQ25180_CURRENT_MIN_MA 5
|
|
#define BQ25180_CURRENT_MAX_MA 1000
|
|
#define BQ25180_VOLTAGE_MIN_MV 3500
|
|
#define BQ25180_VOLTAGE_MAX_MV 4650
|
|
|
|
#define BQ25180_FACTOR_VBAT_TO_MV 10
|
|
|
|
struct bq25180_config {
|
|
struct i2c_dt_spec i2c;
|
|
uint32_t initial_current_microamp;
|
|
uint32_t max_voltage_microvolt;
|
|
uint32_t recharge_voltage_microvolt;
|
|
uint32_t precharge_threshold_voltage_microvolt;
|
|
};
|
|
|
|
/*
|
|
* For ICHG <= 35mA = ICHGCODE + 5mA
|
|
* For ICHG > 35mA = 40 + ((ICHGCODE-31)*10)mA.
|
|
* Maximum programmable current = 1000mA
|
|
*
|
|
* Return: value between 0 and 127, negative on error.
|
|
*/
|
|
static int bq25180_ma_to_ichg(uint32_t current_ma, uint8_t *ichg)
|
|
{
|
|
if (!IN_RANGE(current_ma, BQ25180_CURRENT_MIN_MA, BQ25180_CURRENT_MAX_MA)) {
|
|
LOG_WRN("charging current out of range: %dmA, "
|
|
"clamping to the nearest limit", current_ma);
|
|
}
|
|
current_ma = CLAMP(current_ma, BQ25180_CURRENT_MIN_MA, BQ25180_CURRENT_MAX_MA);
|
|
|
|
if (current_ma <= 35) {
|
|
*ichg = current_ma - 5;
|
|
return 0;
|
|
}
|
|
|
|
*ichg = (current_ma - 40) / 10 + 31;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t bq25180_ichg_to_ma(uint8_t ichg)
|
|
{
|
|
ichg &= BQ25180_ICHG_MSK;
|
|
|
|
if (ichg <= 30) {
|
|
return (ichg + 5);
|
|
}
|
|
|
|
return (ichg - 31) * 10 + 40;
|
|
}
|
|
|
|
static int bq25180_mv_to_vbatreg(const struct bq25180_config *cfg, uint32_t voltage_mv,
|
|
uint8_t *vbat)
|
|
{
|
|
if (!IN_RANGE(voltage_mv, BQ25180_VOLTAGE_MIN_MV, cfg->max_voltage_microvolt)) {
|
|
LOG_WRN("charging voltage out of range: %dmV, "
|
|
"clamping to the nearest limit",
|
|
voltage_mv);
|
|
}
|
|
voltage_mv = CLAMP(voltage_mv, BQ25180_VOLTAGE_MIN_MV, cfg->max_voltage_microvolt);
|
|
|
|
*vbat = (voltage_mv - BQ25180_VOLTAGE_MIN_MV) / BQ25180_FACTOR_VBAT_TO_MV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t bq25180_vbatreg_to_mv(uint8_t vbat)
|
|
{
|
|
vbat &= BQ25180_VBAT_MSK;
|
|
|
|
return (vbat * BQ25180_FACTOR_VBAT_TO_MV) + BQ25180_VOLTAGE_MIN_MV;
|
|
}
|
|
|
|
static int bq25183_charge_enable(const struct device *dev, const bool enable)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t value = enable ? 0 : BQ25180_ICHG_CHG_DIS;
|
|
int ret;
|
|
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_ICHG_CTRL,
|
|
BQ25180_ICHG_CHG_DIS, value);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_set_charge_current(const struct device *dev,
|
|
uint32_t const_charge_current_ua)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = bq25180_ma_to_ichg(const_charge_current_ua / 1000, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_ICHG_CTRL,
|
|
BQ25180_ICHG_MSK, val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_get_charge_current(const struct device *dev,
|
|
uint32_t *const_charge_current_ua)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_ICHG_CTRL, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
*const_charge_current_ua = bq25180_ichg_to_ma(val) * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_set_charge_voltage(const struct device *dev, uint32_t const_charge_voltage_uv)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = bq25180_mv_to_vbatreg(cfg, const_charge_voltage_uv / 1000, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_VBAT_CTRL, BQ25180_VBAT_MSK, val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_get_charge_voltage(const struct device *dev, uint32_t *const_charge_voltage_uv)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_VBAT_CTRL, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
*const_charge_voltage_uv = bq25180_vbatreg_to_mv(val) * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_get_online(const struct device *dev,
|
|
enum charger_online *online)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_STAT0, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if ((val & BQ25180_STAT0_VIN_PGOOD_STAT) != 0x00) {
|
|
*online = CHARGER_ONLINE_FIXED;
|
|
} else {
|
|
*online = CHARGER_ONLINE_OFFLINE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_get_status(const struct device *dev,
|
|
enum charger_status *status)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t stat0;
|
|
uint8_t ichg_ctrl;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_STAT0, &stat0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if ((stat0 & BQ25180_STAT0_VIN_PGOOD_STAT) == 0x00) {
|
|
*status = CHARGER_STATUS_DISCHARGING;
|
|
return 0;
|
|
}
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_ICHG_CTRL, &ichg_ctrl);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if ((ichg_ctrl & BQ25180_ICHG_CHG_DIS) != 0x00) {
|
|
*status = CHARGER_STATUS_NOT_CHARGING;
|
|
return 0;
|
|
}
|
|
|
|
switch (FIELD_GET(BQ25180_STAT0_CHG_STAT_MASK, stat0)) {
|
|
case BQ25180_STAT0_CHG_STAT_NOT_CHARGING:
|
|
*status = CHARGER_STATUS_NOT_CHARGING;
|
|
break;
|
|
case BQ25180_STAT0_CHG_STAT_CONSTANT_CURRENT:
|
|
case BQ25180_STAT0_CHG_STAT_CONSTANT_VOLTAGE:
|
|
*status = CHARGER_STATUS_CHARGING;
|
|
break;
|
|
case BQ25180_STAT0_CHG_STAT_DONE:
|
|
*status = CHARGER_STATUS_FULL;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq25180_get_prop(const struct device *dev, charger_prop_t prop,
|
|
union charger_propval *val)
|
|
{
|
|
switch (prop) {
|
|
case CHARGER_PROP_ONLINE:
|
|
return bq25180_get_online(dev, &val->online);
|
|
case CHARGER_PROP_STATUS:
|
|
return bq25180_get_status(dev, &val->status);
|
|
case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA:
|
|
return bq25180_get_charge_current(dev, &val->const_charge_current_ua);
|
|
case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
|
|
return bq25180_get_charge_voltage(dev, &val->const_charge_voltage_uv);
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int bq25180_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 bq25180_set_charge_current(dev, val->const_charge_current_ua);
|
|
case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
|
|
return bq25180_set_charge_voltage(dev, val->const_charge_voltage_uv);
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static const struct charger_driver_api bq25180_api = {
|
|
.get_property = bq25180_get_prop,
|
|
.set_property = bq25180_set_prop,
|
|
.charge_enable = bq25183_charge_enable,
|
|
};
|
|
|
|
static int bq25180_init(const struct device *dev)
|
|
{
|
|
const struct bq25180_config *cfg = dev->config;
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = i2c_reg_read_byte_dt(&cfg->i2c, BQ25180_MASK_ID, &val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
val &= BQ25180_DEVICE_ID_MSK;
|
|
if (val != BQ25180_DEVICE_ID) {
|
|
LOG_ERR("Invalid device id: %02x", val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Disable the watchdog */
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_IC_CTRL,
|
|
BQ25180_WATCHDOG_SEL_1_MSK,
|
|
BQ25180_WATCHDOG_DISABLE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = bq25180_set_charge_voltage(dev, cfg->max_voltage_microvolt);
|
|
if (ret < 0) {
|
|
LOG_ERR("Could not set the target voltage. (rc: %d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (cfg->recharge_voltage_microvolt > 0) {
|
|
if ((cfg->max_voltage_microvolt - cfg->recharge_voltage_microvolt) > 100000) {
|
|
val = BQ25180_IC_CTRL_VRCH_200;
|
|
} else {
|
|
val = BQ25180_IC_CTRL_VRCH_100;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_IC_CTRL, BQ25180_IC_CTRL_VRCH_MSK,
|
|
val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Precharge threshold voltage */
|
|
if (cfg->precharge_threshold_voltage_microvolt <= 2800000) {
|
|
val = BQ25180_VLOWV_SEL_2_8;
|
|
} else {
|
|
val = BQ25180_VLOWV_SEL_3_0;
|
|
}
|
|
|
|
ret = i2c_reg_update_byte_dt(&cfg->i2c, BQ25180_IC_CTRL, BQ25180_VLOWV_SEL_MSK, val);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (cfg->initial_current_microamp > 0) {
|
|
ret = bq25180_set_charge_current(dev, cfg->initial_current_microamp);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define CHARGER_BQ25180_INIT(inst) \
|
|
static const struct bq25180_config bq25180_config_##inst = { \
|
|
.i2c = I2C_DT_SPEC_INST_GET(inst), \
|
|
.initial_current_microamp = \
|
|
DT_INST_PROP(inst, constant_charge_current_max_microamp), \
|
|
.max_voltage_microvolt = \
|
|
DT_INST_PROP(inst, constant_charge_voltage_max_microvolt), \
|
|
.recharge_voltage_microvolt = \
|
|
DT_INST_PROP_OR(inst, re_charge_voltage_microvolt, 0), \
|
|
.precharge_threshold_voltage_microvolt = \
|
|
DT_INST_PROP(inst, precharge_voltage_threshold_microvolt), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(inst, bq25180_init, NULL, NULL, &bq25180_config_##inst, POST_KERNEL, \
|
|
CONFIG_CHARGER_INIT_PRIORITY, &bq25180_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(CHARGER_BQ25180_INIT)
|