linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] PM: Added functionality to the axp20x_battery driver
@ 2021-10-25 14:44 Thomas Marangoni
  2021-10-25 22:40 ` Sebastian Reichel
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Thomas Marangoni @ 2021-10-25 14:44 UTC (permalink / raw)
  To: sre, wens, linux-pm, linux-kernel; +Cc: Thomas Marangoni

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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	if (ret)
+		return ret;
+
+	val1 = reg;
+
+	ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_H, &reg);
+	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), &reg);
+		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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] PM: Added functionality to the axp20x_battery driver
  2021-10-25 14:44 [PATCH] PM: Added functionality to the axp20x_battery driver Thomas Marangoni
@ 2021-10-25 22:40 ` 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
  2 siblings, 1 reply; 5+ messages in thread
From: Sebastian Reichel @ 2021-10-25 22:40 UTC (permalink / raw)
  To: Thomas Marangoni; +Cc: wens, linux-pm, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 36872 bytes --]

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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	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, &reg);
> +	if (ret)
> +		return ret;
> +
> +	val1 = reg;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_H, &reg);
> +	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), &reg);
> +		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
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PM: Added functionality to the axp20x_battery driver
  2021-10-25 22:40 ` Sebastian Reichel
@ 2021-10-27  7:40   ` Thomas Marangoni
  0 siblings, 0 replies; 5+ messages in thread
From: Thomas Marangoni @ 2021-10-27  7:40 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: wens, linux-pm, linux-kernel

Hello,
> 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

Thank you for the feedback, I will try to split it correctly.

>> 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?
The problem is, that the NTC that measures the temperature isn't built in. Everyone that is using the axp209 can solder an NTC (10k ohm, 1%) of their choice. The K factor, which is needed to calculate the temperature in °C, can differ with the model used.
>> 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().
This will be added into the next patch.
>
>> 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 ;)

Should the devicetree binding file changes bundled into one patch or submitted with the patch adding this feature? Should I  do the same for the ABI documentation?

> Thanks,
>
> -- Sebastian

Thanks

-- Thomas

>> 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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	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, &reg);
>> +	if (ret)
>> +		return ret;
>> +
>> +	val1 = reg;
>> +
>> +	ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_H, &reg);
>> +	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), &reg);
>> +		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
>>

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] PM: Added functionality to the axp20x_battery driver
  2021-10-25 14:44 [PATCH] PM: Added functionality to the axp20x_battery driver Thomas Marangoni
  2021-10-25 22:40 ` Sebastian Reichel
@ 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
  2 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2021-11-08  5:21 UTC (permalink / raw)
  To: Thomas Marangoni, sre, wens, linux-pm, linux-kernel
  Cc: kbuild-all, Thomas Marangoni

[-- Attachment #1: Type: text/plain, Size: 1940 bytes --]

Hi Thomas,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on lee-mfd/for-mfd-next]
[also build test WARNING on sre-power-supply/for-next linux/master linus/master v5.15 next-20211108]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Thomas-Marangoni/PM-Added-functionality-to-the-axp20x_battery-driver/20211025-225315
base:   https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
config: x86_64-randconfig-s021-20211027 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.4-dirty
        # https://github.com/0day-ci/linux/commit/246898bd0bf3718e9ef41526e6d2aa6a3fbb34f7
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Thomas-Marangoni/PM-Added-functionality-to-the-axp20x_battery-driver/20211025-225315
        git checkout 246898bd0bf3718e9ef41526e6d2aa6a3fbb34f7
        # save the attached .config to linux build tree
        make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=x86_64 SHELL=/bin/bash drivers/power/supply/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)
>> drivers/power/supply/axp20x_battery.c:105:11: sparse: sparse: symbol 'axp20x_ocv_values_uV' was not declared. Should it be static?
>> drivers/power/supply/axp20x_battery.c:1004:1: sparse: sparse: symbol 'dev_attr_voltage_low_alert_level1' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 28012 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH] PM: axp20x_ocv_values_uV[] can be static
  2021-10-25 14:44 [PATCH] PM: Added functionality to the axp20x_battery driver Thomas Marangoni
  2021-10-25 22:40 ` Sebastian Reichel
  2021-11-08  5:21 ` kernel test robot
@ 2021-11-08  5:21 ` kernel test robot
  2 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2021-11-08  5:21 UTC (permalink / raw)
  To: Thomas Marangoni, sre, wens, linux-pm, linux-kernel
  Cc: kbuild-all, Thomas Marangoni

drivers/power/supply/axp20x_battery.c:105:11: warning: symbol 'axp20x_ocv_values_uV' was not declared. Should it be static?
drivers/power/supply/axp20x_battery.c:1004:1: warning: symbol 'dev_attr_voltage_low_alert_level1' was not declared. Should it be static?

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: kernel test robot <lkp@intel.com>
---
 axp20x_battery.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
index 5997c8192c73e..ec17841d51733 100644
--- a/drivers/power/supply/axp20x_battery.c
+++ b/drivers/power/supply/axp20x_battery.c
@@ -102,7 +102,7 @@ struct axp20x_batt_ps {
  * 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] = {
+static const int axp20x_ocv_values_uV[AXP20X_OCV_MAX + 1] = {
 	3132800,
 	3273600,
 	3414400,
@@ -1001,7 +1001,7 @@ static ssize_t voltage_low_alert_level1_store(struct device *dev,
 	return count;
 }
 
-DEVICE_ATTR_RW(voltage_low_alert_level1);
+static DEVICE_ATTR_RW(voltage_low_alert_level1);
 
 static ssize_t voltage_low_alert_level2_show(struct device *dev,
 					     struct device_attribute *attr,

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-11-08  5:22 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-25 14:44 [PATCH] PM: Added functionality to the axp20x_battery driver Thomas Marangoni
2021-10-25 22:40 ` 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).