All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] AB8500 charging, the final steps
@ 2022-02-03 17:16 Linus Walleij
  2022-02-03 17:16 ` [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging Linus Walleij
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

I am sending this series now so that we get some review despite
the AB8500 fixes not being applied yet. This series goes on top
of this:
https://lore.kernel.org/linux-pm/20220129004925.639684-1-linus.walleij@linaro.org/

This makes AB8500 charge the Samsung batteries on the following
mobile phones:

Samsung Galaxy Ace 2
Samsung Galaxy S Advance
Samsung Galaxy S III mini
Samsung Galaxy Xcover 2
Samsung Galaxy Beam
Samsung Galaxy Exhibit
Samsung Galaxy Amp

The steps are as follows:

- Add charging setting for maintenance charging in two steps.
  After this we fall back to discharging to the restart threshold.

- Add optional alert mode charging when the battery is alert warm
  or cold, so as to mitigate the condition. I suspect mostly the
  battery gets alert warm and then this will pull down the charging
  current a bit.

- Add BTI (Battery Type Indicator) resistance fields.

- Add VBAT-to-Ri internal resistance look-up and interpolation.
  Samsung seem to use this on all the Samsung SDI battery
  charging code I have seen.

- Add the static data for all the Samsung batteries and detect
  them from compatible property.


All of this is enabled in the AB8500 simultaneously so for each
new feature a user is introduced.

Linus Walleij (6):
  power: supply: ab8500: Standardize maintenance charging
  power: supply: ab8500: Standardize alert mode charging
  power: supply: ab8500: Standardize BTI resistance
  power: supply: Support VBAT-to-Ri lookup tables
  power: supply: ab8500_fg: Use VBAT-to-Ri if possible
  power: supply: Static data for Samsung batteries

 drivers/power/supply/Kconfig               |   6 +
 drivers/power/supply/Makefile              |   1 +
 drivers/power/supply/ab8500-bm.h           |  30 -
 drivers/power/supply/ab8500_bmdata.c       |  46 +-
 drivers/power/supply/ab8500_btemp.c        |  14 +-
 drivers/power/supply/ab8500_chargalg.c     |  45 +-
 drivers/power/supply/ab8500_fg.c           |  35 +-
 drivers/power/supply/power_supply_core.c   | 134 +++-
 drivers/power/supply/samsung-sdi-battery.c | 674 +++++++++++++++++++++
 drivers/power/supply/samsung-sdi-battery.h |  13 +
 include/linux/power_supply.h               | 200 +++++-
 11 files changed, 1106 insertions(+), 92 deletions(-)
 create mode 100644 drivers/power/supply/samsung-sdi-battery.c
 create mode 100644 drivers/power/supply/samsung-sdi-battery.h

-- 
2.34.1


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

* [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  2022-02-07 11:15   ` Vaittinen, Matti
  2022-02-03 17:16 ` [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging Linus Walleij
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

Maintenance charging is the phase of keeping up the charge
after the battery has charged fully using CC/CV charging.

This can be done in many successive phases and is usually
done with a slightly lower constant voltage than CV, and
a slightly lower allowed current.

Add an array of maintenance charging points each with a
current, voltage and safety timer, and add helper functions
to use these. Migrate the AB8500 code over.

This is used in several Samsung products using the AB8500
and these batteries and their complete parameters will
be added later as full examples, but the default battery
in the AB8500 code serves as a reasonable example so far.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/ab8500-bm.h         | 14 ------
 drivers/power/supply/ab8500_bmdata.c     | 27 +++++++---
 drivers/power/supply/ab8500_chargalg.c   | 41 +++++++++++----
 drivers/power/supply/power_supply_core.c | 11 ++++
 include/linux/power_supply.h             | 64 ++++++++++++++++++++++++
 5 files changed, 126 insertions(+), 31 deletions(-)

diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
index 6efd5174dbce..4d74d21cf1eb 100644
--- a/drivers/power/supply/ab8500-bm.h
+++ b/drivers/power/supply/ab8500-bm.h
@@ -331,24 +331,12 @@ struct ab8500_maxim_parameters {
  * struct ab8500_battery_type - different batteries supported
  * @resis_high:			battery upper resistance limit
  * @resis_low:			battery lower resistance limit
- * @maint_a_cur_lvl:		charger current in maintenance A state in mA
- * @maint_a_vol_lvl:		charger voltage in maintenance A state in mV
- * @maint_a_chg_timer_h:	charge time in maintenance A state
- * @maint_b_cur_lvl:		charger current in maintenance B state in mA
- * @maint_b_vol_lvl:		charger voltage in maintenance B state in mV
- * @maint_b_chg_timer_h:	charge time in maintenance B state
  * @low_high_cur_lvl:		charger current in temp low/high state in mA
  * @low_high_vol_lvl:		charger voltage in temp low/high state in mV'
  */
 struct ab8500_battery_type {
 	int resis_high;
 	int resis_low;
-	int maint_a_cur_lvl;
-	int maint_a_vol_lvl;
-	int maint_a_chg_timer_h;
-	int maint_b_cur_lvl;
-	int maint_b_vol_lvl;
-	int maint_b_chg_timer_h;
 	int low_high_cur_lvl;
 	int low_high_vol_lvl;
 };
@@ -393,7 +381,6 @@ struct ab8500_bm_charger_parameters {
  * @usb_safety_tmr_h	safety timer for usb charger
  * @bkup_bat_v		voltage which we charge the backup battery with
  * @bkup_bat_i		current which we charge the backup battery with
- * @no_maintenance	indicates that maintenance charging is disabled
  * @capacity_scaling    indicates whether capacity scaling is to be used
  * @chg_unknown_bat	flag to enable charging of unknown batteries
  * @enable_overshoot	flag to enable VBAT overshoot control
@@ -417,7 +404,6 @@ struct ab8500_bm_data {
 	int usb_safety_tmr_h;
 	int bkup_bat_v;
 	int bkup_bat_i;
-	bool no_maintenance;
 	bool capacity_scaling;
 	bool chg_unknown_bat;
 	bool enable_overshoot;
diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
index d8fc72be0f0e..c104afe83b4b 100644
--- a/drivers/power/supply/ab8500_bmdata.c
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -58,16 +58,25 @@ static struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[]
 	{ .temp = -20, .resistance = 198 /* 595 mOhm */ },
 };
 
+struct power_supply_maintenance_charge_table maint_charge_table[] = {
+	{
+		/* Maintenance charging phase A, 60 hours */
+		.charge_current_max_ua = 400000,
+		.charge_voltage_max_uv = 4050000,
+		.charge_safety_timer_minutes = 60*60,
+	},
+	{
+		/* Maintenance charging phase B, 200 hours */
+		.charge_current_max_ua = 400000,
+		.charge_voltage_max_uv = 4000000,
+		.charge_safety_timer_minutes = 200*60,
+	}
+};
+
 /* Default battery type for reference designs is the unknown type */
 static struct ab8500_battery_type bat_type_thermistor_unknown = {
 	.resis_high = 0,
 	.resis_low = 0,
-	.maint_a_cur_lvl = 400,
-	.maint_a_vol_lvl = 4050,
-	.maint_a_chg_timer_h = 60,
-	.maint_b_cur_lvl = 400,
-	.maint_b_vol_lvl = 4000,
-	.maint_b_chg_timer_h = 200,
 	.low_high_cur_lvl = 300,
 	.low_high_vol_lvl = 4000,
 };
@@ -124,7 +133,6 @@ struct ab8500_bm_data ab8500_bm_data = {
 	.usb_safety_tmr_h       = 4,
 	.bkup_bat_v             = BUP_VCH_SEL_2P6V,
 	.bkup_bat_i             = BUP_ICH_SEL_150UA,
-	.no_maintenance         = false,
 	.capacity_scaling       = false,
 	.chg_unknown_bat        = false,
 	.enable_overshoot       = false,
@@ -179,6 +187,11 @@ int ab8500_bm_of_probe(struct power_supply *psy,
 		/* Charging stops when we drop below this current */
 		bi->charge_term_current_ua = 200000;
 
+	if (!bi->maintenance_charge || !bi->maintenance_charge_size) {
+		bi->maintenance_charge = maint_charge_table;
+		bi->maintenance_charge_size = ARRAY_SIZE(maint_charge_table);
+	}
+
 	/*
 	 * Internal resistance and factory resistance are tightly coupled
 	 * so both MUST be defined or we fall back to defaults.
diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c
index b5a3096e78a1..6054996b6260 100644
--- a/drivers/power/supply/ab8500_chargalg.c
+++ b/drivers/power/supply/ab8500_chargalg.c
@@ -430,7 +430,7 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
 /**
  * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer
  * @di:		pointer to the ab8500_chargalg structure
- * @duration:	duration of ther maintenance timer in hours
+ * @duration:	duration of ther maintenance timer in minutes
  *
  * The maintenance timer is used to maintain the charge in the battery once
  * the battery is considered full. These timers are chosen to match the
@@ -439,9 +439,10 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
 static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di,
 	int duration)
 {
+	/* Set a timer in minutes with a 30 second range */
 	hrtimer_set_expires_range(&di->maintenance_timer,
-		ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
-		ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
+		ktime_set(duration * 60, 0),
+		ktime_set(30, 0));
 	di->events.maintenance_timer_expired = false;
 	hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
 }
@@ -1223,6 +1224,7 @@ static void ab8500_chargalg_external_power_changed(struct power_supply *psy)
 static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 {
 	struct power_supply_battery_info *bi = di->bm->bi;
+	struct power_supply_maintenance_charge_table *mt;
 	int charger_status;
 	int ret;
 
@@ -1433,7 +1435,12 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 		handle_maxim_chg_curr(di);
 		if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
 			di->maintenance_chg) {
-			if (di->bm->no_maintenance)
+			/*
+			 * The battery is fully charged, check if we support
+			 * maintenance charging else go back to waiting for
+			 * the recharge voltage limit.
+			 */
+			if (!power_supply_supports_maintenance_charging(bi))
 				ab8500_chargalg_state_to(di,
 					STATE_WAIT_FOR_RECHARGE_INIT);
 			else
@@ -1454,12 +1461,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 		break;
 
 	case STATE_MAINTENANCE_A_INIT:
+		mt = power_supply_get_maintenance_charging_setting(bi, 0);
+		if (!mt) {
+			/* No maintenance A state, go back to normal */
+			ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
+			power_supply_changed(di->chargalg_psy);
+			break;
+		}
 		ab8500_chargalg_stop_safety_timer(di);
 		ab8500_chargalg_start_maintenance_timer(di,
-			di->bm->bat_type->maint_a_chg_timer_h);
+			mt->charge_safety_timer_minutes);
 		ab8500_chargalg_start_charging(di,
-			di->bm->bat_type->maint_a_vol_lvl,
-			di->bm->bat_type->maint_a_cur_lvl);
+			mt->charge_voltage_max_uv,
+			mt->charge_current_max_ua);
 		ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A);
 		power_supply_changed(di->chargalg_psy);
 		fallthrough;
@@ -1472,11 +1486,18 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 		break;
 
 	case STATE_MAINTENANCE_B_INIT:
+		mt = power_supply_get_maintenance_charging_setting(bi, 1);
+		if (!mt) {
+			/* No maintenance B state, go back to normal */
+			ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
+			power_supply_changed(di->chargalg_psy);
+			break;
+		}
 		ab8500_chargalg_start_maintenance_timer(di,
-			di->bm->bat_type->maint_b_chg_timer_h);
+			mt->charge_safety_timer_minutes);
 		ab8500_chargalg_start_charging(di,
-			di->bm->bat_type->maint_b_vol_lvl,
-			di->bm->bat_type->maint_b_cur_lvl);
+			mt->charge_voltage_max_uv,
+			mt->charge_current_max_ua);
 		ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B);
 		power_supply_changed(di->chargalg_psy);
 		fallthrough;
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index ec838c9bcc0a..6568939e4518 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -590,6 +590,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	info->precharge_voltage_max_uv       = -EINVAL;
 	info->charge_restart_voltage_uv      = -EINVAL;
 	info->overvoltage_limit_uv           = -EINVAL;
+	info->maintenance_charge             = NULL;
 	info->temp_ambient_alert_min         = INT_MIN;
 	info->temp_ambient_alert_max         = INT_MAX;
 	info->temp_alert_min                 = INT_MIN;
@@ -821,6 +822,16 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t
 }
 EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
 
+struct power_supply_maintenance_charge_table *
+power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info,
+					      int index)
+{
+	if (index >= info->maintenance_charge_size)
+		return NULL;
+	return &info->maintenance_charge[index];
+}
+EXPORT_SYMBOL_GPL(power_supply_get_maintenance_charging_setting);
+
 /**
  * power_supply_ocv2cap_simple() - find the battery capacity
  * @table: Pointer to battery OCV lookup table
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index e218041cc000..b998fc4c87ae 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -348,6 +348,52 @@ struct power_supply_resistance_temp_table {
 	int resistance;	/* internal resistance percent */
 };
 
+/**
+ * struct power_supply_maintenance_charge_table - setting for maintenace charging
+ * @charge_current_max_ua: maintenance charging current that is used to keep
+ *   the charge of the battery full as current is consumed after full charging.
+ *   The corresponding charge_voltage_max_uv is used as a safeguard: when we
+ *   reach this voltage the maintenance charging current is turned off. It is
+ *   turned back on if we fall below this voltage.
+ * @charge_voltage_max_uv: maintenance charging voltage that is usually a bit
+ *   lower than the constant_charge_voltage_max_uv. We can apply this settings
+ *   charge_current_max_ua until we get back up to this voltage.
+ * @safety_timer_minutes: maintenance charging safety timer, with an expiry
+ *   time in minutes. We will only use maintenance charging in this setting
+ *   for a certain amount of time, then we will first move to the next
+ *   maintenance charge current and voltage pair in respective array and wait
+ *   for the next safety timer timeout, or, if we reached the last maintencance
+ *   charging setting, disable charging until we reach
+ *   charge_restart_voltage_uv and restart ordinary CC/CV charging from there.
+ *   These timers should be chosen to align with the typical discharge curve
+ *   for the battery.
+ *
+ * When the main CC/CV charging is complete the battery can optionally be
+ * maintenance charged at the voltages from this table: a table of settings is
+ * traversed using a slightly lower current and voltage than what is used for
+ * CC/CV charging. The maintenance charging will for safety reasons not go on
+ * indefinately: we lower the current and voltage with successive maintenance
+ * settings, then disable charging completely after we reach the last one,
+ * and after that we do not restart charging until we reach
+ * charge_restart_voltage_uv (see struct power_supply_battery_info) and restart
+ * ordinary CC/CV charging from there.
+ *
+ * As an example, a Samsung EB425161LA Lithium-Ion battery is CC/CV charged
+ * at 900mA to 4340mV, then maintenance charged at 600mA and 4150mV for
+ * 60 hours, then maintenance charged at 600mA and 4100mV for 200 hours.
+ * After this the charge cycle is restarted waiting for
+ * charge_restart_voltage_uv.
+ *
+ * For most mobile electronics this type of maintenance charging is enough for
+ * the user to disconnect the device and make use of it before both maintenance
+ * charging cycles are complete.
+ */
+struct power_supply_maintenance_charge_table {
+	int charge_current_max_ua;
+	int charge_voltage_max_uv;
+	int charge_safety_timer_minutes;
+};
+
 #define POWER_SUPPLY_OCV_TEMP_MAX 20
 
 /**
@@ -393,6 +439,10 @@ struct power_supply_resistance_temp_table {
  * @constant_charge_voltage_max_uv: voltage in microvolts signifying the end of
  *   the CC (constant current) charging phase and the beginning of the CV
  *   (constant voltage) charging phase.
+ * @maintenance_charge: an array of maintenance charging settings to be used
+ *   after the main CC/CV charging phase is complete.
+ * @maintenance_charge_size: the number of maintenance charging settings in
+ *   maintenance_charge.
  * @factory_internal_resistance_uohm: the internal resistance of the battery
  *   at fabrication time, expressed in microohms. This resistance will vary
  *   depending on the lifetime and charge of the battery, so this is just a
@@ -542,6 +592,8 @@ struct power_supply_battery_info {
 	int overvoltage_limit_uv;
 	int constant_charge_current_max_ua;
 	int constant_charge_voltage_max_uv;
+	struct power_supply_maintenance_charge_table *maintenance_charge;
+	int maintenance_charge_size;
 	int factory_internal_resistance_uohm;
 	int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];
 	int temp_ambient_alert_min;
@@ -595,12 +647,24 @@ extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 extern int
 power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
 				int table_len, int temp);
+extern struct power_supply_maintenance_charge_table *
+power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index);
 extern void power_supply_changed(struct power_supply *psy);
 extern int power_supply_am_i_supplied(struct power_supply *psy);
 extern int power_supply_set_input_current_limit_from_supplier(
 					 struct power_supply *psy);
 extern int power_supply_set_battery_charged(struct power_supply *psy);
 
+static inline bool
+power_supply_supports_maintenance_charging(struct power_supply_battery_info *info)
+{
+	struct power_supply_maintenance_charge_table *mt;
+
+	mt = power_supply_get_maintenance_charging_setting(info, 0);
+
+	return (mt != NULL);
+}
+
 #ifdef CONFIG_POWER_SUPPLY
 extern int power_supply_is_system_supplied(void);
 #else
-- 
2.34.1


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

* [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
  2022-02-03 17:16 ` [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  2022-02-04 14:49   ` Vaittinen, Matti
  2022-02-03 17:16 ` [PATCH 3/6] power: supply: ab8500: Standardize BTI resistance Linus Walleij
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

The AB8500 code is using a special current and voltage setting
when the battery is in "alert mode", i.e. when it is starting
to go outside normal operating conditions so it is too
cold or too hot. This makes sense as a way for the charging
algorithm to deal with hostile environments.

Add the needed members to the struct power_supply_battery_info,
and switch the AB8500 charging code over to using this.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/ab8500-bm.h         |  4 ----
 drivers/power/supply/ab8500_bmdata.c     |  9 +++++++--
 drivers/power/supply/ab8500_chargalg.c   |  4 ++--
 drivers/power/supply/power_supply_core.c |  2 ++
 include/linux/power_supply.h             | 10 ++++++++++
 5 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
index 4d74d21cf1eb..91ef9d4a5222 100644
--- a/drivers/power/supply/ab8500-bm.h
+++ b/drivers/power/supply/ab8500-bm.h
@@ -331,14 +331,10 @@ struct ab8500_maxim_parameters {
  * struct ab8500_battery_type - different batteries supported
  * @resis_high:			battery upper resistance limit
  * @resis_low:			battery lower resistance limit
- * @low_high_cur_lvl:		charger current in temp low/high state in mA
- * @low_high_vol_lvl:		charger voltage in temp low/high state in mV'
  */
 struct ab8500_battery_type {
 	int resis_high;
 	int resis_low;
-	int low_high_cur_lvl;
-	int low_high_vol_lvl;
 };
 
 /**
diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
index c104afe83b4b..c878dc6e4197 100644
--- a/drivers/power/supply/ab8500_bmdata.c
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -77,8 +77,6 @@ struct power_supply_maintenance_charge_table maint_charge_table[] = {
 static struct ab8500_battery_type bat_type_thermistor_unknown = {
 	.resis_high = 0,
 	.resis_low = 0,
-	.low_high_cur_lvl = 300,
-	.low_high_vol_lvl = 4000,
 };
 
 static const struct ab8500_bm_capacity_levels cap_levels = {
@@ -192,6 +190,13 @@ int ab8500_bm_of_probe(struct power_supply *psy,
 		bi->maintenance_charge_size = ARRAY_SIZE(maint_charge_table);
 	}
 
+	if (bi->alert_temp_charge_current_ua < 0 ||
+	    bi->alert_temp_charge_voltage_uv < 0)
+	{
+		bi->alert_temp_charge_current_ua = 300000;
+		bi->alert_temp_charge_voltage_uv = 4000000;
+	}
+
 	/*
 	 * Internal resistance and factory resistance are tightly coupled
 	 * so both MUST be defined or we fall back to defaults.
diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c
index 6054996b6260..01fd2ef6e6b5 100644
--- a/drivers/power/supply/ab8500_chargalg.c
+++ b/drivers/power/supply/ab8500_chargalg.c
@@ -1511,8 +1511,8 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
 
 	case STATE_TEMP_LOWHIGH_INIT:
 		ab8500_chargalg_start_charging(di,
-			di->bm->bat_type->low_high_vol_lvl,
-			di->bm->bat_type->low_high_cur_lvl);
+			bi->alert_temp_charge_voltage_uv,
+			bi->alert_temp_charge_current_ua);
 		ab8500_chargalg_stop_maintenance_timer(di);
 		di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
 		ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 6568939e4518..01f9898ab548 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -591,6 +591,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	info->charge_restart_voltage_uv      = -EINVAL;
 	info->overvoltage_limit_uv           = -EINVAL;
 	info->maintenance_charge             = NULL;
+	info->alert_temp_charge_current_ua   = -EINVAL;
+	info->alert_temp_charge_voltage_uv   = -EINVAL;
 	info->temp_ambient_alert_min         = INT_MIN;
 	info->temp_ambient_alert_max         = INT_MAX;
 	info->temp_alert_min                 = INT_MIN;
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index b998fc4c87ae..5a059c013c12 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -443,6 +443,14 @@ struct power_supply_maintenance_charge_table {
  *   after the main CC/CV charging phase is complete.
  * @maintenance_charge_size: the number of maintenance charging settings in
  *   maintenance_charge.
+ * @alert_temp_charge_current_ua: The charging current to use if the battery
+ *   enters alert temperatures. I.e. if the internal temperature is between
+ *   temp_alert_min and temp_min or temp_alert_max and temp_max. No matter
+ *   the charging phase, this and alert_temp_charge_voltage_uv will be
+ *   applied under alert temperature conditions, usually lowering the charging
+ *   current as an evasive manouver.
+ * @alert_temp_charge_voltage_uv: Same as alert_temp_charge_current_ua, but
+ *   for the charging voltage.
  * @factory_internal_resistance_uohm: the internal resistance of the battery
  *   at fabrication time, expressed in microohms. This resistance will vary
  *   depending on the lifetime and charge of the battery, so this is just a
@@ -594,6 +602,8 @@ struct power_supply_battery_info {
 	int constant_charge_voltage_max_uv;
 	struct power_supply_maintenance_charge_table *maintenance_charge;
 	int maintenance_charge_size;
+	int alert_temp_charge_current_ua;
+	int alert_temp_charge_voltage_uv;
 	int factory_internal_resistance_uohm;
 	int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];
 	int temp_ambient_alert_min;
-- 
2.34.1


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

* [PATCH 3/6] power: supply: ab8500: Standardize BTI resistance
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
  2022-02-03 17:16 ` [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging Linus Walleij
  2022-02-03 17:16 ` [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  2022-02-03 17:16 ` [PATCH 4/6] power: supply: Support VBAT-to-Ri lookup tables Linus Walleij
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

The Battery Type Indicator (BTI) resistor is a resistor mounted
between a special terminal on the battery and ground. By sending
a fixed current (such as 7mA) through this resistor and measuring
the voltage over it, the resistance can be determined, and this
verifies the battery type.

Typical side view of the battery:

  o     o     o
 GND   BTI   +3.8V

Typical example of the electrical layout:

  +3.8 V   BTI
    |       |
    | +     |
 _______   [ ] 7kOhm
   ___      |
    |       |
    |       |
   GND     GND

By verifying this resistance before attempting to charge the
battery we add an additional level of security.

In some systems this is used for plug-and-play of batteries with
different capacity. In other cases, this is merely used to verify
that the right type of battery is connected, if several batteries
have the same physical shape and can be plugged into the same
slot. Sometimes this is just a surplus security mechanism.

Nokia and Samsung among many other vendors are known to use these
BTI resistors.

Add the BTI properties to struct power_supply_battery_info and
switch the AB8500 charger code over to using it.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/ab8500-bm.h         | 12 -----------
 drivers/power/supply/ab8500_bmdata.c     | 14 ++++++-------
 drivers/power/supply/ab8500_btemp.c      | 14 ++++++-------
 drivers/power/supply/ab8500_fg.c         |  4 ----
 drivers/power/supply/power_supply_core.c | 26 +++++++++++++++++++++++-
 include/linux/power_supply.h             | 13 ++++++++++++
 6 files changed, 51 insertions(+), 32 deletions(-)

diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
index 91ef9d4a5222..180a016b3662 100644
--- a/drivers/power/supply/ab8500-bm.h
+++ b/drivers/power/supply/ab8500-bm.h
@@ -327,16 +327,6 @@ struct ab8500_maxim_parameters {
 	int charger_curr_step_ua;
 };
 
-/**
- * struct ab8500_battery_type - different batteries supported
- * @resis_high:			battery upper resistance limit
- * @resis_low:			battery lower resistance limit
- */
-struct ab8500_battery_type {
-	int resis_high;
-	int resis_low;
-};
-
 /**
  * struct ab8500_bm_capacity_levels - ab8500 capacity level data
  * @critical:		critical capacity level in percent
@@ -387,7 +377,6 @@ struct ab8500_bm_charger_parameters {
  * @temp_hysteresis	temperature hysteresis
  * @maxi		maximization parameters
  * @cap_levels		capacity in percent for the different capacity levels
- * @bat_type		table of supported battery types
  * @chg_params		charger parameters
  * @fg_params		fuel gauge parameters
  */
@@ -410,7 +399,6 @@ struct ab8500_bm_data {
 	int temp_hysteresis;
 	const struct ab8500_maxim_parameters *maxi;
 	const struct ab8500_bm_capacity_levels *cap_levels;
-	struct ab8500_battery_type *bat_type;
 	const struct ab8500_bm_charger_parameters *chg_params;
 	const struct ab8500_fg_parameters *fg_params;
 };
diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
index c878dc6e4197..c2587948b880 100644
--- a/drivers/power/supply/ab8500_bmdata.c
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -73,12 +73,6 @@ struct power_supply_maintenance_charge_table maint_charge_table[] = {
 	}
 };
 
-/* Default battery type for reference designs is the unknown type */
-static struct ab8500_battery_type bat_type_thermistor_unknown = {
-	.resis_high = 0,
-	.resis_low = 0,
-};
-
 static const struct ab8500_bm_capacity_levels cap_levels = {
 	.critical	= 2,
 	.low		= 10,
@@ -136,7 +130,6 @@ struct ab8500_bm_data ab8500_bm_data = {
 	.enable_overshoot       = false,
 	.fg_res                 = 100,
 	.cap_levels             = &cap_levels,
-	.bat_type               = &bat_type_thermistor_unknown,
 	.interval_charging      = 5,
 	.interval_not_charging  = 120,
 	.maxi                   = &ab8500_maxi_params,
@@ -208,6 +201,13 @@ int ab8500_bm_of_probe(struct power_supply *psy,
 		bi->resist_table_size = ARRAY_SIZE(temp_to_batres_tbl_thermistor);
 	}
 
+	/* The default battery is emulated by a resistor at 7K */
+	if (bi->bti_resistance_ohm < 0 ||
+	    bi->bti_resistance_tolerance < 0) {
+		bi->bti_resistance_ohm = 7000;
+		bi->bti_resistance_tolerance = 20;
+	}
+
 	if (!bi->ocv_table[0]) {
 		/* Default capacity table at say 25 degrees Celsius */
 		bi->ocv_temp[0] = 25;
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
index 2a6fc151210c..b7e842dff567 100644
--- a/drivers/power/supply/ab8500_btemp.c
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -237,8 +237,8 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
  */
 static int ab8500_btemp_id(struct ab8500_btemp *di)
 {
+	struct power_supply_battery_info *bi = di->bm->bi;
 	int res;
-	u8 i;
 
 	di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
 
@@ -248,13 +248,11 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
 		return -ENXIO;
 	}
 
-	if ((res <= di->bm->bat_type->resis_high) &&
-	    (res >= di->bm->bat_type->resis_low)) {
-		dev_info(di->dev, "Battery detected on BATTEMP"
-			 " low %d < res %d < high: %d"
-			 " index: %d\n",
-			 di->bm->bat_type->resis_low, res,
-			 di->bm->bat_type->resis_high, i);
+	if (power_supply_battery_bti_in_range(bi, res)) {
+		dev_info(di->dev, "Battery detected on BATCTRL (pin C3)"
+			 " resistance %d Ohm = %d Ohm +/- %d%%\n",
+			 res, bi->bti_resistance_ohm,
+			 bi->bti_resistance_tolerance);
 	} else {
 		dev_warn(di->dev, "Battery identified as unknown"
 			 ", resistance %d Ohm\n", res);
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index 6436861db016..39c7e6b0be52 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2241,10 +2241,6 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
 				if (!di->flags.batt_id_received &&
 				    (bi && (bi->technology !=
 					    POWER_SUPPLY_TECHNOLOGY_UNKNOWN))) {
-					const struct ab8500_battery_type *b;
-
-					b = di->bm->bat_type;
-
 					di->flags.batt_id_received = true;
 
 					di->bat_cap.max_mah_design =
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 01f9898ab548..33fe355428cc 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -600,7 +600,9 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	info->temp_min                       = INT_MIN;
 	info->temp_max                       = INT_MAX;
 	info->factory_internal_resistance_uohm  = -EINVAL;
-	info->resist_table = NULL;
+	info->resist_table                   = NULL;
+	info->bti_resistance_ohm             = -EINVAL;
+	info->bti_resistance_tolerance       = -EINVAL;
 
 	for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
 		info->ocv_table[index]       = NULL;
@@ -913,6 +915,28 @@ int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 }
 EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2cap);
 
+bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info,
+				       int resistance)
+{
+	int low, high;
+
+	/* Nothing like this can be checked */
+	if (info->bti_resistance_ohm <= 0)
+		return false;
+
+	/* This will be extremely strict and unlikely to work */
+	if (info->bti_resistance_tolerance <= 0)
+		return (info->bti_resistance_ohm == resistance);
+
+	low = info->bti_resistance_ohm -
+		(info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100;
+	high = info->bti_resistance_ohm +
+		(info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100;
+
+	return ((resistance >= low) && (resistance <= high));
+}
+EXPORT_SYMBOL_GPL(power_supply_battery_bti_in_range);
+
 int power_supply_get_property(struct power_supply *psy,
 			    enum power_supply_property psp,
 			    union power_supply_propval *val)
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 5a059c013c12..72dc2dfc13a7 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -492,6 +492,14 @@ struct power_supply_maintenance_charge_table {
  *   by temperature: highest temperature with lowest resistance first, lowest
  *   temperature with highest resistance last.
  * @resist_table_size: the number of items in the resist_table.
+ * @bti_resistance_ohm: The Battery Type Indicator (BIT) nominal resistance
+ *   in ohms for this battery, if an identification resistor is mounted
+ *   between a third battery terminal and ground. This scheme is used by a lot
+ *   of mobile device batteries.
+ * @bti_resistance_tolerance: The tolerance in percent of the BTI resistance,
+ *   for example 10 for +/- 10%, if the bti_resistance is set to 7000 and the
+ *   tolerance is 10% we will detect a proper battery if the BTI resistance
+ *   is between 6300 and 7700 Ohm.
  *
  * This is the recommended struct to manage static battery parameters,
  * populated by power_supply_get_battery_info(). Most platform drivers should
@@ -616,6 +624,8 @@ struct power_supply_battery_info {
 	int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX];
 	struct power_supply_resistance_temp_table *resist_table;
 	int resist_table_size;
+	int bti_resistance_ohm;
+	int bti_resistance_tolerance;
 };
 
 extern struct atomic_notifier_head power_supply_notifier;
@@ -659,6 +669,8 @@ power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table
 				int table_len, int temp);
 extern struct power_supply_maintenance_charge_table *
 power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index);
+extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info,
+					      int resistance);
 extern void power_supply_changed(struct power_supply *psy);
 extern int power_supply_am_i_supplied(struct power_supply *psy);
 extern int power_supply_set_input_current_limit_from_supplier(
@@ -675,6 +687,7 @@ power_supply_supports_maintenance_charging(struct power_supply_battery_info *inf
 	return (mt != NULL);
 }
 
+
 #ifdef CONFIG_POWER_SUPPLY
 extern int power_supply_is_system_supplied(void);
 #else
-- 
2.34.1


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

* [PATCH 4/6] power: supply: Support VBAT-to-Ri lookup tables
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
                   ` (2 preceding siblings ...)
  2022-02-03 17:16 ` [PATCH 3/6] power: supply: ab8500: Standardize BTI resistance Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  2022-02-03 17:16 ` [PATCH 5/6] power: supply: ab8500_fg: Use VBAT-to-Ri if possible Linus Walleij
  2022-02-03 17:16 ` [PATCH 6/6] power: supply: Static data for Samsung batteries Linus Walleij
  5 siblings, 0 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

In Samsung devices, the method used to compensate for temperature,
age, load etc is by way of VBAT to Ri tables, which correlates the
battery voltage under load (VBAT) to an internal resistance (Ri).

Using this Ri and a measurement of the current out of the battery
(IBAT) the open circuit voltage (OCV) can be calculated as:

  OCV = VBAT - (Ri * IBAT)

The details are described in comments to struct
power_supply_battery_info in the commit.

Since not all batteries supply this VBAT-to-Ri data, the fallback
method to use the temperature-to-Ri lookup table can also be used
as a fallback.

Add two helper functions to check if we have the tables needed for
using power_supply_vbat2ri() or power_supply_temp2resist_simple()
respectively, so capacity estimation code can choose which one
to employ.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/power_supply_core.c |  67 +++++++++++++-
 include/linux/power_supply.h             | 113 ++++++++++++++++++++++-
 2 files changed, 177 insertions(+), 3 deletions(-)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 33fe355428cc..79f6c9606826 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -789,7 +789,7 @@ EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
 
 /**
  * power_supply_temp2resist_simple() - find the battery internal resistance
- * percent
+ * percent from temperature
  * @table: Pointer to battery resistance temperature table
  * @table_len: The table length
  * @temp: Current temperature
@@ -826,6 +826,71 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t
 }
 EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
 
+/**
+ * power_supply_vbat2ri() - find the battery internal resistance
+ * from the battery voltage
+ * @info: The battery information container
+ * @table: Pointer to battery resistance temperature table
+ * @vbat_uv: The battery voltage in microvolt
+ * @charging: If we are charging (true) or not (false)
+ *
+ * This helper function is used to look up battery internal resistance
+ * according to current battery voltage. Depending on whether the battery
+ * is currently charging or not, different resistance will be returned.
+ *
+ * Returns the internal resistance in microohm or negative error code.
+ */
+int power_supply_vbat2ri(struct power_supply_battery_info *info,
+			 int vbat_uv, bool charging)
+{
+	struct power_supply_vbat_ri_table *vbat2ri;
+	int table_len;
+	int i, high, low;
+
+	/*
+	 * If we are charging, and the battery supplies a separate table
+	 * for this state, we use that in order to compensate for the
+	 * charging voltage. Otherwise we use the main table.
+	 */
+	if (charging && info->vbat2ri_charging) {
+		vbat2ri = info->vbat2ri_charging;
+		table_len = info->vbat2ri_charging_size;
+	} else {
+		vbat2ri = info->vbat2ri_discharging;
+		table_len = info->vbat2ri_discharging_size;
+	}
+
+	/*
+	 * If no tables are specified, or if we are above the highest voltage in
+	 * the voltage table, just return the factory specified internal resistance.
+	 */
+	if (!vbat2ri || (table_len <= 0) || (vbat_uv > vbat2ri[0].vbat_uv)) {
+		if (charging && (info->factory_internal_resistance_charging_uohm > 0))
+			return info->factory_internal_resistance_charging_uohm;
+		else
+			return info->factory_internal_resistance_uohm;
+	}
+
+	/* Break loop at table_len - 1 because that is the highest index */
+	for (i = 0; i < table_len - 1; i++)
+		if (vbat_uv > vbat2ri[i].vbat_uv)
+			break;
+
+	/* The library function will deal with high == low */
+	if ((i == 0) || (i == (table_len - 1)))
+		high = i;
+	else
+		high = i - 1;
+	low = i;
+
+	return fixp_linear_interpolate(vbat2ri[low].vbat_uv,
+				       vbat2ri[low].ri_uohm,
+				       vbat2ri[high].vbat_uv,
+				       vbat2ri[high].ri_uohm,
+				       vbat_uv);
+}
+EXPORT_SYMBOL_GPL(power_supply_vbat2ri);
+
 struct power_supply_maintenance_charge_table *
 power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info,
 					      int index)
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 72dc2dfc13a7..bcf2ed2cc51e 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -348,6 +348,11 @@ struct power_supply_resistance_temp_table {
 	int resistance;	/* internal resistance percent */
 };
 
+struct power_supply_vbat_ri_table {
+	int vbat_uv;	/* Battery voltage in microvolt */
+	int ri_uohm;	/* Internal resistance in microohm */
+};
+
 /**
  * struct power_supply_maintenance_charge_table - setting for maintenace charging
  * @charge_current_max_ua: maintenance charging current that is used to keep
@@ -454,7 +459,14 @@ struct power_supply_maintenance_charge_table {
  * @factory_internal_resistance_uohm: the internal resistance of the battery
  *   at fabrication time, expressed in microohms. This resistance will vary
  *   depending on the lifetime and charge of the battery, so this is just a
- *   nominal ballpark figure.
+ *   nominal ballpark figure. This internal resistance is given for the state
+ *   when the battery is discharging.
+ * @factory_internal_resistance_charging_uohm: the internal resistance of the
+ *   battery at fabrication time while charging, expressed in microohms.
+ *   The charging process will affect the internal resistance of the battery
+ *   so this value provides a better resistance under these circumstances.
+ *   This resistance will vary depending on the lifetime and charge of the
+ *   battery, so this is just a nominal ballpark figure.
  * @ocv_temp: array indicating the open circuit voltage (OCV) capacity
  *   temperature indices. This is an array of temperatures in degrees Celsius
  *   indicating which capacity table to use for a certain temperature, since
@@ -492,6 +504,21 @@ struct power_supply_maintenance_charge_table {
  *   by temperature: highest temperature with lowest resistance first, lowest
  *   temperature with highest resistance last.
  * @resist_table_size: the number of items in the resist_table.
+ * @vbat2ri_discharging: this is a table that correlates Battery voltage (VBAT)
+ *   to internal resistance (Ri). The resistance is given in microohm for the
+ *   corresponding voltage in microvolts. The internal resistance is used to
+ *   determine the open circuit voltage so that we can determine the capacity
+ *   of the battery. These voltages to resistance tables apply when the battery
+ *   is discharging. The table must be ordered descending by voltage: highest
+ *   voltage first.
+ * @vbat2ri_discharging_size: the number of items in the vbat2ri_discharging
+ *   table.
+ * @vbat2ri_charging: same function as vbat2ri_discharging but for the state
+ *   when the battery is charging. Being under charge changes the battery's
+ *   internal resistance characteristics so a separate table is needed.*
+ *   The table must be ordered descending by voltage: highest voltage first.
+ * @vbat2ri_charging_size: the number of items in the vbat2ri_charging
+ *   table.
  * @bti_resistance_ohm: The Battery Type Indicator (BIT) nominal resistance
  *   in ohms for this battery, if an identification resistor is mounted
  *   between a third battery terminal and ground. This scheme is used by a lot
@@ -506,7 +533,9 @@ struct power_supply_maintenance_charge_table {
  * use these for consistency.
  *
  * Its field names must correspond to elements in enum power_supply_property.
- * The default field value is -EINVAL.
+ * The default field value is -EINVAL or NULL for pointers.
+ *
+ * CC/CV CHARGING:
  *
  * The charging parameters here assume a CC/CV charging scheme. This method
  * is most common with Lithium Ion batteries (other methods are possible) and
@@ -591,6 +620,66 @@ struct power_supply_maintenance_charge_table {
  * Overcharging Lithium Ion cells can be DANGEROUS and lead to fire or
  * explosions.
  *
+ * DETERMINING BATTERY CAPACITY:
+ *
+ * Several members of the struct deal with trying to determine the remaining
+ * capacity in the battery, usually as a percentage of charge. In practice
+ * many chargers uses a so-called fuel gauge or coloumb counter that measure
+ * how much charge goes into the battery and how much goes out (+/- leak
+ * consumption). This does not help if we do not know how much capacity the
+ * battery has to begin with, such as when it is first used or was taken out
+ * and charged in a separate charger. Therefore many capacity algorithms use
+ * the open circuit voltage with a look-up table to determine the rough
+ * capacity of the battery. The open circuit voltage can be conceptualized
+ * with an ideal voltage source (V) in series with an internal resistance (Ri)
+ * like this:
+ *
+ *      +-------> IBAT >----------------+
+ *      |                    ^          |
+ *     [ ] Ri                |          |
+ *      |                    | VBAT     |
+ *      o <----------        |          |
+ *     +|           ^        |         [ ] Rload
+ *    .---.         |        |          |
+ *    | V |         | OCV    |          |
+ *    '---'         |        |          |
+ *      |           |        |          |
+ *  GND +-------------------------------+
+ *
+ * If we disconnect the load (here simplified as a fixed resistance Rload)
+ * and measure VBAT with a infinite impedance voltage meter we will get
+ * VBAT = OCV and this assumption is sometimes made even under load, assuming
+ * Rload is insignificant. However this will be of dubious quality because the
+ * load is rarely that small and Ri is strongly nonlinear depending on
+ * temperature and how much capacity is left in the battery due to the
+ * chemistry involved.
+ *
+ * In many practical applications we cannot just disconnect the battery from
+ * the load, so instead we often try to measure the instantaneous IBAT (the
+ * current out from the battery), estimate the Ri and thus calculate the
+ * voltage drop over Ri and compensate like this:
+ *
+ *   OCV = VBAT - (IBAT * Ri)
+ *
+ * The tables vbat2ri_discharging and vbat2ri_charging are used to determine
+ * (by interpolation) the Ri from the VBAT under load. These curves are highly
+ * nonlinear and may need many datapoints but can be found in datasheets for
+ * some batteries. This gives the compensated open circuit voltage (OCV) for
+ * the battery even under load. Using this method will also compensate for
+ * temperature changes in the environment: this will also make the internal
+ * resistance change, and it will affect the VBAT under load, so correlating
+ * VBAT to Ri takes both remaining capacity and temperature into consideration.
+ *
+ * Alternatively a manufacturer can specify how the capacity of the battery
+ * is dependent on the battery temperature which is the main factor affecting
+ * Ri. As we know all checmical reactions are faster when it is warm and slower
+ * when it is cold. You can put in 1500mAh and only get 800mAh out before the
+ * voltage drops too low for example. This effect is also highly nonlinear and
+ * the purpose of the table resist_table: this will take a temperature and
+ * tell us how big percentage of Ri the specified temperature correlates to.
+ * Usually we have 100% of the factory_internal_resistance_uohm at 25 degrees
+ * Celsius.
+ *
  * The power supply class itself doesn't use this struct as of now.
  */
 
@@ -613,6 +702,7 @@ struct power_supply_battery_info {
 	int alert_temp_charge_current_ua;
 	int alert_temp_charge_voltage_uv;
 	int factory_internal_resistance_uohm;
+	int factory_internal_resistance_charging_uohm;
 	int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];
 	int temp_ambient_alert_min;
 	int temp_ambient_alert_max;
@@ -624,6 +714,10 @@ struct power_supply_battery_info {
 	int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX];
 	struct power_supply_resistance_temp_table *resist_table;
 	int resist_table_size;
+	struct power_supply_vbat_ri_table *vbat2ri_discharging;
+	int vbat2ri_discharging_size;
+	struct power_supply_vbat_ri_table *vbat2ri_charging;
+	int vbat2ri_charging_size;
 	int bti_resistance_ohm;
 	int bti_resistance_tolerance;
 };
@@ -667,6 +761,8 @@ extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 extern int
 power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
 				int table_len, int temp);
+extern int power_supply_vbat2ri(struct power_supply_battery_info *info,
+				int vbat_uv, bool charging);
 extern struct power_supply_maintenance_charge_table *
 power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index);
 extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info,
