linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic
@ 2021-11-16 12:24 Matti Vaittinen
  2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
                   ` (8 more replies)
  0 siblings, 9 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:24 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

As usual, I picked you as recipients just because I assumed
you could have something to say in here. Again, please let me know
if you wish to be dropped from CC if this gets any further.

power: supply: add simple-gauge for SOC estimation and CC correction

This RFC has received some testing under qemu, with recorded
battery-data and a dummy MFD driver converting the battery-data to
register-values for BD71815 PMIC driver. The testing has not really
covered a lot though. Some tests are also ran on a BeagleBoneBlack
connected to a real PMIC hwrdware - but unfortunately without a real
battery. So - this is still not tested too well.

Regarding the purpose of this RFC:

Patches 1 - 6
I hope that the patches which change the power-supply class for static
battery node information  could be properly reviewed and perhaps even
considered to be merged upstream. It'd be great if someone had the time
to test those in real HW to ensure existing stuff is not borked.
Especially the patches 4 and 6 might be of interest.

The patch 7:
This adds some simple fuel-gauge logic in kernel. I think this is
getting it's shape - but may have few hiccups left - and could benefit
from suggestions how to run the gauge thread. A few downstream setups do
use separate ROHM drivers for BD71815, BD71817, BD71827, BD71828 and
BD71879 which all contain similar fuel-gauge logic to what is implented
in simple-gauge. They poll the PMIC and compute the temperature/capacity
and zero-corrected "state of charge" and report that to the user-space.
Additionally those drivers 'calibrate' the CC based on OCV when battery
is relaxed - or when battery is fully charged. The patch 7 attempts to
make this logic generic and allows IC drivers to fill the IC specific
operations.

It is fair to say that I don't know the user-space software which is
utilizing the existing drivers - or why the SoC computation is
originally placed in-kernel. I can only assume the existing IC users
might have a bit wider smile if the SOC was reported by the kernel also
in the future.

Patches 8 and 9:
The patches 8 and 9 bring in IC level support for ROHM BD71815 and BD71828
PMIC charger IPs. It is also fairly trivial to extend the support to
ND71827 and BD71879 later. The state of these patches is not final yet,
I don't expect them to be thoroughly reviewed yet. They are provided to
give some more context.

Changelog RFC v3:
  - rename sw-gauge to simple-gauge
  batinfo:
     - kerneldoc fixes
     - add batinfo getters usable prior supply registration
     - Add constant battery aging degradation to batinfo
  simple-gauge:
      - use devm_add_action_or_reset
      - Rename to simple_gauge
      - Limit access to power_supply parameters
      - Introduce simple_gauge_drvdata
      - no need to show simple_gauge at config menu. Users should just SELECT it.
      - allow tristate
      - Add blocking 'iteration run' - loop.
      - updated the comment section
      - Fixed clamped SOC which was not updated
      - Small comment improvements
      - Don't allow negative CC after computing capacity corrections
      - Fix gauge looping when last client exits
 ROHM ic-drivers:
      - Updated BD71815 IRQ information
      - adapt to simple_gauge parameter changes
      - Initial BD71815 support
      - Use drvdata properly.
      - Sort includes
      - Prepare to provide dcin_collapse voltage from DT
      - clean unused defines
      - use OCV tables from batinfo if module params not given
      - do not directly call bd71827_voltage_to_capacity from calibration
        but use provided operation.
      - Mask the power-state from relax-condition on BD71815 as is done by
        the ROHM driver. REX state is used to do OCV => SOC conversion
        when battery is relaxed even if REX_CC was not used.
      - Clarify that we require either the module params or DT values for
        battery. Fail probe if parameters are not given.
      - Utilize degrade_cycle_uah aging degradation.
      - Get battery max and min values either as module parameters or from
        static battery node at DT.
      - Allow giving the zero correction threshold as a module param or
        compute it as 10% of "remaining battery voltage" based on max and
        min voltages given via DT.
      - Add proper MODULE_ALIAS
      - Implement VDR table reading from DT
      - Do not require fixed amount of battery parameters
      - Fix Coulomb Counter to uAh conversion
      - Fix endianess related warnings
      - clean-up comment
      - Avoid dividing by zero at VDR computation
      - Use the fwnode API instead of of_* API
      . don't assume 32bit int
      - Fix IC type prints
      - Fix the current sense resistor DT property *-ohm => *-ohms

Changelog RFC v2:
 - lots of logic fixes.
 - rechecked units
 - changed low-voltage correction to capacity correction
 - added first draft of IC driver which could use the swgauge

---

Matti Vaittinen (9):
  dt-bindings: battery: Add temperature-capacity degradation table
  power: supply: add cap2ocv batinfo helper
  power: supply: Support DT originated temperature-capacity tables
  power: supply: Add batinfo getters usable prior supply registration
  power: supply: Add constant battery aging degradation to batinfo
  power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy
  power: supply: add simple-gauge for SOC estimation and CC correction
  mfd: bd71828, bd71815 prepare for power-supply support
  power: supply: Add bd718(15/27/28/78) charger driver

 .../bindings/power/supply/battery.yaml        |   19 +
 drivers/mfd/rohm-bd71828.c                    |   42 +-
 drivers/power/supply/Kconfig                  |   14 +
 drivers/power/supply/Makefile                 |    2 +
 drivers/power/supply/bd71827-power.c          | 2473 +++++++++++++++++
 drivers/power/supply/power_supply_core.c      |  449 ++-
 drivers/power/supply/simple-gauge.c           | 1303 +++++++++
 include/linux/mfd/rohm-bd71827.h              |  295 ++
 include/linux/mfd/rohm-bd71828.h              |   65 +
 include/linux/mfd/rohm-generic.h              |    2 +
 include/linux/power/simple_gauge.h            |  244 ++
 include/linux/power_supply.h                  |   41 +
 12 files changed, 4843 insertions(+), 106 deletions(-)
 create mode 100644 drivers/power/supply/bd71827-power.c
 create mode 100644 drivers/power/supply/simple-gauge.c
 create mode 100644 include/linux/mfd/rohm-bd71827.h
 create mode 100644 include/linux/power/simple_gauge.h

-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
@ 2021-11-16 12:24 ` Matti Vaittinen
  2021-11-16 14:02   ` Rob Herring
  2021-11-18  1:57   ` Linus Walleij
  2021-11-16 12:25 ` [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper Matti Vaittinen
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:24 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Some charger/battery vendors describe the temperature impact to
battery capacity by providing tables with capacity change at
given temperature. Support providing this temperature - capacity
dependency using the simple-battery DT nodes.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
 .../bindings/power/supply/battery.yaml        | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml
index d56ac484fec5..98cc85b92a71 100644
--- a/Documentation/devicetree/bindings/power/supply/battery.yaml
+++ b/Documentation/devicetree/bindings/power/supply/battery.yaml
@@ -114,6 +114,25 @@ properties:
       - description: alert when battery temperature is lower than this value
       - description: alert when battery temperature is higher than this value
 
+  temp-degrade-table:
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    description: |
+      An array of value triplets. First value is "capacity change per degree C"
+      when temperature differs from 'set point'. Second value is "capacity
+      degradation at given 'set point' temperature" and third value is "the
+      'set-point' temperature" where given degradation is correct.
+      Up to 100 value triplets can be provided to specify different degradation
+      for different temperature ranges. When capacity change caused by
+      temperatures is computed the range which 'set point' is closest to the
+      current temperature is used. Capacity change should be in units of
+      micro Ah. Temperature is in units of 0.1 C degree.
+    maxItems: 100
+    items:
+      items:
+        - description: capacity drop per degree C in micro Ah
+        - description: capacity drop at 'set point' temperature in micro Ah
+        - description: 'set point' temperature for this range in 0.1 degree C
+
 required:
   - compatible
 
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
  2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
@ 2021-11-16 12:25 ` Matti Vaittinen
  2021-11-18  2:02   ` Linus Walleij
  2021-11-16 12:25 ` [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Matti Vaittinen
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:25 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

The power-supply core supports concept of OCV (Open Circuit Voltage) =>
SOC (State Of Charge) conversion tables. Usually these tables are used
to estimate SOC based on OCV. Some systems use so called "Zero Adjust"
where at the near end-of-battery condition the SOC from coulomb counter
is used to retrieve the OCV - and OCV and VSYS difference is used to
re-estimate the battery capacity.

Add helper to do look-up the other-way around and also get the OCV
based on SOC

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

---
RFC v3:
 - Kerneldoc fixes
---
 drivers/power/supply/power_supply_core.c | 69 ++++++++++++++++++++++++
 include/linux/power_supply.h             |  5 ++
 2 files changed, 74 insertions(+)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index fc12a4f407f4..295672165836 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -806,6 +806,48 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t
 }
 EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
 
+/**
+ * power_supply_dcap2ocv_simple() - find the battery OCV by capacity
+ * @table: Pointer to battery OCV/CAP lookup table
+ * @table_len: OCV/CAP table length
+ * @cap: Current cap value in units of 0.1%
+ *
+ * OCV (Open Circuit Voltage) is often used to estimate the battery SOC (State
+ * Of Charge). Usually conversion tables are used to store the corresponding
+ * OCV and SOC. Systems which use so called "Zero Adjust" where at the near
+ * end-of-battery condition the SOC from coulomb counter is used to retrieve
+ * the OCV - and OCV and VSYS difference is used to re-estimate the battery
+ * capacity. This helper function can be used to look up battery OCV according
+ * to current capacity value from one OCV table, and the OCV table must be
+ * ordered descending.
+ *
+ * Return: the battery OCV in uV.
+ */
+int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table,
+				int table_len, int dcap)
+{
+	int i, ocv, tmp;
+
+	for (i = 0; i < table_len; i++)
+		if (dcap > table[i].capacity * 10)
+			break;
+
+	if (i > 0 && i < table_len) {
+		tmp = (table[i - 1].ocv - table[i].ocv) *
+		      (dcap - table[i].capacity * 10);
+
+		tmp /= (table[i - 1].capacity - table[i].capacity) * 10;
+		ocv = tmp + table[i].ocv;
+	} else if (i == 0) {
+		ocv = table[0].ocv;
+	} else {
+		ocv = table[table_len - 1].ocv;
+	}
+
+	return ocv;
+}
+EXPORT_SYMBOL_GPL(power_supply_dcap2ocv_simple);
+
 /**
  * power_supply_ocv2cap_simple() - find the battery capacity
  * @table: Pointer to battery OCV lookup table
@@ -866,6 +908,33 @@ power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
 }
 EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table);
 
+/**
+ * power_supply_batinfo_dcap2ocv() - Compute OCV based on SOC
+ * @info: Pointer to battery information.
+ * @dcao: Battery capacity in units of 0.1%
+ * @temp: Temperatur in Celsius
+ *
+ * Compute the open circuit voltage at given temperature matching given
+ * capacity for a battery described by given battery info. Computation is
+ * done based on tables of known capacity - open circuit voltage value pairs.
+ * Requires the OCV tables being populated in the battery info.
+ *
+ * Return: The battery OCV in uV or -EINVAL if OCV table is not available.
+ */
+int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info,
+				 int dcap, int temp)
+{
+	struct power_supply_battery_ocv_table *table;
+	int table_len;
+
+	table = power_supply_find_ocv2cap_table(info, temp, &table_len);
+	if (!table)
+		return -EINVAL;
+
+	return power_supply_dcap2ocv_simple(table, table_len, dcap);
+}
+EXPORT_SYMBOL_GPL(power_supply_batinfo_dcap2ocv);
+
 int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 				 int ocv, int temp)
 {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 9ca1f120a211..fa8cf434f7e3 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -410,11 +410,16 @@ extern void power_supply_put_battery_info(struct power_supply *psy,
 					  struct power_supply_battery_info *info);
 extern int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table,
 				       int table_len, int ocv);
+int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table,
+				int table_len, int dcap);
+
 extern struct power_supply_battery_ocv_table *
 power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
 				int temp, int *table_len);
 extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 					int ocv, int temp);
