Messages in this thread | | | Date | Tue, 26 Oct 2021 00:40:54 +0200 | From | Sebastian Reichel <> | Subject | Re: [PATCH] PM: Added functionality to the axp20x_battery driver |
| |
Hi,
On Mon, Oct 25, 2021 at 04:44:55PM +0200, Thomas Marangoni wrote: > This patch adds missing features of the axp209 battery > functionality to the driver.
I don't consider "Add missing features" as one logical change, so please split this according to Documentation/process/submitting-patches.rst
> New and present features have been added to the device tree configuration. > > Following features have been implemented: > - Set/Get of OCV curve, this is used to tune the capacity status (setting these > values is only possible with the device tree). > - Set/Get of voltage low alert, this will trigger an interrupt if the given > voltage level is reached. Level 1 will print a warning and level 2 will shutdown > the device. > - Set/Get of temperature sense current, this is useful if a none default NTC is > used for temperature sensing. > - Set/Get of temperature sense rate, this defines how often the ADC is getting > the temperature values. > - Set/Get of temperature charging and discharging voltages, this defines the > temperature ranges (as voltage) where the battery can be charged. > (setting these values is only possible with the device tree). > - Get of temperature voltage, this returns the voltage that is present on the NTC.
Why is the temperature not converted to °C allowing to use standard properties and being more user friendly?
> These custom properties have been added to /sys: > - voltage_low_alert_level1 (RW) > - voltage_low_alert_level2 (RW) > - ocv_curve (RO) > - temperature_sense_current (RW) > - temperature_sense_rate (RW) > - temperature_sense_voltage_now (RO) > - temperature_discharge_threshold_voltage_range (RO) > - temperature_charge_threshold_voltage_range (RO) > > These IRQs have been added: > - BATT_PLUGIN (generic, useful for udev) > - BATT_REMOVAL (generic, useful for udev) > - CHARG (generic, useful for udev) > - CHARG_DONE (generic, useful for udev) > - BATT_TEMP_HIGH (prints warning, axp stops charging/discharging) > - BATT_TEMP_LOW (prints warning, axp stops charging/discharging) > - LOW_PWR_LVL1 (prints warning) > - LOW_PWR_LVL2 (prints warning and initializes a system shutdown)
Battery temperature and low power events should also be reported through the HEALTH property if possible (i.e. if this information can be read from a register that keeps the state until the condition changes). In that case the IRQ should also trigger power_supply_changed().
> These properties have been added to be applied from the device tree: > - low-voltage-level1-microvolt > - low-voltage-level2-microvolt > - temperature-sense-current-microamp > - temperature-sense-rate-hertz > - temperature-discharge-range-microvolt > - temperature-charge-range-microvolt > - voltage-max-design-microvolt > - ocv-capacity-table-0 > > Signed-off-by: Thomas Marangoni <thomas.marangoni@mec.at> > --- > drivers/mfd/axp20x.c | 13 + > drivers/power/supply/axp20x_battery.c | 938 +++++++++++++++++++++++++- > 2 files changed, 945 insertions(+), 6 deletions(-)
DT ABI changes require updates to the devicetree binding files [0] & [1] with the DT maintainers being in Cc.
Custom sysfs properties need to be documented in the userspace ABI documentation in a new file [2], or become standard properties and be documented in [3].
[0] Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml [1] Documentation/devicetree/bindings/power/supply/battery.yaml [2] Documentation/ABI/testing/sysfs-class-power-axp20x-battery [3] Documentation/ABI/testing/sysfs-class-power
For now I will stop reviewing here ;)
Thanks,
-- Sebastian
> diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c > index 8161a5dc68e8..05dea452b513 100644 > --- a/drivers/mfd/axp20x.c > +++ b/drivers/mfd/axp20x.c > @@ -191,6 +191,17 @@ static const struct resource axp20x_usb_power_supply_resources[] = { > DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_VBUS_NOT_VALID, "VBUS_NOT_VALID"), > }; > > +static const struct resource axp20x_battery_power_supply_resources[] = { > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG, "CHARG"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG_DONE, "CHARG_DONE"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_TEMP_HIGH, "BATT_TEMP_HIGH"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_TEMP_LOW, "BATT_TEMP_LOW"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_LOW_PWR_LVL1, "LOW_PWR_LVL1"), > + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_LOW_PWR_LVL2, "LOW_PWR_LVL2"), > +}; > + > static const struct resource axp22x_usb_power_supply_resources[] = { > DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"), > DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"), > @@ -604,6 +615,8 @@ static const struct mfd_cell axp20x_cells[] = { > }, { > .name = "axp20x-battery-power-supply", > .of_compatible = "x-powers,axp209-battery-power-supply", > + .num_resources = ARRAY_SIZE(axp20x_battery_power_supply_resources), > + .resources = axp20x_battery_power_supply_resources, > }, { > .name = "axp20x-ac-power-supply", > .of_compatible = "x-powers,axp202-ac-power-supply", > diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c > index 18a9db0df4b1..5997c8192c73 100644 > --- a/drivers/power/supply/axp20x_battery.c > +++ b/drivers/power/supply/axp20x_battery.c > @@ -31,6 +31,7 @@ > #include <linux/iio/iio.h> > #include <linux/iio/consumer.h> > #include <linux/mfd/axp20x.h> > +#include <linux/reboot.h> > > #define AXP20X_PWR_STATUS_BAT_CHARGING BIT(2) > > @@ -56,6 +57,25 @@ > > #define AXP20X_V_OFF_MASK GENMASK(2, 0) > > +#define AXP20X_APS_WARN_MASK GENMASK(7, 0) > + > +#define AXP20X_TEMP_MASK GENMASK(7, 0) > + > +#define AXP20X_ADC_TS_RATE_MASK GENMASK(7, 6) > +#define AXP20X_ADC_TS_RATE_25Hz (0 << 6) > +#define AXP20X_ADC_TS_RATE_50Hz (1 << 6) > +#define AXP20X_ADC_TS_RATE_100Hz (2 << 6) > +#define AXP20X_ADC_TS_RATE_200Hz (3 << 6) > + > +#define AXP20X_ADC_TS_CURRENT_MASK GENMASK(5, 4) > +#define AXP20X_ADC_TS_CURRENT_20uA (0 << 4) > +#define AXP20X_ADC_TS_CURRENT_40uA (1 << 4) > +#define AXP20X_ADC_TS_CURRENT_60uA (2 << 4) > +#define AXP20X_ADC_TS_CURRENT_80uA (3 << 4) > + > + > +#define DRVNAME "axp20x-battery-power-supply" > + > struct axp20x_batt_ps; > > struct axp_data { > @@ -78,6 +98,79 @@ struct axp20x_batt_ps { > const struct axp_data *data; > }; > > +/* > + * OCV curve has fixed values and percentage can be adjusted, this array represents > + * the fixed values in uV > + */ > +const int axp20x_ocv_values_uV[AXP20X_OCV_MAX + 1] = { > + 3132800, > + 3273600, > + 3414400, > + 3555200, > + 3625600, > + 3660800, > + 3696000, > + 3731200, > + 3766400, > + 3801600, > + 3836800, > + 3872000, > + 3942400, > + 4012800, > + 4083200, > + 4153600, > +}; > + > +static irqreturn_t axp20x_battery_power_irq(int irq, void *devid) > +{ > + struct axp20x_batt_ps *axp20x_batt = devid; > + > + power_supply_changed(axp20x_batt->batt); > + > + return IRQ_HANDLED; > +} > + > +static irqreturn_t axp20x_battery_low_voltage_alert1_irq(int irq, void *devid) > +{ > + struct axp20x_batt_ps *axp20x_batt = devid; > + > + dev_warn(axp20x_batt->dev, "Battery voltage low!"); > + > + return IRQ_HANDLED; > +} > + > + > +static irqreturn_t axp20x_battery_low_voltage_alert2_irq(int irq, void *devid) > +{ > + struct axp20x_batt_ps *axp20x_batt = devid; > + > + dev_emerg(axp20x_batt->dev, "Battery voltage very low! Iniatializing shutdown."); > + > + orderly_poweroff(true); > + > + return IRQ_HANDLED; > +} > + > +static irqreturn_t axp20x_battery_temperature_low_irq(int irq, void *devid) > +{ > + struct axp20x_batt_ps *axp20x_batt = devid; > + > + dev_crit(axp20x_batt->dev, "Battery temperature to low!"); > + > + return IRQ_HANDLED; > +} > + > + > +static irqreturn_t axp20x_battery_temperature_high_irq(int irq, void *devid) > +{ > + struct axp20x_batt_ps *axp20x_batt = devid; > + > + dev_crit(axp20x_batt->dev, "Battery temperature to high!"); > + > + return IRQ_HANDLED; > +} > + > + > static int axp20x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, > int *val) > { > @@ -181,6 +274,361 @@ static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp, > return 0; > } > > +static int axp20x_battery_set_ocv_table(struct axp20x_batt_ps *axp_batt, > + struct power_supply_battery_ocv_table ocv_table[AXP20X_OCV_MAX+1], > + int ocv_table_size) > +{ > + int ret, i, error = 0; > + > + if (ocv_table_size != AXP20X_OCV_MAX+1) > + return 1; > + > + for (i = 0; i < ocv_table_size; i++) { > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_OCV(i), > + GENMASK(7, 0), ocv_table[i].capacity); > + > + if (ret) > + error = ret; > + } > + > + return error; > +} > + > +static int axp20x_battery_set_voltage_low_alert1(struct axp20x_batt_ps *axp_batt, > + int voltage_alert) > +{ > + int ret; > + /* converts the warning voltage level in uV to the neeeded reg value */ > + int val1 = (voltage_alert - 2867200) / (1400 * 4); > + > + if (val1 < 0 || val1 > AXP20X_APS_WARN_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_APS_WARN_L1, > + AXP20X_APS_WARN_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_voltage_low_alert1(struct axp20x_batt_ps *axp_batt, > + int *voltage_alert) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_APS_WARN_L1, ®); > + if (ret) > + return ret; > + > + /* converts the reg value to warning voltage level in uV */ > + *voltage_alert = 2867200 + (1400 * (reg & AXP20X_APS_WARN_MASK) * 4); > + > + return ret; > +} > + > +static int axp20x_battery_set_voltage_low_alert2(struct axp20x_batt_ps *axp_batt, > + int voltage_alert) > +{ > + int ret; > + > + /* converts the warning voltage level in uV to the neeeded reg value */ > + int val1 = (voltage_alert - 2867200) / (1400 * 4); > + > + if (val1 < 0 || val1 > AXP20X_APS_WARN_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_APS_WARN_L2, > + AXP20X_APS_WARN_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_voltage_low_alert2(struct axp20x_batt_ps *axp_batt, > + int *voltage_alert) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_APS_WARN_L2, ®); > + if (ret) > + return ret; > + > + /* converts the reg value to warning voltage level in uV */ > + *voltage_alert = 2867200 + (1400 * (reg & AXP20X_APS_WARN_MASK) * 4); > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_sense_current(struct axp20x_batt_ps *axp_batt, > + int sense_current) > +{ > + int ret; > + int reg = -1; > + > + switch (sense_current) { > + case 20: > + reg = AXP20X_ADC_TS_CURRENT_20uA; > + break; > + case 40: > + reg = AXP20X_ADC_TS_CURRENT_40uA; > + break; > + case 60: > + reg = AXP20X_ADC_TS_CURRENT_60uA; > + break; > + case 80: > + reg = AXP20X_ADC_TS_CURRENT_80uA; > + break; > + default: > + return -EINVAL; > + } > + > + if (reg < 0) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_ADC_RATE, > + AXP20X_ADC_TS_CURRENT_MASK, reg); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_sense_current(struct axp20x_batt_ps *axp_batt, > + int *sense_current) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_ADC_RATE, ®); > + if (ret) > + return ret; > + > + reg = reg & AXP20X_ADC_TS_CURRENT_MASK; > + > + switch (reg) { > + case AXP20X_ADC_TS_CURRENT_20uA: > + *sense_current = 20; > + break; > + case AXP20X_ADC_TS_CURRENT_40uA: > + *sense_current = 40; > + break; > + case AXP20X_ADC_TS_CURRENT_60uA: > + *sense_current = 60; > + break; > + case AXP20X_ADC_TS_CURRENT_80uA: > + *sense_current = 80; > + break; > + default: > + *sense_current = -1; > + return -EINVAL; > + } > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_sense_rate(struct axp20x_batt_ps *axp_batt, > + int sample_rate) > +{ > + int ret; > + int reg = -1; > + > + switch (sample_rate) { > + case 25: > + reg = AXP20X_ADC_TS_RATE_25Hz; > + break; > + case 50: > + reg = AXP20X_ADC_TS_RATE_50Hz; > + break; > + case 100: > + reg = AXP20X_ADC_TS_RATE_100Hz; > + break; > + case 200: > + reg = AXP20X_ADC_TS_RATE_200Hz; > + break; > + default: > + return -EINVAL; > + } > + > + if (reg < 0) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_ADC_RATE, > + AXP20X_ADC_TS_RATE_MASK, reg); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_sense_rate(struct axp20x_batt_ps *axp_batt, > + int *sample_rate) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_ADC_RATE, ®); > + if (ret) > + return ret; > + > + reg = reg & AXP20X_ADC_TS_RATE_MASK; > + > + switch (reg) { > + case AXP20X_ADC_TS_RATE_25Hz: > + *sample_rate = 25; > + break; > + case AXP20X_ADC_TS_RATE_50Hz: > + *sample_rate = 50; > + break; > + case AXP20X_ADC_TS_RATE_100Hz: > + *sample_rate = 100; > + break; > + case AXP20X_ADC_TS_RATE_200Hz: > + *sample_rate = 200; > + break; > + default: > + *sample_rate = -1; > + return -EINVAL; > + } > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_discharge_voltage_min(struct axp20x_batt_ps *axp_batt, > + int voltage) > +{ > + int ret; > + > + int val1 = voltage / (0x10 * 800); > + > + if (val1 < 0 || val1 > AXP20X_TEMP_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_LTF_DISCHRG, > + AXP20X_TEMP_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_discharge_voltage_min(struct axp20x_batt_ps *axp_batt, > + int *voltage) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_V_LTF_DISCHRG, ®); > + if (ret) > + return ret; > + > + *voltage = reg * 0x10 * 800; > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_discharge_voltage_max(struct axp20x_batt_ps *axp_batt, > + int voltage) > +{ > + int ret; > + > + int val1 = voltage / (0x10 * 800); > + > + if (val1 < 0 || val1 > AXP20X_TEMP_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_HTF_DISCHRG, > + AXP20X_TEMP_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_discharge_voltage_max(struct axp20x_batt_ps *axp_batt, > + int *voltage) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_V_HTF_DISCHRG, ®); > + if (ret) > + return ret; > + > + *voltage = reg * 0x10 * 800; > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_charge_voltage_min(struct axp20x_batt_ps *axp_batt, > + int voltage) > +{ > + int ret; > + > + int val1 = voltage / (0x10 * 800); > + > + if (val1 < 0 || val1 > AXP20X_TEMP_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_LTF_CHRG, > + AXP20X_TEMP_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_charge_voltage_min(struct axp20x_batt_ps *axp_batt, > + int *voltage) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_V_LTF_CHRG, ®); > + if (ret) > + return ret; > + > + *voltage = reg * 0x10 * 800; > + > + return ret; > +} > + > +static int axp20x_battery_set_temperature_charge_voltage_max(struct axp20x_batt_ps *axp_batt, > + int voltage) > +{ > + int ret; > + > + int val1 = voltage / (0x10 * 800); > + > + if (val1 < 0 || val1 > AXP20X_TEMP_MASK) > + return -EINVAL; > + > + ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_HTF_CHRG, > + AXP20X_TEMP_MASK, val1); > + > + return ret; > +} > + > +static int axp20x_battery_get_temperature_charge_voltage_max(struct axp20x_batt_ps *axp_batt, > + int *voltage) > +{ > + int reg, ret; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_V_HTF_CHRG, ®); > + if (ret) > + return ret; > + > + *voltage = reg * 0x10 * 800; > + > + return ret; > +} > + > +static int axp20x_battery_get_temp_sense_voltage_now(struct axp20x_batt_ps *axp_batt, > + int *voltage) > +{ > + int reg, ret, val1; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_L, ®); > + if (ret) > + return ret; > + > + val1 = reg; > + > + ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_H, ®); > + if (ret) > + return ret; > + > + /* merging high and low value */ > + val1 = (reg << 4) | val1; > + > + /* convert register value to real uV */ > + *voltage = val1 * 800; > + > + return ret; > +} > + > static int axp20x_battery_get_prop(struct power_supply *psy, > enum power_supply_property psp, > union power_supply_propval *val) > @@ -461,7 +909,8 @@ static int axp20x_battery_set_prop(struct power_supply *psy, > return axp20x_set_voltage_min_design(axp20x_batt, val->intval); > > case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: > - return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval); > + return axp20x_batt->data->set_max_voltage(axp20x_batt, > + val->intval); > > case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: > return axp20x_set_constant_charge_current(axp20x_batt, > @@ -472,13 +921,16 @@ static int axp20x_battery_set_prop(struct power_supply *psy, > case POWER_SUPPLY_PROP_STATUS: > switch (val->intval) { > case POWER_SUPPLY_STATUS_CHARGING: > - return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1, > - AXP20X_CHRG_CTRL1_ENABLE, AXP20X_CHRG_CTRL1_ENABLE); > + return regmap_update_bits(axp20x_batt->regmap, > + AXP20X_CHRG_CTRL1, > + AXP20X_CHRG_CTRL1_ENABLE, > + AXP20X_CHRG_CTRL1_ENABLE); > > case POWER_SUPPLY_STATUS_DISCHARGING: > case POWER_SUPPLY_STATUS_NOT_CHARGING: > - return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1, > - AXP20X_CHRG_CTRL1_ENABLE, 0); > + return regmap_update_bits(axp20x_batt->regmap, > + AXP20X_CHRG_CTRL1, > + AXP20X_CHRG_CTRL1_ENABLE, 0); > } > fallthrough; > default: > @@ -510,6 +962,275 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy, > psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; > } > > +/* -- Custom attributes ----------------------------------------------------- */ > + > +static ssize_t voltage_low_alert_level1_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int voltage_alert; > + > + axp20x_battery_get_voltage_low_alert1(axp20x_batt, &voltage_alert); > + status = sprintf(buf, "%d\n", voltage_alert); > + > + return status; > +} > + > +static ssize_t voltage_low_alert_level1_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + unsigned long value; > + int status; > + > + status = kstrtoul(buf, 0, &value); > + if (status) > + return status; > + > + status = axp20x_battery_set_voltage_low_alert1(axp20x_batt, value); > + if (status) > + return status; > + > + return count; > +} > + > +DEVICE_ATTR_RW(voltage_low_alert_level1); > + > +static ssize_t voltage_low_alert_level2_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int voltage_alert; > + > + axp20x_battery_get_voltage_low_alert2(axp20x_batt, &voltage_alert); > + status = sprintf(buf, "%d\n", voltage_alert); > + > + return status; > +} > + > +static ssize_t voltage_low_alert_level2_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + unsigned long value; > + int status; > + > + status = kstrtoul(buf, 0, &value); > + if (status) > + return status; > + > + status = axp20x_battery_set_voltage_low_alert2(axp20x_batt, value); > + if (status) > + return status; > + > + return count; > +} > + > +static DEVICE_ATTR_RW(voltage_low_alert_level2); > + > +static ssize_t ocv_curve_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status, ret, reg, i; > + > + int ocv_curve_size = AXP20X_OCV_MAX+1; > + struct power_supply_battery_ocv_table ocv_curve[AXP20X_OCV_MAX+1]; > + > + > + status = 0; > + for (i = 0; i < ocv_curve_size; i++) { > + ret = regmap_read(axp20x_batt->regmap, AXP20X_OCV(i), ®); > + if (ret) > + status = ret; > + ocv_curve[i].capacity = reg; > + ocv_curve[i].ocv = axp20x_ocv_values_uV[i]; > + } > + > + if (status) > + return status; > + > + status = 0; > + for (i = 0; i < ocv_curve_size; i++) { > + ret = sprintf(buf, "%sOCV_%d=%d\nCAP_%d=%d\n", buf, i, > + ocv_curve[i].ocv, i, ocv_curve[i].capacity); > + if (ret) > + status = ret; > + } > + > + return status; > +} > + > +static DEVICE_ATTR_RO(ocv_curve); > + > +static ssize_t temperature_sense_current_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int sense_current; > + > + axp20x_battery_get_temperature_sense_current(axp20x_batt, &sense_current); > + status = sprintf(buf, "%d\n", sense_current); > + > + return status; > +} > + > +static ssize_t temperature_sense_current_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + unsigned long value; > + int status; > + > + status = kstrtoul(buf, 0, &value); > + if (status) > + return status; > + > + status = axp20x_battery_set_temperature_sense_current(axp20x_batt, value); > + if (status) > + return status; > + > + return count; > +} > + > +static DEVICE_ATTR_RW(temperature_sense_current); > + > +static ssize_t temperature_sense_rate_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int sense_rate; > + > + axp20x_battery_get_temperature_sense_rate(axp20x_batt, &sense_rate); > + status = sprintf(buf, "%d\n", sense_rate); > + > + return status; > +} > + > +static ssize_t temperature_sense_rate_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + unsigned long value; > + int status; > + > + status = kstrtoul(buf, 0, &value); > + if (status) > + return status; > + > + status = axp20x_battery_set_temperature_sense_rate(axp20x_batt, value); > + if (status) > + return status; > + > + return count; > +} > + > +static DEVICE_ATTR_RW(temperature_sense_rate); > + > +static ssize_t temperature_sense_voltage_now_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int voltage; > + > + axp20x_battery_get_temp_sense_voltage_now(axp20x_batt, &voltage); > + status = sprintf(buf, "%d\n", voltage); > + > + return status; > +} > + > +static DEVICE_ATTR_RO(temperature_sense_voltage_now); > + > +static ssize_t temperature_discharge_threshold_voltage_range_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int min_voltage, max_voltage; > + > + axp20x_battery_get_temperature_discharge_voltage_min(axp20x_batt, > + &min_voltage); > + axp20x_battery_get_temperature_discharge_voltage_max(axp20x_batt, > + &max_voltage); > + > + status = sprintf(buf, "MIN=%d\nMAX=%d\n", min_voltage, max_voltage); > + > + return status; > +} > + > +static DEVICE_ATTR_RO(temperature_discharge_threshold_voltage_range); > + > +static ssize_t temperature_charge_threshold_voltage_range_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct power_supply *psy = dev_get_drvdata(dev); > + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); > + int status; > + > + int min_voltage, max_voltage; > + > + axp20x_battery_get_temperature_charge_voltage_min(axp20x_batt, > + &min_voltage); > + axp20x_battery_get_temperature_charge_voltage_max(axp20x_batt, > + &max_voltage); > + > + status = sprintf(buf, "MIN=%d\nMAX=%d\n", min_voltage, max_voltage); > + > + return status; > +} > + > +static DEVICE_ATTR_RO(temperature_charge_threshold_voltage_range); > + > +static struct attribute *axp20x_batt_attrs[] = { > + &dev_attr_voltage_low_alert_level1.attr, > + &dev_attr_voltage_low_alert_level2.attr, > + &dev_attr_ocv_curve.attr, > + &dev_attr_temperature_sense_current.attr, > + &dev_attr_temperature_sense_rate.attr, > + &dev_attr_temperature_sense_voltage_now.attr, > + &dev_attr_temperature_discharge_threshold_voltage_range.attr, > + &dev_attr_temperature_charge_threshold_voltage_range.attr, > + NULL, > +}; > + > +ATTRIBUTE_GROUPS(axp20x_batt); > + > +/* -- Custom attributes END ------------------------------------------------- */ > + > static const struct power_supply_desc axp20x_batt_ps_desc = { > .name = "axp20x-battery", > .type = POWER_SUPPLY_TYPE_BATTERY, > @@ -520,6 +1241,9 @@ static const struct power_supply_desc axp20x_batt_ps_desc = { > .set_property = axp20x_battery_set_prop, > }; > > +static const char * const irq_names[] = { "BATT_PLUGIN", "BATT_REMOVAL", "CHARG", > + "CHARG_DONE", NULL }; > + > static const struct axp_data axp209_data = { > .ccc_scale = 100000, > .ccc_offset = 300000, > @@ -559,10 +1283,12 @@ MODULE_DEVICE_TABLE(of, axp20x_battery_ps_id); > > static int axp20x_power_probe(struct platform_device *pdev) > { > + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); > struct axp20x_batt_ps *axp20x_batt; > struct power_supply_config psy_cfg = {}; > struct power_supply_battery_info info; > struct device *dev = &pdev->dev; > + int i, irq, ret = 0; > > if (!of_device_is_available(pdev->dev.of_node)) > return -ENODEV; > @@ -602,6 +1328,7 @@ static int axp20x_power_probe(struct platform_device *pdev) > > psy_cfg.drv_data = axp20x_batt; > psy_cfg.of_node = pdev->dev.of_node; > + psy_cfg.attr_grp = axp20x_batt_groups; > > axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev); > > @@ -615,14 +1342,105 @@ static int axp20x_power_probe(struct platform_device *pdev) > } > > if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) { > + struct device_node *battery_np; > + > int vmin = info.voltage_min_design_uv; > + int vmax = info.voltage_max_design_uv; > int ccc = info.constant_charge_current_max_ua; > + struct power_supply_battery_ocv_table ocv_table[AXP20X_OCV_MAX+1]; > + int ocv_table_size = 0; > + int lvl1 = 0; > + int lvl2 = 0; > + int temp_sense_current = 0; > + int temp_sense_rate = 0; > + int temp_discharge_min = -1; > + int temp_discharge_max = -1; > + int temp_charge_min = -1; > + int temp_charge_max = -1; > + > + int i = 0, j = 0; > + bool too_many_ocv_tables = false; > + bool too_many_ocv_values = false; > + bool ocv_values_mismatch = false; > + > + battery_np = of_parse_phandle(axp20x_batt->batt->of_node, > + "monitored-battery", 0); > + > + of_property_read_u32(battery_np, "low-voltage-level1-microvolt", > + &lvl1); > + of_property_read_u32(battery_np, "low-voltage-level2-microvolt", > + &lvl2); > + of_property_read_u32(battery_np, "temperature-sense-current-microamp", > + &temp_sense_current); > + of_property_read_u32(battery_np, "temperature-sense-rate-hertz", > + &temp_sense_rate); > + > + of_property_read_u32_index(battery_np, "temperature-discharge-range-microvolt", > + 0, &temp_discharge_min); > + of_property_read_u32_index(battery_np, "temperature-discharge-range-microvolt", > + 1, &temp_discharge_max); > + > + of_property_read_u32_index(battery_np, "temperature-charge-range-microvolt", > + 0, &temp_charge_min); > + of_property_read_u32_index(battery_np, "temperature-charge-range-microvolt", > + 1, &temp_charge_max); > > if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt, > vmin)) > dev_err(&pdev->dev, > "couldn't set voltage_min_design\n"); > > + if (vmax > 0 && axp20x_battery_set_max_voltage(axp20x_batt, > + vmax)) > + dev_err(&pdev->dev, > + "couldn't set voltage_max_design\n"); > + > + if (lvl1 > 0 && axp20x_battery_set_voltage_low_alert1(axp20x_batt, > + lvl1)) > + dev_err(&pdev->dev, > + "couldn't set voltage_low_alert_level1\n"); > + > + if (lvl2 > 0 && axp20x_battery_set_voltage_low_alert2(axp20x_batt, > + lvl2)) > + dev_err(&pdev->dev, > + "couldn't set voltage_low_alert_level2\n"); > + > + if (temp_sense_current > 0 && > + axp20x_battery_set_temperature_sense_current(axp20x_batt, > + temp_sense_current)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_current\n"); > + > + if (temp_sense_rate > 0 && > + axp20x_battery_set_temperature_sense_rate(axp20x_batt, > + temp_sense_rate)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_rate\n"); > + > + if (temp_discharge_min >= 0 && > + axp20x_battery_set_temperature_discharge_voltage_min(axp20x_batt, > + temp_discharge_min)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_rate\n"); > + > + if (temp_discharge_max >= 0 && > + axp20x_battery_set_temperature_discharge_voltage_max(axp20x_batt, > + temp_discharge_max)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_rate\n"); > + > + if (temp_charge_min >= 0 && > + axp20x_battery_set_temperature_charge_voltage_min(axp20x_batt, > + temp_charge_min)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_rate\n"); > + > + if (temp_charge_max >= 0 && > + axp20x_battery_set_temperature_charge_voltage_max(axp20x_batt, > + temp_charge_max)) > + dev_err(&pdev->dev, > + "couldn't set temperature_sense_rate\n"); > + > /* Set max to unverified value to be able to set CCC */ > axp20x_batt->max_ccc = ccc; > > @@ -634,6 +1452,57 @@ static int axp20x_power_probe(struct platform_device *pdev) > axp20x_batt->max_ccc = ccc; > axp20x_set_constant_charge_current(axp20x_batt, ccc); > } > + > + too_many_ocv_tables = false; > + too_many_ocv_values = false; > + ocv_values_mismatch = false; > + for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) { > + if (info.ocv_table_size[i] == -EINVAL || > + info.ocv_temp[i] == -EINVAL || > + info.ocv_table[i] == NULL) > + continue; > + > + if (info.ocv_table_size[i] > (AXP20X_OCV_MAX+1)) { > + too_many_ocv_values = true; > + dev_err(&pdev->dev, "Too many values in ocv table, only %d values are supported", > + AXP20X_OCV_MAX + 1); > + break; > + } > + > + if (i > 0) { > + too_many_ocv_tables = true; > + dev_err(&pdev->dev, "Only one ocv table is supported"); > + break; > + } > + > + for (j = 0; j < info.ocv_table_size[i]; j++) { > + if (info.ocv_table[i][j].ocv != axp20x_ocv_values_uV[j]) { > + ocv_values_mismatch = true; > + break; > + } > + } > + > + if (ocv_values_mismatch) { > + dev_err(&pdev->dev, "ocv tables missmatches requirements"); > + dev_info(&pdev->dev, "ocv table requires following ocv values in that order:"); > + for (j = 0; j < AXP20X_OCV_MAX+1; j++) { > + dev_info(&pdev->dev, "%d uV", > + axp20x_ocv_values_uV[j]); > + } > + break; > + } > + > + ocv_table_size = info.ocv_table_size[i]; > + for (j = 0; j < info.ocv_table_size[i]; j++) > + ocv_table[j] = info.ocv_table[i][j]; > + > + } > + > + if (!too_many_ocv_tables && !too_many_ocv_values && > + !ocv_values_mismatch) > + axp20x_battery_set_ocv_table(axp20x_batt, ocv_table, > + ocv_table_size); > + > } > > /* > @@ -643,13 +1512,70 @@ static int axp20x_power_probe(struct platform_device *pdev) > axp20x_get_constant_charge_current(axp20x_batt, > &axp20x_batt->max_ccc); > > + /* Request irqs after registering, as irqs may trigger immediately */ > + for (i = 0; irq_names[i]; i++) { > + irq = platform_get_irq_byname(pdev, irq_names[i]); > + if (irq < 0) { > + dev_warn(&pdev->dev, "No IRQ for %s: %d\n", > + irq_names[i], irq); > + continue; > + } > + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); > + ret = devm_request_any_context_irq(&pdev->dev, irq, > + axp20x_battery_power_irq, 0, > + DRVNAME, axp20x_batt); > + if (ret < 0) > + dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", > + irq_names[i], ret); > + } > + > + irq = platform_get_irq_byname(pdev, "LOW_PWR_LVL1"); > + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); > + ret = devm_request_any_context_irq(&pdev->dev, irq, > + axp20x_battery_low_voltage_alert1_irq, > + 0, DRVNAME, axp20x_batt); > + > + if (ret < 0) > + dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_LOW_PWR_LVL1 IRQ: %d\n", > + ret); > + > + irq = platform_get_irq_byname(pdev, "LOW_PWR_LVL2"); > + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); > + ret = devm_request_any_context_irq(&pdev->dev, irq, > + axp20x_battery_low_voltage_alert2_irq, > + 0, DRVNAME, axp20x_batt); > + > + if (ret < 0) > + dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_LOW_PWR_LVL2 IRQ: %d\n", > + ret); > + > + irq = platform_get_irq_byname(pdev, "BATT_TEMP_LOW"); > + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); > + ret = devm_request_any_context_irq(&pdev->dev, irq, > + axp20x_battery_temperature_low_irq, > + 0, DRVNAME, axp20x_batt); > + > + if (ret < 0) > + dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_BATT_TEMP_LOW IRQ: %d\n", > + ret); > + > + irq = platform_get_irq_byname(pdev, "BATT_TEMP_HIGH"); > + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); > + ret = devm_request_any_context_irq(&pdev->dev, irq, > + axp20x_battery_temperature_high_irq, > + 0, DRVNAME, axp20x_batt); > + > + if (ret < 0) > + dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_BATT_TEMP_HIGH IRQ: %d\n", > + ret); > + > return 0; > } > > static struct platform_driver axp20x_batt_driver = { > .probe = axp20x_power_probe, > .driver = { > - .name = "axp20x-battery-power-supply", > + .name = DRVNAME, > .of_match_table = axp20x_battery_ps_id, > }, > }; > -- > 2.25.1 > [unhandled content-type:application/pgp-signature] | |