@@ -687,6 +783,19 @@ power_supply_supports_maintenance_charging(struct power_supply_battery_info *inf
 	return (mt != NULL);
 }
 
+static inline bool
+power_supply_supports_vbat2ri(struct power_supply_battery_info *info)
+{
+	return ((info->vbat2ri_discharging != NULL) &&
+		info->vbat2ri_discharging_size > 0);
+}
+
+static inline bool
+power_supply_supports_temp2ri(struct power_supply_battery_info *info)
+{
+	return ((info->resist_table != NULL) &&
+		info->resist_table_size > 0);
+}
 
 #ifdef CONFIG_POWER_SUPPLY
 extern int power_supply_is_system_supplied(void);
-- 
2.34.1


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

* [PATCH 5/6] power: supply: ab8500_fg: Use VBAT-to-Ri if possible
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
                   ` (3 preceding siblings ...)
  2022-02-03 17:16 ` [PATCH 4/6] power: supply: Support VBAT-to-Ri lookup tables Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  2022-02-03 17:16 ` [PATCH 6/6] power: supply: Static data for Samsung batteries Linus Walleij
  5 siblings, 0 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

Augment the AB8500 fuel gauge to use the VBAT-to-Ri method of
estimating the internal resistance if possible. Else fall back
to using the temperature-to-Ri or just the default Ri.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/ab8500_fg.c | 31 +++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 10 deletions(-)

diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index 39c7e6b0be52..e735ba74d1c3 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -877,27 +877,38 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
 /**
  * ab8500_fg_battery_resistance() - Returns the battery inner resistance
  * @di:		pointer to the ab8500_fg structure
+ * @vbat_uncomp_uv: Uncompensated VBAT voltage
  *
  * Returns battery inner resistance added with the fuel gauge resistor value
  * to get the total resistance in the whole link from gnd to bat+ node
  * in milliohm.
  */
-static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
+static int ab8500_fg_battery_resistance(struct ab8500_fg *di, int vbat_uncomp_uv)
 {
 	struct power_supply_battery_info *bi = di->bm->bi;
 	int resistance_percent = 0;
 	int resistance;
 
-	resistance_percent = power_supply_temp2resist_simple(bi->resist_table,
-						 bi->resist_table_size,
-						 di->bat_temp / 10);
 	/*
-	 * We get a percentage of factory resistance here so first get
-	 * the factory resistance in milliohms then calculate how much
-	 * resistance we have at this temperature.
+	 * Determine the resistance at this voltage. First try VBAT-to-Ri else
+	 * just infer it from the surrounding temperature, if nothing works just
+	 * use the internal resistance.
 	 */
-	resistance = (bi->factory_internal_resistance_uohm / 1000);
-	resistance = resistance * resistance_percent / 100;
+	if (power_supply_supports_vbat2ri(bi)) {
+		resistance = power_supply_vbat2ri(bi, vbat_uncomp_uv, di->flags.charging);
+		/* Convert to milliohm */
+		resistance = resistance / 1000;
+	} else if (power_supply_supports_temp2ri(bi)) {
+		resistance_percent = power_supply_temp2resist_simple(bi->resist_table,
+								     bi->resist_table_size,
+								     di->bat_temp / 10);
+		/* Convert to milliohm */
+		resistance = bi->factory_internal_resistance_uohm / 1000;
+		resistance = resistance * resistance_percent / 100;
+	} else {
+		/* Last fallback */
+		resistance = bi->factory_internal_resistance_uohm / 1000;
+	}
 
 	dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
 	    " fg resistance %d, total: %d (mOhm)\n",
@@ -955,7 +966,7 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
 	vbat_uv = vbat_uv / i;
 
 	/* Next we apply voltage compensation from internal resistance */
-	rcomp = ab8500_fg_battery_resistance(di);
+	rcomp = ab8500_fg_battery_resistance(di, vbat_uv);
 	vbat_uv = vbat_uv - (di->inst_curr_ua * rcomp) / 1000;
 
 	/* Always keep this state at latest measurement */
-- 
2.34.1


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

* [PATCH 6/6] power: supply: Static data for Samsung batteries
  2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
                   ` (4 preceding siblings ...)
  2022-02-03 17:16 ` [PATCH 5/6] power: supply: ab8500_fg: Use VBAT-to-Ri if possible Linus Walleij
@ 2022-02-03 17:16 ` Linus Walleij
  5 siblings, 0 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-03 17:16 UTC (permalink / raw)
  To: Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, Matti Vaittinen, Linus Walleij