+int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info,
+				 int dcap, int temp);
 extern int
 power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
 				int table_len, int temp);
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
  2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
  2021-11-16 12:25 ` [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper Matti Vaittinen
@ 2021-11-16 12:25 ` Matti Vaittinen
  2021-11-18  2:10   ` Linus Walleij
  2021-11-16 12:26 ` [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration Matti Vaittinen
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:25 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Support obtaining the "capacity degradation by temperature" - tables
from device-tree to batinfo.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

---
RFCv3:
 - rename simple_gauge_temp_degr to power_supply_temp_degr
---
 drivers/power/supply/power_supply_core.c | 53 ++++++++++++++++++++++++
 include/linux/power_supply.h             | 26 ++++++++++++
 2 files changed, 79 insertions(+)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 295672165836..1a21f692ab81 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -562,10 +562,12 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
 EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
 #endif /* CONFIG_OF */
 
+#define POWER_SUPPLY_TEMP_DGRD_MAX_VALUES 100
 int power_supply_get_battery_info(struct power_supply *psy,
 				  struct power_supply_battery_info *info)
 {
 	struct power_supply_resistance_temp_table *resist_table;
+	u32 *dgrd_table;
 	struct device_node *battery_np;
 	const char *value;
 	int err, len, index;
@@ -588,6 +590,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	info->temp_max                       = INT_MAX;
 	info->factory_internal_resistance_uohm  = -EINVAL;
 	info->resist_table = NULL;
+	info->temp_dgrd_values = 0;
+	info->temp_dgrd = NULL;
 
 	for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
 		info->ocv_table[index]       = NULL;
@@ -677,6 +681,55 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	of_property_read_u32_index(battery_np, "operating-range-celsius",
 				   1, &info->temp_max);
 
+	len = of_property_count_u32_elems(battery_np, "temp-degrade-table");
+	if (len == -EINVAL)
+		len = 0;
+	if (len < 0) {
+		err = len;
+		goto out_put_node;
+	}
+	/* table should consist of value pairs - maximum of 100 pairs */
+	if (len % 3 || len / 3 > POWER_SUPPLY_TEMP_DGRD_MAX_VALUES) {
+		dev_warn(&psy->dev,
+			 "bad amount of temperature-capacity degrade values\n");
+		err = -EINVAL;
+		goto out_put_node;
+	}
+	info->temp_dgrd_values = len / 3;
+	if (info->temp_dgrd_values) {
+		info->temp_dgrd = devm_kcalloc(&psy->dev,
+					       info->temp_dgrd_values,
+					       sizeof(*info->temp_dgrd),
+					       GFP_KERNEL);
+		if (!info->temp_dgrd) {
+			err = -ENOMEM;
+			goto out_put_node;
+		}
+		dgrd_table = kcalloc(len, sizeof(*dgrd_table), GFP_KERNEL);
+		if (!dgrd_table) {
+			err = -ENOMEM;
+			goto out_put_node;
+		}
+		err = of_property_read_u32_array(battery_np,
+						 "temp-degrade-table",
+						 dgrd_table, len);
+		if (err) {
+			dev_warn(&psy->dev,
+				 "bad temperature - capacity degrade values %d\n", err);
+			kfree(dgrd_table);
+			info->temp_dgrd_values = 0;
+			goto out_put_node;
+		}
+		for (index = 0; index < info->temp_dgrd_values; index++) {
+			struct power_supply_temp_degr *d = &info->temp_dgrd[index];
+
+			d->temp_degrade_1C = dgrd_table[index * 3];
+			d->degrade_at_set = dgrd_table[index * 3 + 1];
+			d->temp_set_point = dgrd_table[index * 3 + 2];
+		}
+		kfree(dgrd_table);
+	}
+
 	len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
 	if (len < 0 && len != -EINVAL) {
 		err = len;
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index fa8cf434f7e3..fbc07d403f41 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -214,6 +214,30 @@ union power_supply_propval {
 struct device_node;
 struct power_supply;
 
+/**
+ * struct power_supply_temp_degr - impact of temperature to battery capacity
+ *
+ * Usually temperature impacts on battery capacity. For systems where it is
+ * sufficient to describe capacity change as a series of temperature ranges
+ * where the change is linear (Eg delta cap = temperature_change * constant +
+ * offset) can be described by this structure.
+ *
+ * Please note - in order to avoid unnecessary rounding errors the change
+ * of capacity (uAh) is per change of temperature degree C while the temperature
+ * range floor is in tenths of degree C
+ *
+ * @temp_set_point:	Temperature where cap change is as given in
+ *			degrade_at_set. Units are 0.1 degree C
+ * @degrade_at_set:	Capacity difference (from ideal) at temp_set_point
+ *			temperature
+ * @temp_degrade_1C:	Capacity change / temperature change (uAh / degree C)
+ */
+struct power_supply_temp_degr {
+	int temp_set_point;
+	int degrade_at_set;
+	int temp_degrade_1C;
+};
+
 /* Run-time specific power supply configuration */
 struct power_supply_config {
 	struct device_node *of_node;
@@ -377,6 +401,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 temp_dgrd_values;
+	struct power_supply_temp_degr *temp_dgrd;
 };
 
 extern struct atomic_notifier_head power_supply_notifier;
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (2 preceding siblings ...)
  2021-11-16 12:25 ` [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Matti Vaittinen
@ 2021-11-16 12:26 ` Matti Vaittinen
  2021-11-19  1:42   ` Linus Walleij
  2021-11-16 12:27 ` [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo Matti Vaittinen
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:26 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

In some cases it is beneficial to be able to query the static battery
node properties prior power_supply registration. The device-tree
parsing does not really depend on psy so add APIs which can
be used without the power-supply. Also, convert APIs to operate on
fwnode while at it.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
RFCv3:
  - New patch
---
 drivers/power/supply/power_supply_core.c | 279 ++++++++++++++---------
 include/linux/power_supply.h             |   5 +
 2 files changed, 176 insertions(+), 108 deletions(-)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 1a21f692ab81..47176ed2570b 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -562,16 +562,63 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
 EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
 #endif /* CONFIG_OF */
 
+struct psy_int_tuple {
+	int a;
+	int b;
+};
+
+static int get_fwnode_tuple_array(struct device *dev, struct fwnode_handle *fw,
+				  const char *name,
+				  struct psy_int_tuple **tuple, int *num_tuple)
+{
+	int num_values, i, ret;
+	u32 *tmp_table;
+
+	num_values = fwnode_property_count_u32(fw, name);
+	if (num_values <= 0) {
+		dev_err(dev, "failed to get %s\n", name);
+		return -EINVAL;
+	}
+
+	if (num_values & 0x1)
+		dev_warn(dev, "odd number of '%s' values\n", name);
+
+	tmp_table = kcalloc(num_values, sizeof(*tmp_table), GFP_KERNEL);
+	if (!tmp_table)
+		return -ENOMEM;
+
+	*tuple = devm_kcalloc(dev, num_values / 2, sizeof(**tuple),
+			       GFP_KERNEL);
+	if (!*tuple) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	ret = fwnode_property_read_u32_array(fw, name, tmp_table, num_values);
+	if (ret)
+		goto out;
+
+	*num_tuple = num_values / 2;
+	for (i = 0; i < *num_tuple; i++) {
+		(*tuple)[i].a = tmp_table[i * 2];
+		(*tuple)[i].b = tmp_table[i * 2 + 1];
+	}
+
+out:
+	kfree(tmp_table);
+
+	return ret;
+}
+
 #define POWER_SUPPLY_TEMP_DGRD_MAX_VALUES 100
-int power_supply_get_battery_info(struct power_supply *psy,
-				  struct power_supply_battery_info *info)
+int power_supply_dev_get_battery_info(struct device *dev,
+				      struct fwnode_handle *node,
+				      struct power_supply_battery_info *info)
 {
-	struct power_supply_resistance_temp_table *resist_table;
 	u32 *dgrd_table;
-	struct device_node *battery_np;
-	const char *value;
+	struct fwnode_handle *battery_node;
 	int err, len, index;
-	const __be32 *list;
+	const char *value;
+	u32 tuple[2];
 
 	info->technology                     = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
 	info->energy_full_design_uwh         = -EINVAL;
@@ -599,21 +646,23 @@ 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;
-	}
+	if (!node)
+		node = dev_fwnode(dev);
 
-	battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
-	if (!battery_np)
+	if (!node) {
+		dev_err(dev, "no charger node\n");
 		return -ENODEV;
+	}
 
-	err = of_property_read_string(battery_np, "compatible", &value);
-	if (err)
-		goto out_put_node;
+	battery_node = fwnode_find_reference(node, "monitored-battery", 0);
+	if (IS_ERR(battery_node)) {
+		dev_err(dev, "No battery node found\n");
+		return PTR_ERR(battery_node);
+	}
+
+	if (fwnode_property_match_string(battery_node, "compatible",
+					 "simple-battery")) {
 
-	if (strcmp("simple-battery", value)) {
 		err = -ENODEV;
 		goto out_put_node;
 	}
@@ -622,8 +671,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 	 * in enum power_supply_property. For reasoning, see
 	 * Documentation/power/power_supply_class.rst.
 	 */
-
-	if (!of_property_read_string(battery_np, "device-chemistry", &value)) {
+	if (!fwnode_property_read_string(battery_node, "device-chemistry", &value)) {
 		if (!strcmp("nickel-cadmium", value))
 			info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
 		else if (!strcmp("nickel-metal-hydride", value))
@@ -638,67 +686,73 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		else if (!strcmp("lithium-ion-manganese-oxide", value))
 			info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
 		else
-			dev_warn(&psy->dev, "%s unknown battery type\n", value);
+			dev_warn(dev, "%s unknown battery type\n", value);
 	}
 
-	of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
+	fwnode_property_read_u32(battery_node, "energy-full-design-microwatt-hours",
 			     &info->energy_full_design_uwh);
-	of_property_read_u32(battery_np, "charge-full-design-microamp-hours",
+	fwnode_property_read_u32(battery_node, "charge-full-design-microamp-hours",
 			     &info->charge_full_design_uah);
-	of_property_read_u32(battery_np, "voltage-min-design-microvolt",
+	fwnode_property_read_u32(battery_node, "voltage-min-design-microvolt",
 			     &info->voltage_min_design_uv);
-	of_property_read_u32(battery_np, "voltage-max-design-microvolt",
+	fwnode_property_read_u32(battery_node, "voltage-max-design-microvolt",
 			     &info->voltage_max_design_uv);
-	of_property_read_u32(battery_np, "trickle-charge-current-microamp",
+	fwnode_property_read_u32(battery_node, "trickle-charge-current-microamp",
 			     &info->tricklecharge_current_ua);
-	of_property_read_u32(battery_np, "precharge-current-microamp",
+	fwnode_property_read_u32(battery_node, "precharge-current-microamp",
 			     &info->precharge_current_ua);
-	of_property_read_u32(battery_np, "precharge-upper-limit-microvolt",
+	fwnode_property_read_u32(battery_node, "precharge-upper-limit-microvolt",
 			     &info->precharge_voltage_max_uv);
-	of_property_read_u32(battery_np, "charge-term-current-microamp",
+	fwnode_property_read_u32(battery_node, "charge-term-current-microamp",
 			     &info->charge_term_current_ua);
-	of_property_read_u32(battery_np, "re-charge-voltage-microvolt",
+	fwnode_property_read_u32(battery_node, "re-charge-voltage-microvolt",
 			     &info->charge_restart_voltage_uv);
-	of_property_read_u32(battery_np, "over-voltage-threshold-microvolt",
+	fwnode_property_read_u32(battery_node, "over-voltage-threshold-microvolt",
 			     &info->overvoltage_limit_uv);
-	of_property_read_u32(battery_np, "constant-charge-current-max-microamp",
+	fwnode_property_read_u32(battery_node, "constant-charge-current-max-microamp",
 			     &info->constant_charge_current_max_ua);
-	of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt",
+	fwnode_property_read_u32(battery_node, "constant-charge-voltage-max-microvolt",
 			     &info->constant_charge_voltage_max_uv);
-	of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
+	fwnode_property_read_u32(battery_node, "factory-internal-resistance-micro-ohms",
 			     &info->factory_internal_resistance_uohm);
 
-	of_property_read_u32_index(battery_np, "ambient-celsius",
-				   0, &info->temp_ambient_alert_min);
-	of_property_read_u32_index(battery_np, "ambient-celsius",
-				   1, &info->temp_ambient_alert_max);
-	of_property_read_u32_index(battery_np, "alert-celsius",
-				   0, &info->temp_alert_min);
-	of_property_read_u32_index(battery_np, "alert-celsius",
-				   1, &info->temp_alert_max);
-	of_property_read_u32_index(battery_np, "operating-range-celsius",
-				   0, &info->temp_min);
-	of_property_read_u32_index(battery_np, "operating-range-celsius",
-				   1, &info->temp_max);
-
-	len = of_property_count_u32_elems(battery_np, "temp-degrade-table");
+	if (!fwnode_property_read_u32_array(battery_node, "ambient-celsius",
+					    &tuple[0], 2)) {
+		info->temp_ambient_alert_min = tuple[0];
+		info->temp_ambient_alert_max = tuple[1];
+	}
+
+	if (!fwnode_property_read_u32_array(battery_node, "alert-celsius",
+					    &tuple[0], 2)) {
+		info->temp_alert_min = tuple[0];
+		info->temp_alert_max = tuple[1];
+	}
+
+	if (!fwnode_property_read_u32_array(battery_node,
+					    "operating-range-celsius",
+					    &tuple[0], 2)) {
+		info->temp_min = tuple[0];
+		info->temp_max = tuple[1];
+	}
+
+	len = fwnode_property_count_u32(battery_node, "temp-degrade-table");
 	if (len == -EINVAL)
 		len = 0;
 	if (len < 0) {
+		dev_err(dev, "malformed temp-degrade-table %d\n", len);
 		err = len;
 		goto out_put_node;
 	}
-	/* table should consist of value pairs - maximum of 100 pairs */
+	/* table should consist of value triplets - maximum of 100 triplets */
 	if (len % 3 || len / 3 > POWER_SUPPLY_TEMP_DGRD_MAX_VALUES) {
-		dev_warn(&psy->dev,
+		dev_warn(dev,
 			 "bad amount of temperature-capacity degrade values\n");
 		err = -EINVAL;
 		goto out_put_node;
 	}
 	info->temp_dgrd_values = len / 3;
 	if (info->temp_dgrd_values) {
-		info->temp_dgrd = devm_kcalloc(&psy->dev,
-					       info->temp_dgrd_values,
+		info->temp_dgrd = devm_kcalloc(dev, info->temp_dgrd_values,
 					       sizeof(*info->temp_dgrd),
 					       GFP_KERNEL);
 		if (!info->temp_dgrd) {
@@ -710,12 +764,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
 			err = -ENOMEM;
 			goto out_put_node;
 		}
-		err = of_property_read_u32_array(battery_np,
-						 "temp-degrade-table",
-						 dgrd_table, len);
+		err = fwnode_property_read_u32_array(battery_node,
+						     "temp-degrade-table",
+						     dgrd_table, len);
 		if (err) {
-			dev_warn(&psy->dev,
-				 "bad temperature - capacity degrade values %d\n", err);
+			dev_warn(dev,
+			       "bad temperature - capacity degrade values %d\n",
+			       err);
 			kfree(dgrd_table);
 			info->temp_dgrd_values = 0;
 			goto out_put_node;
@@ -730,92 +785,100 @@ int power_supply_get_battery_info(struct power_supply *psy,
 		kfree(dgrd_table);
 	}
 
-	len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
-	if (len < 0 && len != -EINVAL) {
+	len = fwnode_property_count_u32(battery_node, "ocv-capacity-celsius");
+	if (len == -EINVAL)
+		len = 0;
+
+	if (len < 0) {
+		dev_err(dev, "malformed ocv-capacity-celsius table\n");
 		err = len;
 		goto out_put_node;
 	} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
-		dev_err(&psy->dev, "Too many temperature values\n");
+		dev_err(dev, "Too many temperature values\n");
 		err = -EINVAL;
 		goto out_put_node;
 	} else if (len > 0) {
-		of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
-					   info->ocv_temp, len);
+		u32 *tmp;
+
+		tmp = kcalloc(len, sizeof(*tmp), GFP_KERNEL);
+		if (!tmp)
+			return -ENOMEM;
+
+		fwnode_property_read_u32_array(battery_node,
+					       "ocv-capacity-celsius",
+					       tmp, len);
+		for (index = 0; index < len; index++)
+			info->ocv_temp[index] = tmp[index];
+
+		kfree(tmp);
 	}
 
 	for (index = 0; index < len; index++) {
-		struct power_supply_battery_ocv_table *table;
 		char *propname;
-		int i, tab_len, size;
 
 		propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index);
-		list = of_get_property(battery_np, propname, &size);
-		if (!list || !size) {
-			dev_err(&psy->dev, "failed to get %s\n", propname);
+
+		err = get_fwnode_tuple_array(dev, battery_node, propname,
+					     (struct psy_int_tuple **)
+						&info->ocv_table[index],
+					     &info->ocv_table_size[index]);
+		if (err) {
 			kfree(propname);
-			power_supply_put_battery_info(psy, info);
-			err = -EINVAL;
+			power_supply_dev_put_battery_info(dev, info);
 			goto out_put_node;
 		}
-
 		kfree(propname);
-		tab_len = size / (2 * sizeof(__be32));
-		info->ocv_table_size[index] = tab_len;
-
-		table = info->ocv_table[index] =
-			devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
-		if (!info->ocv_table[index]) {
-			power_supply_put_battery_info(psy, info);
-			err = -ENOMEM;
-			goto out_put_node;
-		}
-
-		for (i = 0; i < tab_len; i++) {
-			table[i].ocv = be32_to_cpu(*list);
-			list++;
-			table[i].capacity = be32_to_cpu(*list);
-			list++;
-		}
 	}
 
-	list = of_get_property(battery_np, "resistance-temp-table", &len);
-	if (!list || !len)
-		goto out_put_node;
-
-	info->resist_table_size = len / (2 * sizeof(__be32));
-	resist_table = info->resist_table = devm_kcalloc(&psy->dev,
-							 info->resist_table_size,
-							 sizeof(*resist_table),
-							 GFP_KERNEL);
-	if (!info->resist_table) {
-		power_supply_put_battery_info(psy, info);
-		err = -ENOMEM;
+	err = get_fwnode_tuple_array(dev, battery_node,
+				     "resistance-temp-table",
+				     (struct psy_int_tuple **)&info->resist_table,
+				     &info->resist_table_size);
+	if (err == -ENOMEM) {
+		power_supply_dev_put_battery_info(dev, info);
 		goto out_put_node;
 	}
-
-	for (index = 0; index < info->resist_table_size; index++) {
-		resist_table[index].temp = be32_to_cpu(*list++);
-		resist_table[index].resistance = be32_to_cpu(*list++);
-	}
+	err = 0;
 
 out_put_node:
-	of_node_put(battery_np);
+	fwnode_handle_put(battery_node);
+
 	return err;
+
+}
+EXPORT_SYMBOL_GPL(power_supply_dev_get_battery_info);
+
+int power_supply_get_battery_info(struct power_supply *psy,
+				  struct power_supply_battery_info *info)
+{
+	struct fwnode_handle *fw = NULL;
+
+	if (psy->of_node)
+		fw = of_fwnode_handle(psy->of_node);
+
+	return power_supply_dev_get_battery_info(&psy->dev, fw, info);
 }
 EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
 
-void power_supply_put_battery_info(struct power_supply *psy,
-				   struct power_supply_battery_info *info)
+void power_supply_dev_put_battery_info(struct device *dev,
+				       struct power_supply_battery_info *info)
 {
 	int i;
 
 	for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
 		if (info->ocv_table[i])
-			devm_kfree(&psy->dev, info->ocv_table[i]);
+			devm_kfree(dev, info->ocv_table[i]);
 	}
 
 	if (info->resist_table)
-		devm_kfree(&psy->dev, info->resist_table);
+		devm_kfree(dev, info->resist_table);
+}
+EXPORT_SYMBOL_GPL(power_supply_dev_put_battery_info);
+
+void power_supply_put_battery_info(struct power_supply *psy,
+				   struct power_supply_battery_info *info)
+{
+	power_supply_dev_put_battery_info(&psy->dev, info);
 }
 EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
 
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index fbc07d403f41..ef7db73a5bd1 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -430,6 +430,11 @@ devm_power_supply_get_by_phandle(struct device *dev, const char *property)
 { return NULL; }
 #endif /* CONFIG_OF */
 
+void power_supply_dev_put_battery_info(struct device *dev,
+				       struct power_supply_battery_info *info);
+int power_supply_dev_get_battery_info(struct device *dev,
+				      struct fwnode_handle *node,
+				      struct power_supply_battery_info *info);
 extern int power_supply_get_battery_info(struct power_supply *psy,
 					 struct power_supply_battery_info *info);
 extern void power_supply_put_battery_info(struct power_supply *psy,
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (3 preceding siblings ...)
  2021-11-16 12:26 ` [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration Matti Vaittinen
@ 2021-11-16 12:27 ` Matti Vaittinen
  2021-11-16 12:27 ` [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Matti Vaittinen
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:27 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Few batteries can be modelled to degrade at constant rate for each
increased cycle. Add degradation constant for such simple model to
batinfo.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
RFCv3: New patch
---
 drivers/power/supply/power_supply_core.c | 3 +++
 include/linux/power_supply.h             | 1 +
 2 files changed, 4 insertions(+)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 47176ed2570b..ebc961b5aa45 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -639,6 +639,7 @@ int power_supply_dev_get_battery_info(struct device *dev,
 	info->resist_table = NULL;
 	info->temp_dgrd_values = 0;
 	info->temp_dgrd = NULL;
+	info->degrade_cycle_uah = 0;
 
 	for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
 		info->ocv_table[index]       = NULL;
@@ -689,6 +690,8 @@ int power_supply_dev_get_battery_info(struct device *dev,
 			dev_warn(dev, "%s unknown battery type\n", value);
 	}
 
+	fwnode_property_read_u32(battery_node, "degrade-cycle-microamp-hours",
+			     &info->degrade_cycle_uah);
 	fwnode_property_read_u32(battery_node, "energy-full-design-microwatt-hours",
 			     &info->energy_full_design_uwh);
 	fwnode_property_read_u32(battery_node, "charge-full-design-microamp-hours",
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index ef7db73a5bd1..c5118265b3ab 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -403,6 +403,7 @@ struct power_supply_battery_info {
 	int resist_table_size;
 	int temp_dgrd_values;
 	struct power_supply_temp_degr *temp_dgrd;
+	int degrade_cycle_uah;
 };
 
 extern struct atomic_notifier_head power_supply_notifier;
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (4 preceding siblings ...)
  2021-11-16 12:27 ` [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo Matti Vaittinen
@ 2021-11-16 12:27 ` Matti Vaittinen
  2021-11-19  1:49   ` Linus Walleij
  2021-11-16 12:28 ` [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction Matti Vaittinen
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:27 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

The battery info functions computing the state-of-charge (SOC) based
on open-circuit-voltage (OCV) are returning SOC using units of 1%.

Some capacity estimation computations require higher accuracy. Add
functions that return SOC using units of 0.1% to reduce rounding error.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

---

RFCv3 changes:
 - Kerneldoc fixes
---
 drivers/power/supply/power_supply_core.c | 65 ++++++++++++++++++++++++
 include/linux/power_supply.h             |  4 ++
 2 files changed, 69 insertions(+)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index ebc961b5aa45..62ea113db3b4 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -1003,6 +1003,45 @@ int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table,
 }
 EXPORT_SYMBOL_GPL(power_supply_ocv2cap_simple);
 
+/**
+ * power_supply_ocv2dcap_simple() - find the battery capacity at 0.1% accuracy
+ * @table: Pointer to battery OCV lookup table
+ * @table_len: OCV table length
+ * @ocv: Current OCV value
+ *
+ * This helper function is used to look up battery capacity according to
+ * current OCV value from one OCV table, and the OCV table must be ordered
+ * descending. Return the SOC in the units of 0.1% for improved accuracy.
+ *
+ * Return: the battery capacity using the unit 0.1%.
+ */
+int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table,
+				int table_len, int ocv)
+{
+	int i, cap, tmp;
+
+	for (i = 0; i < table_len; i++)
+		if (ocv > table[i].ocv)
+			break;
+
+	if (i > 0 && i < table_len) {
+		tmp = (table[i - 1].capacity - table[i].capacity) *
+			(ocv - table[i].ocv) * 10;
+		tmp /= table[i - 1].ocv - table[i].ocv;
+		cap = tmp + table[i].capacity * 10;
+	} else if (i == 0) {
+		cap = table[0].capacity * 10;
+	} else {
+		cap = table[table_len - 1].capacity * 10;
+	}
+
+	if (cap < 0)
+		cap = 0;
+
+	return cap;
+}
+EXPORT_SYMBOL_GPL(power_supply_ocv2dcap_simple);
+
 struct power_supply_battery_ocv_table *
 power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
 				int temp, int *table_len)
@@ -1054,6 +1093,32 @@ int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info,
 }
 EXPORT_SYMBOL_GPL(power_supply_batinfo_dcap2ocv);
 
+/**
+ * power_supply_batinfo_ocv2dcap - compute SOC based on OCV and temperature
+ * @info:	pointer to battery information
+ * @ocv:	Open circuit voltage in uV
+ * @temp:	Temperature in Celsius
+ *
+ * Use OCV tables in battery info to compute the battery capacity based on
+ * provided open circuit voltage at given and temperature.
+ *
+ * Return: battery capacity correspondinggiven OCV and temperature at 0.1%.
+ *         -EINVAL if OCV table is not present.
+ */
+int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info,
+				  int ocv, int temp)
+{
+	struct power_supply_battery_ocv_table *table;
+	int table_len;
+
+	table = power_supply_find_ocv2cap_table(info, temp, &table_len);
+	if (!table)
+		return -EINVAL;
+
+	return power_supply_ocv2dcap_simple(table, table_len, ocv);
+}
+EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2dcap);
+
 int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
 				 int ocv, int temp)
 {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index c5118265b3ab..5e6575e97492 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -444,6 +444,10 @@ extern int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *ta
 				       int table_len, int ocv);
 int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table,
 				int table_len, int dcap);
+int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table,
+				 int table_len, int ocv);
+int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info,
+				  int ocv, int temp);
 
 extern struct power_supply_battery_ocv_table *
 power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (5 preceding siblings ...)
  2021-11-16 12:27 ` [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Matti Vaittinen
@ 2021-11-16 12:28 ` Matti Vaittinen
  2021-11-19  1:54   ` Linus Walleij
  2021-11-16 12:29 ` [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Matti Vaittinen
  2021-11-16 12:29 ` [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver Matti Vaittinen
  8 siblings, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:28 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Add generic 'simple gauge' helper for performing iterative SOC estimation
and coulomb counter correction for devices with a (drifting) coulomb
counter. This should allow few charger/fuel-gauge drivers to use generic
loop instead of implementing their own.

Charger/fuel-gauge drivers can register 'simple-gauge' which does
periodically poll the driver and:
 - get battery state
 - adjust coulomb counter value (to fix drifting caused for example by ADC
   offset) if:
     - Battery is relaxed and OCV<=>SOC table is given
     - Battery is full charged
 - get battery age (cycles) from driver
 - get battery temperature
 - do battery capacity correction
     - by battery temperature
     - by battery age
     - by computed Vbat/OCV difference at low-battery condition if
       low-limit is set and OCV table given
     - by IC specific low-battery correction if provided
 - compute current State Of Charge (SOC)
 - do periodical calibration if IC supports that. (Many ICs do calibration
   of CC by shorting the ADC pins and getting the offset).
 - provide the user-space a consistent interface for getting/setting the
   battery-cycle information for ICs which can't store the battery aging
   information. Uses POWER_SUPPLY_PROP_CYCLE_COUNT for this.

The simple gauge provides the last computed SOC as
POWER_SUPPLY_PROP_CAPACITY to power_supply_class when requested.

Things that should/could be added but are missing from this commit:
 - Support starting calibration in HW when entering to suspend. This
   is useful for ICs supporting delayed calibration to mitigate CC error
   during suspend - and to make periodical wake-up less critical.
 - periodical wake-up for performing SOC estimation computation (RTC
   integration)

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

---
RFCv3:
  - use devm_add_action_or_reset
  - Rename to simple_gauge
  - Limit access to power_supply parameters
  - Introduce simple_gauge_drvdata
  - no need to show simple_gauge at config menu. Users should just SELECT it.
  - allow tristate
  - Add blocking 'iteration run' - loop.
  - updated the comment section
  - Fixed clamped SOC which was not updated
  - Small comment improvements
  - Don't allow negative CC after computing capacity corrections
  - Fix gauge looping when last client exits
---
 drivers/power/supply/Kconfig        |    3 +
 drivers/power/supply/Makefile       |    1 +
 drivers/power/supply/simple-gauge.c | 1303 +++++++++++++++++++++++++++
 include/linux/power/simple_gauge.h  |  244 +++++
 4 files changed, 1551 insertions(+)
 create mode 100644 drivers/power/supply/simple-gauge.c
 create mode 100644 include/linux/power/simple_gauge.h

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 5cf5bb56d2e3..24a8d030a391 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -9,6 +9,9 @@ menuconfig POWER_SUPPLY
 
 if POWER_SUPPLY
 
+config POWER_SIMPLE_GAUGE
+	tristate
+
 config POWER_SUPPLY_DEBUG
 	bool "Power supply debug"
 	help
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 4e55a11aab79..8c8c5f6a6492 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -5,6 +5,7 @@ power_supply-y				:= power_supply_core.o
 power_supply-$(CONFIG_SYSFS)		+= power_supply_sysfs.o
 power_supply-$(CONFIG_LEDS_TRIGGERS)	+= power_supply_leds.o
 
+obj-$(CONFIG_POWER_SIMPLE_GAUGE)	+= simple-gauge.o
 obj-$(CONFIG_POWER_SUPPLY)	+= power_supply.o
 obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
 obj-$(CONFIG_GENERIC_ADC_BATTERY)	+= generic-adc-battery.o
diff --git a/drivers/power/supply/simple-gauge.c b/drivers/power/supply/simple-gauge.c
new file mode 100644
index 000000000000..7bf96b4d35dd
--- /dev/null
+++ b/drivers/power/supply/simple-gauge.c
@@ -0,0 +1,1303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of some generic state-of-charge computations for devices
+ * with coulomb counter
+ *
+ * Copyright 2020 ROHM Semiconductors
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/power/simple_gauge.h>
+#include <linux/wait.h>
+
+#define SWGAUGE_TIMEOUT_JITTER 100
+/* We add 0.5% of uAh to avoid rounding error */
+#define SOC_BY_CAP(uah, round, cap) ((uah + round) * 100 / (cap))
+
+/*
+ * The idea here is to implement (typical?) periodical coulomb-counter polling
+ * and adjusting. I know few ROHM ICs which do this in out-of-tree drivers -
+ * and I think there is few drivers also in-tree doing something similar. I
+ * believe adding some SOC computation logic to core could benefit a few of the
+ * drivers.
+ *
+ * I selected the ROHM algorithm here because I know it better than others.
+ *
+ * So let's go explaining the logic pulled in here:
+ *
+ * Device drivers for (charger) ICs which contain a coulomb counter can
+ * register here and provide IC specific functions as listed in
+ * include/linux/power/simple_gauge.h struct simple_gauge_ops. Drivers can also
+ * specify time interval the IC is polled.
+ *
+ * After registration the simple_gauge does periodically poll the driver and:
+ * 1. get battery state
+ * 2. adjust coulomb counter value (to fix drifting caused for example by ADC
+ *   offset) if:
+ *     - Battery is relaxed and OCV<=>SOC table is given
+ *     - Battery is fully charged
+ * 3. get battery age (cycles) from driver
+ * 4. get battery temperature
+ * 5. do battery capacity correction
+ *     - by battery temperature
+ *     - by battery age
+ *     - by computed Vbat/OCV difference at low-battery condition if
+ *       low-limit is set and OCV table given
+ *     - by IC specific low-battery correction if provided
+ * 6. compute current State Of Charge (SOC) based on corrected capacity
+ * 7. do periodical calibration if IC supports that. (Many ICs do calibration
+ *   of CC by shorting the ADC pins and getting the offset).
+ *   TODO: Support starting calibration in HW when entering to suspend. This
+ *   is useful for ICs supporting delayed calibration to mitigate CC error
+ *   during suspend - and to make periodical wake-up less critical.
+ *
+ * The SW gauge provides the last computed SOC as POWER_SUPPLY_PROP_CAPACITY to
+ * power_supply_class when requested.
+ *
+ * Additionally the SW-gauge provides the user-space a consistent interface for
+ * getting/setting the battery-cycle information for ICs which can't store the
+ * battery aging information (like how many times battery has been charged to
+ * full) over a reset. POWER_SUPPLY_PROP_CYCLE_COUNT is used for this.
+ *
+ * TODO: Some low-power devices which may spend long times suspended may prefer
+ * periodical wake-up for performing SOC estimation/computation even at the
+ * cost of power-consumption caused by such a wake-up. So as a future
+ * improvement it would be nice to see a RTC integration which might allow the
+ * periodic wake-up. (Or is there better ideas?). This could be useful for the
+ * devices which do not support calibration when SOC is turned off and current
+ * consumption is minimal.
+ *
+ * If this is not seen as a complete waste of time - then I would like to get
+ * suggestions and opinions :) Especially for following:
+ * 1. Should this be meld-in power_supply_class? I didn't go to that route as I
+ *    didn't want to obfuscate the power_supply registration with the items in
+ *    the simple_gauge desc and ops. OTOH, the psy now has a pointer to sw-gauge
+ *    and sw-gauge to psy - which is a clear hint that the relation of them
+ *    is quite tight.
+ */
+
+static DEFINE_MUTEX(simple_gauge_lock);
+static DEFINE_MUTEX(simple_gauge_start_lock);
+static LIST_HEAD(simple_gauges);
+
+static int g_running;
+static struct task_struct k;
+
+static int simple_gauge_set_cycle(struct simple_gauge *sw, int new_cycle)
+{
+	int ret = 0, old_cycle = sw->cycle;
+
+	if (!sw->desc.allow_set_cycle && !sw->ops.set_cycle)
+		return -EINVAL;
+
+	if (sw->ops.set_cycle)
+		ret = sw->ops.set_cycle(sw, old_cycle, &new_cycle);
+
+	if (!ret)
+		sw->cycle = new_cycle;
+
+	return ret;
+}
+
+static int simple_gauge_set_property(struct power_supply *psy,
+				 enum power_supply_property psp,
+				 const union power_supply_propval *val)
+{
+	struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+	if (!sg) {
+		WARN_ON(!sg);
+		return -EINVAL;
+	}
+
+	if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT && sg->desc.allow_set_cycle)
+		return simple_gauge_set_cycle(sg, val->intval);
+
+	if (sg->set_custom_property)
+		return sg->set_custom_property(sg, psp, val);
+
+	return -EOPNOTSUPP;
+}
+
+static int simple_gauge_get_property(struct power_supply *psy,
+				 enum power_supply_property psp,
+				 union power_supply_propval *val)
+{
+	struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+	if (!sg) {
+		WARN_ON(!sg);
+		return -EINVAL;
+	}
+	/*
+	 * We should ensure here is that the first iteration
+	 * loop for SOC calculation must've been performed. TODO:
+	 * How to ensure it?
+	 *
+	 * TODO: Add flags to advertice which properties can be computed by
+	 * sw-gauge (with given call-backs) and only return those. Else
+	 * default with driver get_property to allow the driver to compute
+	 * and provide those w/o the SW-gauge.
+	 *
+	 * Or - we could first try calling the driver call-back and do these
+	 * as a fall-back if driver returns an error?
+	 */
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CAPACITY:
+		spin_lock(&sg->lock);
+		val->intval = sg->soc;
+		spin_unlock(&sg->lock);
+		return 0;
+	case POWER_SUPPLY_PROP_CYCLE_COUNT:
+		spin_lock(&sg->lock);
+		val->intval = sg->cycle;
+		spin_unlock(&sg->lock);
+		return 0;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		/* uAh */
+		val->intval = sg->designed_cap;
+		return 0;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		spin_lock(&sg->lock);
+		val->intval = sg->capacity_uah;
+		spin_unlock(&sg->lock);
+		return 0;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		spin_lock(&sg->lock);
+		val->intval = sg->cc_uah;
+		spin_unlock(&sg->lock);
+		return 0;
+	case POWER_SUPPLY_PROP_TEMP:
+		spin_lock(&sg->lock);
+		val->intval = sg->temp;
+		spin_unlock(&sg->lock);
+		return 0;
+	default:
+		break;
+	}
+
+	if (sg->get_custom_property)
+		return sg->get_custom_property(sg, psp, val);
+
+	return -EOPNOTSUPP;
+}
+
+static void gauge_put(struct simple_gauge *sw)
+{
+	sw->refcount = 0;
+	if (!sw->refcount)
+		wake_up(&sw->wq);
+}
+
+static void gauge_get(struct simple_gauge *sw)
+{
+	sw->refcount = 1;
+}
+
+static int gauge_reserved(struct simple_gauge *sw)
+{
+	return sw->refcount;
+}
+
+static int get_dsoc_from_ocv(struct simple_gauge *sw, int *dsoc, int temp, int ocv)
+{
+	int ret;
+
+	/*
+	 * The OCV tables use degree C as units (if I did not misread the
+	 * code - why?) - power_supply class user-space seems to mandate
+	 * the tenths of a degree - so we require this from drivers and
+	 * lose accuracy here :/
+	 */
+	ret = power_supply_batinfo_ocv2cap(&sw->info, ocv, temp / 10);
+	if (ret > 0) {
+		*dsoc = ret * 10;
+		ret = 0;
+	}
+
+	if (ret) {
+		if (!sw->ops.get_soc_by_ocv)
+			return ret;
+		/* For driver callbacks we use tenths of degree */
+		ret = sw->ops.get_soc_by_ocv(sw, ocv, temp, dsoc);
+	}
+	return ret;
+}
+
+static int simple_gauge_get_temp(struct simple_gauge *sw, int *temp)
+{
+	if (sw->ops.get_temp)
+		return sw->ops.get_temp(sw, temp);
+
+	return -EINVAL;
+}
+
+static int age_correct_cap(struct simple_gauge *sw, int *uah)
+{
+	int ret = 0;
+
+	/* If IC provides more complex degradation computation - use it */
+	if (sw->ops.age_correct_cap) {
+		int tmp = *uah;
+
+		ret = sw->ops.age_correct_cap(sw, sw->cycle, &tmp);
+		if (!ret) {
+			*uah = tmp;
+			return 0;
+		}
+	}
+	/* Calculate constant uAh/cycle degradation */
+	if (sw->desc.degrade_cycle_uah) {
+		int lost_cap;
+
+		lost_cap = sw->desc.degrade_cycle_uah * sw->cycle;
+		if (lost_cap > sw->designed_cap)
+			*uah = 0;
+		else
+			*uah -= sw->desc.degrade_cycle_uah * sw->cycle;
+
+		return 0;
+	}
+
+	return ret;
+}
+
+static int adjust_cc_relax(struct simple_gauge *sw, int rex_volt)
+{
+	int ret, temp, dsoc;
+	int full_uah = sw->designed_cap;
+	int uah_now;
+
+	/* get temp */
+	ret = simple_gauge_get_temp(sw, &temp);
+	if (ret)
+		return ret;
+
+	/* get ocv */
+	ret = get_dsoc_from_ocv(sw, &dsoc, temp, rex_volt);
+	if (ret)
+		return ret;
+
+	/*
+	 * Typically ROHM implemented drivers have kept the value CC in PMIC
+	 * corresponding to IDEAL battery capacity and then substracted the
+	 * lost capacity when converting CC value to uAh. I guess this prevents
+	 * CC from hitting the floor.
+	 */
+	uah_now = full_uah * dsoc / 1000 + sw->soc_rounding;
+	if (uah_now > sw->designed_cap)
+		uah_now = sw->designed_cap;
+
+	return sw->ops.update_cc_uah(sw, full_uah);
+}
+
+static int get_state(struct simple_gauge *sw, int *state, int *rex_volt)
+{
+	int ret;
+	enum power_supply_property psp = POWER_SUPPLY_PROP_STATUS;
+	union power_supply_propval pstate;
+
+	*state = 0;
+
+	ret = power_supply_get_property(sw->psy, psp, &pstate);
+	if (ret)
+		return ret;
+
+	if (pstate.intval == POWER_SUPPLY_STATUS_FULL)
+		*state |= SW_GAUGE_FULL;
+	if (pstate.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+	    pstate.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+		*state |= SW_GAUGE_MAY_BE_LOW;
+		if (sw->desc.clamp_soc)
+			*state |= SW_GAUGE_CLAMP_SOC;
+	}
+
+	if (sw->ops.is_relaxed)
+		if (sw->ops.is_relaxed(sw, rex_volt))
+			*state |= SW_GAUGE_RELAX;
+	return ret;
+}
+
+static int adjust_cc_full(struct simple_gauge *sw)
+{
+	int ret = 0, from_full_uah = 0;
+	int full_uah = sw->designed_cap;
+
+	/*
+	 * Some ICs are able to provide the uAh lost since the battery was
+	 * fully charged. Decrease this from the designed capacity and set
+	 * the CC value accordingly.
+	 */
+	if (sw->ops.get_uah_from_full)
+		ret = sw->ops.get_uah_from_full(sw, &from_full_uah);
+
+	if (ret)
+		dev_warn(sw->dev,
+			 "Failed to get capacity lost after fully charged\n");
+
+	full_uah -= from_full_uah;
+
+	/*
+	 * ROHM algorithm adjusts CC here based on designed capacity and not
+	 * based on age/temperature corrected capacity. This helps avoiding
+	 * CC dropping below zero when we estimate aging/temperature impact
+	 * badly. It also allows to keep the estimated SOC in the sw-gauge
+	 * so that all IC drivers do not need to care about it - at least
+	 * in theory. But most importantly - this approach is tested on the
+	 * field :)
+	 */
+	return sw->ops.update_cc_uah(sw, full_uah);
+}
+
+/*
+ * Some charger ICs keep count of battery charge systems but can only store
+ * one or few cycles. They may need to clear the cycle counter and update
+ * counter in SW. This function fetches the counter from HW and allows HW to
+ * clear IC counter if needed.
+ */
+static int update_cycle(struct simple_gauge *sw)
+{
+	int cycle, ret = -EINVAL;
+
+	if (sw->ops.get_cycle) {
+		/*
+		 * We provide old cycle value to driver so driver does not
+		 * need to cache it
+		 */
+		cycle = sw->cycle;
+		ret = sw->ops.get_cycle(sw, &cycle);
+		if (ret)
+			return ret;
+		sw->cycle = cycle;
+	} else
+		sw->cycle++;
+
+	return 0;
+}
+
+static int simple_gauge_cap2ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv)
+{
+	int ret;
+
+	if (sw->ops.get_ocv_by_soc)
+		return sw->ops.get_ocv_by_soc(sw, dsoc, temp, ocv);
+
+	ret = power_supply_batinfo_dcap2ocv(&sw->info, dsoc, temp / 10);
+	if (ret > 0) {
+		*ocv = ret;
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static int load_based_soc_zero_adjust(struct simple_gauge *sw, int *effective_cap,
+				       int cc_uah, int vsys, int temp)
+{
+	int dsoc, ocv_by_cap;
+	int ret, i;
+	int vdrop;
+	struct power_supply_battery_ocv_table *table;
+	int table_len;
+	int soc_adjust = 0;
+
+	/*
+	 * Get OCV for current estimated SOC - we use unit of 0.1% for SOC
+	 * (dsoc) to improve accuracy. Note - batinfo expects 1% - should we
+	 * introduce new DT entry of more accurate OCV table for improved SOC
+	 * => OCV where SOC was given using unit of 0.1% for improved internal
+	 * calculation? User space should still only see 1% - but for "zero
+	 * adjust" where we do SOC => OCV => drop-voltage => SOC correction
+	 * => CC/capacity adjustment we would like to have more accurate SOC
+	 * in these intermediate steps.
+	 */
+	dsoc = SOC_BY_CAP(cc_uah * 10, 0, *effective_cap);
+	ret = simple_gauge_cap2ocv(sw, dsoc, temp, &ocv_by_cap);
+	if (ret) {
+		dev_err(sw->dev, "Failed to convert cap to OCV\n");
+		return ret;
+	}
+	/* Get the difference of OCV (no load) and VBAT (current load) */
+	vdrop = ocv_by_cap - vsys;
+	dev_dbg(sw->dev, "Obtained OCV: %d, vsys %d, Computed Vdrop %d\n",
+		ocv_by_cap, vsys, vdrop);
+	if (vdrop <= 0)
+		return 0;
+
+	/*
+	 * We know that the SOC should be 0 at the moment when voltage with
+	 * this load drops below system limit. So let's scan the OCV table
+	 * and just assume the vdrop stays constant for the rest of the game.
+	 * This way we can see what is the new 'zero adjusted capacity' for
+	 * our battery.
+	 *
+	 * We don't support non DT originated OCV table here.
+	 *
+	 * I guess we could allow user to provide standard OCV tables in desc.
+	 * Or the full batinfo for that matter. But for now we just force the
+	 * drivers W/O OCV tables in DT to just provide whole low-voltage
+	 * call-back.
+	 */
+	table = power_supply_find_ocv2cap_table(&sw->info, temp / 10,
+						&table_len);
+	if (!table) {
+		dev_warn(sw->dev, "No OCV table found\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < table_len; i++)
+		if (table[i].ocv - vdrop <= sw->desc.system_min_voltage)
+			break;
+
+	if (!i)
+		soc_adjust = table[0].capacity;
+	else if (i && i < table_len) {
+		int j;
+		int soc_range = table[i-1].capacity - table[i].capacity;
+		int volt_range = table[i-1].ocv - table[i].ocv;
+		int v_div = volt_range/soc_range;
+
+		for (j = 0; j < soc_range; j++)
+			if (table[i].ocv + v_div * j - vdrop >=
+			   sw->desc.system_min_voltage)
+				break;
+		soc_adjust = table[i].capacity + j;
+	}
+	if (soc_adjust) {
+		int new_full_cap;
+
+		/*
+		 * So we know that actually we will have SOC = 0 when capacity
+		 * is soc_adjust. Lets compute new battery max capacity based
+		 * on this.
+		 */
+		new_full_cap = *effective_cap * (100 - soc_adjust) / 100;
+
+		*effective_cap = new_full_cap;
+	}
+
+	return 0;
+}
+
+static int simple_gauge_zero_cap_adjust(struct simple_gauge *sw, int *effective_cap,
+				       int *cc_uah, int vsys, int temp)
+{
+	int ret, old_eff_cap = *effective_cap;
+
+	if (sw->ops.zero_cap_adjust)
+		ret = sw->ops.zero_cap_adjust(sw, effective_cap, *cc_uah, vsys,
+					      temp);
+	else
+		ret = load_based_soc_zero_adjust(sw, effective_cap, *cc_uah,
+						 vsys, temp);
+	/*
+	 * As we keep HW CC aligned to designed-cap - we need to
+	 * also cancel this new offset from CC measured uAh
+	 */
+	if (!ret)
+		*cc_uah -= old_eff_cap - *effective_cap;
+
+	return ret;
+}
+
+static int find_dcap_change(struct simple_gauge *sw, int temp, int *delta_cap)
+{
+	struct power_supply_temp_degr *dclosest = NULL, *d;
+	int i;
+
+	for (i = 0; i < sw->amount_of_temp_dgr; i++) {
+		d = &sw->temp_dgr[i];
+		if (!dclosest) {
+			dclosest = d;
+			continue;
+		}
+		if (abs(d->temp_set_point - temp) <
+		    abs(dclosest->temp_set_point - temp))
+			dclosest = d;
+	}
+
+	if (!dclosest)
+		return -EINVAL;
+
+	/*
+	 * Temperaure range is in tenths of degrees and degrade value is for a
+	 * degree => divide by 10 after multiplication to fix the scale
+	 */
+	*delta_cap = (dclosest->temp_set_point - temp) *
+		     dclosest->temp_degrade_1C / 10 +
+		     dclosest->degrade_at_set;
+
+	return 0;
+}
+
+static int compute_temp_correct_uah(struct simple_gauge *sw, int *cap_uah, int temp)
+{
+	int ret, uah_corr;
+
+	ret = find_dcap_change(sw, temp, &uah_corr);
+	if (ret)
+		return ret;
+
+	if (*cap_uah < -uah_corr)
+		*cap_uah = 0;
+	else
+		*cap_uah += uah_corr;
+
+	return 0;
+}
+
+static int compute_soc_by_cc(struct simple_gauge *sw, int state)
+{
+	int cc_uah, ret;
+	int current_cap_uah;
+	int temp;
+	int new_soc;
+	bool do_zero_correct, changed = false;
+
+	ret = sw->ops.get_uah(sw, &cc_uah);
+	/* The CC value should never exceed designed_cap as CC value. */
+	if (cc_uah > sw->designed_cap) {
+		cc_uah = sw->designed_cap;
+		sw->ops.update_cc_uah(sw, sw->designed_cap);
+	}
+
+	current_cap_uah = sw->designed_cap;
+
+	dev_dbg(sw->dev, "iteration started - CC %u, cap %u (SOC %u)\n",
+		cc_uah, current_cap_uah,
+		SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah));
+
+	ret = age_correct_cap(sw, &current_cap_uah);
+	if (ret) {
+		dev_err(sw->dev, "Age correction of battery failed\n");
+		return ret;
+	}
+
+	if (current_cap_uah == 0) {
+		dev_warn(sw->dev, "Battery EOL\n");
+		spin_lock(&sw->lock);
+		sw->capacity_uah = 0;
+		sw->soc = 0;
+		changed = true;
+		goto battery_eol_out;
+	}
+
+	/* Do battery temperature compensation */
+	ret = simple_gauge_get_temp(sw, &temp);
+	if (ret) {
+		dev_err(sw->dev, "Failed to get temperature\n");
+		return ret;
+	}
+
+	if (sw->ops.temp_correct_cap)
+		ret = sw->ops.temp_correct_cap(sw, &current_cap_uah, temp);
+	else if (sw->amount_of_temp_dgr)
+		ret = compute_temp_correct_uah(sw, &current_cap_uah, temp);
+
+	if (ret)
+		dev_warn(sw->dev,
+			 "Couldn't do temperature correction to battery cap\n");
+
+	/*
+	 * We keep HW CC counter aligned to ideal battery CAP - EG, when
+	 * battery is full, CC is set according to ideal battery capacity.
+	 * Same when we set it based on OCV. Thus - when we compute SOC we will
+	 * cancel this offset by decreasing the CC uah with the lost capacity
+	 */
+	cc_uah -= (sw->designed_cap - current_cap_uah);
+
+	/* Only need zero correction when discharging */
+	do_zero_correct = !!(state & SW_GAUGE_MAY_BE_LOW);
+
+	/*
+	 * Allow all ICs to have own adjustment functions for low Vsys to allow
+	 * them tackle potential issues in capacity estimation at near
+	 * depleted battery
+	 */
+	if (sw->desc.cap_adjust_volt_threshold && sw->ops.get_vsys &&
+	    do_zero_correct) {
+		int vsys;
+
+		ret = sw->ops.get_vsys(sw, &vsys);
+		if (ret) {
+			dev_err(sw->dev, "Failed to get vsys\n");
+			return ret;
+		}
+
+		if (sw->desc.cap_adjust_volt_threshold >= vsys)
+			ret = simple_gauge_zero_cap_adjust(sw, &current_cap_uah,
+							 &cc_uah, vsys, temp);
+		if (ret)
+			dev_warn(sw->dev, "Low voltage adjustment failed\n");
+	}
+
+	dev_dbg(sw->dev, "Corrected cap %u, designed-cap %u (SOC %u)\n",
+		current_cap_uah, sw->designed_cap,
+		SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah));
+
+	if (cc_uah > sw->designed_cap)
+		cc_uah = sw->designed_cap;
+
+	/*
+	 * With badly behaving CC or wrong VDR values we may make the CC to go
+	 * negative. Floor it to zero to avoid exhausting the battery W/O warning.
+	 */
+	if (cc_uah < 0) {
+		dev_warn(sw->dev,
+			 "Bad battery capacity estimate\n");
+		cc_uah = 0;
+	}
+	/* Store computed values */
+	spin_lock(&sw->lock);
+	sw->cc_uah = cc_uah;
+	sw->temp = temp;
+	sw->capacity_uah = current_cap_uah;
+	new_soc = SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah);
+
+	/*
+	 * Should we only ping user-space when SOC changes more than N%?
+	 * Should the N be configurable (by user-space?)
+	 */
+	if (sw->soc != new_soc)
+		changed = true;
+
+	sw->soc = new_soc;
+	if (sw->clamped_soc >= 0 && state & SW_GAUGE_CLAMP_SOC) {
+		if (sw->clamped_soc < sw->soc)
+			sw->soc = sw->clamped_soc;
+	}
+	sw->clamped_soc = sw->soc;
+
+battery_eol_out:
+	spin_unlock(&sw->lock);
+	if (changed)
+		power_supply_changed(sw->psy);
+
+	return ret;
+}
+
+static void calibrate(struct simple_gauge *sw)
+{
+	if (sw->ops.calibrate)
+		sw->ops.calibrate(sw);
+}
+
+static void iterate(struct simple_gauge *sw)
+{
+	int state, ret, rex_volt;
+
+	/* Adjust battery aging information */
+	ret = update_cycle(sw);
+	if (ret) {
+		dev_err(sw->dev, "Failed to update battery cycle\n");
+		return;
+	}
+
+	ret = get_state(sw, &state, &rex_volt);
+	if (ret) {
+		dev_err(sw->dev, "Failed to get state\n");
+		return;
+	}
+
+	/* Setting CC not possible? Omit CC adjustment */
+	if (sw->ops.update_cc_uah) {
+		if (state & SW_GAUGE_FULL) {
+			ret = adjust_cc_full(sw);
+			if (ret)
+				dev_err(sw->dev, "Failed to do FULL adjust\n");
+		}
+		if (state & SW_GAUGE_RELAX) {
+			ret = adjust_cc_relax(sw, rex_volt);
+			if (ret)
+				dev_err(sw->dev, "Failed to do RELAX adjust\n");
+		}
+	}
+
+	ret = compute_soc_by_cc(sw, state);
+	if (ret)
+		dev_err(sw->dev, "Failed to compute SOC for gauge\n");
+}
+
+static bool should_calibrate(struct simple_gauge *sw, u64 time)
+{
+	if (sw->desc.calibrate_interval &&
+	    sw->next_cal <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER)) {
+		sw->next_cal = time +
+			       msecs_to_jiffies(sw->desc.calibrate_interval);
+		return true;
+	}
+
+	return false;
+
+}
+
+static bool should_compute(struct simple_gauge *sw, u64 time)
+{
+	if (sw->next_iter <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER) ||
+	    sw->force_run) {
+		sw->force_run = 0;
+		sw->next_iter = time +
+				msecs_to_jiffies(sw->desc.poll_interval);
+		return true;
+	}
+
+	return false;
+}
+
+static void adjust_next_tmo(struct simple_gauge *sw, u64 *timeout, u64 now)
+{
+	u64 t = (sw->desc.calibrate_interval && sw->next_cal < sw->next_iter) ?
+			sw->next_cal : sw->next_iter;
+
+	if (!*timeout || t - now < *timeout)
+		*timeout = t - now;
+
+	if (*timeout < msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER))
+		*timeout = msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER);
+}
+
+static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_thread_wait);
+static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_forced_wait);
+
+static int simple_gauge_forced_run;
+
+/**
+ * simple_gauge_run - force running the computation loop for gauge
+ *
+ * Drivers utilizing simple_gauge can trigger running the SOC computation loop even
+ * prior the time-out occurs. This is usable for drivers with longish period
+ * but which may get interrupts form device when some condition changes. Note,
+ * this function schedules the iteration but does not block.
+ *
+ * @sw: gauge fow which the computation should be ran.
+ */
+void simple_gauge_run(struct simple_gauge *sw)
+{
+	sw->force_run = 1;
+	simple_gauge_forced_run = 1;
+	barrier();
+	wake_up(&simple_gauge_thread_wait);
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run);
+
+static unsigned int g_iteration;
+
+static unsigned int simple_gauge_run_locked(struct simple_gauge *sg)
+{
+	unsigned int ctr;
+
+	/* Wait for any ongoing iteration */
+	mutex_lock(&simple_gauge_lock);
+	ctr = g_iteration;
+	simple_gauge_run(sg);
+	mutex_unlock(&simple_gauge_lock);
+
+	return ctr;
+}
+
+/**
+ * simple_gauge_run_blocking_timeout - Run gauge loop and block until ran
+ *
+ * Trigger simple-gauge to run. Wait until simple-gauge has been ran or timeout
+ * occurs.
+ *
+ * @sg:		Pointer to gauge
+ * @timeout_ms:	Timeout in milliseconds.
+ *
+ * Returns: 0 if gauge loop was ran. -EAGAIN if timeout occurred and
+ *	    -ERESTARTSYS if call was interrupted.
+ */
+int simple_gauge_run_blocking_timeout(struct simple_gauge *sg,
+				      unsigned int timeout_ms)
+{
+	unsigned int ctr;
+	int ret;
+
+	ctr = simple_gauge_run_locked(sg);
+	ret = wait_event_interruptible_timeout(simple_gauge_forced_wait,
+					       g_iteration > ctr,
+					       timeout_ms);
+	if (ret == 1)
+		ret = 0;
+	else if (!ret)
+		ret = -EAGAIN;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run_blocking_timeout);
+
+/**
+ * simple_gauge_run_blocking - Run gauge loop and block until ran
+ *
+ * Trigger simple-gauge to run. Wait until simple-gauge has been ran.
+ *
+ * @sg:		Pointer to gauge
+ *
+ * Returns: 0 on success. -ERESTARTSYS if call was interrupted.
+ */
+int simple_gauge_run_blocking(struct simple_gauge *sg)
+{
+	unsigned int ctr;
+	int ret;
+
+	ctr = simple_gauge_run_locked(sg);
+	ret = wait_event_interruptible(simple_gauge_forced_wait,
+				       g_iteration > ctr);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_run_blocking);
+
+static int gauge_thread(void *data)
+{
+
+	for (;;) {
+		u64 timeout = 0;
+		struct simple_gauge *sw;
+		u64 now = get_jiffies_64();
+
+		if (kthread_should_stop()) {
+			g_running = 0;
+			pr_info("gauge thread stopping...\n");
+			/*
+			 * Ensure the g_running is visible to all. OTOH - if
+			 * thread stopping is not supported we can drop this
+			 * clearing.
+			 */
+			smp_wmb();
+			break;
+		}
+
+		simple_gauge_forced_run = 0;
+
+		mutex_lock(&simple_gauge_lock);
+		list_for_each_entry(sw, &simple_gauges, node) {
+			gauge_get(sw);
+			if (should_compute(sw, now))
+				iterate(sw);
+			if (should_calibrate(sw, now))
+				calibrate(sw);
+			adjust_next_tmo(sw, &timeout, now);
+			gauge_put(sw);
+		}
+		/*
+		 * Increase iteration counter and wake up the waiters who have
+		 * requested the blocking forced run. NOTE: We must increase
+		 * iteration here inside the locking to avoid race.
+		 */
+		g_iteration++;
+		mutex_unlock(&simple_gauge_lock);
+		wake_up(&simple_gauge_forced_wait);
+
+		/*
+		 * If the last gauge exited we fall to sleep in order to not
+		 * go lopoping with zero timeout. New client registration will
+		 * wake us up.
+		 */
+		if (!timeout && list_empty(&simple_gauges)) {
+			pr_debug("No clients: going to sleep\n");
+			wait_event_interruptible(simple_gauge_thread_wait,
+						 simple_gauge_forced_run);
+		} else {
+			pr_debug("sleeping %u msec\n",
+				 jiffies_to_msecs(timeout));
+			wait_event_interruptible_timeout(simple_gauge_thread_wait,
+						 simple_gauge_forced_run, timeout);
+		}
+	}
+
+	return 0;
+}
+
+static int start_gauge_thread(struct task_struct *k)
+{
+	int ret = 0;
+
+	/* Ensure the running state is updated. Is this needed? How likely it is
+	 * this would not be updated? Only reason why we have this first check
+	 * is to avoid unnecessarily getting the lock when the thread is started
+	 * - and it should always be except for the first caller. But does this
+	 * memory barrier actually make this almost as heavy as getting the lock
+	 * every time? Maybe we should just drop the barrier and let us hit the
+	 * lock if g_running is in cache or some such (lots of hand waving to
+	 * make it look like I knew what I was talking about :]).
+	 */
+	smp_rmb();
+	if (g_running)
+		return 0;
+
+	/*
+	 * Would this be cleaner if we used schedule_delayed_work? rather than
+	 * kthread? What I like is only one thread for sw-gauge, with
+	 * not-so-high priority. What is the simplest way to achieve it?
+	 */
+	mutex_lock(&simple_gauge_start_lock);
+	if (!g_running) {
+		k = kthread_run(gauge_thread, NULL, "sw-gauge");
+		if (IS_ERR(k))
+			ret = PTR_ERR(k);
+		else
+			g_running = 1;
+	}
+	mutex_unlock(&simple_gauge_start_lock);
+
+	return ret;
+}
+
+/*
+ * I think this is unnecessary. If someone registers SW gauge to the system
+ * then we can probably leave this running even if the gauge was temporarily
+ * removed. So let's consider removing this and thus simplifying the design.
+ *
+ * Perhaps even always launch the thread if SW-gauge is configured in?
+ */
+void stop_gauge_thread(struct task_struct *k);
+void stop_gauge_thread(struct task_struct *k)
+{
+	kthread_stop(k);
+}
+
+static bool is_needed_ops_given(struct simple_gauge_ops *ops)
+{
+	return (ops->get_uah && ops->get_temp && ops->update_cc_uah);
+}
+
+static int simple_gauge_set_ops(struct simple_gauge *sw, struct simple_gauge_ops *ops)
+{
+	if (!is_needed_ops_given(ops))
+		return -EINVAL;
+
+	sw->ops = *ops;
+
+	return 0;
+}
+
+static int simple_gauge_is_writable(struct power_supply *psy,
+				       enum power_supply_property psp)
+{
+	struct simple_gauge *sg = power_supply_get_drvdata(psy);
+
+	if (!sg) {
+		WARN_ON(!sg);
+		return -EINVAL;
+	}
+
+	if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT)
+		return sg->desc.allow_set_cycle;
+
+	if (sg->custom_is_writable)
+		return sg->custom_is_writable(sg, psp);
+
+	return 0;
+}
+
+static int sgauge_config_check(struct device *dev, struct simple_gauge_psy *pcfg)
+{
+	const char *errstr = NULL;
+
+	if (!pcfg) {
+		errstr = "Missing config";
+	} else {
+		if (!pcfg->psy_name)
+			errstr = "No power supply name";
+		if (pcfg->num_additional_props) {
+			if (!pcfg->get_custom_property)
+				errstr = "property reader required";
+		}
+		if (!pcfg->is_writable) {
+			if (pcfg->set_custom_property)
+				errstr =
+				   "set_custom_property() but no is_writable()";
+		}
+	}
+	if (!errstr)
+		return 0;
+
+	dev_err(dev, "%s\n", errstr);
+	return -EINVAL;
+}
+static int simple_gauge_set_props(struct simple_gauge *new,
+				  struct power_supply_desc *pd,
+				  struct simple_gauge_psy *pcfg)
+{
+	int i, j;
+
+	new->properties = kzalloc(sizeof(*pcfg->additional_props) *
+				  pcfg->num_additional_props + SIMPLE_GAUGE_PROP_SIZE,
+				  GFP_KERNEL);
+	if (!new->properties)
+		return -ENOMEM;
+
+	for (i = 0; i < NUM_SIMPLE_GAUGE_PROPS; i++)
+		new->properties[i] = SIMPLE_GAUGE_PROPS[i];
+
+	for (j = 0; j < pcfg->num_additional_props; j++, i++)
+		new->properties[i] = pcfg->additional_props[j];
+
+	pd->properties = new->properties;
+	pd->num_properties = i;
+	new->get_custom_property = pcfg->get_custom_property;
+	new->set_custom_property = pcfg->set_custom_property;
+
+	return 0;
+}
+
+void *simple_gauge_get_drvdata(struct simple_gauge *sg)
+{
+	return sg->desc.drv_data;
+}
+EXPORT_SYMBOL_GPL(simple_gauge_get_drvdata);
+
+/**
+ * psy_register_simple_gauge - register driver to simple_gauge
+ *
+ * @parent:	Parent device for power-supply class device.
+ * @psycfg:	Confiurations for power-supply class.
+ * @ops:	simple_gauge specific operations.
+ * @desc:	simple_gauge configuration data.
+ *
+ * Return:	pointer to simple_gauge on success, an ERR_PTR on failure.
+ *
+ * A power-supply driver for a device with drifting coulomb counter (CC) can
+ * register for periodical polling/CC correction. CC correction is done when
+ * battery is reported to be FULL or relaxed. For FULL battery the CC is set
+ * based on designed capacity and for relaxed battery CC is set based on open
+ * circuit voltage. The simple_gauge takes care of registering a power-supply class
+ * and reporting a few power-supply properties to user-space. See
+ * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on
+ * provided temperature/cycle degradation values and/or system voltage limit.
+ */
+struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent,
+						    struct simple_gauge_psy *pcfg,
+						    struct simple_gauge_ops *ops,
+						    struct simple_gauge_desc *desc)
+{
+	struct power_supply_desc *pd;
+	struct power_supply_config pg = { 0 };
+	int ret;
+	struct simple_gauge *new;
+
+	if (!parent) {
+		pr_err("no parent\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (!desc->poll_interval) {
+		dev_err(parent, "interval missing\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (sgauge_config_check(parent, pcfg))
+		return ERR_PTR(-EINVAL);
+
+	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+	if (!pd)
+		return ERR_PTR(-ENOMEM);
+
+	new = kzalloc(sizeof(*new), GFP_KERNEL);
+	if (!new) {
+		ret = -ENOMEM;
+		goto free_out;
+	}
+	pd->name = pcfg->psy_name;
+	pd->type = POWER_SUPPLY_TYPE_BATTERY;
+
+	pg.drv_data = new;
+	pg.of_node = pcfg->of_node;
+	pg.attr_grp = pcfg->attr_grp;
+
+	ret = simple_gauge_set_props(new, pd, pcfg);
+	if (ret) {
+		ret = -ENOMEM;
+		goto free_out;
+	}
+	new->dev = parent;
+	new->custom_is_writable = pcfg->is_writable;
+	/* We don't want to clamp SOC before it is initialized */
+	new->clamped_soc = -1;
+
+	init_waitqueue_head(&new->wq);
+
+	ret = simple_gauge_set_ops(new, ops);
+	if (ret) {
+		dev_err(new->dev, "bad ops\n");
+		goto free_out;
+	}
+	new->desc = *desc;
+
+	spin_lock_init(&new->lock);
+	pd->get_property = simple_gauge_get_property;
+	pd->set_property = simple_gauge_set_property;
+	if (pcfg->is_writable || desc->allow_set_cycle)
+		pd->property_is_writeable = simple_gauge_is_writable;
+
+	/* Do we need power_supply_register_ws? */
+	/*
+	 * We should not return SOC to user-space before the first
+	 * estimation iteration is ran. How should we sync this? Should we
+	 * actually start the gauge thread and try getting SOC prior
+	 * registering to psy class? That would require us to do the
+	 * battery-info reading here. Or should we have some way of delaying
+	 * sysfs creation from psy-class until first iteration is done? Kind
+	 * of two-step power-supply class registration? Again, I am open to
+	 * all suggestions!
+	 *
+	 * I don't know how this should be integrated to psy class. Should
+	 * this sit between psy-class and driver? Should this be part of
+	 * psy-class or ?? If this was meld in psy-class registration and
+	 * the psy class registration was just given a new parameter for fuel
+	 * gauge desc - then the synchronization between 1st iteration and
+	 * sysfs creation might get more natural.
+	 */
+	new->psy = power_supply_register(parent, pd, &pg);
+	if (IS_ERR(new->psy)) {
+		dev_err(new->dev, "power supply registration failed\n");
+		ret = PTR_ERR(new->psy);
+		goto free_out;
+	}
+
+	ret = power_supply_get_battery_info(new->psy, &new->info);
+	if (ret && !new->ops.get_soc_by_ocv) {
+		dev_err(new->dev, "No OCV => SoC conversion\n");
+		goto info_out;
+	}
+	if (!ret)
+		new->batinfo_got = true;
+
+	if (new->info.temp_dgrd_values) {
+		new->amount_of_temp_dgr = new->info.temp_dgrd_values;
+		new->temp_dgr = new->info.temp_dgrd;
+	} else {
+		new->amount_of_temp_dgr = new->desc.amount_of_temp_dgr;
+		new->temp_dgr = new->desc.temp_dgr;
+	}
+
+	if (desc->designed_cap) {
+		new->designed_cap = desc->designed_cap;
+	} else if (ret || !new->info.charge_full_design_uah) {
+		dev_err(new->dev, "Unknown battery capacity\n");
+		goto info_out;
+	} else {
+		new->designed_cap = new->info.charge_full_design_uah;
+	}
+	/* We add 0.5 % to soc uah in order to avoid flooring */
+	new->soc_rounding = new->designed_cap / 200;
+	mutex_lock(&simple_gauge_lock);
+	list_add(&new->node, &simple_gauges);
+	mutex_unlock(&simple_gauge_lock);
+	ret = start_gauge_thread(&k);
+	if (ret) {
+		/*
+		 * This error is not related to underlying device but to the
+		 * simple_gauge itself. Thus don't print error using the parent
+		 * device
+		 */
+		pr_err("Failed to start fuel-gauge thread\n");
+		goto info_out;
+	}
+	dev_dbg(new->dev, "YaY! SW-gauge registered\n");
+
+	simple_gauge_run_blocking(new);
+
+	return new;
+
+info_out:
+	if (new->batinfo_got)
+		power_supply_put_battery_info(new->psy, &new->info);
+
+	power_supply_unregister(new->psy);
+free_out:
+	if (new) {
+		kfree(new->properties);
+		kfree(new);
+	}
+	kfree(pd);
+
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(psy_register_simple_gauge);
+
+/**
+ * psy_remove_simple_gauge - deregister driver from simple_gauge
+ *
+ * @sw:	gauge driver to be deregistered.
+ */
+void psy_remove_simple_gauge(struct simple_gauge *sw)
+{
+	const struct power_supply_desc *desc;
+
+	mutex_lock(&simple_gauge_lock);
+	list_del(&sw->node);
+	mutex_unlock(&simple_gauge_lock);
+
+	wait_event(sw->wq, !gauge_reserved(sw));
+
+	if (sw->batinfo_got)
+		power_supply_put_battery_info(sw->psy, &sw->info);
+
+	desc = sw->psy->desc;
+	power_supply_unregister(sw->psy);
+
+	kfree(desc);
+	kfree(sw->properties);
+	kfree(sw);
+}
+EXPORT_SYMBOL_GPL(psy_remove_simple_gauge);
+
+static void devm_simple_gauge_release(void *res)
+{
+	psy_remove_simple_gauge(res);
+}
+
+/**
+ * devm_psy_register_simple_gauge - managed register driver to simple_gauge
+ *
+ * @parent:	Parent device for power-supply class device. Swgauge's lifetime
+ *		is also bound to this device.
+ * @psycfg:	Confiurations for power-supply class.
+ * @ops:	simple_gauge specific operations.
+ * @desc:	simple_gauge configuration data.
+ *
+ * Return:	pointer to simple_gauge on success, an ERR_PTR on failure.
+ *
+ * A power-supply driver for a device with drifting coulomb counter (CC) can
+ * register for periodical polling/CC correction. CC correction is done when
+ * battery is reported to be FULL or relaxed. For FULL battery the CC is set
+ * based on designed capacity and for relaxed battery CC is set based on open
+ * circuit voltage. The simple_gauge takes care of registering a power-supply class
+ * and reporting a few power-supply properties to user-space. See
+ * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on
+ * provided temperature/cycle degradation values and/or system voltage limit.
+ */
+struct simple_gauge *__must_check
+devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg,
+			   struct simple_gauge_ops *ops, struct simple_gauge_desc *desc)
+{
+	struct simple_gauge *sw;
+	int ret;
+
+	sw = psy_register_simple_gauge(parent, psycfg, ops, desc);
+	if (IS_ERR(sw))
+		return sw;
+
+	ret = devm_add_action_or_reset(parent, devm_simple_gauge_release, sw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(devm_psy_register_simple_gauge);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("generic fuel-gauge on coulomb counter");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
diff --git a/include/linux/power/simple_gauge.h b/include/linux/power/simple_gauge.h
new file mode 100644
index 000000000000..cd085375ddec
--- /dev/null
+++ b/include/linux/power/simple_gauge.h
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020 ROHM Semiconductors */
+
+#ifndef POWER_SW_GAUGE_H
+#define POWER_SW_GAUGE_H
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define SW_GAUGE_FULL BIT(0)
+#define SW_GAUGE_RELAX BIT(1)
+#define SW_GAUGE_MAY_BE_LOW BIT(2)
+#define SW_GAUGE_CLAMP_SOC BIT(3)
+
+/* Power supply properties handled by simple_gauge */
+static const enum power_supply_property SIMPLE_GAUGE_PROPS[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CYCLE_COUNT,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_TEMP
+};
+#define SIMPLE_GAUGE_PROP_SIZE (sizeof(SIMPLE_GAUGE_PROPS))
+#define NUM_SIMPLE_GAUGE_PROPS (ARRAY_SIZE(SIMPLE_GAUGE_PROPS))
+
+struct simple_gauge;
+
+/**
+ * struct simple_gauge_ops - fuel-gauge operations
+ *
+ * @is_relaxed:		return true if battery is at relaxed state. Update
+ *			rex_volt to contain measured relaxed battery voltage.
+ * @get_temp:		return the battery temperature in tenths of a degree C.
+ * @get_uah_from_full:	some chargers can provide CC value change since battery
+ *			was last charged full. This value can be used by
+ *			sw-gauge when correcting CC based on battery full
+ *			status. This function should return charge lost since
+ *			battery was last load full. Units in uAh.
+ * @get_uah:		return current charge as measured by coulomb counter in
+ *			uAh
+ * @update_cc_uah:	Update CC by given charge in uAh
+ * @get_cycle:		get battery cycle for age compensation
+ * @set_cycle:		some batteries/chargers rely on user-space to store the
+ *			cycle infomration over reset. Those drivers can
+ *			implement the set_cycle callback which user-space can
+ *			use to set the stored battery cycle after reset.
+ * @get_sys:		get the current system voltage in uV. Used for
+ *			IC specific low-voltage SOC correction.
+ * @get_soc_by_ocv:	setups which do not store the OCV/SOC information in
+ *			standard battery_info can implement this function to
+ *			compute SOC based on OCV. SOC should be returned as
+ *			units of 0.1%
+ * @get_ocv_by_soc:	setups which do not store the OCV/SOC information in
+ *			standard battery_info can implement this function to
+ *			compute OCV based on SOC. NOTE: Soc is provided to
+ *			the function in units of 0.1% to improve accuracy.
+ * @age_correct_cap:	batteries/devices with more complicated aging
+ *			correction than constant uAh times battery cycles
+ *			can implement this to adjust capacity based on battery
+ *			cycles. For constant aging use degrade_cycle_uah
+ *			in desc.
+ * @temp_correct_cap:	batteries/devices with more complicated temperature
+ *			correction than ranges of temperatures with constant
+ *			change uah/degree C can implement this to adjust
+ *			capacity based on battery temperature.
+ *			For temperature ranges with constant change uAh/degree
+ *			use temp_dgr and amount_of_temp_dgr at desc.
+ * @calibrate:		many devices implement coulomb counter calibration
+ *			(for example by measuring ADC offset pins shorted).
+ *			Such devices can implement this function for periodical
+ *			calibration.
+ * @suspend_calibrate:	Many small capacity battery devices or devices which
+ *			spend long time MCU suspended can benefit from
+ *			starting the calibration when entering to suspend. Such
+ *			devices can implement this callback to initiaite
+ *			calibration when entering to suspend
+ * @zero_cap_adjust: IC specific SOC estimation adjustment to be performed
+ *			when battery is approaching empty.
+ */
+struct simple_gauge_ops {
+	/*
+	 * Get battery relax - could probably also use PSY class state if
+	 * it was extended with some properties like BATTERY_RELAXED to know
+	 * if OCV can be used.
+	 *
+	 * Currently meaningfull states are charging/discharging/full/relaxed
+	 * Full so we can correct battery capacity and/or CC
+	 * Relax so we know we can use OCV
+	 */
+	bool (*is_relaxed)(struct simple_gauge *gauge, int *rex_volt);
+	int (*get_temp)(struct simple_gauge *gauge, int *temp);
+	int (*get_uah_from_full)(struct simple_gauge *gauge, int *uah);
+	int (*get_uah)(struct simple_gauge *gauge, int *uah);
+	int (*update_cc_uah)(struct simple_gauge *gauge, int bcap);
+	int (*get_cycle)(struct simple_gauge *gauge, int *cycle);
+	int (*set_cycle)(struct simple_gauge *gauge, int old, int *new_cycle);
+	int (*get_vsys)(struct simple_gauge *gauge, int *uv);
+	int (*get_soc_by_ocv)(struct simple_gauge *sw, int ocv, int temp, int *soc);
+	int (*get_ocv_by_soc)(struct simple_gauge *sw, int soc, int temp, int *ocv);
+	int (*age_correct_cap)(struct simple_gauge *gauge, int cycle, int *cap);
+	int (*temp_correct_cap)(struct simple_gauge *gauge, int *cap, int temp);
+	int (*calibrate)(struct simple_gauge *sw);
+	int (*suspend_calibrate)(struct simple_gauge *sw, bool start);
+	int (*zero_cap_adjust)(struct simple_gauge *sw, int *effective_cap,
+				  int cc_uah, int vbat, int temp);
+};
+
+/**
+ * struct simple_gauge_desc - fuel gauge description
+ *
+ * The fuel gauges which benefit from generic computations (typically devices
+ * with coulomb counter. OCV - SOC table and iterative polling / error
+ * correction) provided by the simple_gauge framework must be described by the
+ * simple_gauge_desc prior registration to the simple_gauge framework.
+ *
+ * @name:		Identifying name for gauge (Is this needed?)
+ * @degrade_cycle_uah:	constant lost capacity / battery cycle in uAh.
+ * @amount_of_temp_dgr:	amount of temperature ranges provided in temp_dgr
+ * @temp_dgr:		ranges of constant lost capacity / temperature degree
+ *			in uAh. Ranges should be sorted in asecnding order by
+ *			temperature_floor.
+ * @poll_interval:	time interval in mS at which this fuel gauge iteration
+ *			loop for volage polling and coulomb counter corrections
+ *			should be ran.
+ * @calibrate_interval:	time interval in mS at which this IC should be
+ *			calibrated.
+ * @designed_cap:	designed battery capacity in uAh. Can be given here if
+ *			not available via batinfo.
+ * @allow_set_cycle:	Allow userspace to set cached battery cycle. If no HW
+ *			access is required when new battery cycle value is set
+ *			the driver can omit set_cycle callback and just set
+ *			this to true.
+ * @clamp_soc:		Set true tonot allow computed SOC to increase if state
+ *			is discharging.
+ * @cap_adjust_volt_threshold: some systems want to apply extra computation
+ *			to estimate battery capacity when voltage gets close
+ *			to system limit in order to avoid shut-down for as long
+ *			as possible. Such ICs can set this limit and optionally
+ *			implement zero_cap_adjust callback.
+ * @system_min_voltage:	ICs using the cap_adjust_volt_threshold and no
+ *			zero_cap_adjust call-back should set this voltage
+ *			to Vsys which correspond empty battery situation.
+ */
+struct simple_gauge_desc {
+	int degrade_cycle_uah;
+	int amount_of_temp_dgr;
+	struct power_supply_temp_degr *temp_dgr;
+	int poll_interval;
+	int calibrate_interval;
+	int designed_cap; /* This is also looked from batinfo (DT node) */
+	int cap_adjust_volt_threshold;
+	int system_min_voltage;
+	bool allow_set_cycle;
+	bool clamp_soc;
+	void *drv_data;
+};
+
+/**
+ * struct simple_gauge_psy - power supply configuration
+ *
+ * configuration being further passed to power-supply registration.
+ */
+struct simple_gauge_psy {
+	const char *psy_name;
+	struct device_node *of_node;
+	/* Device specific sysfs attributes, delivered to power_supply */
+	const struct attribute_group **attr_grp;
+
+	enum power_supply_property *additional_props;
+	int num_additional_props;
+
+	int (*is_writable)(struct simple_gauge *gauge,
+			   enum power_supply_property psp);
+	int (*get_custom_property)(struct simple_gauge *gauge,
+				   enum power_supply_property psp,
+				   union power_supply_propval *val);
+	int (*set_custom_property)(struct simple_gauge *gauge,
+				   enum power_supply_property psp,
+				   const union power_supply_propval *val);
+};
+
+/**
+ * struct simple_gauge - simple_gauge runtime data
+ *
+ * Internal to sw-gauge. Should not be directly accessed/modified by drivers
+ */
+struct simple_gauge {
+	struct device *dev;
+	int designed_cap;	/* This should be available for drivers */
+	struct simple_gauge_desc desc;
+	int cycle;
+	u64 next_iter; /* Time of next iteration in jiffies64 */
+	u64 next_cal; /* Time of next calibration in jiffies64 */
+	int force_run;
+	int refcount;
+	struct power_supply *psy;
+	enum power_supply_property *properties;
+
+	int (*get_custom_property)(struct simple_gauge *gauge,
+				  enum power_supply_property psp,
+				  union power_supply_propval *val);
+	int (*set_custom_property)(struct simple_gauge *gauge,
+				 enum power_supply_property psp,
+				 const union power_supply_propval *val);
+	int (*custom_is_writable)(struct simple_gauge *gauge,
+				  enum power_supply_property psp);
+	struct power_supply_battery_info info;
+	struct simple_gauge_ops ops;
+	struct list_head node;
+	int amount_of_temp_dgr;
+	struct power_supply_temp_degr *temp_dgr;
+	spinlock_t lock;
+	bool batinfo_got;
+	wait_queue_head_t wq;
+	int soc_rounding;
+	int clamped_soc;
+	/* Cached values from prev iteration */
+	int soc;		/* SOC computed at previous iteration */
+	int capacity_uah;	/* CAP computed at previous iteration (uAh) */
+	int cc_uah;		/* uAh reported by CC at previous iteration */
+	int temp;		/* Temperature at previous iteration */
+};
+
+struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent,
+						    struct simple_gauge_psy *psycfg,
+						    struct simple_gauge_ops *ops,
+						    struct simple_gauge_desc *desc);
+void psy_remove_simple_gauge(struct simple_gauge *sw);
+
+struct simple_gauge *__must_check
+devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg,
+			   struct simple_gauge_ops *ops,
+			   struct simple_gauge_desc *desc);
+void simple_gauge_run(struct simple_gauge *sw);
+int simple_gauge_run_blocking_timeout(struct simple_gauge *sg,
+				      unsigned int timeout_ms);
+int simple_gauge_run_blocking(struct simple_gauge *sg);
+void *simple_gauge_get_drvdata(struct simple_gauge *sg);
+
+#endif /* POWER_SW_GAUGE_H */
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (6 preceding siblings ...)
  2021-11-16 12:28 ` [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction Matti Vaittinen
@ 2021-11-16 12:29 ` Matti Vaittinen
  2021-11-16 12:29 ` [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver Matti Vaittinen
  8 siblings, 0 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:29 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Add core support for ROHM BD718(15/28/78) PMIC's charger blocks.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
RFCv3:
 - Updated BD71815 IRQ information

drop include/linux/mfd/rohm-bd71827.h
---
 drivers/mfd/rohm-bd71828.c       | 42 +++++++++++++++++----
 include/linux/mfd/rohm-bd71828.h | 65 ++++++++++++++++++++++++++++++++
 include/linux/mfd/rohm-generic.h |  2 +
 3 files changed, 101 insertions(+), 8 deletions(-)

diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c
index 714d9fcbf07b..0dd56c6f827a 100644
--- a/drivers/mfd/rohm-bd71828.c
+++ b/drivers/mfd/rohm-bd71828.c
@@ -45,8 +45,8 @@ static const struct resource bd71828_rtc_irqs[] = {
 
 static struct resource bd71815_power_irqs[] = {
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_RMV, "bd71815-dcin-rmv"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-clps-out"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-clps-in"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-dcin-clps-out"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-dcin-clps-in"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_RES, "bd71815-dcin-ovp-res"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_DET, "bd71815-dcin-ovp-det"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_MON_RES, "bd71815-dcin-mon-res"),
@@ -56,7 +56,7 @@ static struct resource bd71815_power_irqs[] = {
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_RES, "bd71815-vsys-low-res"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_DET, "bd71815-vsys-low-det"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-res"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-det"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_DET, "bd71815-vsys-mon-det"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TEMP, "bd71815-chg-wdg-temp"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TIME, "bd71815-chg-wdg"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_RECHARGE_RES, "bd71815-rechg-res"),
@@ -87,10 +87,10 @@ static struct resource bd71815_power_irqs[] = {
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_2_DET, "bd71815-bat-oc2-det"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_RES, "bd71815-bat-oc3-res"),
 	DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_DET, "bd71815-bat-oc3-det"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-bat-low-res"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-bat-low-det"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-bat-hi-res"),
-	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-bat-hi-det"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-temp-bat-low-res"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-temp-bat-low-det"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-temp-bat-hi-res"),
+	DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-temp-bat-hi-det"),
 };
 
 static struct mfd_cell bd71815_mfd_cells[] = {
@@ -109,6 +109,29 @@ static struct mfd_cell bd71815_mfd_cells[] = {
 	},
 };
 
+static const struct resource bd71828_power_irqs[] = {
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_CHG_TOPOFF_TO_DONE,
+			     "bd71828-chg-done"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_DET, "bd71828-pwr-dcin-in"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_RMV, "bd71828-pwr-dcin-out"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_RES,
+			     "bd71828-vbat-normal"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_DET, "bd71828-vbat-low"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_DET, "bd71828-btemp-hi"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_RES, "bd71828-btemp-cool"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_DET, "bd71828-btemp-lo"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_RES,
+			     "bd71828-btemp-warm"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_DET,
+			     "bd71828-temp-hi"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_RES,
+			     "bd71828-temp-norm"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_DET,
+			     "bd71828-temp-125-over"),
+	DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_RES,
+			     "bd71828-temp-125-under"),
+};
+
 static struct mfd_cell bd71828_mfd_cells[] = {
 	{ .name = "bd71828-pmic", },
 	{ .name = "bd71828-gpio", },
@@ -118,8 +141,11 @@ static struct mfd_cell bd71828_mfd_cells[] = {
 	 * BD70528 clock gate are the register address and mask.
 	 */
 	{ .name = "bd71828-clk", },
-	{ .name = "bd71827-power", },
 	{
+		.name = "bd71828-power",
+		.resources = bd71828_power_irqs,
+		.num_resources = ARRAY_SIZE(bd71828_power_irqs),
+	}, {
 		.name = "bd71828-rtc",
 		.resources = bd71828_rtc_irqs,
 		.num_resources = ARRAY_SIZE(bd71828_rtc_irqs),
diff --git a/include/linux/mfd/rohm-bd71828.h b/include/linux/mfd/rohm-bd71828.h
index 3b5f3a7db4bd..7e0674f184ee 100644
--- a/include/linux/mfd/rohm-bd71828.h
+++ b/include/linux/mfd/rohm-bd71828.h
@@ -188,6 +188,71 @@ enum {
 /* Charger/Battey */
 #define BD71828_REG_CHG_STATE		0x65
 #define BD71828_REG_CHG_FULL		0xd2
+#define BD71828_REG_CHG_EN		0x6F
+#define BD71828_REG_DCIN_STAT		0x68
+#define BD71828_MASK_DCIN_DET		0x01
+#define BD71828_REG_VDCIN_U		0x9c
+#define BD71828_MASK_CHG_EN		0x01
+#define BD71828_CHG_MASK_DCIN_U		0x0f
+#define BD71828_REG_BAT_STAT		0x67
+#define BD71828_REG_BAT_TEMP		0x6c
+#define BD71828_MASK_BAT_TEMP		0x07
+#define BD71828_BAT_TEMP_OPEN		0x07
+#define BD71828_MASK_BAT_DET		0x20
+#define BD71828_MASK_BAT_DET_DONE	0x10
+#define BD71828_REG_CHG_STATE		0x65
+#define BD71828_REG_VBAT_U		0x8c
+#define BD71828_MASK_VBAT_U		0x0f
+#define BD71828_REG_VBAT_REX_AVG_U	0x92
+
+#define BD71828_REG_OCV_PWRON_U		0x8A
+
+#define BD71828_REG_VBAT_MIN_AVG_U	0x8e
+#define BD71828_REG_VBAT_MIN_AVG_L	0x8f
+
+#define BD71828_REG_CC_CNT3		0xb5
+#define BD71828_REG_CC_CNT2		0xb6
+#define BD71828_REG_CC_CNT1		0xb7
+#define BD71828_REG_CC_CNT0		0xb8
+#define BD71828_REG_CC_CURCD_AVG_U	0xb2
+#define BD71828_MASK_CC_CURCD_AVG_U	0x3f
+#define BD71828_MASK_CC_CUR_DIR		0x80
+#define BD71828_REG_VM_BTMP_U		0xa1
+#define BD71828_REG_VM_BTMP_L		0xa2
+#define BD71828_MASK_VM_BTMP_U		0x0f
+#define BD71828_REG_COULOMB_CTRL	0xc4
+#define BD71828_REG_COULOMB_CTRL2	0xd2
+#define BD71828_MASK_REX_CC_CLR		0x01
+#define BD71828_MASK_FULL_CC_CLR	0x10
+#define BD71828_REG_CC_CNT_FULL3	0xbd
+#define BD71828_REG_CC_CNT_CHG3		0xc1
+
+#define BD71828_REG_VBAT_INITIAL1_U	0x86
+#define BD71828_REG_VBAT_INITIAL1_L	0x87
+
+#define BD71828_REG_VBAT_INITIAL2_U	0x88
+#define BD71828_REG_VBAT_INITIAL2_L	0x89
+
+#define BD71828_REG_IBAT_U		0xb0
+#define BD71828_REG_IBAT_L		0xb1
+
+#define BD71828_REG_IBAT_AVG_U		0xb2
+#define BD71828_REG_IBAT_AVG_L		0xb3
+
+#define BD71828_REG_VSYS_AVG_U		0x96
+#define BD71828_REG_VSYS_AVG_L		0x97
+#define BD71828_REG_VSYS_MIN_AVG_U	0x98
+#define BD71828_REG_VSYS_MIN_AVG_L	0x99
+#define BD71828_REG_CHG_SET1		0x75
+#define BD71828_REG_ALM_VBAT_LIMIT_U	0xaa
+#define BD71828_REG_BATCAP_MON_LIMIT_U	0xcc
+#define BD71828_REG_CONF		0x64
+
+#define BD71828_REG_DCIN_CLPS		0x71
+
+#define BD71828_REG_MEAS_CLEAR		0xaf
+
+
 
 /* LEDs */
 #define BD71828_REG_LED_CTRL		0x4A
diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h
index 35b392a0d73a..b60c2ea3e37c 100644
--- a/include/linux/mfd/rohm-generic.h
+++ b/include/linux/mfd/rohm-generic.h
@@ -14,9 +14,11 @@ enum rohm_chip_type {
 	ROHM_CHIP_TYPE_BD9576,
 	ROHM_CHIP_TYPE_BD70528,
 	ROHM_CHIP_TYPE_BD71815,
+	ROHM_CHIP_TYPE_BD71827,
 	ROHM_CHIP_TYPE_BD71828,
 	ROHM_CHIP_TYPE_BD71837,
 	ROHM_CHIP_TYPE_BD71847,
+	ROHM_CHIP_TYPE_BD71878,
 	ROHM_CHIP_TYPE_AMOUNT
 };
 
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver
  2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
                   ` (7 preceding siblings ...)
  2021-11-16 12:29 ` [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Matti Vaittinen
@ 2021-11-16 12:29 ` Matti Vaittinen
  8 siblings, 0 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-16 12:29 UTC (permalink / raw)
  To: Matti Vaittinen, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Matti Vaittinen, Lee Jones,
	Linus Walleij, rostokus, fan.chen, linux-pm, devicetree,
	linux-kernel, linux-power

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

Add charger driver for ROHM BD718(15/27/28/78) PMIC charger block.
Driver utilizes the simple-gauge for battery status / current polling,
CC correction and SOC estimation.

A version of driver which does not utilize simple-gauge can be seen
in ROHM venor specific Linux tree - comparison can visualize how
simple-gauge can simplify IC specific drivers.

https://github.com/RohmSemiconductor/Linux-Kernel-PMIC-Drivers/blob/stable-v5.4.6/drivers/power/supply/bd71827-power.c

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

---
Changes for RFCv3:
  - adapt to changed swgauge name (simple_gauge)
  - adapt to simple_gauge parameter changes
  - Initial BD71815 support
  - Use drvdata properly.
  - Sort includes
  - Prepare to provide dcin_collapse voltage from DT
  - clean unused defines
  - use OCV tables from batinfo if module params not given
  - do not directly call bd71827_voltage_to_capacity from calibration
    but use provided operation.
  - Mask the power-state from relax-condition on BD71815 as is done by
    the ROHM driver. REX state is used to do OCV => SOC conversion
    when battery is relaxed even if REX_CC was not used.
  - Clarify that we require either the module params or DT values for
    battery. Fail probe if parameters are not given.
  - Utilize degrade_cycle_uah aging degradation.
  - Get battery max and min values either as module parameters or from
    static battery node at DT.
  - Allow giving the zero correction threshold as a module param or
    compute it as 10% of "remaining battery voltage" based on max and
    min voltages given via DT.
  - Add proper MODULE_ALIAS
  - Implement VDR table reading from DT
  - Do not require fixed amount of battery parameters
  - Fix Coulomb Counter to uAh conversion
  - Fix endianess related warnings
  - clean-up comment
  - Avoid dividing by zero at VDR computation
  - Use the fwnode API instead of of_* API
  . don't assume 32bit int
  - Fix IC type prints
  - Fix the current sense resistor DT property *-ohm => *-ohms
---
 drivers/power/supply/Kconfig         |   11 +
 drivers/power/supply/Makefile        |    1 +
 drivers/power/supply/bd71827-power.c | 2473 ++++++++++++++++++++++++++
 include/linux/mfd/rohm-bd71827.h     |  295 +++
 4 files changed, 2780 insertions(+)
 create mode 100644 drivers/power/supply/bd71827-power.c
 create mode 100644 include/linux/mfd/rohm-bd71827.h

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 24a8d030a391..384afd53fe46 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -791,6 +791,17 @@ config CHARGER_UCS1002
 	  Say Y to enable support for Microchip UCS1002 Programmable
 	  USB Port Power Controller with Charger Emulation.
 
+config CHARGER_BD71828
+	tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC"
+	depends on MFD_ROHM_BD71828
+	select POWER_SIMPLE_GAUGE
+	help
+	  Say Y here to enable support for charger, battery and fuel gauge
+	  in ROHM BD71815, BD71817, BD71827, ROHM BD71828 power management
+	  ICs. This driver gets various bits of information about battery
+	  and charger states and also implements fuel gauge based on
+	  coulomb counters on PMIC.
+
 config CHARGER_BD99954
 	tristate "ROHM bd99954 charger driver"
 	depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 8c8c5f6a6492..e26f6ae598ad 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_CHARGER_CROS_PCHG)	+= cros_peripheral_charger.o
 obj-$(CONFIG_CHARGER_SC2731)	+= sc2731_charger.o
 obj-$(CONFIG_FUEL_GAUGE_SC27XX)	+= sc27xx_fuel_gauge.o
 obj-$(CONFIG_CHARGER_UCS1002)	+= ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD71828)	+= bd71827-power.o
 obj-$(CONFIG_CHARGER_BD99954)	+= bd99954-charger.o
 obj-$(CONFIG_CHARGER_WILCO)	+= wilco-charger.o
 obj-$(CONFIG_RN5T618_POWER)	+= rn5t618_power.o
diff --git a/drivers/power/supply/bd71827-power.c b/drivers/power/supply/bd71827-power.c
new file mode 100644
index 000000000000..6d2a9bc6f3dd
--- /dev/null
+++ b/drivers/power/supply/bd71827-power.c
@@ -0,0 +1,2473 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * bd71827-power.c
+ * @file ROHM BD71815, BD71827, BD71828 and BD71878 Charger driver
+ *
+ * Copyright 2021.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/linear_range.h>
+#include <linux/mfd/rohm-bd71815.h>
+#include <linux/mfd/rohm-bd71827.h>
+#include <linux/mfd/rohm-bd71828.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power/simple_gauge.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y))
+#define uAMP_TO_mAMP(ma) ((ma) / 1000)
+
+#define LINEAR_INTERPOLATE(y_hi, y_lo, x_hi, x_lo, x) \
+	((y_lo) + ((x) - (x_lo)) * ((y_hi) - (y_lo)) / ((x_hi) - (x_lo)))
+
+#define CAP2DSOC(cap, full_cap) ((cap) * 1000 / (full_cap))
+
+/* common defines */
+#define BD7182x_MASK_VBAT_U			0x1f
+#define BD7182x_MASK_VDCIN_U			0x0f
+#define BD7182x_MASK_IBAT_U			0x3f
+#define BD7182x_MASK_CURDIR_DISCHG		0x80
+#define BD7182x_MASK_CC_CCNTD_HI		0x0FFF
+#define BD7182x_MASK_CC_CCNTD			0x0FFFFFFF
+#define BD7182x_MASK_CHG_STATE			0x7f
+#define BD7182x_MASK_CC_FULL_CLR		0x10
+#define BD7182x_MASK_BAT_TEMP			0x07
+#define BD7182x_MASK_DCIN_DET			BIT(0)
+#define BD7182x_MASK_CONF_PON			BIT(0)
+#define BD71815_MASK_CONF_XSTB			BIT(1)
+#define BD7182x_MASK_BAT_STAT			0x3f
+#define BD7182x_MASK_DCIN_STAT			0x07
+
+#define BD7182x_MASK_CCNTRST			0x80
+#define BD7182x_MASK_CCNTENB			0x40
+#define BD7182x_MASK_CCCALIB			0x20
+#define BD7182x_MASK_WDT_AUTO			0x40
+#define BD7182x_MASK_VBAT_ALM_LIMIT_U		0x01
+#define BD7182x_MASK_CHG_EN			0x01
+
+#define BD7182x_DCIN_COLLAPSE_DEFAULT		0x36
+
+static const struct linear_range dcin_collapse = {
+	.min = 0,
+	.min_sel = 0,
+	.max_sel = 0xff,
+	/* Anti-collapse voltage threshold 0.0V to 20.4V range, 80 mV steps */
+	.step = 80000,
+};
+
+/* Measured min and max value clear bits */
+#define BD718XX_MASK_VSYS_MIN_AVG_CLR		0x10
+
+#define JITTER_DEFAULT				3000
+#define MAX_CURRENT_DEFAULT			890000		/* uA */
+#define AC_NAME					"bd71827_ac"
+#define BAT_NAME				"bd71827_bat"
+
+/*
+ * VBAT Low voltage detection Threshold
+ * 0x00D4*16mV = 212*0.016 = 3.392v
+ */
+#define VBAT_LOW_TH			0x00D4
+
+#define THR_RELAX_CURRENT_DEFAULT		5		/* mA */
+#define THR_RELAX_TIME_DEFAULT			(60 * 60)	/* sec. */
+
+#define DGRD_CYC_CAP_DEFAULT			88	/* 1 micro Ah */
+
+#define DGRD_TEMP_H_DEFAULT			450	/* 0.1 degrees C */
+#define DGRD_TEMP_M_DEFAULT			250	/* 0.1 degrees C */
+#define DGRD_TEMP_L_DEFAULT			50	/* 0.1 degrees C */
+#define DGRD_TEMP_VL_DEFAULT			0	/* 0.1 degrees C */
+
+#define SOC_EST_MAX_NUM_DEFAULT			5
+#define PWRCTRL_NORMAL				0x22
+#define PWRCTRL_RESET				0x23
+
+/*
+ * Originally we relied upon a fixed size table of OCV and VDR params.
+ * However the exising linux power-supply batinfo interface for getting the OCV
+ * values from DT does not have fixed amount of OCV values. Thus we use fixed
+ * parameter amount only for values provided as module params - and use this
+ * only as maximum number of parameters when values come from DT.
+ */
+#define NUM_BAT_PARAMS				23
+#define MAX_NUM_VDR_VALUES NUM_BAT_PARAMS
+
+struct pwr_regs {
+	int used_init_regs;
+	u8 vbat_init;
+	u8 vbat_init2;
+	u8 vbat_init3;
+	u8 vbat_avg;
+	u8 ibat;
+	u8 ibat_avg;
+	u8 meas_clear;
+	u8 vsys_min_avg;
+	u8 btemp_vth;
+	u8 chg_state;
+	u8 coulomb3;
+	u8 coulomb2;
+	u8 coulomb1;
+	u8 coulomb0;
+	u8 coulomb_ctrl;
+	u8 vbat_rex_avg;
+	u8 coulomb_full3;
+	u8 cc_full_clr;
+	u8 coulomb_chg3;
+	u8 bat_temp;
+	u8 dcin_stat;
+	u8 dcin_collapse_limit;
+	u8 chg_set1;
+	u8 chg_en;
+	u8 vbat_alm_limit_u;
+	u8 batcap_mon_limit_u;
+	u8 conf;
+	u8 vdcin;
+#ifdef PWRCTRL_HACK
+	u8 pwrctrl;
+#endif
+};
+
+/* Regions for High, Medium, Low, Very Low temperature */
+enum {
+	VDR_TEMP_HIGH,
+	VDR_TEMP_NORMAL,
+	VDR_TEMP_LOW,
+	VDR_TEMP_VERY_LOW,
+	NUM_VDR_TEMPS
+};
+
+/*
+ * This works as long as we have only one instance of this driver (which is
+ * likely to be the case even with DT originated battery info). Anyways,
+ * consider moving these in allocated data just to pretend to know what I am
+ * doing XD
+ */
+static int vdr_temps[NUM_VDR_TEMPS] = { -EINVAL, -EINVAL, -EINVAL, -EINVAL};
+static int g_num_vdr_params;
+
+static struct pwr_regs pwr_regs_bd71827 = {
+	.vbat_init = BD71827_REG_VM_OCV_PRE_U,
+	.vbat_init2 = BD71827_REG_VM_OCV_PST_U,
+	.vbat_init3 = BD71827_REG_VM_OCV_PWRON_U,
+	.used_init_regs = 3,
+	.vbat_avg = BD71827_REG_VM_SA_VBAT_U,
+	.ibat = BD71827_REG_CC_CURCD_U,
+	.ibat_avg = BD71827_REG_CC_SA_CURCD_U,
+	.meas_clear = BD71827_REG_VM_SA_MINMAX_CLR,
+	.vsys_min_avg = BD71827_REG_VM_SA_VSYS_MIN_U,
+	.btemp_vth = BD71827_REG_VM_BTMP,
+	.chg_state = BD71827_REG_CHG_STATE,
+	.coulomb3 = BD71827_REG_CC_CCNTD_3,
+	.coulomb2 = BD71827_REG_CC_CCNTD_2,
+	.coulomb1 = BD71827_REG_CC_CCNTD_1,
+	.coulomb0 = BD71827_REG_CC_CCNTD_0,
+	.coulomb_ctrl = BD71827_REG_CC_CTRL,
+	.coulomb_full3 = BD71827_REG_FULL_CCNTD_3,
+	.cc_full_clr = BD71827_REG_FULL_CTRL,
+	.coulomb_chg3 = BD71827_REG_CCNTD_CHG_3,
+	.bat_temp = BD71827_REG_BAT_TEMP,
+	.dcin_stat = BD71827_REG_DCIN_STAT,
+	.dcin_collapse_limit = BD71827_REG_DCIN_CLPS,
+	.chg_set1 = BD71827_REG_CHG_SET1,
+	.chg_en = BD71827_REG_CHG_SET1,
+	.vbat_alm_limit_u = BD71827_REG_ALM_VBAT_TH_U,
+	.batcap_mon_limit_u = BD71827_REG_CC_BATCAP1_TH_U,
+	.conf = BD71827_REG_CONF,
+	.vdcin = BD71827_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+	.pwrctrl = BD71827_REG_PWRCTRL,
+	.hibernate_mask = 0x1,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71828 = {
+	.vbat_init = BD71828_REG_VBAT_INITIAL1_U,
+	.vbat_init2 = BD71828_REG_VBAT_INITIAL2_U,
+	.vbat_init3 = BD71828_REG_OCV_PWRON_U,
+	.used_init_regs = 3,
+	.vbat_avg = BD71828_REG_VBAT_U,
+	.ibat = BD71828_REG_IBAT_U,
+	.ibat_avg = BD71828_REG_IBAT_AVG_U,
+	.meas_clear = BD71828_REG_MEAS_CLEAR,
+	.vsys_min_avg = BD71828_REG_VSYS_MIN_AVG_U,
+	.btemp_vth = BD71828_REG_VM_BTMP_U,
+	.chg_state = BD71828_REG_CHG_STATE,
+	.coulomb3 = BD71828_REG_CC_CNT3,
+	.coulomb2 = BD71828_REG_CC_CNT2,
+	.coulomb1 = BD71828_REG_CC_CNT1,
+	.coulomb0 = BD71828_REG_CC_CNT0,
+	.coulomb_ctrl = BD71828_REG_COULOMB_CTRL,
+	.vbat_rex_avg = BD71828_REG_VBAT_REX_AVG_U,
+	.coulomb_full3 = BD71828_REG_CC_CNT_FULL3,
+	.cc_full_clr = BD71828_REG_COULOMB_CTRL2,
+	.coulomb_chg3 = BD71828_REG_CC_CNT_CHG3,
+	.bat_temp = BD71828_REG_BAT_TEMP,
+	.dcin_stat = BD71828_REG_DCIN_STAT,
+	.dcin_collapse_limit = BD71828_REG_DCIN_CLPS,
+	.chg_set1 = BD71828_REG_CHG_SET1,
+	.chg_en   = BD71828_REG_CHG_EN,
+	.vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U,
+	.batcap_mon_limit_u = BD71828_REG_BATCAP_MON_LIMIT_U,
+	.conf = BD71828_REG_CONF,
+	.vdcin = BD71828_REG_VDCIN_U,
+#ifdef PWRCTRL_HACK
+	.pwrctrl = BD71828_REG_PS_CTRL_1,
+	.hibernate_mask = 0x2,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71815 = {
+	.vbat_init = BD71815_REG_VM_OCV_PRE_U,
+	.vbat_init2 = BD71815_REG_VM_OCV_PST_U,
+	.used_init_regs = 2,
+	.vbat_avg = BD71815_REG_VM_SA_VBAT_U,
+	/* BD71815 does not have separate current and current avg */
+	.ibat = BD71815_REG_CC_CURCD_U,
+	.ibat_avg = BD71815_REG_CC_CURCD_U,
+
+	.meas_clear = BD71815_REG_VM_SA_MINMAX_CLR,
+	.vsys_min_avg = BD71815_REG_VM_SA_VSYS_MIN_U,
+	.btemp_vth = BD71815_REG_VM_BTMP,
+	.chg_state = BD71815_REG_CHG_STATE,
+	.coulomb3 = BD71815_REG_CC_CCNTD_3,
+	.coulomb2 = BD71815_REG_CC_CCNTD_2,
+	.coulomb1 = BD71815_REG_CC_CCNTD_1,
+	.coulomb0 = BD71815_REG_CC_CCNTD_0,
+	.coulomb_ctrl = BD71815_REG_CC_CTRL,
+	.vbat_rex_avg = BD71815_REG_REX_SA_VBAT_U,
+	.coulomb_full3 = BD71815_REG_FULL_CCNTD_3,
+	.cc_full_clr = BD71815_REG_FULL_CTRL,
+	.coulomb_chg3 = BD71815_REG_CCNTD_CHG_3,
+	.bat_temp = BD71815_REG_BAT_TEMP,
+	.dcin_stat = BD71815_REG_DCIN_STAT,
+	.dcin_collapse_limit = BD71815_REG_DCIN_CLPS,
+	.chg_set1 = BD71815_REG_CHG_SET1,
+	.chg_en   = BD71815_REG_CHG_SET1,
+	.vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U,
+	.batcap_mon_limit_u = BD71815_REG_CC_BATCAP1_TH_U,
+	.conf = BD71815_REG_CONF,
+
+	.vdcin = BD71815_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+	#error "Not implemented for BD71815"
+#endif
+};
+
+/*
+ * unit 0.1%
+ *
+ * These are the SOCs we use at zero-correction. If OCV is given via DT
+ * we interpolate the OCV tables to get the OCV corresponding these SOCs.
+ *
+ * If VDR tables are given we will ovrride these SOCs by SOCs corresponding
+ * the VDR values.
+ */
+static int soc_table_default[NUM_BAT_PARAMS] = {
+	1000,
+	1000,
+	950,
+	900,
+	850,
+	800,
+	750,
+	700,
+	650,
+	600,
+	550,
+	500,
+	450,
+	400,
+	350,
+	300,
+	250,
+	200,
+	150,
+	100,
+	50,
+	0,
+	-50
+};
+
+/* Module parameters */
+static int use_load_bat_params;
+static int param_thr_voltage;
+static int param_max_voltage;
+static int param_min_voltage;
+
+static int battery_cap_mah;
+
+static int dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT;
+
+static int soc_est_max_num;
+static int ocv_table[NUM_BAT_PARAMS];
+static int soc_table[NUM_BAT_PARAMS];
+static int vdr_table_h[NUM_BAT_PARAMS];
+static int vdr_table_m[NUM_BAT_PARAMS];
+static int vdr_table_l[NUM_BAT_PARAMS];
+static int vdr_table_vl[NUM_BAT_PARAMS];
+
+struct bd71827_power {
+	struct simple_gauge *sw;
+	struct simple_gauge_desc gdesc;
+	struct simple_gauge_ops ops;
+	struct regmap *regmap;
+	enum rohm_chip_type chip_type;
+	struct device *dev;
+	struct power_supply *ac;	/**< alternating current power */
+	int gauge_delay;		/**< Schedule to call gauge algorithm */
+	int	relax_time;		/**< Relax Time */
+
+	struct pwr_regs *regs;
+	/* Reg val to uA */
+	int curr_factor;
+	int rsens;
+	int min_voltage;
+	int max_voltage;
+	int low_thr_voltage;
+	int (*get_temp)(struct bd71827_power *pwr, int *temp);
+	int (*bat_inserted)(struct bd71827_power *pwr);
+	int battery_cap;
+	struct power_supply_battery_info batinfo;
+};
+
+#define __CC_to_UAH(pwr, cc)				\
+({							\
+	u64 __tmp = ((u64)(cc)) * 1000000000LLU;	\
+							\
+	do_div(__tmp, (pwr)->rsens * 36 / 1000);	\
+	__tmp;						\
+})
+
+#define CC16_to_UAH(pwe, cc) ((int)__CC_to_UAH((pwr), (cc)))
+#define CC32_to_UAH(pwe, cc) ((int)(__CC_to_UAH((pwr), (cc)) >> 16))
+
+/*
+ * rsens is typically tens of Mohms so dividing by 1000 should be ok. (usual
+ * values are 10 and 30 Mohms so division is likely to go even). We do this
+ * to avoid doing two do_divs which would be unnecessary performance hit
+ * even if this should not be time critical.
+ */
+#define UAH_to_CC(pwr, uah) ({			\
+	u64 __tmp = (uah);			\
+	u32 __rs = (pwr)->rsens / 1000;		\
+	__tmp *= ((u64)__rs) * 36LLU;		\
+						\
+	do_div(__tmp, 1000000000);		\
+	(int)__tmp;				\
+})
+
+#define CALIB_NORM			0
+#define CALIB_START			1
+#define CALIB_GO			2
+
+enum {
+	STAT_POWER_ON,
+	STAT_INITIALIZED,
+};
+
+static int bd7182x_write16(struct bd71827_power *pwr, int reg, uint16_t val)
+{
+	__be16 tmp;
+
+	tmp = cpu_to_be16(val);
+
+	return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+}
+
+static int bd7182x_read16_himask(struct bd71827_power *pwr, int reg, int himask,
+				 uint16_t *val)
+{
+	struct regmap *regmap = pwr->regmap;
+	int ret;
+	__be16 rvals;
+	u8 *tmp = (u8 *) &rvals;
+
+	ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val));
+	if (!ret) {
+		*tmp &= himask;
+		*val = be16_to_cpu(rvals);
+	}
+	return ret;
+}
+
+#define MAX_INITIAL_OCV_REGS 3
+/* get initial battery voltage and current */
+static int bd71827_get_init_voltage(struct bd71827_power *pwr,
+				     int *ocv)
+{
+	int ret;
+	int i;
+	u8 regs[MAX_INITIAL_OCV_REGS] = {
+		pwr->regs->vbat_init,
+		pwr->regs->vbat_init2,
+		pwr->regs->vbat_init3
+	 };
+	uint16_t vals[MAX_INITIAL_OCV_REGS];
+
+	*ocv = 0;
+
+	for (i = 0; i < pwr->regs->used_init_regs; i++) {
+
+		ret = bd7182x_read16_himask(pwr, regs[i], BD7182x_MASK_VBAT_U,
+					    &vals[i]);
+		if (ret) {
+			dev_err(pwr->dev,
+				"Failed to read initial battery voltage\n");
+			return ret;
+		}
+		*ocv = MAX(vals[i], *ocv);
+
+		dev_dbg(pwr->dev, "VM_OCV_%d = %d\n", i, ((int)vals[i]) * 1000);
+	}
+
+	*ocv *= 1000;
+
+	return ret;
+}
+
+static int bd71827_get_vbat(struct bd71827_power *pwr, int *vcell)
+{
+	uint16_t tmp_vcell;
+	int ret;
+
+	ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg,
+				    BD7182x_MASK_VBAT_U, &tmp_vcell);
+	if (ret)
+		dev_err(pwr->dev, "Failed to read battery average voltage\n");
+	else
+		*vcell = ((int)tmp_vcell) * 1000;
+
+	return ret;
+}
+
+static int bd71827_get_current_ds_adc(struct bd71827_power *pwr, int *curr, int *curr_avg)
+{
+	__be16 tmp_curr;
+	char *tmp = (char *)&tmp_curr;
+	int dir = 1;
+	int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg };
+	int *vals[] = { curr, curr_avg };
+	int ret, i;
+
+	for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr,
+				       sizeof(tmp_curr));
+		if (ret)
+			break;
+
+		if (*tmp & BD7182x_MASK_CURDIR_DISCHG)
+			dir = -1;
+
+		*tmp &= BD7182x_MASK_IBAT_U;
+		tmp_curr = be16_to_cpu(tmp_curr);
+
+		*vals[i] = dir * ((int)tmp_curr) * pwr->curr_factor;
+	}
+
+	return ret;
+}
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv,
+				       int temp __always_unused,
+				       int *dsoc);
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv, int temp,
+				       int *dsoc)
+{
+	int i = 0;
+	struct bd71827_power *pwr;
+
+	/* If ocv_table is not given try luck with batinfo */
+	if (!use_load_bat_params || !ocv_table[0]) {
+		if (!sw)
+			return -EINVAL;
+
+		pwr = simple_gauge_get_drvdata(sw);
+		*dsoc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+		if (*dsoc < 0)
+			return *dsoc;
+
+		return 0;
+	}
+
+	/* Default or module param OCV table. We have NUM_BAT_PARAMS */
+	if (ocv > ocv_table[0]) {
+		*dsoc = soc_table[0];
+	} else {
+		for (i = 0; i < NUM_BAT_PARAMS; i++) {
+			if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) {
+				*dsoc = (soc_table[i] - soc_table[i+1]) *
+				      (ocv - ocv_table[i+1]) /
+				      (ocv_table[i] - ocv_table[i+1]);
+				*dsoc += soc_table[i+1];
+				break;
+			}
+		}
+		if (i == NUM_BAT_PARAMS)
+			*dsoc = soc_table[i - 1];
+	}
+
+	return 0;
+}
+
+/* Unit is tenths of degree C */
+static int bd71827_get_temp(struct simple_gauge *sw, int *temp)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	struct regmap *regmap = pwr->regmap;
+	int ret;
+	int t;
+
+	ret = regmap_read(regmap, pwr->regs->btemp_vth, &t);
+	t = 200 - t;
+
+	if (ret || t > 200) {
+		dev_err(pwr->dev, "Failed to read battery temperature\n");
+		*temp = 2000;
+	} else {
+		*temp = t * 10;
+	}
+
+	return ret;
+}
+
+/* Unit is tenths of degree C */
+static int bd71828_get_temp(struct simple_gauge *sw, int *temp)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	uint16_t t;
+	int ret;
+	int tmp = 200 * 10000;
+
+	ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth,
+				    BD71828_MASK_VM_BTMP_U, &t);
+	if (ret || t > 3200)
+		dev_err(pwr->dev,
+			"Failed to read system min average voltage\n");
+
+	tmp -= 625ULL * (unsigned int)t;
+	*temp = tmp / 1000;
+
+	return ret;
+}
+
+static int bd71827_charge_status(struct bd71827_power *pwr,
+				 int *s, int *h)
+{
+	unsigned int state;
+	int status, health;
+	int ret = 1;
+
+	ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state);
+	if (ret)
+		dev_err(pwr->dev, "charger status reading failed (%d)\n", ret);
+
+	state &= BD7182x_MASK_CHG_STATE;
+
+	dev_dbg(pwr->dev, "CHG_STATE %d\n", state);
+
+	switch (state) {
+	case 0x00:
+		ret = 0;
+		status = POWER_SUPPLY_STATUS_DISCHARGING;
+		health = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	case 0x01:
+	case 0x02:
+	case 0x03:
+	case 0x0E:
+		status = POWER_SUPPLY_STATUS_CHARGING;
+		health = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	case 0x0F:
+		ret = 0;
+		status = POWER_SUPPLY_STATUS_FULL;
+		health = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	case 0x10:
+	case 0x11:
+	case 0x12:
+	case 0x13:
+	case 0x14:
+	case 0x20:
+	case 0x21:
+	case 0x22:
+	case 0x23:
+	case 0x24:
+		ret = 0;
+		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		health = POWER_SUPPLY_HEALTH_OVERHEAT;
+		break;
+	case 0x30:
+	case 0x31:
+	case 0x32:
+	case 0x40:
+		ret = 0;
+		status = POWER_SUPPLY_STATUS_DISCHARGING;
+		health = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	case 0x7f:
+	default:
+		ret = 0;
+		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		health = POWER_SUPPLY_HEALTH_DEAD;
+		break;
+	}
+
+	if (s)
+		*s = status;
+	if (h)
+		*h = health;
+
+	return ret;
+}
+
+static int __write_cc(struct bd71827_power *pwr, uint16_t bcap,
+		      unsigned int reg, uint32_t *new)
+{
+	int ret;
+	__be32 tmp;
+	__be16 *swap_hi = (__be16 *)&tmp;
+	uint16_t *swap_lo = swap_hi + 1;
+
+	*swap_hi = cpu_to_be16(bcap & BD7182x_MASK_CC_CCNTD_HI);
+	*swap_lo = 0;
+
+	ret = regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+	if (ret) {
+		dev_err(pwr->dev, "Failed to write coulomb counter\n");
+		return ret;
+	}
+	if (new)
+		*new = be32_to_cpu(tmp);
+
+	return ret;
+}
+
+static int write_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+	int ret;
+	uint32_t new;
+
+	ret = __write_cc(pwr, bcap, pwr->regs->coulomb3, &new);
+	if (!ret)
+		dev_dbg(pwr->dev, "CC set to 0x%x\n", (int)new);
+
+	return ret;
+}
+
+static int stop_cc(struct bd71827_power *pwr)
+{
+	struct regmap *r = pwr->regmap;
+
+	return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+				  BD7182x_MASK_CCNTENB, 0);
+}
+
+static int start_cc(struct bd71827_power *pwr)
+{
+	struct regmap *r = pwr->regmap;
+
+	return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+				  BD7182x_MASK_CCNTENB, BD7182x_MASK_CCNTENB);
+}
+
+static int update_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+	int ret;
+
+	ret = stop_cc(pwr);
+	if (ret)
+		goto err_out;
+
+	ret = write_cc(pwr, bcap);
+	if (ret)
+		goto enable_out;
+
+	ret = start_cc(pwr);
+	if (ret)
+		goto enable_out;
+
+	return 0;
+
+enable_out:
+	start_cc(pwr);
+err_out:
+	dev_err(pwr->dev, "Coulomb counter write failed  (%d)\n", ret);
+	return ret;
+}
+
+static int bd71828_set_uah(struct simple_gauge *sw, int bcap)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	u16 cc_val = UAH_to_CC(pwr, bcap);
+
+	return update_cc(pwr, cc_val);
+}
+
+static int __read_cc(struct bd71827_power *pwr, u32 *cc, unsigned int reg)
+{
+	int ret;
+	__be32 tmp_cc;
+
+	ret = regmap_bulk_read(pwr->regmap, reg, &tmp_cc, sizeof(tmp_cc));
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read coulomb counter\n");
+		return ret;
+	}
+	*cc = be32_to_cpu(tmp_cc) & BD7182x_MASK_CC_CCNTD;
+
+	return 0;
+}
+
+static int read_cc_full(struct bd71827_power *pwr, u32 *cc)
+{
+	return __read_cc(pwr, cc, pwr->regs->coulomb_full3);
+}
+
+static int read_cc(struct bd71827_power *pwr, u32 *cc)
+{
+	return __read_cc(pwr, cc, pwr->regs->coulomb3);
+}
+
+/** @brief set initial coulomb counter value from battery voltage
+ * @param pwr power device
+ * @return 0
+ */
+static int calibration_coulomb_counter(struct bd71827_power *pwr)
+{
+	struct regmap *regmap = pwr->regmap;
+	u32 bcap;
+	int soc, ocv, ret = 0, tmpret = 0;
+
+	/* Get initial OCV */
+	bd71827_get_init_voltage(pwr, &ocv);
+	dev_dbg(pwr->dev, "ocv %d\n", ocv);
+
+	if (pwr->ops.get_soc_by_ocv) {
+		ret = pwr->ops.get_soc_by_ocv(NULL, ocv, 0, &soc);
+	} else {
+		soc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+		if (soc < 0)
+			return soc;
+	}
+	/* Get init soc from ocv/soc table */
+
+	dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
+	if (soc < 0)
+		soc = 0;
+
+	bcap = UAH_to_CC(pwr, pwr->battery_cap) * soc / 1000;
+	write_cc(pwr, bcap + UAH_to_CC(pwr, pwr->battery_cap) / 200);
+
+	msleep(5000);
+
+	tmpret = write_cc(pwr, bcap);
+	if (tmpret)
+		goto enable_cc_out;
+
+enable_cc_out:
+	/* Start canceling offset of the DS ADC. This needs 1 second at least */
+	ret = regmap_update_bits(regmap, pwr->regs->coulomb_ctrl,
+				 BD7182x_MASK_CCCALIB, BD7182x_MASK_CCCALIB);
+
+	return (tmpret) ? tmpret : ret;
+}
+
+/* This should be used to get VSYS for low limit calculations */
+static int bd71827_get_vsys_min(struct simple_gauge *sw, int *uv)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	uint16_t tmp_vcell;
+	int ret;
+
+	ret = bd7182x_read16_himask(pwr, pwr->regs->vsys_min_avg,
+				    BD7182x_MASK_VBAT_U, &tmp_vcell);
+	if (ret) {
+		dev_err(pwr->dev,
+			"Failed to read system min average voltage\n");
+		return ret;
+	}
+	ret = regmap_update_bits(pwr->regmap, pwr->regs->meas_clear,
+				 BD718XX_MASK_VSYS_MIN_AVG_CLR,
+				 BD718XX_MASK_VSYS_MIN_AVG_CLR);
+	if (ret)
+		dev_warn(pwr->dev, "failed to clear cached Vsys\n");
+
+	*uv = ((int)tmp_vcell) * 1000;
+
+	return 0;
+}
+
+/* This should be used for relax Vbat with BD71827 */
+static int bd71827_get_voltage(struct simple_gauge *sg, int *vbat)
+{
+	int voltage, ret;
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sg);
+
+	ret = bd71827_get_vbat(pwr, &voltage);
+	if (ret)
+		return ret;
+
+	*vbat = voltage;
+
+	return 0;
+}
+
+static int bd71828_get_uah_from_full(struct simple_gauge *sw, int *from_full_uah)
+{
+	int ret;
+	struct bd71827_power *pwr;
+	struct regmap *regmap;
+	u32 full_charged_coulomb_cnt;
+	u32 cc;
+	int diff_coulomb_cnt;
+
+	pwr = simple_gauge_get_drvdata(sw);
+	regmap = pwr->regmap;
+
+	/*
+	 * Read and clear the stored CC value from moment when battery was
+	 * last charged to full.
+	 */
+	ret = read_cc_full(pwr, &full_charged_coulomb_cnt);
+	if (ret) {
+		dev_err(pwr->dev, "failed to read full coulomb counter\n");
+		return ret;
+	}
+
+	ret = regmap_update_bits(regmap, pwr->regs->cc_full_clr,
+					 BD7182x_MASK_CC_FULL_CLR,
+					 BD7182x_MASK_CC_FULL_CLR);
+	/* Get current CC value to estimate change of charge since full */
+	ret = read_cc(pwr, &cc);
+	if (ret)
+		return ret;
+
+	diff_coulomb_cnt = full_charged_coulomb_cnt - cc;
+
+	diff_coulomb_cnt >>= 16;
+
+	/*
+	 * Ignore possible increase in CC which can be caused by ADC offset or
+	 * temperature change
+	 */
+	if (diff_coulomb_cnt > 0)
+		diff_coulomb_cnt = 0;
+
+	*from_full_uah = CC16_to_UAH(pwr, diff_coulomb_cnt);
+
+	return 0;
+}
+
+static int bd71828_get_uah(struct simple_gauge *sw, int *uah)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	u32 cc;
+	int ret;
+
+	ret = read_cc(pwr, &cc);
+	if (!ret)
+		*uah = CC32_to_UAH(pwr, cc);
+
+	return ret;
+}
+
+/*
+ * Standard batinfo supports only accuracy of 1% for SOC - which
+ * may not be sufficient for us. SWGAUGE provides soc in unts of 0.1% here
+ * to allow more accurate computation.
+ */
+static int bd71827_get_ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv)
+{
+	int i = 0;
+	struct bd71827_power *pwr;
+
+	/* If soc_table is not given try luck with batinfo */
+	if (!use_load_bat_params || !ocv_table[0]) {
+		if (!sw)
+			return -EINVAL;
+
+		pwr = simple_gauge_get_drvdata(sw);
+		*ocv = power_supply_batinfo_dcap2ocv(&pwr->batinfo, dsoc, temp);
+		if (*ocv < 0)
+			return *ocv;
+
+		return 0;
+	}
+
+	/* Default or module param OCV table. We have NUM_BAT_PARAMS */
+
+	if (dsoc > soc_table[0]) {
+		*ocv = pwr->max_voltage;
+		return 0;
+	}
+	if (dsoc == 0) {
+		*ocv = ocv_table[NUM_BAT_PARAMS - 2];
+		return 0;
+	}
+
+	i = 0;
+	while (i < NUM_BAT_PARAMS - 1) {
+		if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+			*ocv = (ocv_table[i] - ocv_table[i+1]) *
+			       (dsoc - soc_table[i+1]) / (soc_table[i] -
+			       soc_table[i+1]) + ocv_table[i+1];
+			return 0;
+		}
+		i++;
+	}
+
+	*ocv = ocv_table[NUM_BAT_PARAMS - 1];
+
+	return 0;
+}
+
+static void calc_vdr(int *res, int *vdr, int temp, int dgrd_temp,
+		     int *vdr_hi, int dgrd_temp_hi, int items)
+{
+	int i;
+
+	/* Get VDR weighed by temperature */
+	for (i = 0; i < items; i++)
+		res[i] = LINEAR_INTERPOLATE(vdr_hi[i], vdr[i], dgrd_temp_hi,
+					    dgrd_temp, temp);
+}
+
+/* get VDR(Voltage Drop Rate) value by SOC */
+static int bd71827_get_vdr(struct bd71827_power *pwr, int dsoc, int temp)
+{
+	int i = 0;
+	int vdr = 100;
+	int vdr_table[NUM_BAT_PARAMS];
+
+	/* Calculate VDR by temperature */
+	if (temp >= vdr_temps[VDR_TEMP_HIGH])
+		for (i = 0; i < g_num_vdr_params; i++)
+			vdr_table[i] = vdr_table_h[i];
+	else if (temp >= vdr_temps[VDR_TEMP_NORMAL])
+		calc_vdr(vdr_table, vdr_table_m, temp, vdr_temps[VDR_TEMP_NORMAL],
+			 vdr_table_h, vdr_temps[VDR_TEMP_HIGH],
+			 g_num_vdr_params);
+	else if (temp >= vdr_temps[VDR_TEMP_LOW])
+		calc_vdr(vdr_table, vdr_table_l, temp, vdr_temps[VDR_TEMP_LOW],
+			 vdr_table_m, vdr_temps[VDR_TEMP_NORMAL],
+			 g_num_vdr_params);
+	else if (temp >= vdr_temps[VDR_TEMP_VERY_LOW])
+		calc_vdr(vdr_table, vdr_table_vl, temp,
+			 vdr_temps[VDR_TEMP_VERY_LOW], vdr_table_l,
+			 vdr_temps[VDR_TEMP_LOW], g_num_vdr_params);
+	else
+		for (i = 0; i < g_num_vdr_params; i++)
+			vdr_table[i] = vdr_table_vl[i];
+
+	if (dsoc > soc_table[0]) {
+		vdr = 100;
+	} else if (dsoc == 0) {
+		vdr = vdr_table[g_num_vdr_params - 1];
+	} else {
+		for (i = 0; i < g_num_vdr_params - 1; i++)
+			if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+				vdr = LINEAR_INTERPOLATE(vdr_table[i],
+							 vdr_table[i+1],
+							 soc_table[i],
+							 soc_table[i+1], dsoc);
+
+				break;
+			}
+		if (i == g_num_vdr_params - 1)
+			vdr = vdr_table[i];
+	}
+	dev_dbg(pwr->dev, "vdr = %d\n", vdr);
+	return vdr;
+}
+
+static int bd71828_zero_correct(struct simple_gauge *sw, int *effective_cap,
+				int cc_uah, int vbat, int temp)
+{
+	int ocv_table_load[NUM_BAT_PARAMS];
+	int i, ret;
+	/* Assume fixed-size module param table */
+	static int params = NUM_BAT_PARAMS;
+	int ocv;
+	int dsoc;
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+	/*
+	 * Calculate SOC from CC and effective battery cap.
+	 * Use unit of 0.1% for dsoc to improve accuracy
+	 */
+	dsoc = CAP2DSOC(cc_uah, *effective_cap);
+	dev_dbg(pwr->dev, "dsoc = %d\n", dsoc);
+
+	ret = bd71827_get_ocv(sw, dsoc, 0, &ocv);
+	if (ret)
+		return ret;
+
+	if (!ocv_table[0]) {
+		for (i = 0; i < g_num_vdr_params; i++)
+			ocv_table[i] = power_supply_batinfo_dcap2ocv(&pwr->batinfo,
+								     soc_table[i], temp);
+		/*
+		 * Update amount of OCV values id we didn't have the fixed size
+		 * module param table
+		 */
+		params = g_num_vdr_params;
+	}
+	for (i = 1; i < params; i++) {
+		ocv_table_load[i] = ocv_table[i] - (ocv - vbat);
+		if (ocv_table_load[i] <= pwr->min_voltage) {
+			dev_dbg(pwr->dev, "ocv_table_load[%d] = %d\n", i,
+				ocv_table_load[i]);
+			break;
+		}
+	}
+
+	/*
+	 * For improved accuracy ROHM helps customers to measure some
+	 * battery voltage drop curves to do further SOC estimation improvement.
+	 * If VDR tables are available we perform these corrections.
+	 */
+	if (i < params) {
+		int zero_soc_pos;
+		int j, k, m;
+		int dv;
+		int lost_cap, new_lost_cap;
+		int dsoc0;
+		int vdr, vdr0;
+		int soc_range;
+
+		/*
+		 * The original ROHM algorithm had fixed amount of OCV and VDR
+		 * values. The quiet expectation of the algorithm was that the
+		 * second last value in these tables correspond zero SOC. In
+		 * order to relax this assumption when values come from DT we
+		 * try to scan the SOC table for zero SOC.
+		 */
+		for (zero_soc_pos = params - 1; zero_soc_pos >= 0;
+		     zero_soc_pos--)
+			if (soc_table[zero_soc_pos] >= 0)
+				break;
+
+		if (soc_table[zero_soc_pos])
+			dev_warn_once(pwr->dev,
+				      "VDR/OCV: zero SOC not found\n");
+
+		/*
+		 * We want to know the zero soc position from the last entry
+		 * in SOC table so that we know where the fully depleted cap
+		 * is met.
+		 */
+		zero_soc_pos = params - zero_soc_pos;
+
+		soc_range = (soc_table[i - 1] - soc_table[i]) / 10;
+		if (soc_range < 1) {
+			dev_err_once(pwr->dev, "Bad SOC table values %u, %u\n",
+				soc_table[i - 1], soc_table[i]);
+			return -EINVAL;
+		}
+		dv = (ocv_table_load[i - 1] - ocv_table_load[i]) / soc_range; /* was hard coded 5 */
+		for (j = 1; j < soc_range/* was 5 */; j++) {
+			if ((ocv_table_load[i] + dv * j) >
+			    pwr->min_voltage) {
+				break;
+			}
+		}
+
+		lost_cap = ((params - zero_soc_pos - i) * soc_range /* was 5 */ +
+			   (j - 1)) * *effective_cap / 100;
+		dev_dbg(pwr->dev, "lost_cap-1 = %d\n", lost_cap);
+
+		for (m = 0; m < soc_est_max_num; m++) {
+			new_lost_cap = lost_cap;
+			dsoc0 = CAP2DSOC(lost_cap, *effective_cap);
+			if ((dsoc >= 0 && dsoc0 > dsoc) ||
+			    (dsoc < 0 && dsoc0 < dsoc))
+				dsoc0 = dsoc;
+
+			dev_dbg(pwr->dev, "dsoc0(%d) = %d\n", m, dsoc0);
+
+			vdr = bd71827_get_vdr(pwr, dsoc, temp);
+			vdr0 = bd71827_get_vdr(pwr, dsoc0, temp);
+
+			for (k = 1; k < params; k++) {
+				if (!vdr) {
+					dev_err(pwr->dev,
+						"VDR zero-correction failed\n");
+					break;
+				}
+				ocv_table_load[k] = ocv_table[k] -
+						    (ocv - vbat) * vdr0 / vdr;
+				if (ocv_table_load[k] <= pwr->min_voltage) {
+					dev_dbg(pwr->dev,
+						"ocv_table_load[%d] = %d\n",  k,
+						ocv_table_load[k]);
+					break;
+				}
+			}
+			if (k < params) {
+				dv = (ocv_table_load[k-1] -
+				     ocv_table_load[k]) / 5;
+				for (j = 1; j < 5; j++)
+					if ((ocv_table_load[k] + dv * j) >
+					     pwr->min_voltage)
+						break;
+
+				new_lost_cap = ((params - zero_soc_pos - k) *
+						 5 + (j - 1)) *
+						*effective_cap / 100;
+				if (soc_est_max_num == 1)
+					lost_cap = new_lost_cap;
+				else
+					lost_cap += (new_lost_cap - lost_cap) /
+						    (2 * (soc_est_max_num - m));
+				dev_dbg(pwr->dev, "lost_cap-2(%d) = %d\n", m,
+					lost_cap);
+			}
+			if (new_lost_cap == lost_cap)
+				break;
+		}
+
+		*effective_cap -= lost_cap;
+	}
+
+	return 0;
+}
+
+static int get_chg_online(struct bd71827_power *pwr, int *chg_online)
+{
+	int r, ret;
+
+	ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read DCIN status\n");
+		return ret;
+	}
+	*chg_online = ((r & BD7182x_MASK_DCIN_DET) != 0);
+
+	return 0;
+}
+
+static int get_bat_online(struct bd71827_power *pwr, int *bat_online)
+{
+	int r, ret;
+
+#define BAT_OPEN	0x7
+	ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read battery temperature\n");
+		return ret;
+	}
+	*bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN);
+
+	return 0;
+}
+
+static int bd71828_bat_inserted(struct bd71827_power *pwr)
+{
+	int ret, val;
+
+	ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read CONF register\n");
+		return 0;
+	}
+	ret = val & BD7182x_MASK_CONF_PON;
+
+	if (ret)
+		regmap_update_bits(pwr->regmap, pwr->regs->conf,
+				   BD7182x_MASK_CONF_PON, 0);
+
+	return ret;
+}
+
+static int bd71815_bat_inserted(struct bd71827_power *pwr)
+{
+	int ret, val;
+
+	ret = regmap_read(pwr->regmap, pwr->regs->conf, &val);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read CONF register\n");
+		return ret;
+	}
+
+	ret = !(val & BD71815_MASK_CONF_XSTB);
+	if (ret)
+		regmap_write(pwr->regmap, pwr->regs->conf,  val |
+			     BD71815_MASK_CONF_XSTB);
+
+	return ret;
+}
+
+static int bd71827_init_hardware(struct bd71827_power *pwr)
+{
+	int ret;
+
+	/* TODO: Collapse limit should come from device-tree ? */
+	ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit,
+			   BD7182x_DCIN_COLLAPSE_DEFAULT);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to write DCIN collapse limit\n");
+		return ret;
+	}
+
+	ret = pwr->bat_inserted(pwr);
+	if (ret < 0)
+		return ret;
+
+	if (ret) {
+		int cc_val;
+
+		/* Ensure Coulomb Counter is stopped */
+		ret = stop_cc(pwr);
+		if (ret)
+			return ret;
+
+		/* Set Coulomb Counter Reset bit*/
+		ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+					 BD7182x_MASK_CCNTRST,
+					 BD7182x_MASK_CCNTRST);
+		if (ret)
+			return ret;
+
+		/* Clear Coulomb Counter Reset bit*/
+		ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+					 BD7182x_MASK_CCNTRST, 0);
+		if (ret)
+			return ret;
+
+		/* Set initial Coulomb Counter by HW OCV */
+		calibration_coulomb_counter(pwr);
+
+		/* WDT_FST auto set */
+		ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1,
+					 BD7182x_MASK_WDT_AUTO,
+					 BD7182x_MASK_WDT_AUTO);
+		if (ret)
+			return ret;
+
+		ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u,
+				      VBAT_LOW_TH);
+		if (ret)
+			return ret;
+
+		/* Set monitor threshold to 9/10 of battery uAh capacity */
+		cc_val = UAH_to_CC(pwr, pwr->battery_cap);
+
+		/*
+		 * On BD71815 "we mask the power-state" from relax detection.
+		 * I am unsure what the impact of the power-state would be if
+		 * we didn't - but this is what the vendor driver did - and
+		 * that driver has been used in few projects so I just assume
+		 * this is needed.
+		 */
+		if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) {
+			ret = regmap_set_bits(pwr->regmap,
+					      BD71815_REG_REX_CTRL_1,
+					      REX_PMU_STATE_MASK);
+			if (ret)
+				return ret;
+		}
+
+		ret = bd7182x_write16(pwr, pwr->regs->batcap_mon_limit_u,
+				      cc_val * 9 / 10);
+		if (ret)
+			return ret;
+
+		dev_dbg(pwr->dev, "REG_CC_BATCAP1_TH = %d\n",
+			(cc_val * 9 / 10));
+	}
+
+	return start_cc(pwr);
+}
+
+#define MK_2_100MCELSIUS(m_kevl_in) (((int)(m_kevl_in) - 273150) / 100)
+static int get_vdr_from_dt(struct bd71827_power *pwr,
+			   struct fwnode_handle *node, int temp_values)
+{
+	int i, ret, num_values, *tmp_table;
+	u32 vdr_kelvin[NUM_VDR_TEMPS];
+
+	if (temp_values != NUM_VDR_TEMPS) {
+		dev_err(pwr->dev, "Bad VDR temperature table size (%d). Expected %d",
+			temp_values, NUM_VDR_TEMPS);
+		return -EINVAL;
+	}
+	ret = fwnode_property_read_u32_array(node,
+					    "rohm,volt-drop-temp-millikelvin",
+					    &vdr_kelvin[0], NUM_VDR_TEMPS);
+	if (ret) {
+		dev_err(pwr->dev, "Invalid VDR temperatures in device-tree");
+		return ret;
+	}
+	/* Convert millikelvin to .1 celsius as is expected by VDR algo */
+	for (i = 0; i < NUM_VDR_TEMPS; i++)
+		vdr_temps[i] = MK_2_100MCELSIUS(vdr_kelvin[i]);
+
+	num_values = fwnode_property_count_u32(node, "rohm,volt-drop-soc");
+	if (num_values <= 0 || num_values > MAX_NUM_VDR_VALUES) {
+		dev_err(pwr->dev, "malformed voltage drop parameters\n");
+		return -EINVAL;
+	}
+	g_num_vdr_params = num_values;
+
+	tmp_table = kcalloc(num_values, sizeof(int), GFP_KERNEL);
+	if (!tmp_table)
+		return -ENOMEM;
+	/*
+	 * We collect NUM_VDR_TEMPS + 1 tables, all temperature tables +
+	 * the SOC table
+	 */
+	for (i = 0; i < NUM_VDR_TEMPS + 1; i++) {
+		int index;
+		static const char * const prop[] = {
+			/*
+			 * SOC in units of 0.1 percent. TODO: Check if we have
+			 * standard DT unit for percentage with higher accuracy
+			 */
+			"rohm,volt-drop-soc",
+			"rohm,volt-drop-high-temp-microvolt",
+			"rohm,volt-drop-normal-temp-microvolt",
+			"rohm,volt-drop-low-temp-microvolt",
+			"rohm,volt-drop-very-low-temp-microvolt",
+		};
+		int *tables[5] = {
+			&soc_table[0], &vdr_table_h[0], &vdr_table_m[0],
+			&vdr_table_l[0], &vdr_table_vl[0]
+		};
+
+		if (num_values != fwnode_property_count_u32(node, prop[i])) {
+			dev_err(pwr->dev,
+				"%s: Bad size. Expected %d parameters",
+				prop[i], num_values);
+			ret = -EINVAL;
+			goto out;
+		}
+		ret = fwnode_property_read_u32_array(node, prop[i], tmp_table,
+						     num_values);
+		if (ret) {
+			dev_err(pwr->dev,
+				"Invalid VDR temperatures in device-tree");
+			goto out;
+		}
+		for (index = 0; index < num_values; index++)
+			tables[i][index] = tmp_table[index];
+	}
+out:
+	kfree(tmp_table);
+
+	return ret;
+}
+
+ /* Set default parameters if no module parameters were given */
+static int bd71827_set_battery_parameters(struct bd71827_power *pwr)
+{
+	int i;
+
+	/*
+	 * We support getting battery parameters from static battery node
+	 * or as module parameters.
+	 */
+	if (!use_load_bat_params) {
+		int ret, num_vdr;
+		struct fwnode_handle *node;
+
+		/*
+		 * power_supply_dev_get_battery_info uses devm internally
+		 * so we do not need explicit remove()
+		 */
+		ret = power_supply_dev_get_battery_info(pwr->dev->parent, NULL,
+							&pwr->batinfo);
+		if (ret) {
+			dev_err(pwr->dev, "No battery information (%d)\n", ret);
+			return ret;
+		}
+
+		if (!pwr->batinfo.ocv_table[0]) {
+			dev_err(pwr->dev, "No Open Circuit Voltages for battery\n");
+			return -EINVAL;
+		}
+
+		if (pwr->batinfo.charge_full_design_uah == -EINVAL) {
+			dev_err(pwr->dev, "Unknown battery capacity\n");
+			return -EINVAL;
+		}
+
+		if (pwr->batinfo.voltage_max_design_uv == -EINVAL) {
+			/*
+			 * We could try digging this from OCV table but
+			 * lets just bail-out for now
+			 */
+			dev_err(pwr->dev, "Unknown max voltage\n");
+			return -EINVAL;
+		}
+		pwr->max_voltage = pwr->batinfo.voltage_max_design_uv;
+
+		if (pwr->batinfo.voltage_min_design_uv == -EINVAL) {
+			/* We could try digging this from OCV table....? */
+			dev_err(pwr->dev, "Unknown max voltage\n");
+			return -EINVAL;
+		}
+		pwr->min_voltage = pwr->batinfo.voltage_min_design_uv;
+		/*
+		 * Let's default the zero-correction limit to 10%
+		 * voltage limit. TODO: See what would be the correct value
+		 */
+		pwr->battery_cap = pwr->batinfo.charge_full_design_uah;
+		pwr->gdesc.degrade_cycle_uah = pwr->batinfo.degrade_cycle_uah;
+
+		soc_est_max_num = SOC_EST_MAX_NUM_DEFAULT;
+
+		node = dev_fwnode(pwr->dev->parent);
+		if (!node) {
+			dev_err(pwr->dev, "no charger node\n");
+			return -ENODEV;
+		}
+		node = fwnode_find_reference(node, "monitored-battery", 0);
+		if (IS_ERR(node)) {
+			dev_err(pwr->dev, "No battery node found\n");
+			return PTR_ERR(node);
+		}
+
+		num_vdr = fwnode_property_count_u32(node, "rohm,volt-drop-temp-millikelvin");
+		if (num_vdr > 0) {
+			ret = get_vdr_from_dt(pwr, node, num_vdr);
+			if (ret)
+				return ret;
+		} else {
+			vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+			vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+			vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+			vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+		}
+	} else {
+		if (vdr_temps[VDR_TEMP_HIGH] == -EINVAL ||
+		    vdr_temps[VDR_TEMP_NORMAL] == -EINVAL ||
+		    vdr_temps[VDR_TEMP_LOW] == -EINVAL ||
+		    vdr_temps[VDR_TEMP_VERY_LOW] == -EINVAL) {
+			vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+			vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+			vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+			vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+		}
+
+		pwr->min_voltage = param_max_voltage;
+		pwr->max_voltage = param_min_voltage;
+		pwr->low_thr_voltage = param_thr_voltage;
+		pwr->battery_cap = battery_cap_mah * 1000;
+		pwr->gdesc.degrade_cycle_uah = dgrd_cyc_cap;
+	}
+	/* SOC table is expected to be in descending order. */
+	if (!soc_table[0])
+		for (i = 0; i < NUM_BAT_PARAMS; i++)
+			soc_table[i] = soc_table_default[i];
+
+	if (!pwr->min_voltage || !pwr->max_voltage || !pwr->battery_cap) {
+		dev_err(pwr->dev, "Battery parameters missing\n");
+
+		return -EINVAL;
+	}
+	if (!pwr->low_thr_voltage)
+		pwr->low_thr_voltage = pwr->min_voltage +
+			(pwr->max_voltage - pwr->min_voltage) / 10;
+	return 0;
+}
+
+static int bd71827_charger_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct bd71827_power *pwr = dev_get_drvdata(psy->dev.parent);
+	u32 vot;
+	uint16_t tmp;
+	int online;
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = get_chg_online(pwr, &online);
+		if (!ret)
+			val->intval = online;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin,
+					    BD7182x_MASK_VDCIN_U, &tmp);
+		if (ret)
+			return ret;
+
+		vot = tmp;
+		/* 5 milli volt steps */
+		val->intval = 5000 * vot;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bd71827_battery_get_property(struct simple_gauge *gauge,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(gauge);
+	int ret = 0;
+	int status, health, tmp, curr, curr_avg;
+
+	if (psp == POWER_SUPPLY_PROP_STATUS || psp == POWER_SUPPLY_PROP_HEALTH
+	    || psp == POWER_SUPPLY_PROP_CHARGE_TYPE)
+		ret = bd71827_charge_status(pwr, &status, &health);
+	else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG ||
+		 psp == POWER_SUPPLY_PROP_CURRENT_NOW)
+		ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg);
+	if (ret)
+		return ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = status;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = health;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		if (status == POWER_SUPPLY_STATUS_CHARGING)
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		else
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+	case POWER_SUPPLY_PROP_PRESENT:
+		ret = get_bat_online(pwr, &tmp);
+		if (!ret)
+			val->intval = tmp;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = bd71827_get_vbat(pwr, &tmp);
+		val->intval = tmp;
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_AVG:
+		val->intval = curr_avg;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		val->intval = curr;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		val->intval = pwr->max_voltage;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		val->intval = pwr->min_voltage;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		val->intval = MAX_CURRENT_DEFAULT;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/** @brief ac properties */
+static enum power_supply_property bd71827_charger_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static enum power_supply_property bd71827_battery_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_AVG,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static ssize_t charging_store(struct device *dev,
+					   struct device_attribute *attr,
+					   const char *buf,
+					   size_t count)
+{
+	struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+	ssize_t ret = 0;
+	unsigned long val;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val != 0 && val != 1) {
+		dev_warn(dev, "use 0/1 to disable/enable charging\n");
+		return -EINVAL;
+	}
+
+	if (val)
+		ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+					 BD7182x_MASK_CHG_EN,
+					 BD7182x_MASK_CHG_EN);
+	else
+		ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en,
+					 BD7182x_MASK_CHG_EN,
+					 0);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t charging_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+	int chg_en, chg_online, ret;
+
+	ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en);
+	if (ret)
+		return ret;
+
+	ret = get_chg_online(pwr, &chg_online);
+	if (ret)
+		return ret;
+
+	chg_en &= BD7182x_MASK_CHG_EN;
+	return sprintf(buf, "%x\n", chg_online && chg_en);
+}
+
+static DEVICE_ATTR_RW(charging);
+
+static struct attribute *bd71827_sysfs_attributes[] = {
+	&dev_attr_charging.attr, NULL,
+};
+
+static const struct attribute_group bd71827_sysfs_attr_group = {
+	.attrs = bd71827_sysfs_attributes,
+};
+
+static const struct attribute_group *bd71827_sysfs_attr_groups[] = {
+	&bd71827_sysfs_attr_group, NULL,
+};
+
+/** @brief powers supplied by bd71827_ac */
+static char *bd71827_ac_supplied_to[] = {
+	BAT_NAME,
+};
+
+static const struct power_supply_desc bd71827_ac_desc = {
+	.name		= AC_NAME,
+	.type		= POWER_SUPPLY_TYPE_MAINS,
+	.properties	= bd71827_charger_props,
+	.num_properties	= ARRAY_SIZE(bd71827_charger_props),
+	.get_property	= bd71827_charger_get_property,
+};
+
+static const struct simple_gauge_psy gauge_psy_config = {
+	.psy_name = BAT_NAME,
+	.additional_props = bd71827_battery_props,
+	.num_additional_props = ARRAY_SIZE(bd71827_battery_props),
+	.get_custom_property = bd71827_battery_get_property,
+};
+#ifdef PWRCTRL_HACK
+/*
+ * This is not-so-pretty hack for allowing external code to call
+ * bd71827_chip_hibernate() without this power device-data
+ */
+static struct bd71827_power *hack;
+static DEFINE_SPINLOCK(pwrlock);
+
+static struct bd71827_power *get_power(void)
+{
+	mutex_lock(&pwrlock);
+	if (!hack) {
+		mutex_unlock(&pwrlock);
+		return -ENOENT;
+	}
+	return hack;
+}
+
+static void put_power(void)
+{
+	mutex_unlock(&pwrlock);
+}
+
+static int set_power(struct bd71827_power *pwr)
+{
+	mutex_lock(&pwrlock);
+	hack = pwr;
+	mutex_unlock(&pwrlock);
+}
+
+static void free_power(void)
+{
+	mutex_lock(&pwrlock);
+	hack = NULL;
+	mutex_unlock(&pwrlock);
+}
+
+/*
+ * TODO: Find the corret way to do this
+ */
+void bd71827_chip_hibernate(void)
+{
+	struct bd71827_power *pwr = get_power();
+
+	if (IS_ERR(pwr)) {
+		pr_err("%s called before probe finished\n", __func__);
+		return PTR_ERR(pwr);
+	}
+
+	/* programming sequence in EANAB-151 */
+	regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+			   pwr->regs->hibernate_mask, 0);
+	regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+			   pwr->regs->hibernate_mask,
+			   pwr->regs->hibernate_mask);
+	put_power();
+}
+#endif
+
+#define RSENS_CURR 10000000000LLU
+
+#define BD_ISR_NAME(name) \
+bd7181x_##name##_isr
+
+#define BD_ISR_BAT(name, print, run_gauge)				\
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data)		\
+{									\
+	struct bd71827_power *pwr = (struct bd71827_power *)data;	\
+									\
+	if (run_gauge)							\
+		simple_gauge_run(pwr->sw);				\
+	dev_dbg(pwr->dev, "%s\n", print);				\
+	power_supply_changed(pwr->sw->psy);				\
+									\
+	return IRQ_HANDLED;						\
+}
+
+#define BD_ISR_AC(name, print, run_gauge)				\
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data)		\
+{									\
+	struct bd71827_power *pwr = (struct bd71827_power *)data;	\
+									\
+	if (run_gauge)							\
+		simple_gauge_run(pwr->sw);				\
+	power_supply_changed(pwr->ac);					\
+	dev_dbg(pwr->dev, "%s\n", print);				\
+	power_supply_changed(pwr->sw->psy);				\
+									\
+	return IRQ_HANDLED;						\
+}
+
+#define BD_ISR_DUMMY(name, print)					\
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data)		\
+{									\
+	struct bd71827_power *pwr = (struct bd71827_power *)data;	\
+									\
+	dev_dbg(pwr->dev, "%s\n", print);				\
+									\
+	return IRQ_HANDLED;						\
+}
+
+BD_ISR_BAT(chg_state_changed, "CHG state changed", true)
+/* DCIN voltage changes */
+BD_ISR_AC(dcin_removed, "DCIN removed", true)
+BD_ISR_AC(clps_out, "DCIN voltage back to normal", true)
+BD_ISR_AC(clps_in, "DCIN voltage collapsed", false)
+BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true)
+BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true)
+
+BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold")
+BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold")
+
+BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared")
+BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage")
+BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared")
+BD_ISR_DUMMY(vsys_low_det, "VSYS low")
+BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed")
+BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected")
+BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true)
+BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true)
+BD_ISR_BAT(bat_removed, "Battery removed", true)
+BD_ISR_BAT(bat_det, "Battery detected", true)
+/* TODO: Verify the meaning of these interrupts */
+BD_ISR_BAT(rechg_det, "Recharging", true)
+BD_ISR_BAT(rechg_res, "Recharge ending", true)
+BD_ISR_DUMMY(temp_transit, "Temperature transition")
+BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false)
+BD_ISR_BAT(therm_det, "bd71815-therm-det", true)
+BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false)
+BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true)
+BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false)
+BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true)
+BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true)
+BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true)
+/* What should we do here? */
+BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false)
+BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true)
+BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true)
+BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false)
+BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true)
+BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false)
+BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true)
+BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false)
+BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true)
+BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false)
+BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true)
+BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true)
+BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true)
+BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true)
+
+static irqreturn_t bd7182x_dcin_removed(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	simple_gauge_run(pwr->sw);
+	power_supply_changed(pwr->ac);
+	dev_dbg(pwr->dev, "DCIN removed\n");
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd718x7_chg_done(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	/*
+	 * Battery is likely to be FULL => run simple_gauge to initiate
+	 * CC setting
+	 */
+	simple_gauge_run(pwr->sw);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd7182x_dcin_detected(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "DCIN inserted\n");
+	power_supply_changed(pwr->ac);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_res(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VBAT LOW Resumed\n");
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_det(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VBAT LOW Detected\n");
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_det(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_warn(pwr->dev, "Overtemp Detected\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_res(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "Overtemp Resumed\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_det(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "Lowtemp Detected\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_res(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "Lowtemp Resumed\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_det(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VF Detected\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_res(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VF Resumed\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_det(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VF125 Detected\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_res(int irq, void *data)
+{
+	struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+	dev_dbg(pwr->dev, "VF125 Resumed\n");
+	power_supply_changed(pwr->sw->psy);
+
+	return IRQ_HANDLED;
+}
+
+struct bd7182x_irq_res {
+	const char *name;
+	irq_handler_t handler;
+};
+
+#define BDIRQ(na, hn) { .name = (na), .handler = (hn) }
+
+static int bd7182x_get_irqs(struct platform_device *pdev,
+			    struct bd71827_power *pwr)
+{
+	int i, irq, ret;
+	static const struct bd7182x_irq_res bd71815_irqs[] = {
+		BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)),
+		BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)),
+		BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)),
+		BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)),
+		BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)),
+		BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)),
+		BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)),
+
+		BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)),
+		BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)),
+		BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)),
+		BDIRQ("bd71815-vsys-low-det",  BD_ISR_NAME(vsys_low_det)),
+		BDIRQ("bd71815-vsys-mon-res",  BD_ISR_NAME(vsys_mon_res)),
+		BDIRQ("bd71815-vsys-mon-det",  BD_ISR_NAME(vsys_mon_det)),
+		BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)),
+		BDIRQ("bd71815-chg-wdg",  BD_ISR_NAME(chg_wdg)),
+		BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)),
+		BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)),
+		BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)),
+		BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)),
+		BDIRQ("bd71815-bat-temp-normal", bd71827_temp_bat_hi_res),
+		BDIRQ("bd71815-bat-temp-erange", bd71827_temp_bat_hi_det),
+		BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)),
+		BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)),
+
+		/* Add ISRs for these */
+		BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)),
+		BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)),
+		BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)),
+		BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)),
+		BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)),
+		BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)),
+		BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)),
+		BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)),
+		BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)),
+		BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)),
+		BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)),
+		/* cc-mon 1 & 3 ? */
+		BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)),
+		BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)),
+		BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)),
+		BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)),
+		BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)),
+		BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)),
+		BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)),
+		BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)),
+		BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)),
+		BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)),
+		BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)),
+		/*
+		 * TODO: add rest of the IRQs and re-check the handling.
+		 * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3,
+		 * bd71815-bat-low-res, bd71815-bat-low-det,
+		 * bd71815-bat-hi-res, bd71815-bat-hi-det.
+		 */
+	};
+	static const struct bd7182x_irq_res bd71828_irqs[] = {
+		BDIRQ("bd71828-chg-done", bd718x7_chg_done),
+		BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected),
+		BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed),
+		BDIRQ("bd71828-vbat-normal", bd71827_vbat_low_res),
+		BDIRQ("bd71828-vbat-low", bd71827_vbat_low_det),
+		BDIRQ("bd71828-btemp-hi", bd71827_temp_bat_hi_det),
+		BDIRQ("bd71828-btemp-cool", bd71827_temp_bat_hi_res),
+		BDIRQ("bd71828-btemp-lo", bd71827_temp_bat_low_det),
+		BDIRQ("bd71828-btemp-warm", bd71827_temp_bat_low_res),
+		BDIRQ("bd71828-temp-hi", bd71827_temp_vf_det),
+		BDIRQ("bd71828-temp-norm", bd71827_temp_vf_res),
+		BDIRQ("bd71828-temp-125-over", bd71827_temp_vf125_det),
+		BDIRQ("bd71828-temp-125-under", bd71827_temp_vf125_res),
+	};
+	int num_irqs;
+	const struct bd7182x_irq_res *irqs;
+
+
+	switch (pwr->chip_type) {
+	case ROHM_CHIP_TYPE_BD71827:
+	case ROHM_CHIP_TYPE_BD71828:
+		irqs = &bd71828_irqs[0];
+		num_irqs = ARRAY_SIZE(bd71828_irqs);
+		break;
+	case ROHM_CHIP_TYPE_BD71815:
+		irqs = &bd71815_irqs[0];
+		num_irqs = ARRAY_SIZE(bd71815_irqs);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_irqs; i++) {
+		irq = platform_get_irq_byname(pdev, irqs[i].name);
+
+		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+						irqs[i].handler, 0,
+						irqs[i].name, pwr);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+#define RSENS_DEFAULT_30MOHM 30000000
+
+static int bd7182x_get_rsens(struct bd71827_power *pwr)
+{
+	u64 tmp = RSENS_CURR;
+	int rsens_ohm = RSENS_DEFAULT_30MOHM;
+	struct fwnode_handle *node = NULL;
+
+	if (pwr->dev->parent)
+		node = dev_fwnode(pwr->dev->parent);
+
+	if (node) {
+		int ret;
+		uint32_t rs;
+
+		ret = fwnode_property_read_u32(node,
+					       "rohm,charger-sense-resistor-ohms",
+					       &rs);
+		if (ret) {
+			if (ret == -EINVAL) {
+				rs = RSENS_DEFAULT_30MOHM;
+			} else {
+				dev_err(pwr->dev, "Bad RSENS dt property\n");
+				return ret;
+			}
+		}
+		if (!rs) {
+			dev_err(pwr->dev, "Bad RSENS value\n");
+			return -EINVAL;
+		}
+
+		rsens_ohm = (int)rs;
+	}
+
+	/* Reg val to uA */
+	do_div(tmp, rsens_ohm);
+
+	pwr->curr_factor = tmp;
+	pwr->rsens = rsens_ohm;
+	dev_dbg(pwr->dev, "Setting rsens to %u ohm\n", pwr->rsens);
+	dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor);
+	return 0;
+}
+
+/*
+ * BD71827 has no proper support for detecting relaxed battery.
+ * Driver has implemented current polling and logic has been that:
+ * if for the specified time current consumption has always been below
+ * threshold value when polled - then battery is assumed to be relaxed. This
+ * for sure leads to a problem when current cunsumption has had short
+ * 'spikes' - but this is what the logic has been - and it has probably been
+ * working as the driver is left as is? So let's just keep this logic here.
+ */
+static bool bd71827_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+	int tmp_curr_mA, ret, curr, curr_avg;
+
+	ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to get current\n");
+		return false;
+	}
+
+	tmp_curr_mA = uAMP_TO_mAMP(curr);
+	if ((tmp_curr_mA * tmp_curr_mA) <=
+	    (THR_RELAX_CURRENT_DEFAULT * THR_RELAX_CURRENT_DEFAULT))
+		 /* No load */
+		pwr->relax_time = pwr->relax_time + (JITTER_DEFAULT / 1000);
+	else
+		pwr->relax_time = 0;
+	if (!(pwr->relax_time >= THR_RELAX_TIME_DEFAULT))
+		return false;
+
+	ret = bd71827_get_voltage(sw, rex_volt);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to get Vbat\n");
+		return false;
+	}
+
+	return true;
+}
+
+static bool bd71828_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+	int ret;
+	u16 tmp;
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+	ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_rex_avg,
+				    BD7182x_MASK_VBAT_U, &tmp);
+	if (ret) {
+		dev_err(pwr->dev,
+			"Failed to read battery relax voltage\n");
+		return 0;
+	}
+	*rex_volt = tmp * 1000;
+
+	return !!tmp;
+}
+
+static int bd71828_get_cycle(struct simple_gauge *sw, int *cycle)
+{
+	int tmpret, ret, update = 0;
+	uint16_t charged_coulomb_cnt;
+	int cc_designed_cap;
+	struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+	ret = bd7182x_read16_himask(pwr, pwr->regs->coulomb_chg3, 0xff,
+				    &charged_coulomb_cnt);
+	if (ret) {
+		dev_err(pwr->dev, "Failed to read charging CC (%d)\n", ret);
+		return ret;
+	}
+	dev_dbg(pwr->dev, "charged_coulomb_cnt = 0x%x\n",
+		(int)charged_coulomb_cnt);
+
+	cc_designed_cap = UAH_to_CC(pwr, sw->designed_cap);
+
+	while (charged_coulomb_cnt >= cc_designed_cap) {
+		update = 1;
+		/*
+		 * sw-gauge caches old cycle value so we do not need to care
+		 * about it. We just add new cycles
+		 */
+		*cycle = *cycle + 1;
+		dev_dbg(pwr->dev,  "Update cycle = %d\n", *cycle);
+		charged_coulomb_cnt -= cc_designed_cap;
+	}
+	if (update) {
+		ret = stop_cc(pwr);
+		if (ret)
+			return ret;
+
+		ret = bd7182x_write16(pwr, pwr->regs->coulomb_chg3,
+				      charged_coulomb_cnt);
+		if (ret) {
+			dev_err(pwr->dev, "Failed to update charging CC (%d)\n",
+				ret);
+		}
+
+		tmpret = start_cc(pwr);
+		if (tmpret)
+			return tmpret;
+	}
+	return ret;
+}
+
+static void fgauge_initial_values(struct bd71827_power *pwr)
+{
+	struct simple_gauge_desc *d = &pwr->gdesc;
+	struct simple_gauge_ops *o = &pwr->ops;
+	bool use_vdr = false;
+
+	/* TODO: See if these could be get from DT? */
+	d->poll_interval = JITTER_DEFAULT; /* 3 seconds */
+	d->allow_set_cycle = true;
+	d->cap_adjust_volt_threshold = pwr->low_thr_voltage;
+	d->designed_cap = pwr->battery_cap;
+	d->clamp_soc = true;
+
+	o->get_uah_from_full = bd71828_get_uah_from_full;	/* Ok */
+	o->get_uah = bd71828_get_uah;				/* Ok */
+	o->update_cc_uah = bd71828_set_uah;			/* Ok */
+	o->get_cycle = bd71828_get_cycle;			/* Ok */
+	o->get_vsys = bd71827_get_vsys_min;
+
+	/*
+	 * We have custom OCV table => provide our own volt_to_cap and
+	 * ocv_by_soc which utilize the custom tables.
+	 */
+	if (ocv_table[0]) {
+		dev_dbg(pwr->dev, "OCV values given as parameters\n");
+		o->get_soc_by_ocv = &bd71827_voltage_to_capacity;
+		o->get_ocv_by_soc = &bd71827_get_ocv;
+	}
+
+	if (vdr_table_h[0] && vdr_table_m[0] && vdr_table_l[0] &&
+		   vdr_table_vl[0])
+		use_vdr = true;
+
+	/* TODO:
+	 *	o->suspend_calibrate = bd71828_suspend_calibrate;
+	 */
+	switch (pwr->chip_type) {
+	case ROHM_CHIP_TYPE_BD71828:
+		o->get_temp = bd71828_get_temp;
+		o->is_relaxed = bd71828_is_relaxed;
+		if (use_vdr)
+			o->zero_cap_adjust = bd71828_zero_correct;
+		break;
+	case ROHM_CHIP_TYPE_BD71827:
+		o->get_temp = bd71827_get_temp;
+		o->is_relaxed = bd71827_is_relaxed;
+		if (use_vdr)
+			o->zero_cap_adjust = bd71828_zero_correct;
+		break;
+	case ROHM_CHIP_TYPE_BD71815:
+		o->get_temp = bd71827_get_temp;
+		o->is_relaxed = bd71828_is_relaxed;
+		/*
+		 * TODO: BD71815 has not been used with VDR. This is untested
+		 * but I don't see why it wouldn't work by setting thresholds
+		 * and by populating correct SOC-OCV tables.
+		 */
+		if (use_vdr)
+			o->zero_cap_adjust = bd71828_zero_correct;
+		break;
+	/*
+	 * No need to handle default here as this is done already in probe.
+	 * But this keeps gcc shut-up.
+	 */
+	default:
+		break;
+	}
+}
+
+static int bd71827_power_probe(struct platform_device *pdev)
+{
+	struct bd71827_power *pwr;
+	struct power_supply_config ac_cfg = {};
+	struct simple_gauge_psy psycfg;
+	int ret;
+	struct regmap *regmap;
+
+	psycfg = gauge_psy_config;
+
+	regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!regmap) {
+		dev_err(&pdev->dev, "No parent regmap\n");
+		return -EINVAL;
+	}
+
+	pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL);
+	if (!pwr)
+		return -ENOMEM;
+
+	pwr->regmap = regmap;
+	pwr->dev = &pdev->dev;
+	pwr->chip_type = platform_get_device_id(pdev)->driver_data;
+
+	switch (pwr->chip_type) {
+	case ROHM_CHIP_TYPE_BD71828:
+		pwr->bat_inserted = bd71828_bat_inserted;
+		pwr->regs = &pwr_regs_bd71828;
+		dev_dbg(pwr->dev, "Found ROHM BD71828\n");
+		psycfg.psy_name	= "bd71828-charger";
+		break;
+	case ROHM_CHIP_TYPE_BD71827:
+		pwr->bat_inserted = bd71828_bat_inserted;
+		pwr->regs = &pwr_regs_bd71827;
+		dev_dbg(pwr->dev, "Found ROHM BD71817\n");
+		psycfg.psy_name	= "bd71827-charger";
+		break;
+	case ROHM_CHIP_TYPE_BD71815:
+		pwr->bat_inserted = bd71815_bat_inserted;
+		pwr->regs = &pwr_regs_bd71815;
+		psycfg.psy_name	= "bd71815-charger";
+		dev_dbg(pwr->dev, "Found ROHM BD71815\n");
+	break;
+	default:
+		dev_err(pwr->dev, "Unknown PMIC\n");
+		return -EINVAL;
+	}
+
+	/* We need to set batcap etc before we do set fgauge initial values */
+	ret = bd71827_set_battery_parameters(pwr);
+	if (ret) {
+		dev_err(pwr->dev, "Missing battery parameters\n");
+
+		return ret;
+	}
+	fgauge_initial_values(pwr);
+
+	pwr->gdesc.drv_data = pwr;
+
+	ret = bd7182x_get_rsens(pwr);
+	if (ret) {
+		dev_err(&pdev->dev, "sense resistor missing\n");
+		return ret;
+	}
+
+	dev_set_drvdata(&pdev->dev, pwr);
+	bd71827_init_hardware(pwr);
+
+	psycfg.attr_grp		= &bd71827_sysfs_attr_groups[0];
+	psycfg.of_node		= pdev->dev.parent->of_node;
+
+	ac_cfg.supplied_to	= bd71827_ac_supplied_to;
+	ac_cfg.num_supplicants	= ARRAY_SIZE(bd71827_ac_supplied_to);
+	ac_cfg.drv_data		= pwr;
+
+	pwr->ac = devm_power_supply_register(&pdev->dev, &bd71827_ac_desc,
+					     &ac_cfg);
+	if (IS_ERR(pwr->ac)) {
+		ret = PTR_ERR(pwr->ac);
+		dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+		return ret;
+	}
+
+	pwr->sw = devm_psy_register_simple_gauge(pwr->dev, &psycfg, &pwr->ops,
+					&pwr->gdesc);
+	if (IS_ERR(pwr->sw)) {
+		dev_err(&pdev->dev, "SW-gauge registration failed\n");
+		return PTR_ERR(pwr->sw);
+	}
+
+	ret = bd7182x_get_irqs(pdev, pwr);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request IRQs: %d\n", ret);
+		return ret;
+	};
+
+	/* Configure wakeup capable */
+	device_set_wakeup_capable(pwr->dev, 1);
+	device_set_wakeup_enable(pwr->dev, 1);
+
+	return 0;
+}
+
+static const struct platform_device_id bd71827_charger_id[] = {
+	{ "bd71815-power", ROHM_CHIP_TYPE_BD71815 },
+	{ "bd71827-power", ROHM_CHIP_TYPE_BD71827 },
+	{ "bd71828-power", ROHM_CHIP_TYPE_BD71828 },
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, bd71827_charger_id);
+
+static struct platform_driver bd71827_power_driver = {
+	.driver = {
+		.name = "bd718xx-power",
+	},
+	.probe = bd71827_power_probe,
+	.id_table = bd71827_charger_id,
+};
+
+module_platform_driver(bd71827_power_driver);
+MODULE_ALIAS("platform:bd718xx-power");
+
+module_param(use_load_bat_params, int, 0444);
+MODULE_PARM_DESC(use_load_bat_params, "use_load_bat_params:Use loading battery parameters");
+
+module_param(param_max_voltage, int, 0444);
+MODULE_PARM_DESC(param_max_voltage,
+		 "Maximum voltage of fully charged battery, uV");
+
+module_param(param_min_voltage, int, 0444);
+MODULE_PARM_DESC(param_min_voltage,
+		 "Minimum voltage of fully drained battery, uV");
+
+module_param(param_thr_voltage, int, 0444);
+MODULE_PARM_DESC(param_thr_voltage,
+		 "Threshold voltage for applying zero correction, uV");
+
+module_param(battery_cap_mah, int, 0444);
+MODULE_PARM_DESC(battery_cap_mah, "battery_cap_mah:Battery capacity (mAh)");
+
+module_param(dgrd_cyc_cap, int, 0444);
+MODULE_PARM_DESC(dgrd_cyc_cap, "dgrd_cyc_cap:Degraded capacity per cycle (uAh)");
+
+module_param(soc_est_max_num, int, 0444);
+MODULE_PARM_DESC(soc_est_max_num, "soc_est_max_num:SOC estimation max repeat number");
+
+module_param_array(ocv_table, int, NULL, 0444);
+MODULE_PARM_DESC(ocv_table, "ocv_table:Open Circuit Voltage table (uV)");
+
+module_param_array(vdr_temps, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_temps, "vdr_temps:temperatures for VDR tables. (0.1C)");
+
+module_param_array(vdr_table_h, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_h, "vdr_table_h:Voltage Drop Ratio temperature high area table");
+
+module_param_array(vdr_table_m, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_m, "vdr_table_m:Voltage Drop Ratio temperature middle area table");
+
+module_param_array(vdr_table_l, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_l, "vdr_table_l:Voltage Drop Ratio temperature low area table");
+
+module_param_array(vdr_table_vl, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_vl, "vdr_table_vl:Voltage Drop Ratio temperature very low area table");
+
+MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>");
+MODULE_DESCRIPTION("ROHM BD718(15/17/27/28/78) PMIC Battery Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/rohm-bd71827.h b/include/linux/mfd/rohm-bd71827.h
new file mode 100644
index 000000000000..0f5a343b10ae
--- /dev/null
+++ b/include/linux/mfd/rohm-bd71827.h
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2016
+ *
+ * @author cpham2403@gmail.com
+ */
+
+#ifndef __LINUX_MFD_BD71827_H
+#define __LINUX_MFD_BD71827_H
+
+#include <linux/regmap.h>
+
+#define LDO5VSEL_EQ_H		0
+
+#ifndef LDO5VSEL_EQ_H
+	#error define LDO5VSEL_EQ_H to 1 when connect to High, to 0 when connect to Low
+#else
+	#if LDO5VSEL_EQ_H == 1
+		#define BD71827_REG_LDO5_VOLT	BD71827_REG_LDO5_VOLT_H
+		#define LDO5_MASK		LDO5_H_MASK
+	#elif LDO5VSEL_EQ_H == 0
+		#define BD71827_REG_LDO5_VOLT	BD71827_REG_LDO5_VOLT_L
+		#define LDO5_MASK		LDO5_L_MASK
+	#else
+		#error Define LDO5VSEL_EQ_H only to 0 or 1
+	#endif
+#endif
+
+enum {
+	BD71827_BUCK1	=	0,
+	BD71827_BUCK2,
+	BD71827_BUCK3,
+	BD71827_BUCK4,
+	BD71827_BUCK5,
+	// General Purpose
+	BD71827_LDO1,
+	BD71827_LDO2,
+	BD71827_LDO3,
+	BD71827_LDO4,
+	BD71827_LDO5,
+	BD71827_LDO6,
+	// LDO for Secure Non-Volatile Storage
+	BD71827_LDOSNVS,
+	BD71827_REGULATOR_CNT,
+};
+
+#define BD71827_SUPPLY_STATE_ENABLED	0x1
+
+#define BD71827_BUCK1_VOLTAGE_NUM	0x3F
+#define BD71827_BUCK2_VOLTAGE_NUM	0x3F
+#define BD71827_BUCK3_VOLTAGE_NUM	0x3F
+#define BD71827_BUCK4_VOLTAGE_NUM	0x1F
+#define BD71827_BUCK5_VOLTAGE_NUM	0x1F
+#define BD71827_LDO1_VOLTAGE_NUM	0x3F
+#define BD71827_LDO2_VOLTAGE_NUM	0x3F
+#define BD71827_LDO3_VOLTAGE_NUM	0x3F
+#define BD71827_LDO4_VOLTAGE_NUM	0x3F
+#define BD71827_LDO5_VOLTAGE_NUM	0x3F
+#define BD71827_LDO6_VOLTAGE_NUM	0x1
+#define BD71827_LDOSNVS_VOLTAGE_NUM	0x1
+
+#define BD71827_GPIO_NUM		2	/* BD71827 have 2 GPO */
+
+enum {
+	BD71827_REG_DEVICE		= 0x00,
+	BD71827_REG_PWRCTRL		= 0x01,
+	BD71827_REG_BUCK1_MODE		= 0x02,
+	BD71827_REG_BUCK2_MODE		= 0x03,
+	BD71827_REG_BUCK3_MODE		= 0x04,
+	BD71827_REG_BUCK4_MODE		= 0x05,
+	BD71827_REG_BUCK5_MODE		= 0x06,
+	BD71827_REG_BUCK1_VOLT_RUN	= 0x07,
+	BD71827_REG_BUCK1_VOLT_SUSP	= 0x08,
+	BD71827_REG_BUCK2_VOLT_RUN	= 0x09,
+	BD71827_REG_BUCK2_VOLT_SUSP	= 0x0A,
+	BD71827_REG_BUCK3_VOLT		= 0x0B,
+	BD71827_REG_BUCK4_VOLT		= 0x0C,
+	BD71827_REG_BUCK5_VOLT		= 0x0D,
+	BD71827_REG_LED_CTRL		= 0x0E,
+	BD71827_REG_reserved_0F		= 0x0F,
+	BD71827_REG_LDO_MODE1		= 0x10,
+	BD71827_REG_LDO_MODE2		= 0x11,
+	BD71827_REG_LDO_MODE3		= 0x12,
+	BD71827_REG_LDO_MODE4		= 0x13,
+	BD71827_REG_LDO1_VOLT		= 0x14,
+	BD71827_REG_LDO2_VOLT		= 0x15,
+	BD71827_REG_LDO3_VOLT		= 0x16,
+	BD71827_REG_LDO4_VOLT		= 0x17,
+	BD71827_REG_LDO5_VOLT_H		= 0x18,
+	BD71827_REG_LDO5_VOLT_L		= 0x19,
+	BD71827_REG_BUCK_PD_DIS		= 0x1A,
+	BD71827_REG_LDO_PD_DIS		= 0x1B,
+	BD71827_REG_GPIO		= 0x1C,
+	BD71827_REG_OUT32K		= 0x1D,
+	BD71827_REG_SEC			= 0x1E,
+	BD71827_REG_MIN			= 0x1F,
+	BD71827_REG_HOUR		= 0x20,
+	BD71827_REG_WEEK		= 0x21,
+	BD71827_REG_DAY			= 0x22,
+	BD71827_REG_MONTH		= 0x23,
+	BD71827_REG_YEAR		= 0x24,
+	BD71827_REG_ALM0_SEC		= 0x25,
+	BD71827_REG_ALM0_MIN		= 0x26,
+	BD71827_REG_ALM0_HOUR		= 0x27,
+	BD71827_REG_ALM0_WEEK		= 0x28,
+	BD71827_REG_ALM0_DAY		= 0x29,
+	BD71827_REG_ALM0_MONTH		= 0x2A,
+	BD71827_REG_ALM0_YEAR		= 0x2B,
+	BD71827_REG_ALM1_SEC		= 0x2C,
+	BD71827_REG_ALM1_MIN		= 0x2D,
+	BD71827_REG_ALM1_HOUR		= 0x2E,
+	BD71827_REG_ALM1_WEEK		= 0x2F,
+	BD71827_REG_ALM1_DAY		= 0x30,
+	BD71827_REG_ALM1_MONTH		= 0x31,
+	BD71827_REG_ALM1_YEAR		= 0x32,
+	BD71827_REG_ALM0_MASK		= 0x33,
+	BD71827_REG_ALM1_MASK		= 0x34,
+	BD71827_REG_ALM2		= 0x35,
+	BD71827_REG_TRIM		= 0x36,
+	BD71827_REG_CONF		= 0x37,
+	BD71827_REG_SYS_INIT		= 0x38,
+	BD71827_REG_CHG_STATE		= 0x39,
+	BD71827_REG_CHG_LAST_STATE	= 0x3A,
+	BD71827_REG_BAT_STAT		= 0x3B,
+	BD71827_REG_DCIN_STAT		= 0x3C,
+	BD71827_REG_VSYS_STAT		= 0x3D,
+	BD71827_REG_CHG_STAT		= 0x3E,
+	BD71827_REG_CHG_WDT_STAT	= 0x3F,
+	BD71827_REG_BAT_TEMP		= 0x40,
+	BD71827_REG_ILIM_STAT		= 0x41,
+	BD71827_REG_DCIN_SET		= 0x42,
+	BD71827_REG_DCIN_CLPS		= 0x43,
+	BD71827_REG_VSYS_REG		= 0x44,
+	BD71827_REG_VSYS_MAX		= 0x45,
+	BD71827_REG_VSYS_MIN		= 0x46,
+	BD71827_REG_CHG_SET1		= 0x47,
+	BD71827_REG_CHG_SET2		= 0x48,
+	BD71827_REG_CHG_WDT_PRE		= 0x49,
+	BD71827_REG_CHG_WDT_FST		= 0x4A,
+	BD71827_REG_CHG_IPRE		= 0x4B,
+	BD71827_REG_CHG_IFST		= 0x4C,
+	BD71827_REG_CHG_IFST_TERM	= 0x4D,
+	BD71827_REG_CHG_VPRE		= 0x4E,
+	BD71827_REG_CHG_VBAT_1		= 0x4F,
+	BD71827_REG_CHG_VBAT_2		= 0x50,
+	BD71827_REG_CHG_VBAT_3		= 0x51,
+	BD71827_REG_CHG_LED_1		= 0x52,
+	BD71827_REG_VF_TH		= 0x53,
+	BD71827_REG_BAT_SET_1		= 0x54,
+	BD71827_REG_BAT_SET_2		= 0x55,
+	BD71827_REG_BAT_SET_3		= 0x56,
+	BD71827_REG_ALM_VBAT_TH_U	= 0x57,
+	BD71827_REG_ALM_VBAT_TH_L	= 0x58,
+	BD71827_REG_ALM_DCIN_TH		= 0x59,
+	BD71827_REG_ALM_VSYS_TH		= 0x5A,
+	BD71827_REG_reserved_5B		= 0x5B,
+	BD71827_REG_reserved_5C		= 0x5C,
+	BD71827_REG_VM_VBAT_U		= 0x5D,
+	BD71827_REG_VM_VBAT_L		= 0x5E,
+	BD71827_REG_VM_BTMP		= 0x5F,
+	BD71827_REG_VM_VTH		= 0x60,
+	BD71827_REG_VM_DCIN_U		= 0x61,
+	BD71827_REG_VM_DCIN_L		= 0x62,
+	BD71827_REG_reserved_63		= 0x63,
+	BD71827_REG_VM_VF		= 0x64,
+	BD71827_REG_reserved_65		= 0x65,
+	BD71827_REG_reserved_66		= 0x66,
+	BD71827_REG_VM_OCV_PRE_U	= 0x67,
+	BD71827_REG_VM_OCV_PRE_L	= 0x68,
+	BD71827_REG_reserved_69		= 0x69,
+	BD71827_REG_reserved_6A		= 0x6A,
+	BD71827_REG_VM_OCV_PST_U	= 0x6B,
+	BD71827_REG_VM_OCV_PST_L	= 0x6C,
+	BD71827_REG_VM_SA_VBAT_U	= 0x6D,
+	BD71827_REG_VM_SA_VBAT_L	= 0x6E,
+	BD71827_REG_reserved_6F		= 0x6F,
+	BD71827_REG_reserved_70		= 0x70,
+	BD71827_REG_CC_CTRL		= 0x71,
+	BD71827_REG_CC_BATCAP1_TH_U	= 0x72,
+	BD71827_REG_CC_BATCAP1_TH_L	= 0x73,
+	BD71827_REG_CC_BATCAP2_TH_U	= 0x74,
+	BD71827_REG_CC_BATCAP2_TH_L	= 0x75,
+	BD71827_REG_CC_BATCAP3_TH_U	= 0x76,
+	BD71827_REG_CC_BATCAP3_TH_L	= 0x77,
+	BD71827_REG_CC_STAT		= 0x78,
+	BD71827_REG_CC_CCNTD_3		= 0x79,
+	BD71827_REG_CC_CCNTD_2		= 0x7A,
+	BD71827_REG_CC_CCNTD_1		= 0x7B,
+	BD71827_REG_CC_CCNTD_0		= 0x7C,
+	BD71827_REG_CC_CURCD_U		= 0x7D,
+	BD71827_REG_CC_CURCD_L		= 0x7E,
+	BD71827_REG_CC_OCUR_THR_1	= 0x7F,
+	BD71827_REG_CC_OCUR_DUR_1	= 0x80,
+	BD71827_REG_CC_OCUR_THR_2	= 0x81,
+	BD71827_REG_CC_OCUR_DUR_2	= 0x82,
+	BD71827_REG_CC_OCUR_THR_3	= 0x83,
+	BD71827_REG_CC_OCUR_DUR_3	= 0x84,
+	BD71827_REG_CC_OCUR_MON		= 0x85,
+	BD71827_REG_VM_BTMP_OV_THR	= 0x86,
+	BD71827_REG_VM_BTMP_OV_DUR	= 0x87,
+	BD71827_REG_VM_BTMP_LO_THR	= 0x88,
+	BD71827_REG_VM_BTMP_LO_DUR	= 0x89,
+	BD71827_REG_VM_BTMP_MON		= 0x8A,
+	BD71827_REG_INT_EN_01		= 0x8B,
+	BD71827_REG_INT_EN_02		= 0x8C,
+	BD71827_REG_INT_EN_03		= 0x8D,
+	BD71827_REG_INT_EN_04		= 0x8E,
+	BD71827_REG_INT_EN_05		= 0x8F,
+	BD71827_REG_INT_EN_06		= 0x90,
+	BD71827_REG_INT_EN_07		= 0x91,
+	BD71827_REG_INT_EN_08		= 0x92,
+	BD71827_REG_INT_EN_09		= 0x93,
+	BD71827_REG_INT_EN_10		= 0x94,
+	BD71827_REG_INT_EN_11		= 0x95,
+	BD71827_REG_INT_EN_12		= 0x96,
+	BD71827_REG_INT_STAT		= 0x97,
+	BD71827_REG_INT_STAT_01		= 0x98,
+	BD71827_REG_INT_STAT_02		= 0x99,
+	BD71827_REG_INT_STAT_03		= 0x9A,
+	BD71827_REG_INT_STAT_04		= 0x9B,
+	BD71827_REG_INT_STAT_05		= 0x9C,
+	BD71827_REG_INT_STAT_06		= 0x9D,
+	BD71827_REG_INT_STAT_07		= 0x9E,
+	BD71827_REG_INT_STAT_08		= 0x9F,
+	BD71827_REG_INT_STAT_09		= 0xA0,
+	BD71827_REG_INT_STAT_10		= 0xA1,
+	BD71827_REG_INT_STAT_11		= 0xA2,
+	BD71827_REG_INT_STAT_12		= 0xA3,
+	BD71827_REG_INT_UPDATE		= 0xA4,
+	BD71827_REG_PWRCTRL2		= 0xA8,
+	BD71827_REG_PWRCTRL3		= 0xA9,
+	BD71827_REG_SWRESET		= 0xAA,
+	BD71827_REG_BUCK1_VOLT_IDLE	= 0xAB,
+	BD71827_REG_BUCK2_VOLT_IDLE	= 0xAC,
+	BD71827_REG_ONEVNT_MODE_1	= 0xAD,
+	BD71827_REG_ONEVNT_MODE_2	= 0xAE,
+	BD71827_REG_RESERVE_0		= 0xB0,
+	BD71827_REG_RESERVE_1		= 0xB1,
+	BD71827_REG_RESERVE_2		= 0xB2,
+	BD71827_REG_RESERVE_3		= 0xB3,
+	BD71827_REG_RESERVE_4		= 0xB4,
+	BD71827_REG_RESERVE_5		= 0xB5,
+	BD71827_REG_RESERVE_6		= 0xB6,
+	BD71827_REG_RESERVE_7		= 0xB7,
+	BD71827_REG_RESERVE_8		= 0xB8,
+	BD71827_REG_RESERVE_9		= 0xB9,
+	BD71827_REG_RESERVE_A		= 0xBA,
+	BD71827_REG_RESERVE_B		= 0xBB,
+	BD71827_REG_RESERVE_C		= 0xBC,
+	BD71827_REG_RESERVE_D		= 0xBD,
+	BD71827_REG_RESERVE_E		= 0xBE,
+	BD71827_REG_RESERVE_F		= 0xBF,
+	BD71827_REG_VM_VSYS_U		= 0xC0,
+	BD71827_REG_VM_VSYS_L		= 0xC1,
+	BD71827_REG_VM_SA_VSYS_U	= 0xC2,
+	BD71827_REG_VM_SA_VSYS_L	= 0xC3,
+	BD71827_REG_CC_SA_CURCD_U	= 0xC4,
+	BD71827_REG_CC_SA_CURCD_L	= 0xC5,
+	BD71827_REG_BATID		= 0xC6,
+	BD71827_REG_VM_SA_VBAT_MIN_U	= 0xD4,
+	BD71827_REG_VM_SA_VBAT_MIN_L	= 0xD5,
+	BD71827_REG_VM_SA_VBAT_MAX_U	= 0xD6,
+	BD71827_REG_VM_SA_VBAT_MAX_L	= 0xD7,
+	BD71827_REG_VM_SA_VSYS_MIN_U	= 0xD8,
+	BD71827_REG_VM_SA_VSYS_MIN_L	= 0xD9,
+	BD71827_REG_VM_SA_VSYS_MAX_U	= 0xDA,
+	BD71827_REG_VM_SA_VSYS_MAX_L	= 0xDB,
+	BD71827_REG_VM_SA_MINMAX_CLR	= 0xDC,
+	BD71827_REG_VM_OCV_PWRON_U	= 0xDD,
+	BD71827_REG_VM_OCV_PWRON_L	= 0xDE,
+	BD71827_REG_REX_CCNTD_3		= 0xE0,
+	BD71827_REG_REX_CCNTD_2		= 0xE1,
+	BD71827_REG_REX_CCNTD_1		= 0xE2,
+	BD71827_REG_REX_CCNTD_0		= 0xE3,
+	BD71827_REG_REX_SA_VBAT_U	= 0xE4,
+	BD71827_REG_REX_SA_VBAT_L	= 0xE5,
+	BD71827_REG_REX_CTRL_1		= 0xE6,
+	BD71827_REG_REX_CTRL_2		= 0xE7,
+	BD71827_REG_FULL_CCNTD_3	= 0xE8,
+	BD71827_REG_FULL_CCNTD_2	= 0xE9,
+	BD71827_REG_FULL_CCNTD_1	= 0xEA,
+	BD71827_REG_FULL_CCNTD_0	= 0xEB,
+	BD71827_REG_FULL_CTRL		= 0xEC,
+	BD71827_REG_CCNTD_CHG_3		= 0xF0,
+	BD71827_REG_CCNTD_CHG_2		= 0xF1,
+	BD71827_REG_INT_EN_13		= 0xF8,
+	BD71827_REG_INT_STAT_13		= 0xF9,
+	BD71827_REG_I2C_MAGIC		= 0xFE,
+	BD71827_REG_PRODUCT		= 0xFF,
+	BD71827_MAX_REGISTER		= 0x100,
+};
+
+#define BD71827_REX_CLR_MASK		0x10
+
+#endif /* __LINUX_MFD_BD71827_H */
-- 
2.31.1


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

~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =] 

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

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

