lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20211025144455.16665-1-thomas.marangoni@mec.at>
Date:   Mon, 25 Oct 2021 16:44:55 +0200
From:   Thomas Marangoni <thomas.marangoni@....at>
To:     sre@...nel.org, wens@...e.org, linux-pm@...r.kernel.org,
        linux-kernel@...r.kernel.org
Cc:     Thomas Marangoni <thomas.marangoni@....at>
Subject: [PATCH] PM: Added functionality to the axp20x_battery driver

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@....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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