If we detect a Samsung SDI battery, we return a static
struct power_supply_battery_info and avoid looking further.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/power/supply/Kconfig               |   6 +
 drivers/power/supply/Makefile              |   1 +
 drivers/power/supply/power_supply_core.c   |  28 +-
 drivers/power/supply/samsung-sdi-battery.c | 674 +++++++++++++++++++++
 drivers/power/supply/samsung-sdi-battery.h |  13 +
 5 files changed, 712 insertions(+), 10 deletions(-)
 create mode 100644 drivers/power/supply/samsung-sdi-battery.c
 create mode 100644 drivers/power/supply/samsung-sdi-battery.h

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 6815e5a4c0bd..9bb4978c842f 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -181,6 +181,12 @@ config BATTERY_OLPC
 	help
 	  Say Y to enable support for the battery on the OLPC laptop.
 
+config BATTERY_SAMSUNG_SDI
+	tristate "Samsung SDI batteries"
+	help
+	  Say Y to enable support for Samsung SDI battery data.
+	  These batteries are used in Samsung mobile phones.
+
 config BATTERY_TOSA
 	tristate "Sharp SL-6000 (tosa) battery"
 	depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 2c1b264b2046..ebcd2f5fe26d 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_GOLDFISH)	+= goldfish_battery.o
 obj-$(CONFIG_BATTERY_LEGO_EV3)	+= lego_ev3_battery.o
 obj-$(CONFIG_BATTERY_PMU)	+= pmu_battery.o
 obj-$(CONFIG_BATTERY_OLPC)	+= olpc_battery.o