* Re: [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table
  2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
@ 2021-11-16 14:02   ` Rob Herring
  2021-11-18  1:57   ` Linus Walleij
  1 sibling, 0 replies; 31+ messages in thread
From: Rob Herring @ 2021-11-16 14:02 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Rob Herring, Sebastian Reichel, Linus Walleij, Lee Jones,
	fan.chen, linux-power, Matti Vaittinen, linux-pm, devicetree,
	linux-kernel, rostokus

On Tue, 16 Nov 2021 14:24:48 +0200, Matti Vaittinen wrote:
> Some charger/battery vendors describe the temperature impact to
> battery capacity by providing tables with capacity change at
> given temperature. Support providing this temperature - capacity
> dependency using the simple-battery DT nodes.
> 
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
> ---
>  .../bindings/power/supply/battery.yaml        | 19 +++++++++++++++++++
>  1 file changed, 19 insertions(+)
> 

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:
./Documentation/devicetree/bindings/power/supply/battery.yaml:134:36: [error] syntax error: expected <block end>, but found '<scalar>' (syntax)

dtschema/dtc warnings/errors:
./Documentation/devicetree/bindings/power/supply/battery.yaml:  while parsing a block mapping
  in "<unicode string>", line 134, column 11
did not find expected key
  in "<unicode string>", line 134, column 36
make[1]: *** Deleting file 'Documentation/devicetree/bindings/power/supply/battery.example.dts'
Traceback (most recent call last):
  File "/usr/local/bin/dt-extract-example", line 45, in <module>
    binding = yaml.load(open(args.yamlfile, encoding='utf-8').read())
  File "/usr/local/lib/python3.8/dist-packages/ruamel/yaml/main.py", line 434, in load
    return constructor.get_single_data()
  File "/usr/local/lib/python3.8/dist-packages/ruamel/yaml/constructor.py", line 119, in get_single_data
    node = self.composer.get_single_node()
  File "_ruamel_yaml.pyx", line 706, in _ruamel_yaml.CParser.get_single_node
  File "_ruamel_yaml.pyx", line 724, in _ruamel_yaml.CParser._compose_document
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 889, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 889, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 889, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 889, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 773, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 850, in _ruamel_yaml.CParser._compose_sequence_node
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 891, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 904, in _ruamel_yaml.CParser._parse_next_event
ruamel.yaml.parser.ParserError: while parsing a block mapping
  in "<unicode string>", line 134, column 11
did not find expected key
  in "<unicode string>", line 134, column 36
make[1]: *** [Documentation/devicetree/bindings/Makefile:25: Documentation/devicetree/bindings/power/supply/battery.example.dts] Error 1
make[1]: *** Waiting for unfinished jobs....
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/power/supply/battery.yaml: ignoring, error parsing file
warning: no schema found in file: ./Documentation/devicetree/bindings/power/supply/battery.yaml
make: *** [Makefile:1413: dt_binding_check] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/patch/1555776

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


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

* Re: [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table
  2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
  2021-11-16 14:02   ` Rob Herring
@ 2021-11-18  1:57   ` Linus Walleij
  2021-11-18  5:27     ` Vaittinen, Matti
  1 sibling, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-18  1:57 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:24 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> Some charger/battery vendors describe the temperature impact to
> battery capacity by providing tables with capacity change at
> given temperature. Support providing this temperature - capacity
> dependency using the simple-battery DT nodes.
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

Since we already support providing the capacity at different
temperatures using ocv-capacity-celsius and the array of
arrays ocv-capacity-table-0, 1, 2... you are introducing a
second parallel method of describing how capacity changes
in accordance with temperature, right?

What do you expect to happen if someone specifies both?

If this is an either/or situation then the schema has to
guarantee the exclusiveness for each.

(I would probably just use the formula you have to calculate
a few tables using the existing method but that's just me.)

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper
  2021-11-16 12:25 ` [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper Matti Vaittinen
@ 2021-11-18  2:02   ` Linus Walleij
  2021-11-18  5:30     ` Vaittinen, Matti
  0 siblings, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-18  2:02 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:25 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> The power-supply core supports concept of OCV (Open Circuit Voltage) =>
> SOC (State Of Charge) conversion tables. Usually these tables are used
> to estimate SOC based on OCV. Some systems use so called "Zero Adjust"
> where at the near end-of-battery condition the SOC from coulomb counter
> is used to retrieve the OCV - and OCV and VSYS difference is used to
> re-estimate the battery capacity.
>
> Add helper to do look-up the other-way around and also get the OCV
> based on SOC
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

It seems you will need this for your chargers indeed.

> +int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table,
> +                               int table_len, int dcap)
> +{
> +       int i, ocv, tmp;
> +
> +       for (i = 0; i < table_len; i++)
> +               if (dcap > table[i].capacity * 10)
> +                       break;
> +
> +       if (i > 0 && i < table_len) {
> +               tmp = (table[i - 1].ocv - table[i].ocv) *
> +                     (dcap - table[i].capacity * 10);
> +
> +               tmp /= (table[i - 1].capacity - table[i].capacity) * 10;
> +               ocv = tmp + table[i].ocv;
> +       } else if (i == 0) {
> +               ocv = table[0].ocv;
> +       } else {
> +               ocv = table[table_len - 1].ocv;
> +       }
> +
> +       return ocv;
> +}
> +EXPORT_SYMBOL_GPL(power_supply_dcap2ocv_simple);

Rewrite this using the library fixpoint interpolation function but just
copypasting from my patch:
https://lore.kernel.org/linux-pm/20211116230233.2167104-1-linus.walleij@linaro.org/

Other than that it looks good to me!

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-16 12:25 ` [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Matti Vaittinen
@ 2021-11-18  2:10   ` Linus Walleij
  2021-11-18  6:11     ` Vaittinen, Matti
  0 siblings, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-18  2:10 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:26 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> Support obtaining the "capacity degradation by temperature" - tables
> from device-tree to batinfo.
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

Same questions as on the binding patch.

If we already support different degradation by temperature tables,
why do we need a second mechanism for the same thing?

I'd just calculate a few tables per temperature and be done with
it.

At least documentation needs to be updated to reflect that the two methods
are exclusive and you can only use one of them.

+ * Usually temperature impacts on battery capacity. For systems where it is
+ * sufficient to describe capacity change as a series of temperature ranges
+ * where the change is linear (Eg delta cap = temperature_change * constant +
+ * offset) can be described by this structure.

But what chemistry has this property? This seems to not be coming from
the real physical world. I would perhaps accept differential equations
but *linear* battery characteristics?

If the intent is only for emulation of something that doesn't exist in
reality I doubt how useful it is, all battery technologies I have seen
have been nonlinear and hence we have the tables.

If you want to simulate a linear discharge, then just write a few tables
with linear dissipation progression, it's easier I think.

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table
  2021-11-18  1:57   ` Linus Walleij
@ 2021-11-18  5:27     ` Vaittinen, Matti
  0 siblings, 0 replies; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-18  5:27 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Hi deee Ho peeps!

On 11/18/21 03:57, Linus Walleij wrote:
> On Tue, Nov 16, 2021 at 1:24 PM Matti Vaittinen
> <matti.vaittinen@fi.rohmeurope.com> wrote:
> 
>> Some charger/battery vendors describe the temperature impact to
>> battery capacity by providing tables with capacity change at
>> given temperature. Support providing this temperature - capacity
>> dependency using the simple-battery DT nodes.
>>
>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
> 
> Since we already support providing the capacity at different
> temperatures using ocv-capacity-celsius and the array of
> arrays ocv-capacity-table-0, 1, 2... you are introducing a
> second parallel method of describing how capacity changes
> in accordance with temperature, right?

Oh, right. This is why sending out RFCs at early stage can be beneficial :)

The way I have seen OCV-CAP and TEMP-CAP 'dependencies' modelled has 
been that the OCV-CAP is defined only in one temperature (say, 25 C).

The impact of the temperature has then been estimated by storing values 
which reflect the delta CAP when temperature changes from this 'nominal 
temperature'. Hence it never even crossed my mind that the temperature 
impact to CAP should actually be modelled in OCV tables.

> What do you expect to happen if someone specifies both?

Right. I see this now. The current implementation would indeed apply the 
temperature impact twice. I didn't even think of this as we have only 
provided the OCV-CAP for one temperature.

> If this is an either/or situation then the schema has to
> guarantee the exclusiveness for each.
> 
> (I would probably just use the formula you have to calculate
> a few tables using the existing method but that's just me.)

I need to try to find out how the temperature-degradation is really used 
in setups which use our chargers (sigh. this is always the hard part for 
me) and see if we can replace the temp-degradation table by several 
OCV-CAP tables for different temperatures. I am afraid we may lack the 
OCV information for different temperatures - but I'll see. I'd rather 
not add overlapping properties.

Anyways - Thanks a lot Linus for giving me another view on this :) 
You're really helpful.

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] 31+ messages in thread

* Re: [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper
  2021-11-18  2:02   ` Linus Walleij
@ 2021-11-18  5:30     ` Vaittinen, Matti
  0 siblings, 0 replies; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-18  5:30 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On 11/18/21 04:02, Linus Walleij wrote:
> On Tue, Nov 16, 2021 at 1:25 PM Matti Vaittinen
> <matti.vaittinen@fi.rohmeurope.com> wrote:
> 
>> The power-supply core supports concept of OCV (Open Circuit Voltage) =>
>> SOC (State Of Charge) conversion tables. Usually these tables are used
>> to estimate SOC based on OCV. Some systems use so called "Zero Adjust"
>> where at the near end-of-battery condition the SOC from coulomb counter
>> is used to retrieve the OCV - and OCV and VSYS difference is used to
>> re-estimate the battery capacity.
>>
>> Add helper to do look-up the other-way around and also get the OCV
>> based on SOC
>>
>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
> 
> It seems you will need this for your chargers indeed.
> 
>> +int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table,
>> +                               int table_len, int dcap)
>> +{
>> +       int i, ocv, tmp;
>> +
>> +       for (i = 0; i < table_len; i++)
>> +               if (dcap > table[i].capacity * 10)
>> +                       break;
>> +
>> +       if (i > 0 && i < table_len) {
>> +               tmp = (table[i - 1].ocv - table[i].ocv) *
>> +                     (dcap - table[i].capacity * 10);
>> +
>> +               tmp /= (table[i - 1].capacity - table[i].capacity) * 10;
>> +               ocv = tmp + table[i].ocv;
>> +       } else if (i == 0) {
>> +               ocv = table[0].ocv;
>> +       } else {
>> +               ocv = table[table_len - 1].ocv;
>> +       }
>> +
>> +       return ocv;
>> +}
>> +EXPORT_SYMBOL_GPL(power_supply_dcap2ocv_simple);
> 
> Rewrite this using the library fixpoint interpolation function but just
> copypasting from my patch:
> https://lore.kernel.org/linux-pm/20211116230233.2167104-1-linus.walleij@linaro.org/

Thanks :)
I actually saw this (yesterday?) but didn't revise my patches. Thanks 
for the head's up - it's always good to have this kind of helpers :)

> 
> Other than that it looks good to me!

Thanks!

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] 31+ messages in thread

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-18  2:10   ` Linus Walleij
@ 2021-11-18  6:11     ` Vaittinen, Matti
  2021-11-26 11:56       ` Vaittinen, Matti
  0 siblings, 1 reply; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-18  6:11 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Hi Linus,

On 11/18/21 04:10, Linus Walleij wrote:
> On Tue, Nov 16, 2021 at 1:26 PM Matti Vaittinen
> <matti.vaittinen@fi.rohmeurope.com> wrote:
> 
>> Support obtaining the "capacity degradation by temperature" - tables
>> from device-tree to batinfo.
>>
>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
> 
> Same questions as on the binding patch.
> 
> If we already support different degradation by temperature tables,
> why do we need a second mechanism for the same thing?

Thanks for bringing this up. As I said, I didn't notice that we could 
indeed use the CAP-OCV tables for different temperatures to bring in 
this information :) I see certain benefit from the possibility of not 
requiring to measure the OCV at different temperatures - but it may not 
be meaningful. As I replied to your patch 1/9 review - I need to (try 
to) do some more research...

> I'd just calculate a few tables per temperature and be done with
> it.
> 
> At least documentation needs to be updated to reflect that the two methods
> are exclusive and you can only use one of them.
> 
> + * Usually temperature impacts on battery capacity. For systems where it is
> + * sufficient to describe capacity change as a series of temperature ranges
> + * where the change is linear (Eg delta cap = temperature_change * constant +
> + * offset) can be described by this structure.
> 
> But what chemistry has this property? This seems to not be coming from
> the real physical world. I would perhaps accept differential equations
> but *linear* battery characteristics?

linear *on a given small temperature range*. I think this is improvement 
to situation where we don't do temperature compensation at all. And, by 
shortening the temperature area where CAP change is linear, we can 
approach any non linear behaviour, right? With the current support of 
100 values, you can divide the temperature range to 100 pieces with 
linear CAP degradation impact - I think you get reasonably good 
estimates there even if your device was expected to be operational at 
temperatures where min...max is 100C. (that would allow us to divide the 
range to 1C slots and assume linear degradation at each 1C range. Or 
didvide this unevenly so that the temperature areas where change is more 
constant we could have bigger slots, and at the areas where change is 
faster we could have smaller slots. I don't see linear interpolation as 
such a big problem there?

> If the intent is only for emulation of something that doesn't exist in
> reality I doubt how useful it is, all battery technologies I have seen
> have been nonlinear and hence we have the tables.
> 
> If you want to simulate a linear discharge, then just write a few tables
> with linear dissipation progression, it's easier I think.

"linear dissipation progression" - that should be my next favourite 
sentence to sound professional ;) I need to first google what it is in 
Finnish though XD

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] 31+ messages in thread

