Messages in this thread Patch in this message | | | From | Thomas Marangoni <> | Subject | [PATCH] PM: Added functionality to the axp20x_battery driver | Date | Mon, 25 Oct 2021 16:44:55 +0200 |
| |
This patch adds missing features of the axp209 battery functionality to the driver. 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.
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)
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(-)
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
| |