+obj-$(CONFIG_BATTERY_SAMSUNG_SDI)	+= samsung-sdi-battery.o
 obj-$(CONFIG_BATTERY_TOSA)	+= tosa_battery.o
 obj-$(CONFIG_BATTERY_COLLIE)	+= collie_battery.o
 obj-$(CONFIG_BATTERY_INGENIC)	+= ingenic-battery.o
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 79f6c9606826..076ad838cb0f 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -23,6 +23,7 @@
 #include <linux/thermal.h>
 #include <linux/fixp-arith.h>
 #include "power_supply.h"
+#include "samsung-sdi-battery.h"
 
 /* exported for the APM Power driver, APM emulation */
 struct class *power_supply_class;
@@ -573,6 +574,23 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	int err, len, index;
 	const __be32 *list;
 
+	if (!psy->of_node) {
+		dev_warn(&psy->dev, "%s currently only supports devicetree\n",
+			 __func__);
+		return -ENXIO;
+	}
+
+	battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
+	if (!battery_np)
+		return -ENODEV;
+
+	/* Try static batteries first */
+	err = samsung_sdi_battery_get_info(&psy->dev, battery_np, &info);
+	if (!err) {
+		*info_out = info;
+		return err;
+	}
+
 	info = devm_kmalloc(&psy->dev, sizeof(*info), GFP_KERNEL);
 	if (!info)
 		return -ENOMEM;
@@ -610,16 +628,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		info->ocv_table_size[index]  = -EINVAL;
 	}
 
-	if (!psy->of_node) {
-		dev_warn(&psy->dev, "%s currently only supports devicetree\n",
-			 __func__);
-		return -ENXIO;
-	}
-
-	battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
-	if (!battery_np)
-		return -ENODEV;
-
 	err = of_property_read_string(battery_np, "compatible", &value);
 	if (err)
 		goto out_put_node;