* Re: [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration
  2021-11-16 12:26 ` [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration Matti Vaittinen
@ 2021-11-19  1:42   ` Linus Walleij
  0 siblings, 0 replies; 31+ messages in thread
From: Linus Walleij @ 2021-11-19  1:42 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:26 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> In some cases it is beneficial to be able to query the static battery
> node properties prior power_supply registration. The device-tree
> parsing does not really depend on psy so add APIs which can
> be used without the power-supply. Also, convert APIs to operate on
> fwnode while at it.
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

This looks quite useful!
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy
  2021-11-16 12:27 ` [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Matti Vaittinen
@ 2021-11-19  1:49   ` Linus Walleij
  2021-11-19  8:11     ` Matti Vaittinen
  0 siblings, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-19  1:49 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:27 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> The battery info functions computing the state-of-charge (SOC) based
> on open-circuit-voltage (OCV) are returning SOC using units of 1%.
>
> Some capacity estimation computations require higher accuracy. Add
> functions that return SOC using units of 0.1% to reduce rounding error.
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

That sounds useful.

> +int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table,
> +                               int table_len, int ocv)
> +{
> +       int i, cap, tmp;

Why a whole new function? Just rename the original power_supply_ocv2cap_simple()
to power_supply_ocv2dcap_simple and patch it to return the finegrained value,
then add a wrapper that use that function but drops it down by one order
of magnitude.

> +int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info,
> +                                 int ocv, int temp)

Same with this, saves a lot of code!

(Also will use my new interpolation routines since you refactor
on top of that.)

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction
  2021-11-16 12:28 ` [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction Matti Vaittinen
@ 2021-11-19  1:54   ` Linus Walleij
  0 siblings, 0 replies; 31+ messages in thread
From: Linus Walleij @ 2021-11-19  1:54 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

On Tue, Nov 16, 2021 at 1:28 PM Matti Vaittinen
<matti.vaittinen@fi.rohmeurope.com> wrote:

> Add generic 'simple gauge' helper for performing iterative SOC estimation
> and coulomb counter correction for devices with a (drifting) coulomb
> counter. This should allow few charger/fuel-gauge drivers to use generic
> loop instead of implementing their own.
>
> Charger/fuel-gauge drivers can register 'simple-gauge' which does
> periodically poll the driver and:
>  - get battery state
>  - adjust coulomb counter value (to fix drifting caused for example by ADC
>    offset) if:
>      - Battery is relaxed and OCV<=>SOC table is given
>      - Battery is full charged
>  - get battery age (cycles) from driver
>  - get battery temperature
>  - do battery capacity correction
>      - by battery temperature
>      - by battery age
>      - by computed Vbat/OCV difference at low-battery condition if
>        low-limit is set and OCV table given
>      - by IC specific low-battery correction if provided
>  - compute current State Of Charge (SOC)
>  - do periodical calibration if IC supports that. (Many ICs do calibration
>    of CC by shorting the ADC pins and getting the offset).
>  - provide the user-space a consistent interface for getting/setting the
>    battery-cycle information for ICs which can't store the battery aging
>    information. Uses POWER_SUPPLY_PROP_CYCLE_COUNT for this.
>
> The simple gauge provides the last computed SOC as
> POWER_SUPPLY_PROP_CAPACITY to power_supply_class when requested.
>
> Things that should/could be added but are missing from this commit:
>  - Support starting calibration in HW when entering to suspend. This
>    is useful for ICs supporting delayed calibration to mitigate CC error
>    during suspend - and to make periodical wake-up less critical.
>  - periodical wake-up for performing SOC estimation computation (RTC
>    integration)
>
> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>

This is the right ambition, I haven't looked close at it but the way
you use it seem to be what you need, so:
Acked-by: Linus Walleij <linus.walleij@linaro.org>
(and the rest of the patches to the Rohm chips)

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy
  2021-11-19  1:49   ` Linus Walleij
@ 2021-11-19  8:11     ` Matti Vaittinen
  0 siblings, 0 replies; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-19  8:11 UTC (permalink / raw)
  To: Linus Walleij, Matti Vaittinen
  Cc: Sebastian Reichel, Rob Herring, Lee Jones, rostokus, fan.chen,
	linux-pm, devicetree, linux-kernel, linux-power

On 11/19/21 03:49, Linus Walleij wrote:
> On Tue, Nov 16, 2021 at 1:27 PM Matti Vaittinen
> <matti.vaittinen@fi.rohmeurope.com> wrote:
> 
>> The battery info functions computing the state-of-charge (SOC) based
>> on open-circuit-voltage (OCV) are returning SOC using units of 1%.
>>
>> Some capacity estimation computations require higher accuracy. Add
>> functions that return SOC using units of 0.1% to reduce rounding error.
>>
>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
> 
> That sounds useful.
> 
>> +int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table,
>> +                               int table_len, int ocv)
>> +{
>> +       int i, cap, tmp;
> 
> Why a whole new function? Just rename the original power_supply_ocv2cap_simple()
> to power_supply_ocv2dcap_simple and patch it to return the finegrained value,
> then add a wrapper that use that function but drops it down by one order
> of magnitude.

/me feels slightly stupid.
I wonder ehy you had to explain this to me :) Well, thanks and agreed!

> 
>> +int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info,
>> +                                 int ocv, int temp)
> 
> Same with this, saves a lot of code!
> 
> (Also will use my new interpolation routines since you refactor
> on top of that.)

yup. I think this will be _much_ cleaner. I need to revise also the IC 
driver patches because I think I implemented something like your 
interpolation routines there too. So - thanks.

Best Regards
	Matti


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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-18  6:11     ` Vaittinen, Matti
@ 2021-11-26 11:56       ` Vaittinen, Matti
  2021-11-26 12:35         ` Matti Vaittinen
  2021-11-27  0:54         ` Linus Walleij
  0 siblings, 2 replies; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-26 11:56 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Hi dee Ho again,

On 11/18/21 08:11, Matti Vaittinen wrote:
> Hi Linus,
> 
> On 11/18/21 04:10, Linus Walleij wrote:
>> On Tue, Nov 16, 2021 at 1:26 PM Matti Vaittinen
>> <matti.vaittinen@fi.rohmeurope.com> wrote:
>>
>>> Support obtaining the "capacity degradation by temperature" - tables
>>> from device-tree to batinfo.
>>>
>>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
>>
>> Same questions as on the binding patch.
>>
>> If we already support different degradation by temperature tables,
>> why do we need a second mechanism for the same thing?
> 
> Thanks for bringing this up. As I said, I didn't notice that we could 
> indeed use the CAP-OCV tables for different temperatures to bring in 
> this information :) I see certain benefit from the possibility of not 
> requiring to measure the OCV at different temperatures - but it may not 
> be meaningful. As I replied to your patch 1/9 review - I need to (try 
> to) do some more research...

I tried doing some pondering here today. Unfortunately, the Friday 
afternoon is probably the worst time to try this - my brains cease 
operating at the afternoon - and double so at the Friday. Friday 
afternoons are good for babbling via email though ;)

I don't see providing OCV tables at different temperature gives the 
degradation of battery capacity. Whoah. A big thought for Friday.

We get the OCV => SOC correspondance at different temperatures. I 
however don't see how this gives the OCV => energy relation. As far as I 
know both the OCV and the 'amount of uAhs battery is able to store' are 
impacted by temperature change. This means, seeing the OCV => SOC at 
different temperatures does not tell us what is the impact of 
temperature to the OCV, and what is the impact to SOC.

For cases like the ROHM Chargers, we are interested on how much has the 
'ability to store uAhs' changed due to the temperature. When we know the 
amount of uAhs we can store, we can use the coulomb counter value to 
estimate what we still have left in the battery.

In addition to this we do use the OCV information for the "nearly 
depleted battery" - to improve the estimation by zero-correction 
algorithm. I must admit Friday afternoon is not the time I can quite 
recap this part. I think it was something like:

1. Measure VBat with system load (VBAT)
2. Find OCV corresponding the current SOC estimate (SOC based on coulomb 
counter value) - OCV_NOW
3. Compute VDROP caused by the load (OCV_NOW - VBAT)
4. Assume VDROP stays constant (or use ROHM VDR parameters if provided)
5. Using VDROP compute the OCV_MIN which matches the minimum battery 
voltage where system is still operational
6. Use the OCV_MIN and "OCV at SOC0 from calibration data" difference to 
adjust the battery capacity.

(Explanation done at Friday afternoon accuracy here).

>> I'd just calculate a few tables per temperature and be done with
>> it.
>>
>> At least documentation needs to be updated to reflect that the two 
>> methods
>> are exclusive and you can only use one of them.

I don't see these exclusive (at Friday afternoon at least). I think they 
can complement each-others. The temp_degradation table gives us the 
temperature impact on <energy storing ability>, eg, how much the battery 
capacity has changed from designed one due to the temperature.

OCV-SOC tables at various temperatures tell us how OCV looks like when 
we have X% of battery left at different temperatures. Estimation of how 
much the X% is in absolute uAhs can be done by taking into account the 
designed_cap, aging degradation and the temperature degradation (and the 
position of moon, amount of muons created by cosmic rays hitting 
athmosphere at knee energy region and so on...)

Or am I just getting something terribly wrong (again)? :)
(I still for example like internal functions named as __foo() )

Yours
--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] 31+ messages in thread

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-26 11:56       ` Vaittinen, Matti
@ 2021-11-26 12:35         ` Matti Vaittinen
  2021-11-27  0:55           ` Linus Walleij
  2021-11-27  0:54         ` Linus Walleij
  1 sibling, 1 reply; 31+ messages in thread
From: Matti Vaittinen @ 2021-11-26 12:35 UTC (permalink / raw)
  To: Vaittinen, Matti, Linus Walleij
  Cc: Sebastian Reichel, Rob Herring, Lee Jones, rostokus, fan.chen,
	linux-pm, devicetree, linux-kernel, linux-power, Mutanen, Mikko

On 11/26/21 13:56, Vaittinen, Matti wrote:
> Hi dee Ho again,
> 
> On 11/18/21 08:11, Matti Vaittinen wrote:
>> Hi Linus,
>>
>> On 11/18/21 04:10, Linus Walleij wrote:
>>> On Tue, Nov 16, 2021 at 1:26 PM Matti Vaittinen
>>> <matti.vaittinen@fi.rohmeurope.com> wrote:
>>>
>>>> Support obtaining the "capacity degradation by temperature" - tables
>>>> from device-tree to batinfo.
>>>>
>>>> Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
>>>
>>> Same questions as on the binding patch.
>>>
>>> If we already support different degradation by temperature tables,
>>> why do we need a second mechanism for the same thing?
>>
>> Thanks for bringing this up. As I said, I didn't notice that we could
>> indeed use the CAP-OCV tables for different temperatures to bring in
>> this information :) I see certain benefit from the possibility of not
>> requiring to measure the OCV at different temperatures - but it may not
>> be meaningful. As I replied to your patch 1/9 review - I need to (try
>> to) do some more research...
> 
> 
> I don't see providing OCV tables at different temperature gives the
> degradation of battery capacity. Whoah. A big thought for Friday.
> 
> We get the OCV => SOC correspondance at different temperatures. I
> however don't see how this gives the OCV => energy relation.

After reading what I wrote even I didn't know what I tried to say. Well, 
I think I tried to explain that I don't see how we can use this 
information to do any estimation what the Coulomb Counter reading 
represent at the given temperature. This is what the 
temperature-degradation tables aim to give us.

  As far as I
> know both the OCV and the 'amount of uAhs battery is able to store' are
> impacted by temperature change. This means, seeing the OCV => SOC at
> different temperatures does not tell us what is the impact of
> temperature to the OCV, and what is the impact to SOC.

I think I tried to say that these curves don't help us to tell how many 
uAhs we have in battery with different temperatures when battery is 
empty, or half full or, ... Again, what we would like to know is what 
SOC our CC value represents - and use OCV to just adjust this further 
(or in some cases to correct the CC value using OCV - if we can trust 
the battery to be properly relaxed).

Hope this did clarify. Afraid it didn't :)

Best Regards
	-- Matti Vaittinen


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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-26 11:56       ` Vaittinen, Matti
  2021-11-26 12:35         ` Matti Vaittinen
@ 2021-11-27  0:54         ` Linus Walleij
  2021-11-28  8:51           ` Vaittinen, Matti
  1 sibling, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-27  0:54 UTC (permalink / raw)
  To: Vaittinen, Matti
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Hi Matti,

don't worry you are probably right. You are the domain expert working
on stuff like this inside a company that actually makes charger ICs.
I am just some guy on the street. So I certainly trust you on this.

On Fri, Nov 26, 2021 at 12:56 PM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

> I don't see providing OCV tables at different temperature gives the
> degradation of battery capacity. Whoah. A big thought for Friday.

I think we are getting close to academic definitions of the problem,
so we'd need input from people who actually work on charging and
knows how this works? Or we need to read up on it :/

> We get the OCV => SOC correspondance at different temperatures.

I read
OCV = Open Circuit Voltage (which we can't measure but must calculate)
and
SOC = State of Charge (a percentage of the capacity)

And we agree what the bindings and code gives today is
(temp, OCV) -> SoC (%)

And this assumption is:

A) wrong as in the earth is flat or it makes the same sens to
   take a random number and use as capacity

B) wrong as in not good enough and a bit rough around the
   edges and you have an idea how to improve it

I assume (B) and what we are seeing on the battery indicator on
most of the worlds mobile phones etc is sometimes a bit so-so
because they have only used the above. (I think some people can
attest to experiencing this problem.)

And now we want something better, think medical equipment.

> I
> however don't see how this gives the OCV => energy relation. As far as I
> know both the OCV and the 'amount of uAhs battery is able to store' are
> impacted by temperature change. This means, seeing the OCV => SOC at
> different temperatures does not tell us what is the impact of
> temperature to the OCV, and what is the impact to SOC.

It is definitely true that both the OCV and SOC changes according to
temperature.

But it is also true that these tables for a certain temperature are written
with an OCV measured at this temperature, so the OCV used in the
table is already compensated for the temperature, right?

> For cases like the ROHM Chargers, we are interested on how much has the
> 'ability to store uAhs' changed due to the temperature. When we know the
> amount of uAhs we can store, we can use the coulomb counter value to
> estimate what we still have left in the battery.
>
> In addition to this we do use the OCV information for the "nearly
> depleted battery" - to improve the estimation by zero-correction
> algorithm. I must admit Friday afternoon is not the time I can quite
> recap this part. I think it was something like:
>
> 1. Measure VBat with system load (VBAT)
> 2. Find OCV corresponding the current SOC estimate (SOC based on coulomb
> counter value) - OCV_NOW
> 3. Compute VDROP caused by the load (OCV_NOW - VBAT)
> 4. Assume VDROP stays constant (or use ROHM VDR parameters if provided)
> 5. Using VDROP compute the OCV_MIN which matches the minimum battery
> voltage where system is still operational
> 6. Use the OCV_MIN and "OCV at SOC0 from calibration data" difference to
> adjust the battery capacity.

That's a neat trick!
If you look at drivers/power/supply/ab8500_fg.c function
ab8500_fg_load_comp_volt_to_capacity() you find how
someone else chose to do this with a bit of averaging etc.

> >> I'd just calculate a few tables per temperature and be done with
> >> it.
> >>
> >> At least documentation needs to be updated to reflect that the two
> >> methods
> >> are exclusive and you can only use one of them.
>
> I don't see these exclusive (at Friday afternoon at least). I think they
> can complement each-others. The temp_degradation table gives us the
> temperature impact on <energy storing ability>, eg, how much the battery
> capacity has changed from designed one due to the temperature.
>
> OCV-SOC tables at various temperatures tell us how OCV looks like when
> we have X% of battery left at different temperatures. Estimation of how
> much the X% is in absolute uAhs can be done by taking into account the
> designed_cap, aging degradation and the temperature degradation (and the
> position of moon, amount of muons created by cosmic rays hitting
> athmosphere at knee energy region and so on...)
>
> Or am I just getting something terribly wrong (again)? :)
> (I still for example like internal functions named as __foo() )

OK so yeah I think you are at something here. Which is generic.

The battery indicator in my Tesla in Swedish winter times looks
like this:

+-------------------+---+
|       25%         | * |
+-------------------+---+

So the star * indicates some extra capacity that is taken away
because of the low temperature.

This must be because the system is aware about the impact on
the battery available uAh of the temperature. As you use the
battery it will get warmer and the capacity will increase and the
little star goes away.

Current random mobile phones are not this great and do not
estimate the capacity fall because of the temperature, just shows
a percentage of the full capacity at the temperature right now
whatever that capacity may be, so it is a relative scale and we
can never show anything as nice as what the Tesla does with
this.

Then the question is: is the method used by Rohm universal and
well-known and something many chargers will do exactly this
way, so it should be in the core, or is it a particularity that should
be in your driver?

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-26 12:35         ` Matti Vaittinen
@ 2021-11-27  0:55           ` Linus Walleij
  0 siblings, 0 replies; 31+ messages in thread
From: Linus Walleij @ 2021-11-27  0:55 UTC (permalink / raw)
  To: Matti Vaittinen
  Cc: Vaittinen, Matti, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power, Mutanen, Mikko

On Fri, Nov 26, 2021 at 1:35 PM Matti Vaittinen
<mazziesaccount@gmail.com> wrote:

> Hope this did clarify. Afraid it didn't :)

I got it first time, I think. Or I just think I got it but didn't ;)

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-27  0:54         ` Linus Walleij
@ 2021-11-28  8:51           ` Vaittinen, Matti
  2021-11-30  1:34             ` Linus Walleij
  0 siblings, 1 reply; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-28  8:51 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Morning Linus & All,

First of all - thanks. I've no words to say how much I do appreciate 
this discussion. (There was zero sarcasm - I don't get much of 
discussion on these topics elsewhere...)

On 11/27/21 02:54, Linus Walleij wrote:
> Hi Matti,
> 
> don't worry you are probably right. You are the domain expert working
> on stuff like this inside a company that actually makes charger ICs.
> I am just some guy on the street. So I certainly trust you on this.

Please don't XD

I am working in a company which does pretty much _all_ frigging 
components from simple resistors to some CPU cores. There are PMICs, 
chargers, touch screens, SERDES, audio ICs - pretty much every 
semiconductor IC one can think of.

What comes to me - I have _very_ limited knowledge on any of these. I 
just do the drivers for whatever component they need one for. And for 
that purpose I do some research and a lot of "guesswork" - almost as if 
I was just a random guy on the street ;) I do _rarely_ have any proper 
documentation about how things are supposed to work - and if I do, I 
know how things are supposed to work at hardware-level, not how software 
should be orchestrating things :/

This discussion with guys like you helps _a lot_ regarding all the 
guesswork :)

