power: axp20x_battery: implement calibration

This commit is contained in:
ayakael 2026-02-08 09:18:08 -05:00
commit adc1869bbc
No known key found for this signature in database
GPG key ID: 70904985A46AF9AF
2 changed files with 106 additions and 6 deletions

View file

@ -13,7 +13,6 @@
#include <linux/mfd/axp20x.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/power_supply.h>
@ -53,6 +52,9 @@ static irqreturn_t axp20x_ac_power_irq(int irq, void *devid)
{
struct axp20x_ac_power *power = devid;
regmap_update_bits(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, 0x03, 0x00);
regmap_update_bits(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, 0x03, 0x03);
power_supply_changed(power->supply);
return IRQ_HANDLED;
@ -335,7 +337,7 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
return -EINVAL;
}
axp_data = of_device_get_match_data(&pdev->dev);
axp_data = device_get_match_data(&pdev->dev);
power = devm_kzalloc(&pdev->dev,
struct_size(power, irqs, axp_data->num_irq_names),

View file

@ -22,7 +22,6 @@
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
@ -56,6 +55,10 @@
#define AXP20X_V_OFF_MASK GENMASK(2, 0)
#define AXP228_FULL_CAPACITY_CALIBRATE_EN BIT(5)
#define AXP228_CAPACITY_CALIBRATE BIT(4)
#define AXP228_CALIBRATE_MASK (BIT(4) | BIT(5))
struct axp20x_batt_ps;
struct axp_data {
@ -75,6 +78,9 @@ struct axp20x_batt_ps {
struct iio_channel *batt_v;
/* Maximum constant charge current */
unsigned int max_ccc;
int energy_full_design;
int current_now;
int voltage_now;
const struct axp_data *data;
};
@ -276,6 +282,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
/* IIO framework gives mA but Power Supply framework gives uA */
val->intval *= 1000;
axp20x_batt->current_now = val->intval;
break;
case POWER_SUPPLY_PROP_CAPACITY:
@ -324,8 +331,60 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
/* IIO framework gives mV but Power Supply framework gives uV */
val->intval *= 1000;
axp20x_batt->current_now = val->intval;
break;
case POWER_SUPPLY_PROP_ENERGY_FULL:
case POWER_SUPPLY_PROP_ENERGY_NOW:
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
/* When no battery is present, return 0 */
ret = regmap_read(axp20x_batt->regmap, AXP20X_PWR_OP_MODE,
&reg);
if (ret)
return ret;
if (!(reg & AXP20X_PWR_OP_BATT_PRESENT)) {
val->intval = 0;
return 0;
}
if(psp == POWER_SUPPLY_PROP_ENERGY_FULL) {
// TODO
val->intval = axp20x_batt->energy_full_design;
return 0;
}
if(psp == POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN) {
val->intval = axp20x_batt->energy_full_design;
return 0;
}
ret = regmap_read(axp20x_batt->regmap, AXP20X_FG_RES, &reg);
if (ret)
return ret;
if (axp20x_batt->data->has_fg_valid && !(reg & AXP22X_FG_VALID))
return -EINVAL;
val1 = reg & AXP209_FG_PERCENT;
val1 = max(min(val1, 100), 0);
val->intval = (val1 * ((long long int)axp20x_batt->energy_full_design)) / 100;
break;
case POWER_SUPPLY_PROP_CALIBRATE:
// report both calibrate enable flag and calibration status
ret = regmap_read(axp20x_batt->regmap, AXP20X_CC_CTRL, &reg);
if (ret)
return ret;
val1 = reg & AXP228_CALIBRATE_MASK;
val->intval = val1;
break;
case POWER_SUPPLY_PROP_POWER_NOW:
val->intval = (axp20x_batt->voltage_now / 10000) * axp20x_batt->current_now;
val->intval = val->intval / 100; // uW
break;
default:
return -EINVAL;
}
@ -454,6 +513,7 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
const union power_supply_propval *val)
{
struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
int val1;
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
@ -468,6 +528,16 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return axp20x_set_max_constant_charge_current(axp20x_batt,
val->intval);
case POWER_SUPPLY_PROP_CALIBRATE:
if (val->intval) {
// enable calibrate
val1 = AXP228_FULL_CAPACITY_CALIBRATE_EN | AXP228_CAPACITY_CALIBRATE;
} else {
// disable calibrate
val1 = 0;
}
return regmap_update_bits(axp20x_batt->regmap, AXP20X_CC_CTRL, AXP228_CALIBRATE_MASK, val1);
case POWER_SUPPLY_PROP_STATUS:
switch (val->intval) {
case POWER_SUPPLY_STATUS_CHARGING:
@ -497,6 +567,11 @@ static enum power_supply_property axp20x_battery_props[] = {
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_ENERGY_FULL,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_CALIBRATE,
POWER_SUPPLY_PROP_POWER_NOW,
};
static int axp20x_battery_prop_writeable(struct power_supply *psy,
@ -506,7 +581,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX ||
psp == POWER_SUPPLY_PROP_CALIBRATE;
}
static const struct power_supply_desc axp20x_batt_ps_desc = {
@ -602,7 +678,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
psy_cfg.drv_data = axp20x_batt;
psy_cfg.of_node = pdev->dev.of_node;
axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev);
axp20x_batt->data = (struct axp_data *)device_get_match_data(dev);
axp20x_batt->batt = devm_power_supply_register(&pdev->dev,
&axp20x_batt_ps_desc,
@ -616,6 +692,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
int vmin = info->voltage_min_design_uv;
int ccc = info->constant_charge_current_max_ua;
int cfd = info->charge_full_design_uah;
if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
vmin))
@ -633,7 +710,22 @@ static int axp20x_power_probe(struct platform_device *pdev)
axp20x_batt->max_ccc = ccc;
axp20x_set_constant_charge_current(axp20x_batt, ccc);
}
}
axp20x_batt->energy_full_design = info->energy_full_design_uwh;
// tell pmic about our battery
if (cfd) {
// [14:8], [7:0], cfd = Value * 1.456mAh
cfd = cfd / 1456;
regmap_update_bits(axp20x_batt->regmap, AXP288_FG_DES_CAP0_REG, 0xff, cfd & 0xff);
regmap_update_bits(axp20x_batt->regmap, AXP288_FG_DES_CAP1_REG, 0xff, BIT(7) | ((cfd >> 8) & 0xff));
} else {
dev_warn(axp20x_batt->dev, "charge full design is not set");
}
} else {
axp20x_batt->energy_full_design = 8000000;
dev_warn(axp20x_batt->dev, "energy full design is not set, default to %d\n", axp20x_batt->energy_full_design);
}
/*
* Update max CCC to a valid value if battery info is present or set it
@ -642,6 +734,12 @@ static int axp20x_power_probe(struct platform_device *pdev)
axp20x_get_constant_charge_current(axp20x_batt,
&axp20x_batt->max_ccc);
regmap_update_bits(axp20x_batt->regmap, AXP20X_VBUS_IPSOUT_MGMT, 0x03, 0x03);
regmap_update_bits(axp20x_batt->regmap, AXP20X_OFF_CTRL, 0x08, 0x08);
regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL2, 0x30, 0x20);
regmap_update_bits(axp20x_batt->regmap, AXP20X_PEK_KEY, 0x0f, 0x0b);
regmap_update_bits(axp20x_batt->regmap, AXP20X_GPIO0_CTRL, 0x07, 0x00);
return 0;
}