drivers: charger: add charger support in X-Powers AXP2101

AXP2101 is MFD device. Zephyr already support the regulator part. This
commit introduces intial support for the charger one.

Signed-off-by: Valerio Setti <vsetti@baylibre.com>
This commit is contained in:
Valerio Setti 2025-04-03 17:22:06 +02:00 committed by Benjamin Cabé
commit 65a7e79eab
5 changed files with 493 additions and 0 deletions

View file

@ -3,6 +3,7 @@
zephyr_library()
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/charger.h)
zephyr_library_sources_ifdef(CONFIG_CHARGER_AXP2101 charger_axp2101.c)
zephyr_library_sources_ifdef(CONFIG_CHARGER_BQ24190 charger_bq24190.c)
zephyr_library_sources_ifdef(CONFIG_CHARGER_BQ25180 charger_bq25180.c)
zephyr_library_sources_ifdef(CONFIG_CHARGER_BQ25713 charger_bq25713.c)

View file

@ -51,6 +51,7 @@ config CHARGER_SYSTEM_VOLTAGE_NOTIFICATIONS
Enables support for system voltage notifications
endmenu
source "drivers/charger/Kconfig.axp2101"
source "drivers/charger/Kconfig.sbs_charger"
source "drivers/charger/Kconfig.bq24190"
source "drivers/charger/Kconfig.bq25180"

View file

@ -0,0 +1,12 @@
# Copyright (c) 2025 BayLibre SAS
# SPDX-License-Identifier: Apache-2.0
config CHARGER_AXP2101
bool "X-Powers AXP2101 PMIC charger driver"
default y
depends on DT_HAS_X_POWERS_AXP2101_CHARGER_ENABLED
depends on DT_HAS_X_POWERS_AXP2101_ENABLED
select I2C
select MFD
help
Enable the AXP2101 PMIC charger driver

View file