> 
> On Fri, Nov 26, 2021 at 12:56 PM Vaittinen, Matti
> <Matti.Vaittinen@fi.rohmeurope.com> wrote:
> 
>> I don't see providing OCV tables at different temperature gives the
>> degradation of battery capacity. Whoah. A big thought for Friday.
> 
> I think we are getting close to academic definitions of the problem,
> so we'd need input from people who actually work on charging and
> knows how this works? Or we need to read up on it :/

Studying is hard work. I am so glad I get to chat with someone who has 
obviously done some of it ;) And just to stress the point - that someone 
is You :]

>> We get the OCV => SOC correspondance at different temperatures.
> 
> I read
> OCV = Open Circuit Voltage (which we can't measure but must calculate)

Me too. Although we can measure this when designing the system in order 
to extract those OCV tables, right?

> and
> SOC = State of Charge (a percentage of the capacity)

Yes.

> And we agree what the bindings and code gives today is
> (temp, OCV) -> SoC (%)
> 
> And this assumption is:
> 
> A) wrong as in the earth is flat or it makes the same sens to
>     take a random number and use as capacity
> 
> B) wrong as in not good enough and a bit rough around the
>     edges and you have an idea how to improve it > I assume (B) and what we are seeing on the battery indicator on
> most of the worlds mobile phones etc is sometimes a bit so-so
> because they have only used the above. (I think some people can
> attest to experiencing this problem.) >
> And now we want something better, think medical equipment.

