diff --git a/drivers/charger/CMakeLists.txt b/drivers/charger/CMakeLists.txt index 62d4969f4bc..3c5dbe1f7e0 100644 --- a/drivers/charger/CMakeLists.txt +++ b/drivers/charger/CMakeLists.txt @@ -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) diff --git a/drivers/charger/Kconfig b/drivers/charger/Kconfig index 040a60a4590..2c6956278dc 100644 --- a/drivers/charger/Kconfig +++ b/drivers/charger/Kconfig @@ -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" diff --git a/drivers/charger/Kconfig.axp2101 b/drivers/charger/Kconfig.axp2101 new file mode 100644 index 00000000000..70675625865 --- /dev/null +++ b/drivers/charger/Kconfig.axp2101 @@ -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 diff --git a/drivers/charger/charger_axp2101.c b/drivers/charger/charger_axp2101.c new file mode 100644 index 00000000000..5396c2cce45 --- /dev/null +++ b/drivers/charger/charger_axp2101.c @@ -0,0 +1,404 @@ +/* + * Copyright 2025 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT x_powers_axp2101_charger + +#include + +#include +#include +#include +#include +#include +#include + +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) diff --git a/dts/bindings/charger/x-powers,axp2101-charger.yaml b/dts/bindings/charger/x-powers,axp2101-charger.yaml new file mode 100644 index 00000000000..fa70b76b091 --- /dev/null +++ b/dts/bindings/charger/x-powers,axp2101-charger.yaml @@ -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.