From: Thomas Marangoni <thomas.marangoni@mec.at>
To: sre@kernel.org, wens@csie.org, linux-pm@vger.kernel.org,
linux-kernel@vger.kernel.org
Cc: Thomas Marangoni <thomas.marangoni@mec.at>
Subject: [PATCH] PM: Added functionality to the axp20x_battery driver
Date: Mon, 25 Oct 2021 16:44:55 +0200 [thread overview]
Message-ID: <20211025144455.16665-1-thomas.marangoni@mec.at> (raw)
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
next reply other threads:[~2021-10-25 14:49 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-10-25 14:44 Thomas Marangoni [this message]
2021-10-25 22:40 ` [PATCH] PM: Added functionality to the axp20x_battery driver Sebastian Reichel
2021-10-27 7:40 ` Thomas Marangoni
2021-11-08 5:21 ` kernel test robot
2021-11-08 5:21 ` [RFC PATCH] PM: axp20x_ocv_values_uV[] can be static kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211025144455.16665-1-thomas.marangoni@mec.at \
--to=thomas.marangoni@mec.at \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=sre@kernel.org \
--cc=wens@csie.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox