[PATCH 7/7] power: supply: axp20x_battery: Add support for the AXP2101
From: Olivier Louvignes
Date: Fri Oct 23 2026 - 06:06:01 EST
Program the AXP2101's charger from the monitored battery: the end of
charge voltage (CV_CHG_SET, 4.0 to 4.4 V), the constant charge current
(ICC_CHG_SET, 25 mA steps to 200 mA then 100 mA steps to 1 A), and the
termination and precharge currents (25 mA steps). Report whether the
battery is present, the charge state (precharge, constant current or
constant voltage charging, done, or by the current direction) and the
settings.
The supply is read-only and reports no capacity: the AXP2101's own fuel
gauge needs a battery model, and the HiBy R1 uses a CW2015 for that.
Tested on a HiBy R1: the charger registers read back as the stock
firmware's values after boot, and the status is Full on USB,
Discharging unplugged and Charging after a replug.
Signed-off-by: Olivier Louvignes <olivier@xxxxxxxxx>
---
drivers/power/supply/axp20x_battery.c | 221 ++++++++++++++++++++++++++
1 file changed, 221 insertions(+)
diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
index ee8701a6e907..a653ad923144 100644
--- a/drivers/power/supply/axp20x_battery.c
+++ b/drivers/power/supply/axp20x_battery.c
@@ -91,6 +91,26 @@
#define AXP717_TS_PIN_DISABLE BIT(4)
+#define AXP2101_PWR_OP_BATT_PRESENT BIT(3)
+#define AXP2101_PWR_STATUS_MASK GENMASK(6, 5)
+#define AXP2101_PWR_STATUS_BAT_STANDBY 0
+#define AXP2101_PWR_STATUS_BAT_CHRG 1
+#define AXP2101_PWR_STATUS_BAT_DISCHRG 2
+#define AXP2101_CHRG_STATE_MASK GENMASK(2, 0)
+#define AXP2101_CHRG_STATE_TRICKLE 0
+#define AXP2101_CHRG_STATE_PRECHARGE 1
+#define AXP2101_CHRG_STATE_CC 2
+#define AXP2101_CHRG_STATE_CV 3
+#define AXP2101_CHRG_STATE_DONE 4
+/* CV_CHG_SET: end of charge voltage selector, 1 to 5 for 4.0 to 4.4 V. */
+#define AXP2101_CHRG_CV_VOLT_MASK GENMASK(2, 0)
+#define AXP2101_ICC_MASK GENMASK(4, 0)
+#define AXP2101_ICC_MAX_SEL 16 /* 1 A; 17 to 31 are reserved */
+#define AXP2101_ITERM_MASK GENMASK(3, 0)
+#define AXP2101_ITERM_ENABLE BIT(4)
+#define AXP2101_IPRECHG_MASK GENMASK(3, 0)
+#define AXP2101_CURRENT_STEP 25000
+
struct axp20x_batt_ps;
struct axp_data {
@@ -213,6 +233,44 @@ static int axp717_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt,
}
}
+/* End of charge voltages of CV_CHG_SET selectors 0 to 7; 0, 6 and 7 are reserved. */
+static const int axp2101_cv_uv[] = {
+ 0, 4000000, 4100000, 4200000, 4350000, 4400000, 0, 0,
+};
+
+static int axp2101_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt,
+ int *val)
+{
+ int ret, reg;
+
+ ret = regmap_read(axp20x_batt->regmap, AXP2101_CV_CHG_SET, ®);
+ if (ret)
+ return ret;
+
+ *val = axp2101_cv_uv[reg & AXP2101_CHRG_CV_VOLT_MASK];
+ return *val ? 0 : -EINVAL;
+}
+
+static int axp2101_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt,
+ int val)
+{
+ int sel;
+
+ for (sel = 0; sel < ARRAY_SIZE(axp2101_cv_uv); sel++)
+ if (axp2101_cv_uv[sel] && axp2101_cv_uv[sel] == val)
+ return regmap_update_bits(axp20x_batt->regmap,
+ AXP2101_CV_CHG_SET,
+ AXP2101_CHRG_CV_VOLT_MASK, sel);
+
+ return -EINVAL;
+}
+
+/* CONSTANT_CHARGE_CURRENT: 25 mA steps up to 200 mA, then 100 mA steps. */
+static int axp2101_ccc_from_sel(unsigned int sel)
+{
+ return sel <= 8 ? sel * 25000 : 200000 + (sel - 8) * 100000;
+}
+
static int axp813_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt,
int *val)
{
@@ -425,6 +483,89 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
return 0;
}
+static int axp2101_battery_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
+ int ret, reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = regmap_read(axp20x_batt->regmap, AXP2101_PMU_STATUS_1,
+ ®);
+ if (ret)
+ return ret;
+
+ val->intval = !!(reg & AXP2101_PWR_OP_BATT_PRESENT);
+ return 0;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = regmap_read(axp20x_batt->regmap, AXP2101_PMU_STATUS_2,
+ ®);
+ if (ret)
+ return ret;
+
+ switch (FIELD_GET(AXP2101_CHRG_STATE_MASK, reg)) {
+ case AXP2101_CHRG_STATE_PRECHARGE:
+ case AXP2101_CHRG_STATE_CC:
+ case AXP2101_CHRG_STATE_CV:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ return 0;
+ case AXP2101_CHRG_STATE_DONE:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ return 0;
+ }
+
+ /* Trickle charging or not charging: by the current direction. */
+ switch (FIELD_GET(AXP2101_PWR_STATUS_MASK, reg)) {
+ case AXP2101_PWR_STATUS_BAT_STANDBY:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ return 0;
+ case AXP2101_PWR_STATUS_BAT_CHRG:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ return 0;
+ case AXP2101_PWR_STATUS_BAT_DISCHRG:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ return 0;
+ }
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = regmap_read(axp20x_batt->regmap, AXP2101_ICC_CHG_SET,
+ ®);
+ if (ret)
+ return ret;
+
+ val->intval = axp2101_ccc_from_sel(FIELD_GET(AXP2101_ICC_MASK,
+ reg));
+ return 0;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return axp2101_battery_get_max_voltage(axp20x_batt,
+ &val->intval);
+
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = regmap_read(axp20x_batt->regmap, AXP2101_ITERM_CHG_SET,
+ ®);
+ if (ret)
+ return ret;
+
+ if (!(reg & AXP2101_ITERM_ENABLE))
+ val->intval = 0;
+ else
+ val->intval = FIELD_GET(AXP2101_ITERM_MASK, reg) *
+ AXP2101_CURRENT_STEP;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
static int axp717_battery_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -852,6 +993,15 @@ static enum power_supply_property axp20x_battery_props[] = {
POWER_SUPPLY_PROP_CAPACITY,
};
+static enum power_supply_property axp2101_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+};
+
static enum power_supply_property axp717_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
@@ -895,6 +1045,15 @@ static const struct power_supply_desc axp209_batt_ps_desc = {
.set_property = axp20x_battery_set_prop,
};
+/* Read-only: the charge settings come from the monitored battery. */
+static const struct power_supply_desc axp2101_batt_ps_desc = {
+ .name = "axp20x-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = axp2101_battery_props,
+ .num_properties = ARRAY_SIZE(axp2101_battery_props),
+ .get_property = axp2101_battery_get_prop,
+};
+
static const struct power_supply_desc axp717_batt_ps_desc = {
.name = "axp20x-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
@@ -978,6 +1137,57 @@ static void axp209_set_battery_info(struct platform_device *pdev,
}
}
+static int axp2101_bat_cfg_iio_channels(struct platform_device *pdev,
+ struct axp20x_batt_ps *axp_batt)
+{
+ return 0;
+}
+
+/*
+ * Programs the charger from the monitored battery: end of charge voltage,
+ * constant charge current, termination and precharge currents. What the
+ * battery doesn't give is left as it is.
+ */
+static void axp2101_set_battery_info(struct platform_device *pdev,
+ struct axp20x_batt_ps *axp_batt,
+ struct power_supply_battery_info *info)
+{
+ int cv = info->constant_charge_voltage_max_uv;
+ int ccc = info->constant_charge_current_max_ua;
+ int iterm = info->charge_term_current_ua;
+ int iprechg = info->precharge_current_ua;
+ unsigned int sel;
+
+ if (cv > 0 && axp2101_battery_set_max_voltage(axp_batt, cv))
+ dev_err(&pdev->dev, "unsupported charge voltage %d uV\n", cv);
+
+ if (ccc > 0) {
+ if (ccc <= 200000)
+ sel = ccc / AXP2101_CURRENT_STEP;
+ else
+ sel = min((ccc - 200000) / 100000 + 8,
+ AXP2101_ICC_MAX_SEL);
+ axp_batt->max_ccc = axp2101_ccc_from_sel(sel);
+ regmap_update_bits(axp_batt->regmap, AXP2101_ICC_CHG_SET,
+ AXP2101_ICC_MASK, sel);
+ }
+
+ if (iterm > 0) {
+ sel = min(iterm / AXP2101_CURRENT_STEP,
+ (int)FIELD_MAX(AXP2101_ITERM_MASK));
+ regmap_update_bits(axp_batt->regmap, AXP2101_ITERM_CHG_SET,
+ AXP2101_ITERM_MASK | AXP2101_ITERM_ENABLE,
+ sel | AXP2101_ITERM_ENABLE);
+ }
+
+ if (iprechg > 0) {
+ sel = min(iprechg / AXP2101_CURRENT_STEP,
+ (int)FIELD_MAX(AXP2101_IPRECHG_MASK));
+ regmap_update_bits(axp_batt->regmap, AXP2101_IPRECHG_SET,
+ AXP2101_IPRECHG_MASK, sel);
+ }
+}
+
static void axp717_set_battery_info(struct platform_device *pdev,
struct axp20x_batt_ps *axp_batt,
struct power_supply_battery_info *info)
@@ -1047,6 +1257,14 @@ static const struct axp_data axp221_data = {
.set_bat_info = axp209_set_battery_info,
};
+static const struct axp_data axp2101_data = {
+ .bat_ps_desc = &axp2101_batt_ps_desc,
+ .get_max_voltage = axp2101_battery_get_max_voltage,
+ .set_max_voltage = axp2101_battery_set_max_voltage,
+ .cfg_iio_chan = axp2101_bat_cfg_iio_channels,
+ .set_bat_info = axp2101_set_battery_info,
+};
+
static const struct axp_data axp717_data = {
.ccc_scale = 64000,
.ccc_offset = 0,
@@ -1082,6 +1300,9 @@ static const struct of_device_id axp20x_battery_ps_id[] = {
}, {
.compatible = "x-powers,axp717-battery-power-supply",
.data = (void *)&axp717_data,
+ }, {
+ .compatible = "x-powers,axp2101-battery-power-supply",
+ .data = (void *)&axp2101_data,
}, {
.compatible = "x-powers,axp813-battery-power-supply",
.data = (void *)&axp813_data,
--
2.39.5