@ -0,0 +1,404 @@
/*
* Copyright 2025 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT x_powers_axp2101_charger
#include <errno.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>
LOG_MODULE_REGISTER(charger_axp2101, CONFIG_CHARGER_LOG_LEVEL);
#define AXP2101_PMU_STATUS1 0x00
#define VBUS_GOOD_INDICATION BIT(5)
#define AXP2101_PMU_STATUS2 0x01
#define CHARGING_STATUS GENMASK(2, 0)
#define TRICKLE_CHARGE 0x0
#define PRE_CHARGE 0x1
#define CONSTANT_CURRENT 0x2
#define CONSTANT_VOLTAGE 0x3
#define CHARGE_DONE 0x4
#define NOT_CHARGING 0x5
#define AXP2101_CHARGER_CONTROL 0x18
#define BUTTON_BATTERY_CHARGE_ENABLE BIT(2)
#define CELL_BATTERY_CHARGE_ENABLE BIT(1)
#define AXP2101_IPRECH_CHARGER_SETTING 0x61
#define PRE_CHARGE_CURRENT_STEP_UA 25000
#define AXP2101_ICC_CHARGER_SETTING 0x62
#define AXP2101_ITERM_CHARGER_SETTING 0x63
#define CHARGE_TERMINATION_ENABLE BIT(4)
#define TERMINATION_CURRENT_LIMIT GENMASK(3, 0)
#define TERMINATION_CURRENT_STEP_UA 25000
#define AXP2101_CV_CHARGER_VOLTAGE 0x64
struct axp2101_config {
struct i2c_dt_spec i2c;
bool vbackup_enable;
};
struct axp2101_data {
uint32_t cc_current_ua;
uint32_t cc_voltage_uv;
uint32_t termination_current_ua;
};
static const uint32_t constant_charge_current_lut[] = {
[0] = 0,
[1] = 0,
[2] = 0,
[3] = 0,
[4] = 100000,
[5] = 125000,
[6] = 150000,
[7] = 175000,
[8] = 200000,
[9] = 300000,
[10] = 400000,
[11] = 500000,
[12] = 600000,
[13] = 700000,
[14] = 800000,
[15] = 900000,
[16] = 1000000,
};
static const uint32_t constant_charge_voltage_lut[] = {
[0] = 0,
[1] = 4000000,
[2] = 4100000,
[3] = 4200000,
[4] = 4350000,
[5] = 4400000,
};
static int get_index_in_lut(uint32_t value, const uint32_t *lut, size_t lut_len)
{
int i;
for (i = 0; i < (int) lut_len; i++) {
if (value == lut[i]) {
return i;
}
}
return -1;
}
static enum charger_online is_charger_online(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_PMU_STATUS1, &tmp);
if (ret < 0) {
return ret;
}
val->online = (tmp & VBUS_GOOD_INDICATION) ?
CHARGER_ONLINE_FIXED : CHARGER_ONLINE_OFFLINE;
return 0;
}
static int get_constant_charge_current_ua(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_ICC_CHARGER_SETTING, &tmp);
if (ret < 0) {
return ret;
}
val->const_charge_current_ua = constant_charge_current_lut[tmp];
return 0;
}
static int set_constant_charge_current_ua(const struct device *dev,
const union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
struct axp2101_data *data = dev->data;
int lut_index;
lut_index = get_index_in_lut(val->const_charge_current_ua,
constant_charge_current_lut,
ARRAY_SIZE(constant_charge_current_lut));
if (lut_index < 0) {
return -EINVAL;
}
data->cc_current_ua = constant_charge_current_lut[lut_index];
return i2c_reg_write_byte_dt(&config->i2c, AXP2101_ICC_CHARGER_SETTING,
(uint8_t) lut_index);
}
static int get_pre_charge_current_ua(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_IPRECH_CHARGER_SETTING, &tmp);
if (ret < 0) {
return ret;
}
val->precharge_current_ua = PRE_CHARGE_CURRENT_STEP_UA * ((uint32_t) tmp);
return 0;
}
static int get_termination_current_ua(const struct device *dev,
union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_ITERM_CHARGER_SETTING, &tmp);
if (ret < 0) {
return ret;
}
if ((tmp & CHARGE_TERMINATION_ENABLE) == 0) {
val->charge_term_current_ua = 0;
} else {
tmp = tmp & TERMINATION_CURRENT_LIMIT;
val->charge_term_current_ua = TERMINATION_CURRENT_STEP_UA * ((uint32_t) tmp);
}
return 0;
}
static int set_termination_current_ua(const struct device *dev,
const union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint32_t mask, tmp = 0;
tmp = val->charge_term_current_ua;
if ((tmp > 200000) || ((tmp % TERMINATION_CURRENT_STEP_UA) != 0)) {
return -EINVAL;
}
if (tmp != 0) {
tmp = (uint8_t) (tmp / TERMINATION_CURRENT_STEP_UA);
tmp |= CHARGE_TERMINATION_ENABLE;
}
mask = TERMINATION_CURRENT_LIMIT | CHARGE_TERMINATION_ENABLE;
return i2c_reg_update_byte_dt(&config->i2c, AXP2101_ITERM_CHARGER_SETTING,
mask, tmp);
}
static int get_constant_charge_voltage_uv(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_CV_CHARGER_VOLTAGE, &tmp);
if (ret < 0) {
return ret;
}
if (tmp > ARRAY_SIZE(constant_charge_voltage_lut)) {
return -EINVAL;
}
val->const_charge_voltage_uv = constant_charge_voltage_lut[tmp];
return 0;
}
static int set_constant_charge_voltage_uv(const struct device *dev,
const union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
int lut_index;
lut_index = get_index_in_lut(val->const_charge_voltage_uv,
constant_charge_voltage_lut,
ARRAY_SIZE(constant_charge_voltage_lut));
if (lut_index < 0) {
return -EINVAL;
}
return i2c_reg_write_byte_dt(&config->i2c, AXP2101_CV_CHARGER_VOLTAGE, (uint8_t) lut_index);
}
static int get_status(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_PMU_STATUS2, &tmp);
if (ret < 0) {
return ret;
}
tmp = tmp & CHARGING_STATUS;
if ((tmp >= TRICKLE_CHARGE) && (tmp <= CONSTANT_VOLTAGE)) {
val->status = CHARGER_STATUS_CHARGING;
} else if (tmp == CHARGE_DONE) {
val->status = CHARGER_STATUS_FULL;
} else if (tmp == NOT_CHARGING) {
val->status = CHARGER_STATUS_NOT_CHARGING;
} else {
return -EINVAL;
}
return 0;
}
static int get_charge_type(const struct device *dev, union charger_propval *val)
{
const struct axp2101_config *const config = dev->config;
uint8_t tmp;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, AXP2101_PMU_STATUS2, &tmp);
if (ret < 0) {
return ret;
}
tmp = tmp & CHARGING_STATUS;
if ((tmp == TRICKLE_CHARGE) || (tmp == PRE_CHARGE)) {
val->charge_type = CHARGER_CHARGE_TYPE_TRICKLE;
} else if ((tmp >= CONSTANT_CURRENT) && (tmp <= CHARGE_DONE)) {
val->charge_type = CHARGER_CHARGE_TYPE_STANDARD;
} else if (tmp == NOT_CHARGING) {
val->charge_type = CHARGER_CHARGE_TYPE_UNKNOWN;
} else {
return -EINVAL;
}
return 0;
}
static int axp2101_get_prop(const struct device *dev, charger_prop_t prop,
union charger_propval *val)
{
switch (prop) {
case CHARGER_PROP_ONLINE:
return is_charger_online(dev, val);
case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA:
return get_constant_charge_current_ua(dev, val);
case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
return get_constant_charge_voltage_uv(dev, val);
case CHARGER_PROP_PRECHARGE_CURRENT_UA:
return get_pre_charge_current_ua(dev, val);
case CHARGER_PROP_CHARGE_TERM_CURRENT_UA:
return get_termination_current_ua(dev, val);
case CHARGER_PROP_CHARGE_TYPE:
return get_charge_type(dev, val);
case CHARGER_PROP_STATUS:
return get_status(dev, val);
default:
return -ENOTSUP;
}
}
static int axp2101_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 set_constant_charge_current_ua(dev, val);
case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
return set_constant_charge_voltage_uv(dev, val);
case CHARGER_PROP_CHARGE_TERM_CURRENT_UA:
return set_termination_current_ua(dev, val);
default:
return -ENOTSUP;
}
}
static int axp2101_charge_enable(const struct device *dev, const bool enable)
{
const struct axp2101_config *const config = dev->config;
return i2c_reg_update_byte_dt(&config->i2c, AXP2101_CHARGER_CONTROL,
CELL_BATTERY_CHARGE_ENABLE,
(enable) ? CELL_BATTERY_CHARGE_ENABLE : 0);
}
static int axp2101_init(const struct device *dev)
{
const struct axp2101_config *const config = dev->config;
struct axp2101_data *data = dev->data;
union charger_propval val;
int ret;
if (config->vbackup_enable) {
ret = i2c_reg_update_byte_dt(&config->i2c, AXP2101_CHARGER_CONTROL,
BUTTON_BATTERY_CHARGE_ENABLE,
BUTTON_BATTERY_CHARGE_ENABLE);
if (ret < 0) {
return ret;
}
}
val.const_charge_current_ua = data->cc_current_ua;
ret = set_constant_charge_current_ua(dev, &val);
if (ret < 0) {
return ret;
}
val.const_charge_voltage_uv = data->cc_voltage_uv;
ret = set_constant_charge_voltage_uv(dev, &val);
if (ret < 0) {
return ret;
}
val.charge_term_current_ua = data->termination_current_ua;
ret = set_termination_current_ua(dev, &val);
if (ret < 0) {
return ret;
}
return 0;
}
static DEVICE_API(charger, axp2101_driver_api) = {
.get_property = axp2101_get_prop,
.set_property = axp2101_set_prop,
.charge_enable = axp2101_charge_enable,
};
#define AXP2101_INIT(inst) \
static const struct axp2101_config axp2101_config_##inst = { \
.i2c = I2C_DT_SPEC_GET(DT_PARENT(DT_INST(inst, DT_DRV_COMPAT))), \
.vbackup_enable = DT_INST_PROP(inst, vbackup_enable), \
}; \
static struct axp2101_data axp2101_data_##inst = { \
.cc_current_ua = DT_INST_PROP(inst, constant_charge_current_max_microamp), \
.cc_voltage_uv = DT_INST_PROP(inst, constant_charge_voltage_max_microvolt), \
.termination_current_ua = DT_INST_PROP_OR(inst, charge-term-current-microamp, \
125000), \
}; \
DEVICE_DT_INST_DEFINE(inst, axp2101_init, NULL, &axp2101_data_##inst, \
&axp2101_config_##inst, POST_KERNEL, \
CONFIG_CHARGER_INIT_PRIORITY, \
&axp2101_driver_api);
DT_INST_FOREACH_STATUS_OKAY(AXP2101_INIT)

View file

@ -0,0 +1,75 @@
# Copyright 2025 BayLibre SAS
# SPDX-License-Identifier: Apache-2.0
description: |
Charger part of the AXP2101 PMU MFD device.
This charger should be instantiated as child of the AXP2101 MFD device, i.e.
axp2101@34 {
compatible = "x-powers,axp2101";
reg = <0x34>;
charger {
compatible = "x-powers,axp2101-charger";
constant-charge-current-max-microamp = <300000>;
constant-charge-voltage-max-microvolt = <4200000>;
};
regulators {
...
}
}
compatible: "x-powers,axp2101-charger"
include:
- base.yaml
- battery.yaml
properties:
# Extending the definition from battery.yaml
constant-charge-current-max-microamp:
required: true
enum:
- 100000
- 125000
- 150000
- 175000
- 200000
- 300000
- 400000
- 500000
- 600000
- 700000
- 800000
- 900000
- 1000000
# Extending the definition from battery.yaml
constant-charge-voltage-max-microvolt:
required: true
enum:
- 4000000
- 4100000
- 4200000
- 4350000
- 4400000
# Extending the definition from battery.yaml
charge-term-current-microamp:
enum:
- 0
- 25000
- 50000
- 75000
- 100000
- 125000
- 150000
- 200000
vbackup-enable:
type: boolean
description:
Enable Vbackup on boot. This is normally used to charge a button battery
that is used by some RTC IC.