I'd vothe the (B) too.

In theory the OCV => SOC is correct. (Part of the) Problem is that we 
can't measure the OCV reliably/accurately when system is running 
(because there is load connected). Also, there is probably some change 
here caused by battery aging. So for proper state-of-charge information 
we can't rely the OCV only. (This is where the coulomb counter comes 
in). Furthermore, I would not say it is medical only. I think that even 
the consumer electronics can benefit (a lot) from any accuracy 
improvement we can provide. I guess the improved accuracy can prolong 
the battery life and help avoiding unnecessary charging.

>> I
>> however don't see how this gives the OCV => energy relation. As far as I
>> know both the OCV and the 'amount of uAhs battery is able to store' are
>> impacted by temperature change. This means, seeing the OCV => SOC at
>> different temperatures does not tell us what is the impact of
>> temperature to the OCV, and what is the impact to SOC.
> 
> It is definitely true that both the OCV and SOC changes according to
> temperature.
> 
> But it is also true that these tables for a certain temperature are written
> with an OCV measured at this temperature, so the OCV used in the
> table is already compensated for the temperature, right?

Yes. OCV is fitted but we typically don't have the accurate OCV at all 
times. This meand that we need to use the coulomb-counter value - which 
makes it mandatory for us to find out the absolute capacity at given 
temperature.

