blob: 8a5ff935777d3bcb5e5b67c4bde0c65e60ffd337 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_npm1300_charger
#include <math.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/linear_range.h>
#include <zephyr/drivers/sensor/npm1300_charger.h>
struct npm1300_charger_config {
struct i2c_dt_spec i2c;
int32_t term_microvolt;
int32_t term_warm_microvolt;
int32_t current_microamp;
int32_t dischg_limit_microamp;
int32_t vbus_limit_microamp;
uint8_t thermistor_idx;
uint16_t thermistor_beta;
bool charging_enable;
};
struct npm1300_charger_data {
uint16_t voltage;
uint16_t current;
uint16_t temp;
uint8_t status;
uint8_t error;
uint8_t ibat_stat;
uint8_t vbus_stat;
};
/* nPM1300 base addresses */
#define CHGR_BASE 0x03U
#define ADC_BASE 0x05U
#define VBUS_BASE 0x02U
/* nPM1300 charger register offsets */
#define CHGR_OFFSET_EN_SET 0x04U
#define CHGR_OFFSET_EN_CLR 0x05U
#define CHGR_OFFSET_ISET 0x08U
#define CHGR_OFFSET_ISET_DISCHG 0x0AU
#define CHGR_OFFSET_VTERM 0x0CU
#define CHGR_OFFSET_VTERM_R 0x0DU
#define CHGR_OFFSET_CHG_STAT 0x34U
#define CHGR_OFFSET_ERR_REASON 0x36U
/* nPM1300 ADC register offsets */
#define ADC_OFFSET_TASK_VBAT 0x00U
#define ADC_OFFSET_TASK_TEMP 0x01U
#define ADC_OFFSET_CONFIG 0x09U
#define ADC_OFFSET_NTCR_SEL 0x0AU
#define ADC_OFFSET_RESULTS 0x10U
#define ADC_OFFSET_IBAT_EN 0x24U
/* nPM1300 VBUS register offsets */
#define VBUS_OFFSET_TASK_UPDATE 0x00U
#define VBUS_OFFSET_ILIM 0x01U
#define VBUS_OFFSET_STATUS 0x07U
/* Ibat status */
#define IBAT_STAT_DISCHARGE 0x04U
#define IBAT_STAT_CHARGE_TRICKLE 0x0CU
#define IBAT_STAT_CHARGE_COOL 0x0DU
#define IBAT_STAT_CHARGE_NORMAL 0x0FU
struct adc_results_t {
uint8_t ibat_stat;
uint8_t msb_vbat;
uint8_t msb_ntc;
uint8_t msb_die;
uint8_t msb_vsys;
uint8_t lsb_a;
uint8_t reserved1;
uint8_t reserved2;
uint8_t msb_ibat;
uint8_t msb_vbus;
uint8_t lsb_b;
} __packed;
/* ADC result masks */
#define ADC_MSB_SHIFT 2U
#define ADC_LSB_MASK 0x03U
#define ADC_LSB_VBAT_SHIFT 0U
#define ADC_LSB_NTC_SHIFT 2U
#define ADC_LSB_IBAT_SHIFT 4U
/* Linear range for charger terminal voltage */
static const struct linear_range charger_volt_ranges[] = {
LINEAR_RANGE_INIT(3500000, 50000, 0U, 3U), LINEAR_RANGE_INIT(4000000, 50000, 4U, 13U)};
/* Linear range for charger current */
static const struct linear_range charger_current_range = LINEAR_RANGE_INIT(32000, 2000, 16U, 400U);
/* Linear range for Discharge limit */
static const struct linear_range discharge_limit_range = LINEAR_RANGE_INIT(268090, 3230, 83U, 415U);
/* Linear range for vbusin current limit */
static const struct linear_range vbus_current_ranges[] = {
LINEAR_RANGE_INIT(100000, 0, 1U, 1U), LINEAR_RANGE_INIT(500000, 100000, 5U, 15U)};
/* Read multiple registers from specified address */
static int reg_read_burst(const struct device *dev, uint8_t base, uint8_t offset, void *data,
size_t len)
{
const struct npm1300_charger_config *const config = dev->config;
uint8_t buff[] = {base, offset};
return i2c_write_read_dt(&config->i2c, buff, sizeof(buff), data, len);
}
static int reg_read(const struct device *dev, uint8_t base, uint8_t offset, uint8_t *data)
{
return reg_read_burst(dev, base, offset, data, 1U);
}
/* Write single register to specified address */
static int reg_write(const struct device *dev, uint8_t base, uint8_t offset, uint8_t data)
{
const struct npm1300_charger_config *const config = dev->config;
uint8_t buff[] = {base, offset, data};
return i2c_write_dt(&config->i2c, buff, sizeof(buff));
}
static int reg_write2(const struct device *dev, uint8_t base, uint8_t offset, uint8_t data1,
uint8_t data2)
{
const struct npm1300_charger_config *const config = dev->config;
uint8_t buff[] = {base, offset, data1, data2};
return i2c_write_dt(&config->i2c, buff, sizeof(buff));
}
static void calc_temp(const struct npm1300_charger_config *const config, uint16_t code,
struct sensor_value *valp)
{
/* Ref: Datasheet Figure 42: Battery temperature (Kelvin) */
float log_result = log((1024.f / (float)code) - 1);
float inv_temp_k = (1.f / 298.15f) - (log_result / (float)config->thermistor_beta);
float temp = (1.f / inv_temp_k) - 273.15f;
valp->val1 = (int32_t)temp;
valp->val2 = (int32_t)(fmodf(temp, 1.f) * 1000000.f);
}
static uint16_t adc_get_res(uint8_t msb, uint8_t lsb, uint16_t lsb_shift)
{
return ((uint16_t)msb << ADC_MSB_SHIFT) | ((lsb >> lsb_shift) & ADC_LSB_MASK);
}
static void calc_current(const struct npm1300_charger_config *const config,
struct npm1300_charger_data *const data, struct sensor_value *valp)
{
int32_t full_scale_ma;
int32_t current;
switch (data->ibat_stat) {
case IBAT_STAT_DISCHARGE:
full_scale_ma = config->dischg_limit_microamp / 1000;
break;
case IBAT_STAT_CHARGE_TRICKLE:
full_scale_ma = -config->current_microamp / 10000;
break;
case IBAT_STAT_CHARGE_COOL:
full_scale_ma = -config->current_microamp / 2000;
break;
case IBAT_STAT_CHARGE_NORMAL:
full_scale_ma = -config->current_microamp / 1000;
break;
default:
full_scale_ma = 0;
break;
}
current = (data->current * full_scale_ma) / 1024;
valp->val1 = current / 1000;
valp->val2 = (current % 1000) * 1000;
}
int npm1300_charger_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *valp)
{
const struct npm1300_charger_config *const config = dev->config;
struct npm1300_charger_data *const data = dev->data;
int32_t tmp;
switch ((uint32_t)chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
tmp = data->voltage * 5000 / 1024;
valp->val1 = tmp / 1000;
valp->val2 = (tmp % 1000) * 1000;
break;
case SENSOR_CHAN_GAUGE_TEMP:
calc_temp(config, data->temp, valp);
break;
case SENSOR_CHAN_GAUGE_AVG_CURRENT:
calc_current(config, data, valp);
break;
case SENSOR_CHAN_NPM1300_CHARGER_STATUS:
valp->val1 = data->status;
valp->val2 = 0;
break;
case SENSOR_CHAN_NPM1300_CHARGER_ERROR:
valp->val1 = data->error;
valp->val2 = 0;
break;
case SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT:
valp->val1 = config->current_microamp / 1000000;
valp->val2 = config->current_microamp % 1000000;
break;
case SENSOR_CHAN_GAUGE_MAX_LOAD_CURRENT:
valp->val1 = config->dischg_limit_microamp / 1000000;
valp->val2 = config->dischg_limit_microamp % 1000000;
break;
default:
return -ENOTSUP;
}
return 0;
}
int npm1300_charger_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct npm1300_charger_data *data = dev->data;
struct adc_results_t results;
bool last_vbus;
int ret;
/* Read charge status and error reason */
ret = reg_read(dev, CHGR_BASE, CHGR_OFFSET_CHG_STAT, &data->status);
if (ret != 0) {
return ret;
}
ret = reg_read(dev, CHGR_BASE, CHGR_OFFSET_ERR_REASON, &data->error);
if (ret != 0) {
return ret;
}
/* Read adc results */
ret = reg_read_burst(dev, ADC_BASE, ADC_OFFSET_RESULTS, &results, sizeof(results));
if (ret != 0) {
return ret;
}
data->voltage = adc_get_res(results.msb_vbat, results.lsb_a, ADC_LSB_VBAT_SHIFT);
data->temp = adc_get_res(results.msb_ntc, results.lsb_a, ADC_LSB_NTC_SHIFT);
data->current = adc_get_res(results.msb_ibat, results.lsb_b, ADC_LSB_IBAT_SHIFT);
data->ibat_stat = results.ibat_stat;
/* Trigger temperature measurement */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_TASK_TEMP, 1U);
if (ret != 0) {
return ret;
}
/* Trigger current and voltage measurement */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_TASK_VBAT, 1U);
if (ret != 0) {
return ret;
}
/* Read vbus status, and set SW current limit on new vbus detection */
last_vbus = (data->vbus_stat & 1U) != 0U;
ret = reg_read(dev, VBUS_BASE, VBUS_OFFSET_STATUS, &data->vbus_stat);
if (ret != 0) {
return ret;
}
if (!last_vbus && ((data->vbus_stat & 1U) != 0U)) {
ret = reg_write(dev, VBUS_BASE, VBUS_OFFSET_TASK_UPDATE, 1U);
if (ret != 0) {
return ret;
}
}
return ret;
}
int npm1300_charger_init(const struct device *dev)
{
const struct npm1300_charger_config *const config = dev->config;
uint16_t idx;
int ret;
if (!i2c_is_ready_dt(&config->i2c)) {
return -ENODEV;
}
/* Configure thermistor */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_NTCR_SEL, config->thermistor_idx + 1U);
if (ret != 0) {
return ret;
}
/* Configure termination voltages */
ret = linear_range_group_get_win_index(charger_volt_ranges, ARRAY_SIZE(charger_volt_ranges),
config->term_microvolt, config->term_microvolt,
&idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write(dev, CHGR_BASE, CHGR_OFFSET_VTERM, idx);
if (ret != 0) {
return ret;
}
ret = linear_range_group_get_win_index(charger_volt_ranges, ARRAY_SIZE(charger_volt_ranges),
config->term_warm_microvolt,
config->term_warm_microvolt, &idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write(dev, CHGR_BASE, CHGR_OFFSET_VTERM_R, idx);
if (ret != 0) {
return ret;
}
/* Set current, allow rounding down to closest value */
ret = linear_range_get_win_index(&charger_current_range,
config->current_microamp - charger_current_range.step,
config->current_microamp, &idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write2(dev, CHGR_BASE, CHGR_OFFSET_ISET, idx / 2U, idx & 1U);
if (ret != 0) {
return ret;
}
/* Set discharge limit, allow rounding down to closest value */
ret = linear_range_get_win_index(&discharge_limit_range,
config->dischg_limit_microamp - discharge_limit_range.step,
config->dischg_limit_microamp, &idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write2(dev, CHGR_BASE, CHGR_OFFSET_ISET_DISCHG, idx / 2U, idx & 1U);
if (ret != 0) {
return ret;
}
/* Configure vbus current limit */
ret = linear_range_group_get_win_index(vbus_current_ranges, ARRAY_SIZE(vbus_current_ranges),
config->vbus_limit_microamp,
config->vbus_limit_microamp, &idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write(dev, VBUS_BASE, VBUS_OFFSET_ILIM, idx);
if (ret != 0) {
return ret;
}
/* Enable current measurement */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_IBAT_EN, 1U);
if (ret != 0) {
return ret;
}
/* Trigger current and voltage measurement */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_TASK_VBAT, 1U);
if (ret != 0) {
return ret;
}
/* Trigger temperature measurement */
ret = reg_write(dev, ADC_BASE, ADC_OFFSET_TASK_TEMP, 1U);
if (ret != 0) {
return ret;
}
/* Enable charging if configured */
if (config->charging_enable) {
ret = reg_write(dev, CHGR_BASE, CHGR_OFFSET_EN_SET, 1U);
if (ret != 0) {
return ret;
}
}
return 0;
}
static const struct sensor_driver_api npm1300_charger_battery_driver_api = {
.sample_fetch = npm1300_charger_sample_fetch,
.channel_get = npm1300_charger_channel_get,
};
#define NPM1300_CHARGER_INIT(n) \
static struct npm1300_charger_data npm1300_charger_data_##n; \
\
static const struct npm1300_charger_config npm1300_charger_config_##n = { \
.i2c = I2C_DT_SPEC_GET(DT_INST_PARENT(n)), \
.term_microvolt = DT_INST_PROP(n, term_microvolt), \
.term_warm_microvolt = \
DT_INST_PROP_OR(n, term_warm_microvolt, DT_INST_PROP(n, term_microvolt)), \
.current_microamp = DT_INST_PROP(n, current_microamp), \
.dischg_limit_microamp = DT_INST_PROP(n, dischg_limit_microamp), \
.vbus_limit_microamp = DT_INST_PROP(n, vbus_limit_microamp), \
.thermistor_idx = DT_INST_ENUM_IDX(n, thermistor_ohms), \
.thermistor_beta = DT_INST_PROP(n, thermistor_beta), \
.charging_enable = DT_INST_PROP(n, charging_enable), \
}; \
\
SENSOR_DEVICE_DT_INST_DEFINE(n, &npm1300_charger_init, NULL, &npm1300_charger_data_##n, \
&npm1300_charger_config_##n, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&npm1300_charger_battery_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NPM1300_CHARGER_INIT)