diff --git a/drivers/power/supply/samsung-sdi-battery.c b/drivers/power/supply/samsung-sdi-battery.c
new file mode 100644
index 000000000000..039a5f6ecf3c
--- /dev/null
+++ b/drivers/power/supply/samsung-sdi-battery.c
@@ -0,0 +1,674 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Battery data and characteristics for Samsung SDI (Samsung Digital Interface)
+ * batteries. The data is retrieved automatically into drivers using
+ * the power_supply_get_battery_info() call.
+ *
+ * The BTI (battery type indicator) resistance in the code drops was very
+ * unreliable. The resistance listed here was obtained by simply measuring
+ * the BTI resistance with a multimeter on the battery.
+ */
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/power_supply.h>
+#include "samsung-sdi-battery.h"
+
+struct samsung_sdi_battery {
+	char *compatible;
+	char *name;
+	struct power_supply_battery_info info;
+};
+
+/*
+ * Voltage to internal resistance tables. The internal resistance varies
+ * depending on the VBAT voltage, so look this up from a table. Different
+ * tables apply depending on whether we are charging or not.
+ */
+
+struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = {
+	{ .vbat_uv = 4071000, .ri_uohm = 158000 },
+	{ .vbat_uv = 4019000, .ri_uohm = 187000 },
+	{ .vbat_uv = 3951000, .ri_uohm = 191000 },
+	{ .vbat_uv = 3901000, .ri_uohm = 193000 },
+	{ .vbat_uv = 3850000, .ri_uohm = 273000 },
+	{ .vbat_uv = 3800000, .ri_uohm = 305000 },
+	{ .vbat_uv = 3750000, .ri_uohm = 205000 },
+	{ .vbat_uv = 3700000, .ri_uohm = 290000 },
+	{ .vbat_uv = 3650000, .ri_uohm = 262000 },
+	{ .vbat_uv = 3618000, .ri_uohm = 290000 },
+	{ .vbat_uv = 3505000, .ri_uohm = 235000 },
+	{ .vbat_uv = 3484000, .ri_uohm = 253000 },
+	{ .vbat_uv = 3413000, .ri_uohm = 243000 },
+	{ .vbat_uv = 3393000, .ri_uohm = 285000 },
+	{ .vbat_uv = 3361000, .ri_uohm = 281000 },
+	{ .vbat_uv = 3302000, .ri_uohm = 286000 },
+	{ .vbat_uv = 3280000, .ri_uohm = 250000 },
+};
+
+struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = {
+	{ .vbat_uv = 4190000, .ri_uohm = 214000 },
+	{ .vbat_uv = 4159000, .ri_uohm = 252000 },
+	{ .vbat_uv = 4121000, .ri_uohm = 245000 },
+	{ .vbat_uv = 4069000, .ri_uohm = 228000 },
+	{ .vbat_uv = 4046000, .ri_uohm = 229000 },
+	{ .vbat_uv = 4026000, .ri_uohm = 233000 },
+	{ .vbat_uv = 4007000, .ri_uohm = 240000 },
+	{ .vbat_uv = 3982000, .ri_uohm = 291000 },
+	{ .vbat_uv = 3945000, .ri_uohm = 276000 },
+	{ .vbat_uv = 3924000, .ri_uohm = 266000 },
+	{ .vbat_uv = 3910000, .ri_uohm = 258000 },
+	{ .vbat_uv = 3900000, .ri_uohm = 271000 },
+	{ .vbat_uv = 3844000, .ri_uohm = 279000 },
+	{ .vbat_uv = 3772000, .ri_uohm = 217000 },
+	{ .vbat_uv = 3673000, .ri_uohm = 208000 },
+	{ .vbat_uv = 3571000, .ri_uohm = 208000 },
+	{ .vbat_uv = 3510000, .ri_uohm = 228000 },
+};
+
+struct power_supply_vbat_ri_table samsung_vbat2res_discharging_1500mah[] = {
+	{ .vbat_uv = 4240000, .ri_uohm = 160000 },
+	{ .vbat_uv = 4210000, .ri_uohm = 179000 },
+	{ .vbat_uv = 4180000, .ri_uohm = 183000 },
+	{ .vbat_uv = 4160000, .ri_uohm = 184000 },
+	{ .vbat_uv = 4140000, .ri_uohm = 191000 },
+	{ .vbat_uv = 4120000, .ri_uohm = 204000 },
+	{ .vbat_uv = 4080000, .ri_uohm = 200000 },
+	{ .vbat_uv = 4027000, .ri_uohm = 202000 },
+	{ .vbat_uv = 3916000, .ri_uohm = 221000 },
+	{ .vbat_uv = 3868000, .ri_uohm = 209000 },
+	{ .vbat_uv = 3818000, .ri_uohm = 276000 },
+	{ .vbat_uv = 3780000, .ri_uohm = 275000 },
+	{ .vbat_uv = 3746000, .ri_uohm = 230000 },
+	{ .vbat_uv = 3717000, .ri_uohm = 247000 },
+	{ .vbat_uv = 3695000, .ri_uohm = 263000 },
+	{ .vbat_uv = 3675000, .ri_uohm = 272000 },
+	{ .vbat_uv = 3667000, .ri_uohm = 285000 },
+	{ .vbat_uv = 3658000, .ri_uohm = 276000 },
+	{ .vbat_uv = 3647000, .ri_uohm = 263000 },
+	{ .vbat_uv = 3629000, .ri_uohm = 236000 },
+	{ .vbat_uv = 3618000, .ri_uohm = 229000 },
+	{ .vbat_uv = 3588000, .ri_uohm = 275000 },
+	{ .vbat_uv = 3503000, .ri_uohm = 341000 },
+	{ .vbat_uv = 3400000, .ri_uohm = 269000 },
+	{ .vbat_uv = 3360000, .ri_uohm = 328000 },
+	{ .vbat_uv = 3330000, .ri_uohm = 305000 },
+	{ .vbat_uv = 3300000, .ri_uohm = 339000 },
+};
+
+struct power_supply_vbat_ri_table samsung_vbat2res_charging_1500mah[] = {
+	{ .vbat_uv = 4302000, .ri_uohm = 200000 },
+	{ .vbat_uv = 4258000, .ri_uohm = 206000 },
+	{ .vbat_uv = 4200000, .ri_uohm = 231000 },
+	{ .vbat_uv = 4150000, .ri_uohm = 198000 },
+	{ .vbat_uv = 4100000, .ri_uohm = 205000 },
+	{ .vbat_uv = 4077000, .ri_uohm = 261000 },
+	{ .vbat_uv = 4039000, .ri_uohm = 238000 },
+	{ .vbat_uv = 4004000, .ri_uohm = 264000 },
+	{ .vbat_uv = 3975000, .ri_uohm = 287000 },
+	{ .vbat_uv = 3917000, .ri_uohm = 295000 },
+	{ .vbat_uv = 3891000, .ri_uohm = 298000 },
+	{ .vbat_uv = 3873000, .ri_uohm = 293000 },
+	{ .vbat_uv = 3854000, .ri_uohm = 277000 },
+	{ .vbat_uv = 3851000, .ri_uohm = 291000 },
+	{ .vbat_uv = 3788000, .ri_uohm = 286000 },
+	{ .vbat_uv = 3758000, .ri_uohm = 230000 },
+	{ .vbat_uv = 3750000, .ri_uohm = 177000 },
+	{ .vbat_uv = 3712000, .ri_uohm = 164000 },
+	{ .vbat_uv = 3674000, .ri_uohm = 161000 },
+	{ .vbat_uv = 3590000, .ri_uohm = 164000 },
+};
+
+struct power_supply_vbat_ri_table samsung_vbat2res_discharging_2000mah[] = {
+	{ .vbat_uv = 4194000, .ri_uohm = 121000 },
+	{ .vbat_uv = 4169000, .ri_uohm = 188000 },
+	{ .vbat_uv = 4136000, .ri_uohm = 173000 },
+	{ .vbat_uv = 4108000, .ri_uohm = 158000 },
+	{ .vbat_uv = 4064000, .ri_uohm = 143000 },
+	{ .vbat_uv = 3956000, .ri_uohm = 160000 },
+	{ .vbat_uv = 3847000, .ri_uohm = 262000 },
+	{ .vbat_uv = 3806000, .ri_uohm = 280000 },
+	{ .vbat_uv = 3801000, .ri_uohm = 266000 },
+	{ .vbat_uv = 3794000, .ri_uohm = 259000 },
+	{ .vbat_uv = 3785000, .ri_uohm = 234000 },
+	{ .vbat_uv = 3779000, .ri_uohm = 227000 },
+	{ .vbat_uv = 3772000, .ri_uohm = 222000 },
+	{ .vbat_uv = 3765000, .ri_uohm = 221000 },
+	{ .vbat_uv = 3759000, .ri_uohm = 216000 },
+	{ .vbat_uv = 3754000, .ri_uohm = 206000 },
+	{ .vbat_uv = 3747000, .ri_uohm = 212000 },
+	{ .vbat_uv = 3743000, .ri_uohm = 208000 },
+	{ .vbat_uv = 3737000, .ri_uohm = 212000 },
+	{ .vbat_uv = 3733000, .ri_uohm = 200000 },
+	{ .vbat_uv = 3728000, .ri_uohm = 203000 },
+	{ .vbat_uv = 3722000, .ri_uohm = 207000 },
+	{ .vbat_uv = 3719000, .ri_uohm = 208000 },
+	{ .vbat_uv = 3715000, .ri_uohm = 209000 },
+	{ .vbat_uv = 3712000, .ri_uohm = 211000 },
+	{ .vbat_uv = 3709000, .ri_uohm = 210000 },
+	{ .vbat_uv = 3704000, .ri_uohm = 216000 },
+	{ .vbat_uv = 3701000, .ri_uohm = 218000 },
+	{ .vbat_uv = 3698000, .ri_uohm = 222000 },
+	{ .vbat_uv = 3694000, .ri_uohm = 218000 },
+	{ .vbat_uv = 3692000, .ri_uohm = 215000 },
+	{ .vbat_uv = 3688000, .ri_uohm = 224000 },
+	{ .vbat_uv = 3686000, .ri_uohm = 224000 },
+	{ .vbat_uv = 3683000, .ri_uohm = 228000 },
+	{ .vbat_uv = 3681000, .ri_uohm = 228000 },
+	{ .vbat_uv = 3679000, .ri_uohm = 229000 },
+	{ .vbat_uv = 3676000, .ri_uohm = 232000 },
+	{ .vbat_uv = 3675000, .ri_uohm = 229000 },
+	{ .vbat_uv = 3673000, .ri_uohm = 229000 },
+	{ .vbat_uv = 3672000, .ri_uohm = 223000 },
+	{ .vbat_uv = 3669000, .ri_uohm = 224000 },
+	{ .vbat_uv = 3666000, .ri_uohm = 224000 },
+	{ .vbat_uv = 3663000, .ri_uohm = 221000 },
+	{ .vbat_uv = 3660000, .ri_uohm = 218000 },
+	{ .vbat_uv = 3657000, .ri_uohm = 215000 },
+	{ .vbat_uv = 3654000, .ri_uohm = 212000 },
+	{ .vbat_uv = 3649000, .ri_uohm = 215000 },
+	{ .vbat_uv = 3644000, .ri_uohm = 215000 },
+	{ .vbat_uv = 3636000, .ri_uohm = 215000 },
+	{ .vbat_uv = 3631000, .ri_uohm = 206000 },
+	{ .vbat_uv = 3623000, .ri_uohm = 205000 },
+	{ .vbat_uv = 3616000, .ri_uohm = 193000 },
+	{ .vbat_uv = 3605000, .ri_uohm = 193000 },
+	{ .vbat_uv = 3600000, .ri_uohm = 198000 },
+	{ .vbat_uv = 3597000, .ri_uohm = 198000 },
+	{ .vbat_uv = 3592000, .ri_uohm = 203000 },
+	{ .vbat_uv = 3591000, .ri_uohm = 188000 },
+	{ .vbat_uv = 3587000, .ri_uohm = 188000 },
+	{ .vbat_uv = 3583000, .ri_uohm = 177000 },
+	{ .vbat_uv = 3577000, .ri_uohm = 170000 },
+	{ .vbat_uv = 3568000, .ri_uohm = 135000 },
+	{ .vbat_uv = 3552000, .ri_uohm = 54000 },
+	{ .vbat_uv = 3526000, .ri_uohm = 130000 },
+	{ .vbat_uv = 3501000, .ri_uohm = 48000 },
+	{ .vbat_uv = 3442000, .ri_uohm = 183000 },
+	{ .vbat_uv = 3326000, .ri_uohm = 372000 },
+	{ .vbat_uv = 3161000, .ri_uohm = 452000 },
+};
+
+struct power_supply_vbat_ri_table samsung_vbat2res_charging_2000mah[] = {
+	{ .vbat_uv = 4360000, .ri_uohm = 128000 },
+	{ .vbat_uv = 4325000, .ri_uohm = 130000 },
+	{ .vbat_uv = 4316000, .ri_uohm = 148000 },
+	{ .vbat_uv = 4308000, .ri_uohm = 162000 },
+	{ .vbat_uv = 4301000, .ri_uohm = 162000 },
+	{ .vbat_uv = 4250000, .ri_uohm = 162000 },
+	{ .vbat_uv = 4230000, .ri_uohm = 164000 },
+	{ .vbat_uv = 4030000, .ri_uohm = 164000 },
+	{ .vbat_uv = 4000000, .ri_uohm = 193000 },
+	{ .vbat_uv = 3950000, .ri_uohm = 204000 },
+	{ .vbat_uv = 3850000, .ri_uohm = 210000 },
+	{ .vbat_uv = 3800000, .ri_uohm = 230000 },
+	{ .vbat_uv = 3790000, .ri_uohm = 240000 },
+	{ .vbat_uv = 3780000, .ri_uohm = 311000 },
+	{ .vbat_uv = 3760000, .ri_uohm = 420000 },
+	{ .vbat_uv = 3700000, .ri_uohm = 504000 },
+	{ .vbat_uv = 3600000, .ri_uohm = 565000 },
+};
+
+/*
+ * Temperature to internal resistance scaling tables.
+ *
+ * "resistance" is the percentage of the resistance determined from the voltage
+ * so this represents the capacity ratio at different temperatures.
+ *
+ * FIXME: the proper table is missing: Samsung does not provide the necessary
+ * temperature compensation tables so we just state 100% for every temperature.
+ * If you have the datasheets, please provide these tables.
+ */
+static struct power_supply_resistance_temp_table samsung_temp2res[] = {
+	{ .temp = 50, .resistance = 100 },
+	{ .temp = 40, .resistance = 100 },
+	{ .temp = 30, .resistance = 100 },
+	{ .temp = 20, .resistance = 100 },
+	{ .temp = 10, .resistance = 100 },
+	{ .temp = 00, .resistance = 100 },
+	{ .temp = -10, .resistance = 100 },
+	{ .temp = -20, .resistance = 100 },
+};
+
+/*
+ * Capacity tables for different Open Circuit Voltages (OCV).
+ * These must be sorted by falling OCV value.
+ */
+
+static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = {
+	{ .ocv = 4178000, .capacity = 100},
+	{ .ocv = 4148000, .capacity = 99},
+	{ .ocv = 4105000, .capacity = 95},
+	{ .ocv = 4078000, .capacity = 92},
+	{ .ocv = 4057000, .capacity = 89},
+	{ .ocv = 4013000, .capacity = 85},
+	{ .ocv = 3988000, .capacity = 82},
+	{ .ocv = 3962000, .capacity = 77},
+	{ .ocv = 3920000, .capacity = 70},
+	{ .ocv = 3891000, .capacity = 65},
+	{ .ocv = 3874000, .capacity = 62},
+	{ .ocv = 3839000, .capacity = 59},
+	{ .ocv = 3816000, .capacity = 55},
+	{ .ocv = 3798000, .capacity = 50},
+	{ .ocv = 3778000, .capacity = 40},
+	{ .ocv = 3764000, .capacity = 30},
+	{ .ocv = 3743000, .capacity = 25},
+	{ .ocv = 3711000, .capacity = 20},
+	{ .ocv = 3691000, .capacity = 18},
+	{ .ocv = 3685000, .capacity = 15},
+	{ .ocv = 3680000, .capacity = 12},
+	{ .ocv = 3662000, .capacity = 10},
+	{ .ocv = 3638000, .capacity = 9},
+	{ .ocv = 3593000, .capacity = 7},
+	{ .ocv = 3566000, .capacity = 6},
+	{ .ocv = 3497000, .capacity = 4},
+	{ .ocv = 3405000, .capacity = 2},
+	{ .ocv = 3352000, .capacity = 1},
+	{ .ocv = 3300000, .capacity = 0},
+};
+
+static struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = {
+	{ .ocv = 4328000, .capacity = 100},
+	{ .ocv = 4299000, .capacity = 99},
+	{ .ocv = 4281000, .capacity = 98},
+	{ .ocv = 4241000, .capacity = 95},
+	{ .ocv = 4183000, .capacity = 90},
+	{ .ocv = 4150000, .capacity = 87},
+	{ .ocv = 4116000, .capacity = 84},
+	{ .ocv = 4077000, .capacity = 80},
+	{ .ocv = 4068000, .capacity = 79},
+	{ .ocv = 4058000, .capacity = 77},
+	{ .ocv = 4026000, .capacity = 75},
+	{ .ocv = 3987000, .capacity = 72},
+	{ .ocv = 3974000, .capacity = 69},
+	{ .ocv = 3953000, .capacity = 66},
+	{ .ocv = 3933000, .capacity = 63},
+	{ .ocv = 3911000, .capacity = 60},
+	{ .ocv = 3900000, .capacity = 58},
+	{ .ocv = 3873000, .capacity = 55},
+	{ .ocv = 3842000, .capacity = 52},
+	{ .ocv = 3829000, .capacity = 50},
+	{ .ocv = 3810000, .capacity = 45},
+	{ .ocv = 3793000, .capacity = 40},
+	{ .ocv = 3783000, .capacity = 35},
+	{ .ocv = 3776000, .capacity = 30},
+	{ .ocv = 3762000, .capacity = 25},
+	{ .ocv = 3746000, .capacity = 20},
+	{ .ocv = 3739000, .capacity = 18},
+	{ .ocv = 3715000, .capacity = 15},
+	{ .ocv = 3700000, .capacity = 12},
+	{ .ocv = 3690000, .capacity = 10},
+	{ .ocv = 3680000, .capacity = 9},
+	{ .ocv = 3670000, .capacity = 7},
+	{ .ocv = 3656000, .capacity = 5},
+	{ .ocv = 3634000, .capacity = 4},
+	{ .ocv = 3614000, .capacity = 3},
+	{ .ocv = 3551000, .capacity = 2},
+	{ .ocv = 3458000, .capacity = 1},
+	{ .ocv = 3300000, .capacity = 0},
+};
+
+static struct power_supply_battery_ocv_table samsung_ocv_cap_1700mah[] = {
+	{ .ocv = 4269000, .capacity = 100},
+	{ .ocv = 4227000, .capacity = 99},
+	{ .ocv = 4174000, .capacity = 95},
+	{ .ocv = 4139000, .capacity = 92},
+	{ .ocv = 4106000, .capacity = 89},
+	{ .ocv = 4062000, .capacity = 85},
+	{ .ocv = 4029000, .capacity = 82},
+	{ .ocv = 3958000, .capacity = 75},
+	{ .ocv = 3908000, .capacity = 70},
+	{ .ocv = 3872000, .capacity = 66},
+	{ .ocv = 3820000, .capacity = 60},
+	{ .ocv = 3798000, .capacity = 57},
+	{ .ocv = 3774000, .capacity = 53},
+	{ .ocv = 3757000, .capacity = 50},
+	{ .ocv = 3735000, .capacity = 45},
+	{ .ocv = 3716000, .capacity = 40},
+	{ .ocv = 3701000, .capacity = 35},
+	{ .ocv = 3689000, .capacity = 30},
+	{ .ocv = 3678000, .capacity = 25},
+	{ .ocv = 3658000, .capacity = 20},
+	{ .ocv = 3646000, .capacity = 18},
+	{ .ocv = 3619000, .capacity = 15},
+	{ .ocv = 3589000, .capacity = 12},
+	{ .ocv = 3578000, .capacity = 10},
+	{ .ocv = 3568000, .capacity = 9},
+	{ .ocv = 3545000, .capacity = 7},
+	{ .ocv = 3509000, .capacity = 5},
+	{ .ocv = 3485000, .capacity = 4},
+	{ .ocv = 3449000, .capacity = 3},
+	{ .ocv = 3403000, .capacity = 2},
+	{ .ocv = 3348000, .capacity = 1},
+	{ .ocv = 3300000, .capacity = 0},
+};
+
+static struct power_supply_battery_ocv_table samsung_ocv_cap_2000mah[] = {
+	{ .ocv = 4320000, .capacity = 100},
+	{ .ocv = 4296000, .capacity = 99},
+	{ .ocv = 4283000, .capacity = 98},
+	{ .ocv = 4245000, .capacity = 95},
+	{ .ocv = 4185000, .capacity = 90},
+	{ .ocv = 4152000, .capacity = 87},
+	{ .ocv = 4119000, .capacity = 84},
+	{ .ocv = 4077000, .capacity = 80},
+	{ .ocv = 4057000, .capacity = 78},
+	{ .ocv = 4048000, .capacity = 77},
+	{ .ocv = 4020000, .capacity = 74},
+	{ .ocv = 4003000, .capacity = 72},
+	{ .ocv = 3978000, .capacity = 69},
+	{ .ocv = 3955000, .capacity = 66},
+	{ .ocv = 3934000, .capacity = 63},
+	{ .ocv = 3912000, .capacity = 60},
+	{ .ocv = 3894000, .capacity = 58},
+	{ .ocv = 3860000, .capacity = 55},
+	{ .ocv = 3837000, .capacity = 52},
+	{ .ocv = 3827000, .capacity = 50},
+	{ .ocv = 3806000, .capacity = 45},
+	{ .ocv = 3791000, .capacity = 40},
+	{ .ocv = 3779000, .capacity = 35},
+	{ .ocv = 3770000, .capacity = 30},
+	{ .ocv = 3758000, .capacity = 25},
+	{ .ocv = 3739000, .capacity = 20},
+	{ .ocv = 3730000, .capacity = 18},
+	{ .ocv = 3706000, .capacity = 15},
+	{ .ocv = 3684000, .capacity = 13},
+	{ .ocv = 3675000, .capacity = 10},
+	{ .ocv = 3673000, .capacity = 9},
+	{ .ocv = 3665000, .capacity = 7},
+	{ .ocv = 3649000, .capacity = 5},
+	{ .ocv = 3628000, .capacity = 4},
+	{ .ocv = 3585000, .capacity = 3},
+	{ .ocv = 3525000, .capacity = 2},
+	{ .ocv = 3441000, .capacity = 1},
+	{ .ocv = 3300000, .capacity = 0},
+};
+
+struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = {
+	{
+		/* Maintenance charging phase A, 60 hours */
+		.charge_current_max_ua = 600000,
+		.charge_voltage_max_uv = 4150000,
+		.charge_safety_timer_minutes = 60*60,
+	},
+	{
+		/* Maintenance charging phase B, 200 hours */
+		.charge_current_max_ua = 600000,
+		.charge_voltage_max_uv = 4100000,
+		.charge_safety_timer_minutes = 200*60,
+	}
+};
+
+struct samsung_sdi_battery samsung_sdi_batteries[] = {
+	{
+		/*
+		 * Used in Samsung GT-I8190 "Golden"
+		 * Data from vendor boardfile board-golden-bm.c
+		 */
+		.compatible = "samsung,eb-l1m7flu",
+		.name = "EB-L1M7FLU",
+		.info = {
+			.charge_full_design_uah = 1500000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 100000,
+			.factory_internal_resistance_charging_uohm = 200000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4350000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 900000,
+			.constant_charge_voltage_max_uv = 4340000,
+			.charge_term_current_ua = 200000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_1500mah,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
+			.vbat2ri_discharging = samsung_vbat2res_discharging_1500mah,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_1500mah),
+			.vbat2ri_charging = samsung_vbat2res_charging_1500mah,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_1500mah),
+			.bti_resistance_ohm = 2400,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+	{
+		/*
+		 * Used in Samsung SGH-T599 "Codina TMO" and SGH-I407 "Kyle"
+		 * Data from vendor boardfile board-kyle-bm.c
+		 */
+		.compatible = "samsung,eb425161la",
+		.name = "EB425161LA",
+		.info = {
+			.charge_full_design_uah = 1500000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 136000,
+			.factory_internal_resistance_charging_uohm = 200000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4350000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 900000,
+			.constant_charge_voltage_max_uv = 4340000,
+			.charge_term_current_ua = 200000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_1500mah,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
+			.vbat2ri_discharging = samsung_vbat2res_discharging_1500mah,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_1500mah),
+			.vbat2ri_charging = samsung_vbat2res_charging_1500mah,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_1500mah),
+			.bti_resistance_ohm = 2400,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+	{
+		/*
+		 * Used in Samsung GT-I8160 "Codina"
+		 * Data from vendor boardfile board-codina-bm.c
+		 */
+		.compatible = "samsung,eb425161lu",
+		.name = "EB425161LU",
+		.info = {
+			.charge_full_design_uah = 1500000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 100000,
+			.factory_internal_resistance_charging_uohm = 200000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4350000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 900000,
+			.constant_charge_voltage_max_uv = 4340000,
+			.charge_term_current_ua = 200000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_1500mah,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah),
+			.vbat2ri_discharging = samsung_vbat2res_discharging_1500mah,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_1500mah),
+			.vbat2ri_charging = samsung_vbat2res_charging_1500mah,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_1500mah),
+			.bti_resistance_ohm = 2400,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+	{
+		/*
+		 * Used in Samsung GT-S7710 "Skomer"
+		 * Data from vendor boardfile board-skomer-bm.c
+		 */
+		.compatible = "samsung,eb485159lu",
+		.name = "EB485159LU",
+		.info = {
+			.charge_full_design_uah = 1700000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 100000,
+			.factory_internal_resistance_charging_uohm = 200000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4350000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 900000,
+			.constant_charge_voltage_max_uv = 4340000,
+			.charge_term_current_ua = 200000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_1700mah,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1700mah),
+			/* CHECKME: vendor uses the 1500 mAh table, check against datasheet */
+			.vbat2ri_discharging = samsung_vbat2res_discharging_1500mah,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_1500mah),
+			.vbat2ri_charging = samsung_vbat2res_charging_1500mah,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_1500mah),
+			.bti_resistance_ohm = 2400,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+	{
+		/*
+		 * Used in Samsung GT-I9070 "Janice"
+		 * Data from vendor boardfile board-janice-bm.c
+		 */
+		.compatible = "samsung,eb535151vu",
+		.name = "EB535151VU",
+		.info = {
+			.charge_full_design_uah = 1500000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 100000,
+			.factory_internal_resistance_charging_uohm = 200000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4180000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 900000,
+			.constant_charge_voltage_max_uv = 4200000,
+			.charge_term_current_ua = 200000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_eb585157lu,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb585157lu),
+			.vbat2ri_discharging = samsung_vbat2res_discharging_eb585157lu,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb585157lu),
+			.vbat2ri_charging = samsung_vbat2res_charging_eb585157lu,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb585157lu),
+			.bti_resistance_ohm = 1500,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+	{
+		/*
+		 * Used in Samsung GT-I8530 "Gavini"
+		 * Data from vendor boardfile board-gavini-bm.c
+		 */
+		.compatible = "samsung,eb585157lu",
+		.name = "EB585157LU",
+		.info = {
+			.charge_full_design_uah = 2000000,
+			.technology = POWER_SUPPLY_TECHNOLOGY_LION,
+			.factory_internal_resistance_uohm = 105000,
+			.factory_internal_resistance_charging_uohm = 160000,
+			/* If you have data on this fix the min_design_uv */
+			.voltage_min_design_uv = 3300000,
+			.voltage_max_design_uv = 4320000,
+			.overvoltage_limit_uv = 4500000,
+			.constant_charge_current_max_ua = 1500000,
+			.constant_charge_voltage_max_uv = 4350000,
+			.charge_term_current_ua = 120000,
+			.maintenance_charge = samsung_maint_charge_table,
+			.maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table),
+			.alert_temp_charge_current_ua = 300000,
+			.alert_temp_charge_voltage_uv = 4000000,
+			.temp_min = -5,
+			.temp_alert_min = 0,
+			.temp_alert_max = 40,
+			.temp_max = 60,
+			.resist_table = samsung_temp2res,
+			.resist_table_size = ARRAY_SIZE(samsung_temp2res),
+			/* If you have tables for more temperatures, add them */
+			.ocv_temp[0] = 25,
+			.ocv_table[0] = samsung_ocv_cap_2000mah,
+			.ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_2000mah),
+			.vbat2ri_discharging = samsung_vbat2res_discharging_2000mah,
+			.vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_2000mah),
+			.vbat2ri_charging = samsung_vbat2res_charging_2000mah,
+			.vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_2000mah),
+			.bti_resistance_ohm = 2400,
+			.bti_resistance_tolerance = 20,
+		},
+	},
+};
+
+int samsung_sdi_battery_get_info(struct device *dev,
+				 struct device_node *np,
+				 struct power_supply_battery_info **info)
+{
+	struct samsung_sdi_battery *batt;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(samsung_sdi_batteries); i++) {
+		batt = &samsung_sdi_batteries[i];
+		if (of_device_is_compatible(np, batt->compatible))
+			break;
+	}
+
+	if (i == ARRAY_SIZE(samsung_sdi_batteries))
+		return -ENODEV;
+
+	*info = &batt->info;
+	dev_info(dev, "Samsung SDI %s battery %d mAh\n",
+		 batt->name, batt->info.charge_full_design_uah / 1000);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(samsung_sdi_battery_get_info);
diff --git a/drivers/power/supply/samsung-sdi-battery.h b/drivers/power/supply/samsung-sdi-battery.h
new file mode 100644
index 000000000000..08783847dfcb
--- /dev/null
+++ b/drivers/power/supply/samsung-sdi-battery.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG_SDI)
+extern int samsung_sdi_battery_get_info(struct device *dev,
+				struct device_node *np,
+				struct power_supply_battery_info **info);
+#else
+static inline int samsung_sdi_battery_get_info(struct device *dev,
+				struct device_node *np,
+				struct power_supply_battery_info **info)
+{
+	return -ENODEV;
+}
+#endif
-- 
2.34.1


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