>> For cases like the ROHM Chargers, we are interested on how much has the
>> 'ability to store uAhs' changed due to the temperature. When we know the
>> amount of uAhs we can store, we can use the coulomb counter value to
>> estimate what we still have left in the battery.
>>
>> In addition to this we do use the OCV information for the "nearly
>> depleted battery" - to improve the estimation by zero-correction
>> algorithm. I must admit Friday afternoon is not the time I can quite
>> recap this part. I think it was something like:
>>
>> 1. Measure VBat with system load (VBAT)
>> 2. Find OCV corresponding the current SOC estimate (SOC based on coulomb
>> counter value) - OCV_NOW
>> 3. Compute VDROP caused by the load (OCV_NOW - VBAT)
>> 4. Assume VDROP stays constant (or use ROHM VDR parameters if provided)
>> 5. Using VDROP compute the OCV_MIN which matches the minimum battery
>> voltage where system is still operational
>> 6. Use the OCV_MIN and "OCV at SOC0 from calibration data" difference to
>> adjust the battery capacity.
> 
> That's a neat trick!
> If you look at drivers/power/supply/ab8500_fg.c function
> ab8500_fg_load_comp_volt_to_capacity() you find how
> someone else chose to do this with a bit of averaging etc.

Thanks for the pointer. I haven't read that yet but I will do. Although, 
I'll probably postpone that reading to the next week.

