All of lore.kernel.org
 help / color / mirror / Atom feed
From: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
To: Matti Vaittinen <mazziesaccount@gmail.com>,
	Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
Cc: Sebastian Reichel <sre@kernel.org>,
	Rob Herring <robh+dt@kernel.org>,
	Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>,
	Lee Jones <lee.jones@linaro.org>,
	Linus Walleij <linus.walleij@linaro.org>,
	rostokus@gmail.com, fan.chen@mediatek.com,
	linux-pm@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com
Subject: [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction
Date: Tue, 16 Nov 2021 14:28:13 +0200	[thread overview]
Message-ID: <26a80d9081382976cec58f9c3bc0ecb181c5836e.1637061794.git.matti.vaittinen@fi.rohmeurope.com> (raw)
In-Reply-To: <cover.1637061794.git.matti.vaittinen@fi.rohmeurope.com>

[-- 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 --]

  parent reply	other threads:[~2021-11-16 12:28 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Matti Vaittinen [this message]
2021-11-19  1:54   ` [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction 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
2021-11-17  2:06   ` kernel test robot
2021-11-17 10:10   ` kernel test robot
2021-11-18 13:10   ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=26a80d9081382976cec58f9c3bc0ecb181c5836e.1637061794.git.matti.vaittinen@fi.rohmeurope.com \
    --to=matti.vaittinen@fi.rohmeurope.com \
    --cc=devicetree@vger.kernel.org \
    --cc=fan.chen@mediatek.com \
    --cc=lee.jones@linaro.org \
    --cc=linus.walleij@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-power@fi.rohmeurope.com \
    --cc=mazziesaccount@gmail.com \
    --cc=robh+dt@kernel.org \
    --cc=rostokus@gmail.com \
    --cc=sre@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.