* Re: [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging
  2022-02-03 17:16 ` [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging Linus Walleij
@ 2022-02-04 14:49   ` Vaittinen, Matti
  2022-02-13  0:01     ` Linus Walleij
  0 siblings, 1 reply; 12+ messages in thread
From: Vaittinen, Matti @ 2022-02-04 14:49 UTC (permalink / raw)
  To: Linus Walleij, Sebastian Reichel, Marcus Cooper; +Cc: linux-pm

Hi dee Ho Linus,

On 2/3/22 19:16, Linus Walleij wrote:
> The AB8500 code is using a special current and voltage setting
> when the battery is in "alert mode", i.e. when it is starting
> to go outside normal operating conditions so it is too
> cold or too hot. This makes sense as a way for the charging
> algorithm to deal with hostile environments.
> 
> Add the needed members to the struct power_supply_battery_info,
> and switch the AB8500 charging code over to using this.

Thanks for making this generic.

> 
> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
> ---
>   drivers/power/supply/ab8500-bm.h         |  4 ----
>   drivers/power/supply/ab8500_bmdata.c     |  9 +++++++--
>   drivers/power/supply/ab8500_chargalg.c   |  4 ++--
>   drivers/power/supply/power_supply_core.c |  2 ++
>   include/linux/power_supply.h             | 10 ++++++++++
>   5 files changed, 21 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
> index 4d74d21cf1eb..91ef9d4a5222 100644
> --- a/drivers/power/supply/ab8500-bm.h
> +++ b/drivers/power/supply/ab8500-bm.h
> @@ -331,14 +331,10 @@ struct ab8500_maxim_parameters {
>    * struct ab8500_battery_type - different batteries supported
>    * @resis_high:			battery upper resistance limit
>    * @resis_low:			battery lower resistance limit
> - * @low_high_cur_lvl:		charger current in temp low/high state in mA
> - * @low_high_vol_lvl:		charger voltage in temp low/high state in mV'
>    */
>   struct ab8500_battery_type {
>   	int resis_high;
>   	int resis_low;
> -	int low_high_cur_lvl;
> -	int low_high_vol_lvl;

I am just wondering if we might have cases where the 'mitigation action' 
should be different for low and high temperature alerts? As this is 
going to the DT it'd be nice to be prepared for different cases. I am 
definitely not an expert here but I could imagine that in some case 
increasing charge current at low temperature could warm-up the battery 
allowing charging for few more minutes(?) May be I am not making any 
sense here so please just ignore me if this sounds like nonsense - I 
keep often talking more and faster than thinking.

Best Regards
	-- Matti Vaittinen

-- 
The Linux Kernel guy at ROHM Semiconductors

Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~ this year is the year of a signature writers block ~~

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

* Re: [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging
  2022-02-03 17:16 ` [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging Linus Walleij
@ 2022-02-07 11:15   ` Vaittinen, Matti
  2022-02-12 23:21     ` Linus Walleij
  0 siblings, 1 reply; 12+ messages in thread
From: Vaittinen, Matti @ 2022-02-07 11:15 UTC (permalink / raw)
  To: Linus Walleij, Sebastian Reichel, Marcus Cooper; +Cc: linux-pm, hdegoede

Hello Linus,

I found this laying in my inbox.
/* tiny bit of sarcasm */
After weekend full of FOSDEM it's good to clear my head by reading some 
solid code :p So thanks for this.
/* Sarcasm stops here */

On 2/3/22 19:16, Linus Walleij wrote:
> Maintenance charging is the phase of keeping up the charge
> after the battery has charged fully using CC/CV charging.
> 
> This can be done in many successive phases and is usually
> done with a slightly lower constant voltage than CV, and
> a slightly lower allowed current.
> 
> Add an array of maintenance charging points each with a
> current, voltage and safety timer, and add helper functions
> to use these. Migrate the AB8500 code over.

Lucklily you always make it relatively easy by actually explaining what 
happens. :) I do like your descriptions.

> 
> This is used in several Samsung products using the AB8500
> and these batteries and their complete parameters will
> be added later as full examples, but the default battery
> in the AB8500 code serves as a reasonable example so far.
> 
> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
> ---
>   drivers/power/supply/ab8500-bm.h         | 14 ------
>   drivers/power/supply/ab8500_bmdata.c     | 27 +++++++---
>   drivers/power/supply/ab8500_chargalg.c   | 41 +++++++++++----
>   drivers/power/supply/power_supply_core.c | 11 ++++
>   include/linux/power_supply.h             | 64 ++++++++++++++++++++++++
>   5 files changed, 126 insertions(+), 31 deletions(-)
> 
> diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h
> index 6efd5174dbce..4d74d21cf1eb 100644
> --- a/drivers/power/supply/ab8500-bm.h
> +++ b/drivers/power/supply/ab8500-bm.h
> @@ -331,24 +331,12 @@ struct ab8500_maxim_parameters {
>    * struct ab8500_battery_type - different batteries supported
>    * @resis_high:			battery upper resistance limit
>    * @resis_low:			battery lower resistance limit
> - * @maint_a_cur_lvl:		charger current in maintenance A state in mA
> - * @maint_a_vol_lvl:		charger voltage in maintenance A state in mV
> - * @maint_a_chg_timer_h:	charge time in maintenance A state
> - * @maint_b_cur_lvl:		charger current in maintenance B state in mA
> - * @maint_b_vol_lvl:		charger voltage in maintenance B state in mV
> - * @maint_b_chg_timer_h:	charge time in maintenance B state
>    * @low_high_cur_lvl:		charger current in temp low/high state in mA
>    * @low_high_vol_lvl:		charger voltage in temp low/high state in mV'
>    */
>   struct ab8500_battery_type {
>   	int resis_high;
>   	int resis_low;
> -	int maint_a_cur_lvl;
> -	int maint_a_vol_lvl;
> -	int maint_a_chg_timer_h;
> -	int maint_b_cur_lvl;
> -	int maint_b_vol_lvl;
> -	int maint_b_chg_timer_h;
>   	int low_high_cur_lvl;
>   	int low_high_vol_lvl;
>   };
> @@ -393,7 +381,6 @@ struct ab8500_bm_charger_parameters {
>    * @usb_safety_tmr_h	safety timer for usb charger
>    * @bkup_bat_v		voltage which we charge the backup battery with
>    * @bkup_bat_i		current which we charge the backup battery with
> - * @no_maintenance	indicates that maintenance charging is disabled
>    * @capacity_scaling    indicates whether capacity scaling is to be used
>    * @chg_unknown_bat	flag to enable charging of unknown batteries
>    * @enable_overshoot	flag to enable VBAT overshoot control
> @@ -417,7 +404,6 @@ struct ab8500_bm_data {
>   	int usb_safety_tmr_h;
>   	int bkup_bat_v;
>   	int bkup_bat_i;
> -	bool no_maintenance;
>   	bool capacity_scaling;
>   	bool chg_unknown_bat;
>   	bool enable_overshoot;
> diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
> index d8fc72be0f0e..c104afe83b4b 100644
> --- a/drivers/power/supply/ab8500_bmdata.c
> +++ b/drivers/power/supply/ab8500_bmdata.c
> @@ -58,16 +58,25 @@ static struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[]
>   	{ .temp = -20, .resistance = 198 /* 595 mOhm */ },
>   };
>   
> +struct power_supply_maintenance_charge_table maint_charge_table[] = {

Could this be static const? And maybe less generic name?

> +	{
> +		/* Maintenance charging phase A, 60 hours */
> +		.charge_current_max_ua = 400000,
> +		.charge_voltage_max_uv = 4050000,
> +		.charge_safety_timer_minutes = 60*60,
> +	},
> +	{
> +		/* Maintenance charging phase B, 200 hours */
> +		.charge_current_max_ua = 400000,
> +		.charge_voltage_max_uv = 4000000,
> +		.charge_safety_timer_minutes = 200*60,
> +	}
> +};
> +
>   /* Default battery type for reference designs is the unknown type */
>   static struct ab8500_battery_type bat_type_thermistor_unknown = {
>   	.resis_high = 0,
>   	.resis_low = 0,
> -	.maint_a_cur_lvl = 400,
> -	.maint_a_vol_lvl = 4050,
> -	.maint_a_chg_timer_h = 60,
> -	.maint_b_cur_lvl = 400,
> -	.maint_b_vol_lvl = 4000,
> -	.maint_b_chg_timer_h = 200,
>   	.low_high_cur_lvl = 300,
>   	.low_high_vol_lvl = 4000,
>   };
> @@ -124,7 +133,6 @@ struct ab8500_bm_data ab8500_bm_data = {

static ?

>   	.usb_safety_tmr_h       = 4,
>   	.bkup_bat_v             = BUP_VCH_SEL_2P6V,
>   	.bkup_bat_i             = BUP_ICH_SEL_150UA,
> -	.no_maintenance         = false,
>   	.capacity_scaling       = false,
>   	.chg_unknown_bat        = false,
>   	.enable_overshoot       = false,
> @@ -179,6 +187,11 @@ int ab8500_bm_of_probe(struct power_supply *psy,
>   		/* Charging stops when we drop below this current */
>   		bi->charge_term_current_ua = 200000;
>   
> +	if (!bi->maintenance_charge || !bi->maintenance_charge_size) {
> +		bi->maintenance_charge = maint_charge_table;
> +		bi->maintenance_charge_size = ARRAY_SIZE(maint_charge_table);
> +	}
> +
>   	/*
>   	 * Internal resistance and factory resistance are tightly coupled
>   	 * so both MUST be defined or we fall back to defaults.
> diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c
> index b5a3096e78a1..6054996b6260 100644
> --- a/drivers/power/supply/ab8500_chargalg.c
> +++ b/drivers/power/supply/ab8500_chargalg.c
> @@ -430,7 +430,7 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
>   /**
>    * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer
>    * @di:		pointer to the ab8500_chargalg structure
> - * @duration:	duration of ther maintenance timer in hours
> + * @duration:	duration of ther maintenance timer in minutes
>    *
>    * The maintenance timer is used to maintain the charge in the battery once
>    * the battery is considered full. These timers are chosen to match the
> @@ -439,9 +439,10 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
>   static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di,
>   	int duration)
>   {
> +	/* Set a timer in minutes with a 30 second range */
>   	hrtimer_set_expires_range(&di->maintenance_timer,
> -		ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
> -		ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
> +		ktime_set(duration * 60, 0),
> +		ktime_set(30, 0));
>   	di->events.maintenance_timer_expired = false;
>   	hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
>   }
> @@ -1223,6 +1224,7 @@ static void ab8500_chargalg_external_power_changed(struct power_supply *psy)
>   static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
>   {
>   	struct power_supply_battery_info *bi = di->bm->bi;
> +	struct power_supply_maintenance_charge_table *mt;
>   	int charger_status;
>   	int ret;
>   
> @@ -1433,7 +1435,12 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
>   		handle_maxim_chg_curr(di);
>   		if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
>   			di->maintenance_chg) {
> -			if (di->bm->no_maintenance)
> +			/*
> +			 * The battery is fully charged, check if we support
> +			 * maintenance charging else go back to waiting for
> +			 * the recharge voltage limit.
> +			 */
> +			if (!power_supply_supports_maintenance_charging(bi))
>   				ab8500_chargalg_state_to(di,
>   					STATE_WAIT_FOR_RECHARGE_INIT);
>   			else
> @@ -1454,12 +1461,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
>   		break;
>   
>   	case STATE_MAINTENANCE_A_INIT:
> +		mt = power_supply_get_maintenance_charging_setting(bi, 0);
> +		if (!mt) {
> +			/* No maintenance A state, go back to normal */
> +			ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
> +			power_supply_changed(di->chargalg_psy);
> +			break;
> +		}
>   		ab8500_chargalg_stop_safety_timer(di);
>   		ab8500_chargalg_start_maintenance_timer(di,
> -			di->bm->bat_type->maint_a_chg_timer_h);
> +			mt->charge_safety_timer_minutes);
>   		ab8500_chargalg_start_charging(di,
> -			di->bm->bat_type->maint_a_vol_lvl,
> -			di->bm->bat_type->maint_a_cur_lvl);
> +			mt->charge_voltage_max_uv,
> +			mt->charge_current_max_ua);
>   		ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A);
>   		power_supply_changed(di->chargalg_psy);
>   		fallthrough;
> @@ -1472,11 +1486,18 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
>   		break;
>   
>   	case STATE_MAINTENANCE_B_INIT:
> +		mt = power_supply_get_maintenance_charging_setting(bi, 1);
> +		if (!mt) {
> +			/* No maintenance B state, go back to normal */
> +			ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
> +			power_supply_changed(di->chargalg_psy);
> +			break;
> +		}
>   		ab8500_chargalg_start_maintenance_timer(di,
> -			di->bm->bat_type->maint_b_chg_timer_h);
> +			mt->charge_safety_timer_minutes);
>   		ab8500_chargalg_start_charging(di,
> -			di->bm->bat_type->maint_b_vol_lvl,
> -			di->bm->bat_type->maint_b_cur_lvl);
> +			mt->charge_voltage_max_uv,
> +			mt->charge_current_max_ua);
>   		ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B);
>   		power_supply_changed(di->chargalg_psy);
>   		fallthrough;
> diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
> index ec838c9bcc0a..6568939e4518 100644
> --- a/drivers/power/supply/power_supply_core.c
> +++ b/drivers/power/supply/power_supply_core.c
> @@ -590,6 +590,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
>   	info->precharge_voltage_max_uv       = -EINVAL;
>   	info->charge_restart_voltage_uv      = -EINVAL;
>   	info->overvoltage_limit_uv           = -EINVAL;
> +	info->maintenance_charge             = NULL;
>   	info->temp_ambient_alert_min         = INT_MIN;
>   	info->temp_ambient_alert_max         = INT_MAX;
>   	info->temp_alert_min                 = INT_MIN;
> @@ -821,6 +822,16 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t
>   }
>   EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
>   
> +struct power_supply_maintenance_charge_table *
> +power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info,
> +					      int index)
> +{
> +	if (index >= info->maintenance_charge_size)
> +		return NULL;
> +	return &info->maintenance_charge[index];
> +}
> +EXPORT_SYMBOL_GPL(power_supply_get_maintenance_charging_setting);
> +
>   /**
>    * power_supply_ocv2cap_simple() - find the battery capacity
>    * @table: Pointer to battery OCV lookup table
> diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
> index e218041cc000..b998fc4c87ae 100644
> --- a/include/linux/power_supply.h
> +++ b/include/linux/power_supply.h
> @@ -348,6 +348,52 @@ struct power_supply_resistance_temp_table {
>   	int resistance;	/* internal resistance percent */
>   };
>   
> +/**
> + * struct power_supply_maintenance_charge_table - setting for maintenace charging
> + * @charge_current_max_ua: maintenance charging current that is used to keep
> + *   the charge of the battery full as current is consumed after full charging.
> + *   The corresponding charge_voltage_max_uv is used as a safeguard: when we
> + *   reach this voltage the maintenance charging current is turned off. It is
> + *   turned back on if we fall below this voltage.
> + * @charge_voltage_max_uv: maintenance charging voltage that is usually a bit
> + *   lower than the constant_charge_voltage_max_uv. We can apply this settings
> + *   charge_current_max_ua until we get back up to this voltage.
> + * @safety_timer_minutes: maintenance charging safety timer, with an expiry
> + *   time in minutes. We will only use maintenance charging in this setting
> + *   for a certain amount of time, then we will first move to the next
> + *   maintenance charge current and voltage pair in respective array and wait
> + *   for the next safety timer timeout, or, if we reached the last maintencance
> + *   charging setting, disable charging until we reach
> + *   charge_restart_voltage_uv and restart ordinary CC/CV charging from there.
> + *   These timers should be chosen to align with the typical discharge curve
> + *   for the battery.
> + *
> + * When the main CC/CV charging is complete the battery can optionally be
> + * maintenance charged at the voltages from this table: a table of settings is
> + * traversed using a slightly lower current and voltage than what is used for
> + * CC/CV charging. The maintenance charging will for safety reasons not go on
> + * indefinately: we lower the current and voltage with successive maintenance
> + * settings, then disable charging completely after we reach the last one,
> + * and after that we do not restart charging until we reach
> + * charge_restart_voltage_uv (see struct power_supply_battery_info) and restart
> + * ordinary CC/CV charging from there.
> + *
> + * As an example, a Samsung EB425161LA Lithium-Ion battery is CC/CV charged
> + * at 900mA to 4340mV, then maintenance charged at 600mA and 4150mV for
> + * 60 hours, then maintenance charged at 600mA and 4100mV for 200 hours.
> + * After this the charge cycle is restarted waiting for
> + * charge_restart_voltage_uv.
> + *
> + * For most mobile electronics this type of maintenance charging is enough for
> + * the user to disconnect the device and make use of it before both maintenance
> + * charging cycles are complete.

I do like your way of adding proper documentation.

Following is not expected to be done for this patch series - but I think 
we should try to enhance the power-supply core to provide some more 
framework support. For example this maintenance charging could be taken 
care of by the core(?) Eg, drivers would only register the callbacks for 
setting charging currents/voltages - and potentially event of requesting 
the maintenance charging (unless core could detect this for the drivers).

I think bunch of drivers do polling or timers or at least IRQ bottom 
halves for fuel-gauging etc. For example the last series from Hans seems 
to need periodic coulomb counter polling for fuel-gauging. Perhaps we 
could try moving to direction of adding some common "loop 
implementation" to the core - and allow drivers just register required 
callbacks? I believe we could simplify many of the drivers.

What do you think?

-- 
The Linux Kernel guy at ROHM Semiconductors

Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~ this year is the year of a signature writers block ~~

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

* Re: [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging
  2022-02-07 11:15   ` Vaittinen, Matti
@ 2022-02-12 23:21     ` Linus Walleij
  2022-02-16  6:28       ` Vaittinen, Matti
  0 siblings, 1 reply; 12+ messages in thread
From: Linus Walleij @ 2022-02-12 23:21 UTC (permalink / raw)
  To: Vaittinen, Matti; +Cc: Sebastian Reichel, Marcus Cooper, linux-pm, hdegoede

On Mon, Feb 7, 2022 at 12:15 PM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

> > @@ -124,7 +133,6 @@ struct ab8500_bm_data ab8500_bm_data = {
>
> static ?

Could be, but not related to this patch, and gets deleted in this series :)

Fixed the other static.

> Following is not expected to be done for this patch series - but I think
> we should try to enhance the power-supply core to provide some more
> framework support. For example this maintenance charging could be taken
> care of by the core(?) Eg, drivers would only register the callbacks for
> setting charging currents/voltages - and potentially event of requesting
> the maintenance charging (unless core could detect this for the drivers).
>
> I think bunch of drivers do polling or timers or at least IRQ bottom
> halves for fuel-gauging etc. For example the last series from Hans seems
> to need periodic coulomb counter polling for fuel-gauging. Perhaps we
> could try moving to direction of adding some common "loop
> implementation" to the core - and allow drivers just register required
> callbacks? I believe we could simplify many of the drivers.

We have this loop in ab8500_chargalg.c, it's rather a state machine,
ab8500_chargalg_algorithm() is called periodically from
ab8500_chargalg_periodic_work() which just constantly
queues itself. So it is a HZ-based loop.

which I think is a good model because many charging chips are
essentially state machines written in vhdl, so we can mimic these
with code.

This charger has these states:

enum ab8500_chargalg_states {
        STATE_HANDHELD_INIT,
        STATE_HANDHELD,
        STATE_CHG_NOT_OK_INIT,
        STATE_CHG_NOT_OK,
        STATE_HW_TEMP_PROTECT_INIT,
        STATE_HW_TEMP_PROTECT,
        STATE_NORMAL_INIT,
        STATE_NORMAL,
        STATE_WAIT_FOR_RECHARGE_INIT,
        STATE_WAIT_FOR_RECHARGE,
        STATE_MAINTENANCE_A_INIT,
        STATE_MAINTENANCE_A,
        STATE_MAINTENANCE_B_INIT,
        STATE_MAINTENANCE_B,
        STATE_TEMP_UNDEROVER_INIT,
        STATE_TEMP_UNDEROVER,
        STATE_TEMP_LOWHIGH_INIT,
        STATE_TEMP_LOWHIGH,
        STATE_OVV_PROTECT_INIT,
        STATE_OVV_PROTECT,
        STATE_SAFETY_TIMER_EXPIRED_INIT,
        STATE_SAFETY_TIMER_EXPIRED,
        STATE_BATT_REMOVED_INIT,
        STATE_BATT_REMOVED,
        STATE_WD_EXPIRED_INIT,
        STATE_WD_EXPIRED,
};

The _INIT states are just a name for the first entry of a new state.

For each state there is one or two callbacks to code handling the
hardware.

This I think could be centralized, but I don't have the picture of what
all software controlled chargers could need? (Also short on time right
now but that will change I suppose.)

Yours,
Linus Walleij

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

* Re: [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging
  2022-02-04 14:49   ` Vaittinen, Matti
@ 2022-02-13  0:01     ` Linus Walleij
  0 siblings, 0 replies; 12+ messages in thread
From: Linus Walleij @ 2022-02-13  0:01 UTC (permalink / raw)
  To: Vaittinen, Matti; +Cc: Sebastian Reichel, Marcus Cooper, linux-pm

On Fri, Feb 4, 2022 at 3:49 PM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

> I am just wondering if we might have cases where the 'mitigation action'
> should be different for low and high temperature alerts? As this is
> going to the DT it'd be nice to be prepared for different cases. I am
> definitely not an expert here but I could imagine that in some case
> increasing charge current at low temperature could warm-up the battery
> allowing charging for few more minutes(?) May be I am not making any
> sense here so please just ignore me if this sounds like nonsense - I
> keep often talking more and faster than thinking.

It makes perfect sense of course.

I augmented the patch to account for this, and also augmented the
AB8500 charging algorithm to support the two cases, sadly I don't have
corresponding battery data from Samsung so the currents and voltages
will be the same in my static data, but it makes a lot of sense
so let's do it like this.

Yours,
Linus Walleij

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

* Re: [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging
  2022-02-12 23:21     ` Linus Walleij
@ 2022-02-16  6:28       ` Vaittinen, Matti
  0 siblings, 0 replies; 12+ messages in thread
From: Vaittinen, Matti @ 2022-02-16  6:28 UTC (permalink / raw)
  To: Linus Walleij; +Cc: Sebastian Reichel, Marcus Cooper, linux-pm, hdegoede

On 2/13/22 01:21, Linus Walleij wrote:
> On Mon, Feb 7, 2022 at 12:15 PM Vaittinen, Matti
> <Matti.Vaittinen@fi.rohmeurope.com> wrote:
> 
>>> @@ -124,7 +133,6 @@ struct ab8500_bm_data ab8500_bm_data = {
>>
>> static ?
> 
> Could be, but not related to this patch, and gets deleted in this series :)

Oh. Sorry for the noise. I misread the patch :)

> 
> Fixed the other static.
> 
>> Following is not expected to be done for this patch series - but I think
>> we should try to enhance the power-supply core to provide some more
>> framework support. For example this maintenance charging could be taken
>> care of by the core(?) Eg, drivers would only register the callbacks for
>> setting charging currents/voltages - and potentially event of requesting
>> the maintenance charging (unless core could detect this for the drivers).
>>
>> I think bunch of drivers do polling or timers or at least IRQ bottom
>> halves for fuel-gauging etc. For example the last series from Hans seems
>> to need periodic coulomb counter polling for fuel-gauging. Perhaps we
>> could try moving to direction of adding some common "loop
>> implementation" to the core - and allow drivers just register required
>> callbacks? I believe we could simplify many of the drivers.
> 
> We have this loop in ab8500_chargalg.c, it's rather a state machine,
> ab8500_chargalg_algorithm() is called periodically from
> ab8500_chargalg_periodic_work() which just constantly
> queues itself. So it is a HZ-based loop.

Yep. I have a feeling there are a few drivers which do have a 
'state-machine' loop for polling something and then kicking actions 
based on this. It'd be great if we could find some common set of states.

I think this same loop could be used for fuel-gauge (coulomb counter) 
polling. It seems to me that is also needed by some chargers. This is 
also why I originally sent out the RFC for simple-gauge.

I wonder if we could have a thread in core which would run at given 
interval (or upon a request) - and check the state + call state specific 
driver callbacks if such are registered && trigger the change 
notifications if state change warrants such.

I guess this is somewhat ambitious - but maybe it would be the direction 
to keep in mind. To my uneducated eyes it seems the charger/fuel-gauge 
drivers are largish - and many of them are implementing some sort of 
'state-machine' with somewhat similar logic.

I think we might be able to allow register a callback for 'checking the 
current state' (for state polling - this could be used by core) - and a 
function which could be called to trigger running the loop 'prematurely' 
for example from IRQ.

After that we would need the well defined set of states + callbacks - 
which I think is the hard part. I guess the way to go could be starting 
from some simple IC - and adding more states/callbacks when more 
complicated state machines gets to be supported?

> 
> which I think is a good model because many charging chips are
> essentially state machines written in vhdl, so we can mimic these
> with code.
> 
> This charger has these states:
> 
> enum ab8500_chargalg_states {
>          STATE_HANDHELD_INIT,
>          STATE_HANDHELD,
>          STATE_CHG_NOT_OK_INIT,
>          STATE_CHG_NOT_OK,
>          STATE_HW_TEMP_PROTECT_INIT,
>          STATE_HW_TEMP_PROTECT,
>          STATE_NORMAL_INIT,
>          STATE_NORMAL,
>          STATE_WAIT_FOR_RECHARGE_INIT,
>          STATE_WAIT_FOR_RECHARGE,
>          STATE_MAINTENANCE_A_INIT,
>          STATE_MAINTENANCE_A,
>          STATE_MAINTENANCE_B_INIT,
>          STATE_MAINTENANCE_B,
>          STATE_TEMP_UNDEROVER_INIT,
>          STATE_TEMP_UNDEROVER,
>          STATE_TEMP_LOWHIGH_INIT,
>          STATE_TEMP_LOWHIGH,
>          STATE_OVV_PROTECT_INIT,
>          STATE_OVV_PROTECT,
>          STATE_SAFETY_TIMER_EXPIRED_INIT,
>          STATE_SAFETY_TIMER_EXPIRED,
>          STATE_BATT_REMOVED_INIT,
>          STATE_BATT_REMOVED,
>          STATE_WD_EXPIRED_INIT,
>          STATE_WD_EXPIRED,
> };
> 
> The _INIT states are just a name for the first entry of a new state.
> 
> For each state there is one or two callbacks to code handling the
> hardware.
> 
> This I think could be centralized, but I don't have the picture of what
> all software controlled chargers could need?

Nor do I. And I doubt anyone can define perfect generic set of states. I 
guess that if we did start from some set of states - then we would have 
some set to refine with new chargers :) I've always liked the approach 
where one starts by doing something that serves a purpose - and then 
rewrites it as needed ;)

  (Also short on time right
> now but that will change I suppose.)

Uh. I know the feeling. That is also why the simple-gauge RFC gets new 
versions only every now and then... After the latest comments from you I 
tried to see if I could find some spline implementation that could be 
used in-kernel - and then I was again yanked to other tasks before I was 
able to accomplish anything. So no worries - I don't expect this common 
state-machine to be done for this patch series - I just wanted to hear 
your thoughts.

Best Regards
	--Matti

-- 
The Linux Kernel guy at ROHM Semiconductors

Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND

~~ this year is the year of a signature writers block ~~

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

end of thread, other threads:[~2022-02-16  6:29 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-03 17:16 [PATCH 0/6] AB8500 charging, the final steps Linus Walleij
2022-02-03 17:16 ` [PATCH 1/6] power: supply: ab8500: Standardize maintenance charging Linus Walleij
2022-02-07 11:15   ` Vaittinen, Matti
2022-02-12 23:21     ` Linus Walleij
2022-02-16  6:28       ` Vaittinen, Matti
2022-02-03 17:16 ` [PATCH 2/6] power: supply: ab8500: Standardize alert mode charging Linus Walleij
2022-02-04 14:49   ` Vaittinen, Matti
2022-02-13  0:01     ` Linus Walleij
2022-02-03 17:16 ` [PATCH 3/6] power: supply: ab8500: Standardize BTI resistance Linus Walleij
2022-02-03 17:16 ` [PATCH 4/6] power: supply: Support VBAT-to-Ri lookup tables Linus Walleij
2022-02-03 17:16 ` [PATCH 5/6] power: supply: ab8500_fg: Use VBAT-to-Ri if possible Linus Walleij
2022-02-03 17:16 ` [PATCH 6/6] power: supply: Static data for Samsung batteries Linus Walleij

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.