> 
>>>> I'd just calculate a few tables per temperature and be done with
>>>> it.
>>>>
>>>> At least documentation needs to be updated to reflect that the two
>>>> methods
>>>> are exclusive and you can only use one of them.
>>
>> I don't see these exclusive (at Friday afternoon at least). I think they
>> can complement each-others. The temp_degradation table gives us the
>> temperature impact on <energy storing ability>, eg, how much the battery
>> capacity has changed from designed one due to the temperature.
>>
>> OCV-SOC tables at various temperatures tell us how OCV looks like when
>> we have X% of battery left at different temperatures. Estimation of how
>> much the X% is in absolute uAhs can be done by taking into account the
>> designed_cap, aging degradation and the temperature degradation (and the
>> position of moon, amount of muons created by cosmic rays hitting
>> athmosphere at knee energy region and so on...)
>>
>> Or am I just getting something terribly wrong (again)? :)
>> (I still for example like internal functions named as __foo() )
> 
> OK so yeah I think you are at something here. Which is generic.
> 
> The battery indicator in my Tesla in Swedish winter times looks
> like this:

Oh, you're driving Tesla. I've wondered how do they work at winter time? 
At the Oulu Finland we can also have quite freezing cold winters so I 
wouldn't dare to buy Tesla (even if they weren't quite that expensive). 
I must admit that I do like the acceleration though. Well, on that front 
my old motorbike must suffice and for me my car is purely for 
transportation and not for run :/

> 
> +-------------------+---+
> |       25%         | * |
> +-------------------+---+
> 
> So the star * indicates some extra capacity that is taken away
> because of the low temperature.
> 
> This must be because the system is aware about the impact on
> the battery available uAh of the temperature. As you use the
> battery it will get warmer and the capacity will increase and the
> little star goes away.

Getting back to the topic - I think this is a good example. Coulomb 
counter can tell us the amoount of uAh we have drawn from the battery 
(with the drawback of for example the ADC offset causing drifting).

The temperature-degradation tables (and some figures explaining the 
impact of aging) can tell us how much absolute capacity we have lost 
(the star thingee) - some of which can be recovered when battery is 
warming - and finally we do get the SOC in %.

> Current random mobile phones are not this great and do not
> estimate the capacity fall because of the temperature, just shows
> a percentage of the full capacity at the temperature right now
> whatever that capacity may be, so it is a relative scale and we
> can never show anything as nice as what the Tesla does with
> this.

Yes. What I was after here is using the temperature-corrected capacity 
as a base of computing the relative SOC. I don't think we should tell 
user the battery is only half-full when it actually can't hold more 
charge in cold weather (due to reduced capacity) - because this 
instructs people to start charging.

OTOH, I do _really_ like your Tesla example of telling the user that 
even though your battery is as full as it can, it does not mean it lasts 
the same <XX> hours it usually does when you are indoors. I am pretty 
sure the current power-supply framework allows us to expose the current 
full_cap to userlans - so building your Tesla example with the star 
should be doable - if the drivers can somehow get the information about 
the absolute capacity drop.


> Then the question is: is the method used by Rohm universal and
> well-known and something many chargers will do exactly this
> way, so it should be in the core, or is it a particularity that should
> be in your driver?

I am not sure this is the correct question. I'd rephrase it to: Would it 
be beneficial for drivers to come if the core did support this 
functionality - or is the feature unnecessary, or are there better 
alternatives. If core does not provide the support - then it is a high 
mountain for one to climb if he wants to use such improvement.

I think that the agreement we can do is that the OCV+temperature => SOC 
tables do not provide quite same information as the suggested 
temperature => capacity-drop tables would. Whether there are better 
alternatives - or if this is generally useful remains to be discussed - 
and this is something where I _do_ appreciate your (and everyone else's) 
input!

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] 31+ messages in thread

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-28  8:51           ` Vaittinen, Matti
@ 2021-11-30  1:34             ` Linus Walleij
  2021-11-30  6:33               ` Vaittinen, Matti
  0 siblings, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-11-30  1:34 UTC (permalink / raw)
  To: Vaittinen, Matti
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power

Hi Matti,

not so much time so trying to answer the central question here!

On Sun, Nov 28, 2021 at 9:51 AM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

> I am pretty
> sure the current power-supply framework allows us to expose the current
> full_cap to userlans - so building your Tesla example with the star
> should be doable - if the drivers can somehow get the information about
> the absolute capacity drop.

To do this we would need to export a capacity at current temperature
and capacity at nominal temperature (which is usually 25 deg C)
so you can scale to something. This isn't in sysfs today but we could
probably add it (and then the world of UI:s battery icons need to change
in response).

> > Then the question is: is the method used by Rohm universal and
> > well-known and something many chargers will do exactly this
> > way, so it should be in the core, or is it a particularity that should
> > be in your driver?
>
> I am not sure this is the correct question. I'd rephrase it to: Would it
> be beneficial for drivers to come if the core did support this
> functionality - or is the feature unnecessary, or are there better
> alternatives. If core does not provide the support - then it is a high
> mountain for one to climb if he wants to use such improvement.

I think we need this.

> I think that the agreement we can do is that the OCV+temperature => SOC
> tables do not provide quite same information as the suggested
> temperature => capacity-drop tables would. Whether there are better
> alternatives - or if this is generally useful remains to be discussed -
> and this is something where I _do_ appreciate your (and everyone else's)
> input!

temperature + OCV => SOC isn't enough I think.

We probably need something to tell us what the total usable
capacity will be under different temperatures. I suspect an
interpolated table is best though, this is going to be quite
nonlinear in practice.

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-30  1:34             ` Linus Walleij
@ 2021-11-30  6:33               ` Vaittinen, Matti
  2021-12-02  1:57                 ` Linus Walleij
  0 siblings, 1 reply; 31+ messages in thread
From: Vaittinen, Matti @ 2021-11-30  6:33 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power, shimi >> 清水 崇弘

On 11/30/21 03:34, Linus Walleij wrote:
> Hi Matti,
> 
> not so much time so trying to answer the central question here!
> 
> On Sun, Nov 28, 2021 at 9:51 AM Vaittinen, Matti
> <Matti.Vaittinen@fi.rohmeurope.com> wrote:
> 
>> I am pretty
>> sure the current power-supply framework allows us to expose the current
>> full_cap to userlans - so building your Tesla example with the star
>> should be doable - if the drivers can somehow get the information about
>> the absolute capacity drop.
> 
> To do this we would need to export a capacity at current temperature
> and capacity at nominal temperature (which is usually 25 deg C)
> so you can scale to something.

Hmm. I wonder if we need this. Perhaps the existing:
CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN 

   design charge values, when battery considered full/empty. 

 

ENERGY_FULL_DESIGN, ENERGY_EMPTY_DESIGN 

   same as above but for energy. 

 

CHARGE_FULL, CHARGE_EMPTY 

   These attributes means "last remembered value of charge when battery 

   became full/empty". It also could mean "value of charge when battery 

   considered full/empty at given conditions (temperature, age)". 

   I.e. these attributes represents real thresholds, not design values.

are enough? I am unsure the user is interested in knowing which part of 
the lost battery cap is caused by the temperature, and which is caused 
by some other phenomena. Or, do you think that showing loss of "not 
recoverable" capacity loss (like loss caused by aging) is something that 
should not be displayed...? (It'd be nice to see that when buying the 
second-hand Tesla - and that would for sure be a subject to 
'reprogramming'... :rolleyes:)

> This isn't in sysfs today but we could
> probably add it (and then the world of UI:s battery icons need to change
> in response).

Yes. I'd really like the userland displaying the difference of the 
designed-charge and full-charge already now. I could almost consider 
sending a patch if:

a) I could draw icons / design UIs - which I really can't
b) I knew which userland software to patch...

> 
>>> Then the question is: is the method used by Rohm universal and
>>> well-known and something many chargers will do exactly this
>>> way, so it should be in the core, or is it a particularity that should
>>> be in your driver?
>>
>> I am not sure this is the correct question. I'd rephrase it to: Would it
>> be beneficial for drivers to come if the core did support this
>> functionality - or is the feature unnecessary, or are there better
>> alternatives. If core does not provide the support - then it is a high
>> mountain for one to climb if he wants to use such improvement.
> 
> I think we need this.
> 
>> I think that the agreement we can do is that the OCV+temperature => SOC
>> tables do not provide quite same information as the suggested
>> temperature => capacity-drop tables would. Whether there are better
>> alternatives - or if this is generally useful remains to be discussed -
>> and this is something where I _do_ appreciate your (and everyone else's)
>> input!
> 
> temperature + OCV => SOC isn't enough I think.
> 
> We probably need something to tell us what the total usable
> capacity will be under different temperatures. I suspect an
> interpolated table is best though, this is going to be quite
> nonlinear in practice.

Hmm. Fair enough. Maybe instead of providing 'temperature range where 
degradation is constant' we should simply support providing the 
data-points. Eg, an array of known 
temperature-[degradation/change]-from-[designed/full]-capacity pairs and 
leave selecting the best fitting model to the software. Linear 
interpolation is simple, and may suffice for cases where we have enough 
of data-points - but you are correct that there probably are better 
alternatives. Nice thing is software is that it can be changed over time 
- so even implementing it with linear approach means opening a room for 
further improvements ;)

Well, I don't know how constant such degradation is over time. I just 
guess it is not constant but might be proportional to age-compensated 
capacity rather than the designed capacity. It'd be nice to use correct 
approximation of reality in device-tree... So, perhaps the data-points 
should not be absolute uAh values but values relative to age-corrected 
battery capacity (or if age correction is not available, then values 
relative to the designed capacity).

Sigh, so many things to improve, so little time :)

By the way, I was reading the ab8500 fuel-gauge driver as you suggested. 
So, if I am correct, you used the battery internal resistance + current 
to compute the voltage-drop caused by battery internal resistance. This 
for sure improves the accuracy when we assume VBAT can be used as OCV.

Epilogue:
In general, I see bunch of power-supply drivers scheduling work for 
running some battery-measurements. Some do this periodically (I think 
the ab8500 did this although I lost the track when I tried to see in 
which case the periodic work was not scheduled - and maybe for 
fuel-gauging) or after an IRQ is triggered (for example to see if change 
indication should be sent).

I think we could simplify a few drivers if the core provided some helper 
thread (like the simple-gauge) which could be woken by drivers (to do 
fuel-gauge operations, or to just conditionally send the change 
notification).

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] 31+ messages in thread

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-11-30  6:33               ` Vaittinen, Matti
@ 2021-12-02  1:57                 ` Linus Walleij
  2021-12-02  6:29                   ` Vaittinen, Matti
  0 siblings, 1 reply; 31+ messages in thread
From: Linus Walleij @ 2021-12-02  1:57 UTC (permalink / raw)
  To: Vaittinen, Matti
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power, shimi >> 清水 崇弘

On Tue, Nov 30, 2021 at 7:33 AM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

> Hmm. Fair enough. Maybe instead of providing 'temperature range where
> degradation is constant' we should simply support providing the
> data-points. Eg, an array of known
> temperature-[degradation/change]-from-[designed/full]-capacity pairs and
> leave selecting the best fitting model to the software. Linear
> interpolation is simple, and may suffice for cases where we have enough
> of data-points - but you are correct that there probably are better
> alternatives. Nice thing is software is that it can be changed over time
> - so even implementing it with linear approach means opening a room for
> further improvements ;)

Yeah someone will implement spline interpolation in the kernel one
day I bet...

> Well, I don't know how constant such degradation is over time. I just
> guess it is not constant but might be proportional to age-compensated
> capacity rather than the designed capacity. It'd be nice to use correct
> approximation of reality in device-tree...

IIUC the degradation of a battery is related to number of full charge cycles,
i.e. the times that the battery has been emptied and recharged fully.
This is of course never happening in practice, so e.g. two recharge cycles
from 50% to 100% is one full charge cycle. So you integrate this
over time (needs to be saved in a file system or flash if the battery does
not say it itself).

This measures how much the lithium ions have moved around in the
electrolyte and thus how much chemical interaction the battery has
seen.

Then the relationship between complete charge cycles and capacity
degradation is certainly also going to be something nonlinear so it
needs manufacturer data for the battery.

> By the way, I was reading the ab8500 fuel-gauge driver as you suggested.
> So, if I am correct, you used the battery internal resistance + current
> to compute the voltage-drop caused by battery internal resistance. This
> for sure improves the accuracy when we assume VBAT can be used as OCV.

Yes this is how it is done. With a few measurements averaged to
iron out the noise.

> Epilogue:
> In general, I see bunch of power-supply drivers scheduling work for
> running some battery-measurements. Some do this periodically (I think
> the ab8500 did this although I lost the track when I tried to see in
> which case the periodic work was not scheduled - and maybe for
> fuel-gauging) or after an IRQ is triggered (for example to see if change
> indication should be sent).

Yes there is some tight community of electronic engineers who read the
right articles and design these things. We don't know them :(

> I think we could simplify a few drivers if the core provided some helper
> thread (like the simple-gauge) which could be woken by drivers (to do
> fuel-gauge operations, or to just conditionally send the change
> notification).

I think so too, I don't think they are very rocket science once the
right abstractions fall out.

Yours,
Linus Walleij

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

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-12-02  1:57                 ` Linus Walleij
@ 2021-12-02  6:29                   ` Vaittinen, Matti
  2021-12-05  0:30                     ` Linus Walleij
  0 siblings, 1 reply; 31+ messages in thread
From: Vaittinen, Matti @ 2021-12-02  6:29 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power, shimi >> 清水 崇弘

On 12/2/21 03:57, Linus Walleij wrote:
> On Tue, Nov 30, 2021 at 7:33 AM Vaittinen, Matti
> <Matti.Vaittinen@fi.rohmeurope.com> wrote:
> 
>> Well, I don't know how constant such degradation is over time. I just
>> guess it is not constant but might be proportional to age-compensated
>> capacity rather than the designed capacity. It'd be nice to use correct
>> approximation of reality in device-tree...
> 
> IIUC the degradation of a battery is related to number of full charge cycles,
> i.e. the times that the battery has been emptied and recharged fully.
> This is of course never happening in practice, so e.g. two recharge cycles
> from 50% to 100% is one full charge cycle. So you integrate this
> over time (needs to be saved in a file system or flash if the battery does
> not say it itself). 

Yes.

> This measures how much the lithium ions have moved around in the
> electrolyte and thus how much chemical interaction the battery has
> seen.
> 
> Then the relationship between complete charge cycles and capacity
> degradation is certainly also going to be something nonlinear so it
> needs manufacturer data for the battery.

Right. As far as I understand, at least part of the 'aging degradation' 
comes from the fact that battery materials are 'vaporizing' when battery 
is charged. And as far as I understand, the temperature in which 
charging occurs has a big impact on this. Eg, higher the temperature 
where you do charging, worse the degradation. Which means that the cycle 
count should actually be weighed by the charging temperature.

But this kind of missed my point :) I was thinking of how to give the 
absolute (uAh) value of capacity drop caused by the temperature. My 
original RFC patch gave this as linear change of absolute uAh's at a 
temperature range.

As you pointed, we should not include the linearity in the DT model. So 
the next step would be to just give measured value pairs (should be done 
by battery vendor or computed by some theoretical basis) of absolute 
uAh/temperature - and leave fitting of the data points to be done by SW.

What I was now considering is that maybe the capacity drop (in uAhs) 
caused by the temperature change - is not the same for new and old 
battery. It sounds more logical to me that the capacity drop caused by 
the temperature is proportional to the maximum capacity battery is 
having at that point of it's life. Eg, if new battery can hold 80 units 
of energy, and drops 20 units of energy when temperature changes from T0 
=> T1 - an badly aged battery which now only can hold 40 units would 
lose only 10 units at that same temperature drop T0 => T1. I was 
wondering if such an assumption is closer to the truth than saying that 
bot of the batteries would lose same 20 units - meaning that the new 
battery would lose 25% of energy at temperature drop T0 => T1 but old 
one would lose 50% of the capacity. I somehow think both of the 
batteries, old and new, would lose same % of capacity at the temperature 
change.

So, if this assumption is correct, then we should give the temperature 
impact as proportion of the full capacity taking the aging into account. 
So if we happen to know the aging impact to the capacity, then software 
should use aging compensation prior computing the temperature impact. If 
aging information or impact is not known, then designed capacity can be 
used as a fall-back, even though it means we will probably be somewhat 
off for old batteries.

My problem here is that I just assume the impact of temperature is 
proportional to the full-capacity which takes the aging into account. 
Knowing how this really is would be cool so we could get the temperature 
impact modelled correctly in DT.

>> By the way, I was reading the ab8500 fuel-gauge driver as you suggested.
>> So, if I am correct, you used the battery internal resistance + current
>> to compute the voltage-drop caused by battery internal resistance. This
>> for sure improves the accuracy when we assume VBAT can be used as OCV.
> 
> Yes this is how it is done. With a few measurements averaged to
> iron out the noise.
> 
>> Epilogue:
>> In general, I see bunch of power-supply drivers scheduling work for
>> running some battery-measurements. Some do this periodically (I think
>> the ab8500 did this although I lost the track when I tried to see in
>> which case the periodic work was not scheduled - and maybe for
>> fuel-gauging) or after an IRQ is triggered (for example to see if change
>> indication should be sent).
> 
> Yes there is some tight community of electronic engineers who read the
> right articles and design these things. We don't know them :(

Right. By the way, I heard tha the TI has patent protecting some type of 
battery internal resistance usage here. OTOH, ROHM has patent over some 
of the VDROP value table stuff. Occasionally it feels like the ice is 
getting thinner at each step here. :/

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] 31+ messages in thread

* Re: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables
  2021-12-02  6:29                   ` Vaittinen, Matti
@ 2021-12-05  0:30                     ` Linus Walleij
  0 siblings, 0 replies; 31+ messages in thread
From: Linus Walleij @ 2021-12-05  0:30 UTC (permalink / raw)
  To: Vaittinen, Matti
  Cc: Matti Vaittinen, Sebastian Reichel, Rob Herring, Lee Jones,
	rostokus, fan.chen, linux-pm, devicetree, linux-kernel,
	linux-power, shimi >> 清水 崇弘

Hi Matti,

On Thu, Dec 2, 2021 at 7:29 AM Vaittinen, Matti
<Matti.Vaittinen@fi.rohmeurope.com> wrote:

(fast forward the stuff where we are in violent agreement)

> What I was now considering is that maybe the capacity drop (in uAhs)
> caused by the temperature change - is not the same for new and old
> battery. It sounds more logical to me that the capacity drop caused by
> the temperature is proportional to the maximum capacity battery is
> having at that point of it's life. Eg, if new battery can hold 80 units
> of energy, and drops 20 units of energy when temperature changes from T0
> => T1 - an badly aged battery which now only can hold 40 units would
> lose only 10 units at that same temperature drop T0 => T1. I was
> wondering if such an assumption is closer to the truth than saying that
> bot of the batteries would lose same 20 units - meaning that the new
> battery would lose 25% of energy at temperature drop T0 => T1 but old
> one would lose 50% of the capacity. I somehow think both of the
> batteries, old and new, would lose same % of capacity at the temperature
> change.
>
> So, if this assumption is correct, then we should give the temperature
> impact as proportion of the full capacity taking the aging into account.

This looks plausible.

> My problem here is that I just assume the impact of temperature is
> proportional to the full-capacity which takes the aging into account.
> Knowing how this really is would be cool so we could get the temperature
> impact modelled correctly in DT.

I suppose we should check some IEEE articles to verify that this is the
case before assuming. I have access to them but no time to read :(

> > Yes there is some tight community of electronic engineers who read the
> > right articles and design these things. We don't know them :(
>
> Right. By the way, I heard tha the TI has patent protecting some type of
> battery internal resistance usage here. OTOH, ROHM has patent over some
> of the VDROP value table stuff. Occasionally it feels like the ice is
> getting thinner at each step here. :/

This is none of our concern. Patents are concerns for people shipping
devices, not for open source code. Also patents are only valid for
20 years and we are looking at longer times anyway. If we define
generic DT properties for this they will be used more than 20 years
from now. We even have patented code in the kernel, see:
Documentation/RCU/rcu.rst

Yours,
Linus Walleij

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

end of thread, other threads:[~2021-12-05  0:30 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-16 12:24 [RFC PATCH v3 0/9] power: supply: Add some fuel-gauge logic Matti Vaittinen
2021-11-16 12:24 ` [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Matti Vaittinen
2021-11-16 14:02   ` Rob Herring
2021-11-18  1:57   ` Linus Walleij
2021-11-18  5:27     ` Vaittinen, Matti
2021-11-16 12:25 ` [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper Matti Vaittinen
2021-11-18  2:02   ` Linus Walleij
2021-11-18  5:30     ` Vaittinen, Matti
2021-11-16 12:25 ` [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Matti Vaittinen
2021-11-18  2:10   ` Linus Walleij
2021-11-18  6:11     ` Vaittinen, Matti
2021-11-26 11:56       ` Vaittinen, Matti
2021-11-26 12:35         ` Matti Vaittinen
2021-11-27  0:55           ` Linus Walleij
2021-11-27  0:54         ` Linus Walleij
2021-11-28  8:51           ` Vaittinen, Matti
2021-11-30  1:34             ` Linus Walleij
2021-11-30  6:33               ` Vaittinen, Matti
2021-12-02  1:57                 ` Linus Walleij
2021-12-02  6:29                   ` Vaittinen, Matti
2021-12-05  0:30                     ` Linus Walleij
2021-11-16 12:26 ` [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration Matti Vaittinen
2021-11-19  1:42   ` Linus Walleij
2021-11-16 12:27 ` [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo Matti Vaittinen
2021-11-16 12:27 ` [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Matti Vaittinen
2021-11-19  1:49   ` Linus Walleij
2021-11-19  8:11     ` Matti Vaittinen
2021-11-16 12:28 ` [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction Matti Vaittinen
2021-11-19  1:54   ` Linus Walleij
2021-11-16 12:29 ` [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Matti Vaittinen
2021-11-16 12:29 ` [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver Matti Vaittinen

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