linux-pm.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver
@ 2025-07-21 12:25 Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 1/5] power: supply: Add adc-battery-helper Hans de Goede
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:25 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

Hi All,

Here is a patch-series which primary goal is to add support for
using the coulomb-counter in the Intel Dollar Cove TI PMIC as
battery power-supply class device.

This PMIC used on some Intel Bay/Cherry Trail systems has some builtin
fuel-gauge functionality which just like the UG3105 fuel-gauge is not
a full featured autonomous fuel-gauge.

To prepare for adding support for this, this series factors out the UG3105
capacity estimating code, generalized so that it can be re-used in other
drivers.

This series has been tested on an Asus ME176C tablet with UG3105 fuel-gauge
and on an Acer A1 840 tablet which uses the Intel Dollar Cove TI coulomb-
counter as fuel-gauge.

Changes in v3:
- Drop hardcoded open-circuit-voltage to capacity mapping table, replacing
  it with using the "ocv-capacity-table-n" device-property and replace DIY
  code with power_supply_batinfo_ocv2cap()
- Rebase on latest upstream where ug3105_battery was already switched to
  "ocv-capacity-table-n" device-prop + power_supply_batinfo_ocv2cap()
- Switch fg druver to iio_read_channel_processed_scale() which moves
  the Vbat ADC calibration handling and scaling to the IIO ADC driver
- Drop (already fixed) "power: supply: adc-battery-helper: Fix reporting
  capacity > 100%"

Changes in v2:
- Drop the 2 already merged bug-fix patches
- Add missing MODULE_DESCRIPTION() to the new adc-battery-helper module

Regards,

Hans

*** BLURB HERE ***

Hans de Goede (5):
  power: supply: Add adc-battery-helper
  power: supply: ug3105_battery: Switch to adc-battery-helper
  power: supply: ug3105_battery: Put FG in standby on remove and
    shutdown
  power: supply: adc-battery-helper: Add support for optional
    charge_finished GPIO
  power: supply: Add new Intel Dollar Cove TI battery driver

 drivers/power/supply/Kconfig               |  16 +
 drivers/power/supply/Makefile              |   2 +
 drivers/power/supply/adc-battery-helper.c  | 327 +++++++++++++++++
 drivers/power/supply/adc-battery-helper.h  |  62 ++++
 drivers/power/supply/intel_dc_ti_battery.c | 394 +++++++++++++++++++++
 drivers/power/supply/ug3105_battery.c      | 346 ++++--------------
 6 files changed, 867 insertions(+), 280 deletions(-)
 create mode 100644 drivers/power/supply/adc-battery-helper.c
 create mode 100644 drivers/power/supply/adc-battery-helper.h
 create mode 100644 drivers/power/supply/intel_dc_ti_battery.c

-- 
2.49.0


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

* [PATCH v3 1/5] power: supply: Add adc-battery-helper
  2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
@ 2025-07-21 12:26 ` Hans de Goede
  2025-07-26 22:40   ` Linus Walleij
  2025-07-21 12:26 ` [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper Hans de Goede
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

The TI PMIC used on some Intel Bay/Cherry Trail systems has some builtin
fuel-gauge functionality which just like the UG3105 fuel-gauge is not
a full featured autonomous fuel-gauge.

These fuel-gauges offer accurate current and voltage measurements but
their coulomb-counters are intended to work together with an always on
micro-controller monitoring the fuel-gauge.

Add an adc-battery-helper offering open-circuit-voltage (ocv) and through
that capacity estimation for devices where such limited functionality
fuel-gauges are exposed directly to Linux.

This is a copy of the existing UG3105 estimating code, generalized so that
it can be re-used in other drivers.

The next commit will replace the UG3105 driver's version of this code with
using the adc-battery-helper.

The API has been designed for easy integration into existing power-supply
drivers. For example this functionality might also be a useful addition
to the generic-adc-battery driver.

The requirement of needing the adc_battery_helper struct to be the first
member of a battery driver's data struct is not ideal. This is a compromise
which is necessary to allow directly using the helper's get_property(),
external_power_changed() and suspend()/resume() functions as power-supply /
suspend-resume callbacks.

Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v3:
- Drop hardcoded open-circuit-voltage to capacity mapping table, this
  table should be provided as "ocv-capacity-table-n" device-property.
- Replace DIY code with power_supply_batinfo_ocv2cap()
---
 drivers/power/supply/Kconfig              |   3 +
 drivers/power/supply/Makefile             |   1 +
 drivers/power/supply/adc-battery-helper.c | 315 ++++++++++++++++++++++
 drivers/power/supply/adc-battery-helper.h |  59 ++++
 4 files changed, 378 insertions(+)
 create mode 100644 drivers/power/supply/adc-battery-helper.c
 create mode 100644 drivers/power/supply/adc-battery-helper.h

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 79ddb006e2da..8cfaded2cc5c 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -35,6 +35,9 @@ config APM_POWER
 	  Say Y here to enable support APM status emulation using
 	  battery class devices.
 
+config ADC_BATTERY_HELPER
+	tristate
+
 config GENERIC_ADC_BATTERY
 	tristate "Generic battery support using IIO"
 	depends on IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index f943c9150b32..c85c5c441e0f 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -7,6 +7,7 @@ power_supply-$(CONFIG_LEDS_TRIGGERS)	+= power_supply_leds.o
 
 obj-$(CONFIG_POWER_SUPPLY)	+= power_supply.o
 obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
+obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o
 obj-$(CONFIG_GENERIC_ADC_BATTERY)	+= generic-adc-battery.o
 
 obj-$(CONFIG_APM_POWER)		+= apm_power.o
diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c
new file mode 100644
index 000000000000..919634db5472
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ *
+ * Some fuel-gauges are not full-featured autonomous fuel-gauges.
+ * These fuel-gauges offer accurate current and voltage measurements but
+ * their coulomb-counters are intended to work together with an always on
+ * micro-controller monitoring the fuel-gauge.
+ *
+ * This adc-battery-helper code offers open-circuit-voltage (ocv) and through
+ * that capacity estimation for devices where such limited functionality
+ * fuel-gauges are exposed directly to Linux.
+ *
+ * This helper requires the hw to provide accurate battery current_now and
+ * voltage_now measurement and this helper the provides the following properties
+ * based on top of those readings:
+ *
+ *	POWER_SUPPLY_PROP_STATUS
+ *	POWER_SUPPLY_PROP_VOLTAGE_OCV
+ *	POWER_SUPPLY_PROP_VOLTAGE_NOW
+ *	POWER_SUPPLY_PROP_CURRENT_NOW
+ *	POWER_SUPPLY_PROP_CAPACITY
+ *
+ * As well as optional the following properties assuming an always present
+ * system-scope battery, allowing direct use of adc_battery_helper_get_prop()
+ * in this common case:
+ *	POWER_SUPPLY_PROP_PRESENT
+ *	POWER_SUPPLY_PROP_SCOPE
+ *
+ * Using this helper is as simple as:
+ *
+ * 1. Embed a struct adc_battery_helper this MUST be the first member of
+ *    the battery driver's data struct.
+ * 2. Use adc_battery_helper_props[] or add the above properties to
+ *    the list of properties in power_supply_desc
+ * 3. Call adc_battery_helper_init() after registering the power_supply and
+ *    before returning from the probe() function
+ * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property()
+ *    method, or call it for the above properties.
+ * 5. Use adc_battery_helper_external_power_changed() as the power-supply's
+ *    external_power_changed() method or call it from that method.
+ * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or
+ *    call them from the driver's suspend-resume methods.
+ *
+ * The provided get_voltage_and_current_now() method will be called by this
+ * helper at adc_battery_helper_init() time and later.
+ *
+ * Copyright (c) 2021-2024 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/devm-helpers.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+
+#include "adc-battery-helper.h"
+
+#define MOV_AVG_WINDOW						8
+#define INIT_POLL_TIME						(5 * HZ)
+#define POLL_TIME						(30 * HZ)
+#define SETTLE_TIME						(1 * HZ)
+
+#define INIT_POLL_COUNT						30
+
+#define CURR_HYST_UA						65000
+
+#define LOW_BAT_UV						3700000
+#define FULL_BAT_HYST_UV					38000
+
+#define AMBIENT_TEMP_CELCIUS					25
+
+static int adc_battery_helper_get_status(struct adc_battery_helper *help)
+{
+	int full_uv =
+		help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV;
+
+	if (help->curr_ua > CURR_HYST_UA)
+		return POWER_SUPPLY_STATUS_CHARGING;
+
+	if (help->curr_ua < -CURR_HYST_UA)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	if (help->supplied && help->ocv_avg_uv > full_uv)
+		return POWER_SUPPLY_STATUS_FULL;
+
+	return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static void adc_battery_helper_work(struct work_struct *work)
+{
+	struct adc_battery_helper *help = container_of(work, struct adc_battery_helper,
+						       work.work);
+	int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size;
+	struct device *dev = help->psy->dev.parent;
+	int volt_uv, prev_volt_uv = help->volt_uv;
+	int curr_ua, prev_curr_ua = help->curr_ua;
+	bool prev_supplied = help->supplied;
+	int prev_status = help->status;
+
+	guard(mutex)(&help->lock);
+
+	ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua);
+	if (ret)
+		goto out;
+
+	help->volt_uv = volt_uv;
+	help->curr_ua = curr_ua;
+
+	help->ocv_uv[help->ocv_avg_index] =
+		help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000;
+	dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n",
+		help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]);
+	help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW;
+	help->poll_count++;
+
+	help->ocv_avg_uv = 0;
+	win_size = min(help->poll_count, MOV_AVG_WINDOW);
+	for (i = 0; i < win_size; i++)
+		help->ocv_avg_uv += help->ocv_uv[i];
+	help->ocv_avg_uv /= win_size;
+
+	help->supplied = power_supply_am_i_supplied(help->psy);
+	help->status = adc_battery_helper_get_status(help);
+	if (help->status == POWER_SUPPLY_STATUS_FULL)
+		help->capacity = 100;
+	else
+		help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info,
+							      help->ocv_avg_uv,
+							      AMBIENT_TEMP_CELCIUS);
+
+	/*
+	 * Skip internal resistance calc on charger [un]plug and
+	 * when the battery is almost empty (voltage low).
+	 */
+	if (help->supplied != prev_supplied ||
+	    help->volt_uv < LOW_BAT_UV ||
+	    help->poll_count < 2)
+		goto out;
+
+	/*
+	 * Assuming that the OCV voltage does not change significantly
+	 * between 2 polls, then we can calculate the internal resistance
+	 * on a significant current change by attributing all voltage
+	 * change between the 2 readings to the internal resistance.
+	 */
+	curr_diff_ua = abs(help->curr_ua - prev_curr_ua);
+	if (curr_diff_ua < CURR_HYST_UA)
+		goto out;
+
+	volt_diff_uv = abs(help->volt_uv - prev_volt_uv);
+	res_mohm = volt_diff_uv * 1000 / curr_diff_ua;
+
+	if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) ||
+	    (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) {
+		dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm);
+		goto out;
+	}
+
+	dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm);
+
+	help->intern_res_mohm[help->intern_res_avg_index] = res_mohm;
+	help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW;
+	help->intern_res_poll_count++;
+
+	help->intern_res_avg_mohm = 0;
+	win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW);
+	for (i = 0; i < win_size; i++)
+		help->intern_res_avg_mohm += help->intern_res_mohm[i];
+	help->intern_res_avg_mohm /= win_size;
+
+out:
+	queue_delayed_work(system_wq, &help->work,
+			   (help->poll_count <= INIT_POLL_COUNT) ?
+					INIT_POLL_TIME : POLL_TIME);
+
+	if (help->status != prev_status)
+		power_supply_changed(help->psy);
+}
+
+const enum power_supply_property adc_battery_helper_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_OCV,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_SCOPE,
+};
+EXPORT_SYMBOL_GPL(adc_battery_helper_properties);
+
+static_assert(ARRAY_SIZE(adc_battery_helper_properties) ==
+	      ADC_HELPER_NUM_PROPERTIES);
+
+int adc_battery_helper_get_property(struct power_supply *psy,
+				    enum power_supply_property psp,
+				    union power_supply_propval *val)
+{
+	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+	int dummy, ret = 0;
+
+	/*
+	 * Avoid racing with adc_battery_helper_work() while it is updating
+	 * variables and avoid calling get_voltage_and_current_now() reentrantly.
+	 */
+	guard(mutex)(&help->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = help->status;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+		val->intval = help->ocv_avg_uv;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = help->capacity;
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_get_property);
+
+void adc_battery_helper_external_power_changed(struct power_supply *psy)
+{
+	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
+
+	dev_dbg(help->psy->dev.parent, "external power changed\n");
+	mod_delayed_work(system_wq, &help->work, SETTLE_TIME);
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed);
+
+static void adc_battery_helper_start_work(struct adc_battery_helper *help)
+{
+	help->poll_count = 0;
+	help->ocv_avg_index = 0;
+
+	queue_delayed_work(system_wq, &help->work, 0);
+	flush_delayed_work(&help->work);
+}
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+			    adc_battery_helper_get_func get_voltage_and_current_now)
+{
+	struct device *dev = psy->dev.parent;
+	int ret;
+
+	help->psy = psy;
+	help->get_voltage_and_current_now = get_voltage_and_current_now;
+
+	ret = devm_mutex_init(dev, &help->lock);
+	if (ret)
+		return ret;
+
+	ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work);
+	if (ret)
+		return ret;
+
+	if (!help->psy->battery_info ||
+	    help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
+	    help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
+	    !psy->battery_info->ocv_table[0]) {
+		dev_err(dev, "error required properties are missing\n");
+		return -ENODEV;
+	}
+
+	/* Use provided internal resistance as start point (in milli-ohm) */
+	help->intern_res_avg_mohm =
+		help->psy->battery_info->factory_internal_resistance_uohm / 1000;
+	/* Also add it to the internal resistance moving average window */
+	help->intern_res_mohm[0] = help->intern_res_avg_mohm;
+	help->intern_res_avg_index = 1;
+	help->intern_res_poll_count = 1;
+
+	adc_battery_helper_start_work(help);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_init);
+
+int adc_battery_helper_suspend(struct device *dev)
+{
+	struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&help->work);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_suspend);
+
+int adc_battery_helper_resume(struct device *dev)
+{
+	struct adc_battery_helper *help = dev_get_drvdata(dev);
+
+	adc_battery_helper_start_work(help);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(adc_battery_helper_resume);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("ADC battery capacity estimation helper");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h
new file mode 100644
index 000000000000..6daba08c1a21
--- /dev/null
+++ b/drivers/power/supply/adc-battery-helper.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Helper for batteries with accurate current and voltage measurement, but
+ * without temperature measurement or without a "resistance-temp-table".
+ * Copyright (c) 2021-2024 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#define ADC_BAT_HELPER_MOV_AVG_WINDOW				8
+
+struct power_supply;
+
+/*
+ * The adc battery helper code needs voltage- and current-now to be sampled as
+ * close to each other (in sample-time) as possible. A single getter function is
+ * used to allow the battery driver to handle this in the best way possible.
+ */
+typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, int *curr);
+
+struct adc_battery_helper {
+	struct power_supply *psy;
+	struct delayed_work work;
+	struct mutex lock;
+	adc_battery_helper_get_func get_voltage_and_current_now;
+	int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW];		/* micro-volt */
+	int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW];	/* milli-ohm */
+	int poll_count;
+	int ocv_avg_index;
+	int ocv_avg_uv;						/* micro-volt */
+	int intern_res_poll_count;
+	int intern_res_avg_index;
+	int intern_res_avg_mohm;				/* milli-ohm */
+	int volt_uv;						/* micro-volt */
+	int curr_ua;						/* micro-ampere */
+	int capacity;						/* procent */
+	int status;
+	bool supplied;
+};
+
+extern const enum power_supply_property adc_battery_helper_properties[];
+/* Must be const cannot be an external. Asserted in adc-battery-helper.c */
+#define ADC_HELPER_NUM_PROPERTIES 7
+
+int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
+			    adc_battery_helper_get_func get_voltage_and_current_now);
+/*
+ * The below functions can be directly used as power-supply / suspend-resume
+ * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data
+ * directly to struct adc_battery_helper. Therefor struct adc_battery_helper
+ * MUST be the first member of the battery driver's data struct.
+ */
+int adc_battery_helper_get_property(struct power_supply *psy,
+				    enum power_supply_property psp,
+				    union power_supply_propval *val);
+void adc_battery_helper_external_power_changed(struct power_supply *psy);
+int adc_battery_helper_suspend(struct device *dev);
+int adc_battery_helper_resume(struct device *dev);
-- 
2.49.0


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

* [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper
  2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 1/5] power: supply: Add adc-battery-helper Hans de Goede
@ 2025-07-21 12:26 ` Hans de Goede
  2025-07-26 22:41   ` Linus Walleij
  2025-07-21 12:26 ` [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown Hans de Goede
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

Switch ug3105_battery to using the new adc-battery-helper, since the
helper's algorithms are a copy of the replaced ug3105_battery code
this should not cause any functional differences.

Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v3:
- Rebase on latest upstream ug3105_battery.c
---
 drivers/power/supply/Kconfig          |   1 +
 drivers/power/supply/ug3105_battery.c | 344 +++++---------------------
 2 files changed, 65 insertions(+), 280 deletions(-)

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 8cfaded2cc5c..e33a5360d33a 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -1046,6 +1046,7 @@ config CHARGER_SURFACE
 config BATTERY_UG3105
 	tristate "uPI uG3105 battery monitor driver"
 	depends on I2C
+	select ADC_BATTERY_HELPER
 	help
 	  Battery monitor driver for the uPI uG3105 battery monitor.
 
diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c
index e8a1de7cade0..70dd58e121e3 100644
--- a/drivers/power/supply/ug3105_battery.c
+++ b/drivers/power/supply/ug3105_battery.c
@@ -10,7 +10,22 @@
  * is off or suspended, the coulomb counter is not used atm.
  *
  * Possible improvements:
- * 1. Activate commented out total_coulomb_count code
+ * 1. Add coulumb counter reading, e.g. something like this:
+ * Read + reset coulomb counter every 10 polls (every 300 seconds)
+ *
+ * if ((chip->poll_count % 10) == 0) {
+ *	val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
+ *	if (val < 0)
+ *		goto out;
+ *
+ *	i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
+ *				  UG3105_CTRL1_RESET_COULOMB_CNT);
+ *
+ *	chip->total_coulomb_count += (s16)val;
+ *	dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
+ *		(s16)val, chip->total_coulomb_count);
+ * }
+ *
  * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty
  *    and remember that we did this (and clear the flag for this on susp/resume)
  * 3. When the battery is full check if the flag that we set total_coulomb_count
@@ -31,24 +46,16 @@
  * has shown that an estimated 7404mWh increase of the battery's energy results
  * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R.
  *
- * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021 - 2025 Hans de Goede <hansg@kernel.org>
  */
 
-#include <linux/devm-helpers.h>
 #include <linux/module.h>
-#include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/i2c.h>
 #include <linux/mod_devicetable.h>
 #include <linux/power_supply.h>
-#include <linux/workqueue.h>
 
-#define UG3105_MOV_AVG_WINDOW					8
-#define UG3105_INIT_POLL_TIME					(5 * HZ)
-#define UG3105_POLL_TIME					(30 * HZ)
-#define UG3105_SETTLE_TIME					(1 * HZ)
-
-#define UG3105_INIT_POLL_COUNT					30
+#include "adc-battery-helper.h"
 
 #define UG3105_REG_MODE						0x00
 #define UG3105_REG_CTRL1					0x01
@@ -61,34 +68,13 @@
 
 #define UG3105_CTRL1_RESET_COULOMB_CNT				0x03
 
-#define UG3105_CURR_HYST_UA					65000
-
-#define UG3105_LOW_BAT_UV					3700000
-#define UG3105_FULL_BAT_HYST_UV					38000
-
-#define AMBIENT_TEMP_CELCIUS					25
-
 struct ug3105_chip {
+	/* Must be the first member see adc-battery-helper documentation */
+	struct adc_battery_helper helper;
 	struct i2c_client *client;
 	struct power_supply *psy;
-	struct delayed_work work;
-	struct mutex lock;
-	int ocv[UG3105_MOV_AVG_WINDOW];		/* micro-volt */
-	int intern_res[UG3105_MOV_AVG_WINDOW];	/* milli-ohm */
-	int poll_count;
-	int ocv_avg_index;
-	int ocv_avg;				/* micro-volt */
-	int intern_res_poll_count;
-	int intern_res_avg_index;
-	int intern_res_avg;			/* milli-ohm */
-	int volt;				/* micro-volt */
-	int curr;				/* micro-ampere */
-	int total_coulomb_count;
 	int uv_per_unit;
 	int ua_per_unit;
-	int status;
-	int capacity;
-	bool supplied;
 };
 
 static int ug3105_read_word(struct i2c_client *client, u8 reg)
@@ -102,230 +88,43 @@ static int ug3105_read_word(struct i2c_client *client, u8 reg)
 	return val;
 }
 
-static int ug3105_get_status(struct ug3105_chip *chip)
-{
-	int full = chip->psy->battery_info->constant_charge_voltage_max_uv -
-		   UG3105_FULL_BAT_HYST_UV;
-
-	if (chip->curr > UG3105_CURR_HYST_UA)
-		return POWER_SUPPLY_STATUS_CHARGING;
-
-	if (chip->curr < -UG3105_CURR_HYST_UA)
-		return POWER_SUPPLY_STATUS_DISCHARGING;
-
-	if (chip->supplied && chip->ocv_avg > full)
-		return POWER_SUPPLY_STATUS_FULL;
-
-	return POWER_SUPPLY_STATUS_NOT_CHARGING;
-}
-
-static void ug3105_work(struct work_struct *work)
-{
-	struct ug3105_chip *chip = container_of(work, struct ug3105_chip,
-						work.work);
-	int i, val, curr_diff, volt_diff, res, win_size;
-	bool prev_supplied = chip->supplied;
-	int prev_status = chip->status;
-	int prev_volt = chip->volt;
-	int prev_curr = chip->curr;
-	struct power_supply *psy;
-
-	mutex_lock(&chip->lock);
-
-	psy = chip->psy;
-	if (!psy)
-		goto out;
-
-	val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
-	if (val < 0)
-		goto out;
-	chip->volt = val * chip->uv_per_unit;
-
-	val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
-	if (val < 0)
-		goto out;
-	chip->curr = (s16)val * chip->ua_per_unit;
-
-	chip->ocv[chip->ocv_avg_index] =
-		chip->volt - chip->curr * chip->intern_res_avg / 1000;
-	chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
-	chip->poll_count++;
-
-	/*
-	 * See possible improvements comment above.
-	 *
-	 * Read + reset coulomb counter every 10 polls (every 300 seconds)
-	 * if ((chip->poll_count % 10) == 0) {
-	 *	val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
-	 *	if (val < 0)
-	 *		goto out;
-	 *
-	 *	i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
-	 *				  UG3105_CTRL1_RESET_COULOMB_CNT);
-	 *
-	 *	chip->total_coulomb_count += (s16)val;
-	 *	dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
-	 *		(s16)val, chip->total_coulomb_count);
-	 * }
-	 */
-
-	chip->ocv_avg = 0;
-	win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW);
-	for (i = 0; i < win_size; i++)
-		chip->ocv_avg += chip->ocv[i];
-	chip->ocv_avg /= win_size;
-
-	chip->supplied = power_supply_am_i_supplied(psy);
-	chip->status = ug3105_get_status(chip);
-	if (chip->status == POWER_SUPPLY_STATUS_FULL)
-		chip->capacity = 100;
-	else
-		chip->capacity = power_supply_batinfo_ocv2cap(chip->psy->battery_info,
-							      chip->ocv_avg,
-							      AMBIENT_TEMP_CELCIUS);
-
-	/*
-	 * Skip internal resistance calc on charger [un]plug and
-	 * when the battery is almost empty (voltage low).
-	 */
-	if (chip->supplied != prev_supplied ||
-	    chip->volt < UG3105_LOW_BAT_UV ||
-	    chip->poll_count < 2)
-		goto out;
-
-	/*
-	 * Assuming that the OCV voltage does not change significantly
-	 * between 2 polls, then we can calculate the internal resistance
-	 * on a significant current change by attributing all voltage
-	 * change between the 2 readings to the internal resistance.
-	 */
-	curr_diff = abs(chip->curr - prev_curr);
-	if (curr_diff < UG3105_CURR_HYST_UA)
-		goto out;
-
-	volt_diff = abs(chip->volt - prev_volt);
-	res = volt_diff * 1000 / curr_diff;
-
-	if ((res < (chip->intern_res_avg * 2 / 3)) ||
-	    (res > (chip->intern_res_avg * 4 / 3))) {
-		dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res);
-		goto out;
-	}
-
-	dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res);
-
-	chip->intern_res[chip->intern_res_avg_index] = res;
-	chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
-	chip->intern_res_poll_count++;
-
-	chip->intern_res_avg = 0;
-	win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW);
-	for (i = 0; i < win_size; i++)
-		chip->intern_res_avg += chip->intern_res[i];
-	chip->intern_res_avg /= win_size;
-
-out:
-	mutex_unlock(&chip->lock);
-
-	queue_delayed_work(system_wq, &chip->work,
-			   (chip->poll_count <= UG3105_INIT_POLL_COUNT) ?
-					UG3105_INIT_POLL_TIME : UG3105_POLL_TIME);
-
-	if (chip->status != prev_status && psy)
-		power_supply_changed(psy);
-}
-
-static enum power_supply_property ug3105_battery_props[] = {
-	POWER_SUPPLY_PROP_STATUS,
-	POWER_SUPPLY_PROP_PRESENT,
-	POWER_SUPPLY_PROP_SCOPE,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_VOLTAGE_OCV,
-	POWER_SUPPLY_PROP_CURRENT_NOW,
-	POWER_SUPPLY_PROP_CAPACITY,
-};
-
-static int ug3105_get_property(struct power_supply *psy,
-			       enum power_supply_property psp,
-			       union power_supply_propval *val)
+static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
 {
 	struct ug3105_chip *chip = power_supply_get_drvdata(psy);
-	int ret = 0;
+	int ret;
 
-	mutex_lock(&chip->lock);
+	ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
+	if (ret < 0)
+		return ret;
 
-	if (!chip->psy) {
-		ret = -EAGAIN;
-		goto out;
-	}
+	*volt = ret * chip->uv_per_unit;
 
-	switch (psp) {
-	case POWER_SUPPLY_PROP_STATUS:
-		val->intval = chip->status;
-		break;
-	case POWER_SUPPLY_PROP_PRESENT:
-		val->intval = 1;
-		break;
-	case POWER_SUPPLY_PROP_SCOPE:
-		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
-		break;
-	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-		ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
-		if (ret < 0)
-			break;
-		val->intval = ret * chip->uv_per_unit;
-		ret = 0;
-		break;
-	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
-		val->intval = chip->ocv_avg;
-		break;
-	case POWER_SUPPLY_PROP_CURRENT_NOW:
-		ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
-		if (ret < 0)
-			break;
-		val->intval = (s16)ret * chip->ua_per_unit;
-		ret = 0;
-		break;
-	case POWER_SUPPLY_PROP_CAPACITY:
-		val->intval = chip->capacity;
-		break;
-	default:
-		ret = -EINVAL;
-	}
+	ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
+	if (ret < 0)
+		return ret;
 
-out:
-	mutex_unlock(&chip->lock);
-	return ret;
-}
-
-static void ug3105_external_power_changed(struct power_supply *psy)
-{
-	struct ug3105_chip *chip = power_supply_get_drvdata(psy);
-
-	dev_dbg(&chip->client->dev, "external power changed\n");
-	mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
+	*curr = (s16)ret * chip->ua_per_unit;
+	return 0;
 }
 
 static const struct power_supply_desc ug3105_psy_desc = {
 	.name		= "ug3105_battery",
 	.type		= POWER_SUPPLY_TYPE_BATTERY,
-	.get_property	= ug3105_get_property,
-	.external_power_changed	= ug3105_external_power_changed,
-	.properties	= ug3105_battery_props,
-	.num_properties	= ARRAY_SIZE(ug3105_battery_props),
+	.get_property	= adc_battery_helper_get_property,
+	.external_power_changed	= adc_battery_helper_external_power_changed,
+	.properties	= adc_battery_helper_properties,
+	.num_properties	= ADC_HELPER_NUM_PROPERTIES,
 };
 
-static void ug3105_init(struct ug3105_chip *chip)
+static void ug3105_start(struct i2c_client *client)
 {
-	chip->poll_count = 0;
-	chip->ocv_avg_index = 0;
-	chip->total_coulomb_count = 0;
-	i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
-				  UG3105_MODE_RUN);
-	i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
-				  UG3105_CTRL1_RESET_COULOMB_CNT);
-	queue_delayed_work(system_wq, &chip->work, 0);
-	flush_delayed_work(&chip->work);
+	i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN);
+	i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT);
+}
+
+static void ug3105_stop(struct i2c_client *client)
+{
+	i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY);
 }
 
 static int ug3105_probe(struct i2c_client *client)
@@ -333,7 +132,6 @@ static int ug3105_probe(struct i2c_client *client)
 	struct power_supply_config psy_cfg = {};
 	struct device *dev = &client->dev;
 	u32 curr_sense_res_uohm = 10000;
-	struct power_supply *psy;
 	struct ug3105_chip *chip;
 	int ret;
 
@@ -342,23 +140,8 @@ static int ug3105_probe(struct i2c_client *client)
 		return -ENOMEM;
 
 	chip->client = client;
-	mutex_init(&chip->lock);
-	ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work);
-	if (ret)
-		return ret;
 
-	psy_cfg.drv_data = chip;
-	psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
-	if (IS_ERR(psy))
-		return PTR_ERR(psy);
-
-	if (!psy->battery_info ||
-	    psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
-	    psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
-	    !psy->battery_info->ocv_table[0]) {
-		dev_err(dev, "error required properties are missing\n");
-		return -ENODEV;
-	}
+	ug3105_start(client);
 
 	device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm);
 
@@ -366,35 +149,36 @@ static int ug3105_probe(struct i2c_client *client)
 	 * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10
 	 * coming from somewhere for some reason (verified with a volt-meter).
 	 */
-	chip->uv_per_unit = 45000000/65536;
+	chip->uv_per_unit = 45000000 / 65536;
 	/* Datasheet says 8.1 uV per unit for the current ADC */
 	chip->ua_per_unit = 8100000 / curr_sense_res_uohm;
 
-	/* Use provided internal resistance as start point (in milli-ohm) */
-	chip->intern_res_avg = psy->battery_info->factory_internal_resistance_uohm / 1000;
-	/* Also add it to the internal resistance moving average window */
-	chip->intern_res[0] = chip->intern_res_avg;
-	chip->intern_res_avg_index = 1;
-	chip->intern_res_poll_count = 1;
+	psy_cfg.drv_data = chip;
+	chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
+	if (IS_ERR(chip->psy)) {
+		ret = PTR_ERR(chip->psy);
+		goto stop;
+	}
 
-	mutex_lock(&chip->lock);
-	chip->psy = psy;
-	mutex_unlock(&chip->lock);
-
-	ug3105_init(chip);
+	ret = adc_battery_helper_init(&chip->helper, chip->psy,
+				      ug3105_get_voltage_and_current_now);
+	if (ret)
+		goto stop;
 
 	i2c_set_clientdata(client, chip);
 	return 0;
+
+stop:
+	ug3105_stop(client);
+	return ret;
 }
 
 static int __maybe_unused ug3105_suspend(struct device *dev)
 {
 	struct ug3105_chip *chip = dev_get_drvdata(dev);
 
-	cancel_delayed_work_sync(&chip->work);
-	i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
-				  UG3105_MODE_STANDBY);
-
+	adc_battery_helper_suspend(dev);
+	ug3105_stop(chip->client);
 	return 0;
 }
 
@@ -402,8 +186,8 @@ static int __maybe_unused ug3105_resume(struct device *dev)
 {
 	struct ug3105_chip *chip = dev_get_drvdata(dev);
 
-	ug3105_init(chip);
-
+	ug3105_start(chip->client);
+	adc_battery_helper_resume(dev);
 	return 0;
 }
 
@@ -426,6 +210,6 @@ static struct i2c_driver ug3105_i2c_driver = {
 };
 module_i2c_driver(ug3105_i2c_driver);
 
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org");
 MODULE_DESCRIPTION("uPI uG3105 battery monitor driver");
 MODULE_LICENSE("GPL");
-- 
2.49.0


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

* [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown
  2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 1/5] power: supply: Add adc-battery-helper Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper Hans de Goede
@ 2025-07-21 12:26 ` Hans de Goede
  2025-07-26 22:42   ` Linus Walleij
  2025-07-21 12:26 ` [PATCH v3 4/5] power: supply: adc-battery-helper: Add support for optional charge_finished GPIO Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver Hans de Goede
  4 siblings, 1 reply; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

Put the fuel-gauge in standby mode when the driver is unbound and on
system shutdown.

This avoids unnecessary battery drain when the system is off.

Signed-off-by: Hans de Goede <hansg@kernel.org>
---
 drivers/power/supply/ug3105_battery.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c
index 70dd58e121e3..c4d4ac859fa4 100644
--- a/drivers/power/supply/ug3105_battery.c
+++ b/drivers/power/supply/ug3105_battery.c
@@ -206,6 +206,8 @@ static struct i2c_driver ug3105_i2c_driver = {
 		.pm = &ug3105_pm_ops,
 	},
 	.probe = ug3105_probe,
+	.remove = ug3105_stop,
+	.shutdown = ug3105_stop,
 	.id_table = ug3105_id,
 };
 module_i2c_driver(ug3105_i2c_driver);
-- 
2.49.0


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

* [PATCH v3 4/5] power: supply: adc-battery-helper: Add support for optional charge_finished GPIO
  2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
                   ` (2 preceding siblings ...)
  2025-07-21 12:26 ` [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown Hans de Goede
@ 2025-07-21 12:26 ` Hans de Goede
  2025-07-21 12:26 ` [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver Hans de Goede
  4 siblings, 0 replies; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

Charger ICs often have a status pin which indicates when the charger has
finished charging the battery. Sometimes the status of this pin can be
read over a GPIO.

Add support for optionally reading a charge-finished GPIO and when
available use this to determine when to return POWER_SUPPLY_STATUS_FULL.

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Hans de Goede <hansg@kernel.org>
---
 drivers/power/supply/adc-battery-helper.c | 18 +++++++++++++++---
 drivers/power/supply/adc-battery-helper.h |  5 ++++-
 drivers/power/supply/ug3105_battery.c     |  2 +-
 3 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c
index 919634db5472..c3e42a740496 100644
--- a/drivers/power/supply/adc-battery-helper.c
+++ b/drivers/power/supply/adc-battery-helper.c
@@ -51,6 +51,7 @@
 
 #include <linux/cleanup.h>
 #include <linux/devm-helpers.h>
+#include <linux/gpio/consumer.h>
 #include <linux/mutex.h>
 #include <linux/power_supply.h>
 #include <linux/workqueue.h>
@@ -82,8 +83,17 @@ static int adc_battery_helper_get_status(struct adc_battery_helper *help)
 	if (help->curr_ua < -CURR_HYST_UA)
 		return POWER_SUPPLY_STATUS_DISCHARGING;
 
-	if (help->supplied && help->ocv_avg_uv > full_uv)
-		return POWER_SUPPLY_STATUS_FULL;
+	if (help->supplied) {
+		bool full;
+
+		if (help->charge_finished)
+			full = gpiod_get_value_cansleep(help->charge_finished);
+		else
+			full = help->ocv_avg_uv > full_uv;
+
+		if (full)
+			return POWER_SUPPLY_STATUS_FULL;
+	}
 
 	return POWER_SUPPLY_STATUS_NOT_CHARGING;
 }
@@ -255,13 +265,15 @@ static void adc_battery_helper_start_work(struct adc_battery_helper *help)
 }
 
 int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
-			    adc_battery_helper_get_func get_voltage_and_current_now)
+			    adc_battery_helper_get_func get_voltage_and_current_now,
+			    struct gpio_desc *charge_finished_gpio)
 {
 	struct device *dev = psy->dev.parent;
 	int ret;
 
 	help->psy = psy;
 	help->get_voltage_and_current_now = get_voltage_and_current_now;
+	help->charge_finished = charge_finished_gpio;
 
 	ret = devm_mutex_init(dev, &help->lock);
 	if (ret)
diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h
index 6daba08c1a21..1876c4239729 100644
--- a/drivers/power/supply/adc-battery-helper.h
+++ b/drivers/power/supply/adc-battery-helper.h
@@ -11,6 +11,7 @@
 #define ADC_BAT_HELPER_MOV_AVG_WINDOW				8
 
 struct power_supply;
+struct gpio_desc;
 
 /*
  * The adc battery helper code needs voltage- and current-now to be sampled as
@@ -21,6 +22,7 @@ typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt,
 
 struct adc_battery_helper {
 	struct power_supply *psy;
+	struct gpio_desc *charge_finished;
 	struct delayed_work work;
 	struct mutex lock;
 	adc_battery_helper_get_func get_voltage_and_current_now;
@@ -44,7 +46,8 @@ extern const enum power_supply_property adc_battery_helper_properties[];
 #define ADC_HELPER_NUM_PROPERTIES 7
 
 int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
-			    adc_battery_helper_get_func get_voltage_and_current_now);
+			    adc_battery_helper_get_func get_voltage_and_current_now,
+			    struct gpio_desc *charge_finished_gpio);
 /*
  * The below functions can be directly used as power-supply / suspend-resume
  * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data
diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c
index c4d4ac859fa4..210e0f9aa5e0 100644
--- a/drivers/power/supply/ug3105_battery.c
+++ b/drivers/power/supply/ug3105_battery.c
@@ -161,7 +161,7 @@ static int ug3105_probe(struct i2c_client *client)
 	}
 
 	ret = adc_battery_helper_init(&chip->helper, chip->psy,
-				      ug3105_get_voltage_and_current_now);
+				      ug3105_get_voltage_and_current_now, NULL);
 	if (ret)
 		goto stop;
 
-- 
2.49.0


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

* [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver
  2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
                   ` (3 preceding siblings ...)
  2025-07-21 12:26 ` [PATCH v3 4/5] power: supply: adc-battery-helper: Add support for optional charge_finished GPIO Hans de Goede
@ 2025-07-21 12:26 ` Hans de Goede
  2025-07-26 22:45   ` Linus Walleij
  4 siblings, 1 reply; 10+ messages in thread
From: Hans de Goede @ 2025-07-21 12:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Hans de Goede, Linus Walleij, linux-pm

Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail /
Cherry Trail SoCs. One is made by X-Powers and is called the AXP288.

The AXP288's builtin charger and fuel-gauge functions are already
supported by the axp288_charger / axp288_fuel_gauge drivers.

The other "Dollar Cove" PMIC is made by TI and does not have any clear TI
denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC".

The Intel Dollar Cove TI PMIC comes with a coulomb-counters with limited
functionality which is intended to work together with an always on
micro-controller monitoring it for fuel-gauge functionality.

Most devices with the Dollar Cove TI PMIC have full-featured fuel-gauge
functionality exposed through ACPI with the information coming from either
the embedded-controller or a separate full-featured full-gauge IC.

But some designs lack this, add a battery-monitoring driver using the
PMIC's coulomb-counter combined with the adc-battery-helper for capacity
estimation for these designs.

Register definitions were taken from kernel/drivers/platform/x86/dc_ti_cc.c
from the Acer A1-840 Android kernel source-code archive named:
"App. Guide_Acer_20151221_A_A.zip"
which is distributed by Acer from the Acer A1-840 support page:
https://www.acer.com/us-en/support/product-support/A1-840/downloads

Signed-off-by: Hans de Goede <hansg@kernel.org>
---
Changes in v3:
- Switch to iio_read_channel_processed_scale() which moves the Vbat ADC
  calibration handling and scaling to the ADC driver
---
 drivers/power/supply/Kconfig               |  12 +
 drivers/power/supply/Makefile              |   1 +
 drivers/power/supply/intel_dc_ti_battery.c | 394 +++++++++++++++++++++
 3 files changed, 407 insertions(+)
 create mode 100644 drivers/power/supply/intel_dc_ti_battery.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index e33a5360d33a..72f3b2b4d346 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -247,6 +247,18 @@ config BATTERY_INGENIC
 	  This driver can also be built as a module. If so, the module will be
 	  called ingenic-battery.
 
+config BATTERY_INTEL_DC_TI
+	tristate "Intel Bay / Cherry Trail Dollar Cove TI battery driver"
+	depends on INTEL_SOC_PMIC_CHTDC_TI && INTEL_DC_TI_ADC && IIO && ACPI
+	select ADC_BATTERY_HELPER
+	help
+	  Choose this option if you want to monitor battery status on Intel
+	  Bay Trail / Cherry Trail tablets using the Dollar Cove TI PMIC's
+	  coulomb-counter as fuel-gauge.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called intel_dc_ti_battery.
+
 config BATTERY_IPAQ_MICRO
 	tristate "iPAQ Atmel Micro ASIC battery driver"
 	depends on MFD_IPAQ_MICRO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index c85c5c441e0f..51e37e8bdeb3 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_BATTERY_OLPC)	+= olpc_battery.o
 obj-$(CONFIG_BATTERY_SAMSUNG_SDI)	+= samsung-sdi-battery.o
 obj-$(CONFIG_BATTERY_COLLIE)	+= collie_battery.o
 obj-$(CONFIG_BATTERY_INGENIC)	+= ingenic-battery.o
+obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
 obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
 obj-$(CONFIG_BATTERY_WM97XX)	+= wm97xx_battery.o
 obj-$(CONFIG_BATTERY_SBS)	+= sbs-battery.o
diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c
new file mode 100644
index 000000000000..457d23b689e9
--- /dev/null
+++ b/drivers/power/supply/intel_dc_ti_battery.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC
+ *
+ * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured
+ * autonomous fuel-gauge. It is intended to work together with an always on
+ * micro-controller monitoring it.
+ *
+ * Since Linux does not monitor coulomb-counter changes while the device
+ * is off or suspended, voltage based capacity estimation from
+ * the adc-battery-helper code is used.
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ *
+ * Register definitions and calibration code was taken from
+ * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel
+ * which has the following copyright header:
+ *
+ * Copyright (C) 2014 Intel Corporation
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ *
+ * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive
+ * named: "App. Guide_Acer_20151221_A_A.zip"
+ * which is distributed by Acer from the Acer A1-840 support page:
+ * https://www.acer.com/us-en/support/product-support/A1-840/downloads
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/consumer.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/timekeeping.h>
+
+#include "adc-battery-helper.h"
+
+#define DC_TI_PMIC_VERSION_REG		0x00
+#define PMIC_VERSION_A0			0xC0
+#define PMIC_VERSION_A1			0xC1
+
+#define DC_TI_CC_CNTL_REG		0x60
+#define CC_CNTL_CC_CTR_EN		BIT(0)
+#define CC_CNTL_CC_CLR_EN		BIT(1)
+#define CC_CNTL_CC_CAL_EN		BIT(2)
+#define CC_CNTL_CC_OFFSET_EN		BIT(3)
+#define CC_CNTL_SMPL_INTVL		GENMASK(5, 4)
+#define CC_CNTL_SMPL_INTVL_15MS		FIELD_PREP(CC_CNTL_SMPL_INTVL, 0)
+#define CC_CNTL_SMPL_INTVL_62MS		FIELD_PREP(CC_CNTL_SMPL_INTVL, 1)
+#define CC_CNTL_SMPL_INTVL_125MS	FIELD_PREP(CC_CNTL_SMPL_INTVL, 2)
+#define CC_CNTL_SMPL_INTVL_250MS	FIELD_PREP(CC_CNTL_SMPL_INTVL, 3)
+
+#define DC_TI_SMPL_CTR0_REG		0x69
+#define DC_TI_SMPL_CTR1_REG		0x68
+#define DC_TI_SMPL_CTR2_REG		0x67
+
+#define DC_TI_CC_OFFSET_HI_REG		0x61
+#define CC_OFFSET_HI_MASK		0x3F
+#define DC_TI_CC_OFFSET_LO_REG		0x62
+
+#define DC_TI_SW_OFFSET_REG		0x6C
+
+#define DC_TI_CC_ACC3_REG		0x63
+#define DC_TI_CC_ACC2_REG		0x64
+#define DC_TI_CC_ACC1_REG		0x65
+#define DC_TI_CC_ACC0_REG		0x66
+
+#define DC_TI_CC_INTG1_REG		0x6A
+#define DC_TI_CC_INTG1_MASK		0x3F
+#define DC_TI_CC_INTG0_REG		0x6B
+
+#define DC_TI_EEPROM_ACCESS_CONTROL	0x88
+#define EEPROM_UNLOCK			0xDA
+#define EEPROM_LOCK			0x00
+
+#define DC_TI_EEPROM_CC_GAIN_REG	0xF4
+#define CC_TRIM_REVISION		GENMASK(3, 0)
+#define CC_GAIN_CORRECTION		GENMASK(7, 4)
+
+#define PMIC_VERSION_A0_TRIM_REV	3
+#define PMIC_VERSION_A1_MIN_TRIM_REV	1
+
+#define DC_TI_EEPROM_CC_OFFSET_REG	0xFD
+
+#define DC_TI_EEPROM_CTRL		0xFE
+#define EEPROM_BANK0_SEL		0x01
+#define EEPROM_BANK1_SEL		0x02
+
+#define SMPL_INTVL_US			15000
+#define SMPL_INTVL_MS			(SMPL_INTVL_US / USEC_PER_MSEC)
+#define CALIBRATION_TIME_US		(10 * SMPL_INTVL_US)
+#define SLEEP_SLACK_US			2500
+
+/* CC gain correction is in 0.0025 increments */
+#define CC_GAIN_STEP			25
+#define CC_GAIN_DIV			10000
+
+/* CC offset is in 0.5 units per 250ms (default sample interval) */
+#define CC_OFFSET_DIV			2
+#define CC_OFFSET_SMPL_INTVL_MS		250
+
+/* CC accumulator scale is 366.2 ųCoulumb / unit */
+#define CC_ACC_TO_UA(acc, smpl_ctr)	\
+	((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS))
+
+#define DEV_NAME			"chtdc_ti_battery"
+
+struct dc_ti_battery_chip {
+	/* Must be the first member see adc-battery-helper documentation */
+	struct adc_battery_helper helper;
+	struct device *dev;
+	struct regmap *regmap;
+	struct iio_channel *vbat_channel;
+	struct power_supply *psy;
+	int cc_gain;
+	int cc_offset;
+};
+
+static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr)
+{
+	struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy);
+	s64 cnt_start_usec, now_usec, sleep_usec;
+	unsigned int reg_val;
+	s32 acc, smpl_ctr;
+	int ret;
+
+	/*
+	 * Enable coulomb-counter before reading Vbat from ADC, so that the CC
+	 * samples are from the same time period as the Vbat reading.
+	 */
+	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN);
+	if (ret)
+		goto out_err;
+
+	cnt_start_usec = ktime_get_ns() / NSEC_PER_USEC;
+
+	/* Read Vbat, convert IIO mV to power-supply ųV */
+	ret = iio_read_channel_processed_scale(chip->vbat_channel, volt, 1000);
+	if (ret < 0)
+		goto out_err;
+
+	/* Sleep at least 3 sample-times + slack to get 3+ CC samples */
+	now_usec = ktime_get_ns() / NSEC_PER_USEC;
+	sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - (now_usec - cnt_start_usec);
+	if (sleep_usec > 0 && sleep_usec < 1000000)
+		usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US);
+
+	/*
+	 * The PMIC latches the coulomb- and sample-counters upon reading the
+	 * CC_ACC0 register. Reading multiple registers at once is not supported.
+	 *
+	 * Step 1: Read CC_ACC0 - CC_ACC3
+	 */
+	ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	acc = reg_val;
+
+	ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	acc |= reg_val << 8;
+
+	ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	acc |= reg_val << 16;
+
+	ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	acc |= reg_val << 24;
+
+	/* Step 2: Read SMPL_CTR0 - SMPL_CTR2 */
+	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	smpl_ctr = reg_val;
+
+	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	smpl_ctr |= reg_val << 8;
+
+	ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, &reg_val);
+	if (ret)
+		goto out_err;
+
+	smpl_ctr |= reg_val << 16;
+
+	/* Disable the coulumb-counter again */
+	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
+	if (ret)
+		goto out_err;
+
+	/* Apply calibration */
+	acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS /
+	       (CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV);
+	acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV;
+	*curr = CC_ACC_TO_UA(acc, smpl_ctr);
+
+	return 0;
+
+out_err:
+	dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret);
+	return ret;
+}
+
+static const struct power_supply_desc dc_ti_battery_psy_desc = {
+	.name		= "intel_dc_ti_battery",
+	.type		= POWER_SUPPLY_TYPE_BATTERY,
+	.get_property	= adc_battery_helper_get_property,
+	.external_power_changed	= adc_battery_helper_external_power_changed,
+	.properties	= adc_battery_helper_properties,
+	.num_properties	= ADC_HELPER_NUM_PROPERTIES,
+};
+
+static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip)
+{
+	u8 pmic_version, cc_trim_rev;
+	unsigned int reg_val;
+	int ret;
+
+	/* Set sample rate to 15 ms and calibrate the coulomb-counter */
+	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN |
+			   CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN);
+	if (ret)
+		goto out;
+
+	fsleep(CALIBRATION_TIME_US);
+
+	/* Disable coulomb-counter it is only used while getting the current */
+	ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG,
+			   CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, &reg_val);
+	if (ret)
+		goto out;
+
+	pmic_version = reg_val;
+
+	/*
+	 * As per the PMIC vendor (TI), the calibration offset and gain err
+	 * values are stored in EEPROM Bank 0 and Bank 1 of the PMIC.
+	 * We need to read the stored offset and gain margins and need
+	 * to apply the corrections to the raw coulomb counter value.
+	 */
+
+	/* Unlock the EEPROM Access */
+	ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK);
+	if (ret)
+		goto out;
+
+	/* Select Bank 1 to read CC GAIN Err correction */
+	ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, &reg_val);
+	if (ret)
+		goto out;
+
+	cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val);
+
+	dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d\n", pmic_version, cc_trim_rev);
+
+	if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) &&
+	    !(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) {
+		dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n");
+		goto out_relock;
+	}
+
+	chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val);
+
+	/* Select Bank 0 to read CC OFFSET Correction */
+	ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL);
+	if (ret)
+		goto out_relock;
+
+	ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, &reg_val);
+	if (ret)
+		goto out_relock;
+
+	chip->cc_offset = (s8)reg_val;
+
+	dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain);
+
+out_relock:
+	/* Re-lock the EEPROM Access */
+	regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK);
+out:
+	if (ret)
+		dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret);
+
+	return ret;
+}
+
+static int dc_ti_battery_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+	struct power_supply_config psy_cfg = {};
+	struct fwnode_reference_args args;
+	struct gpio_desc *charge_finished;
+	struct dc_ti_battery_chip *chip;
+	int ret;
+
+	/* On most devices with a Dollar Cove TI the battery is handled by ACPI */
+	if (!acpi_quirk_skip_acpi_ac_and_battery())
+		return -ENODEV;
+
+	/* ACPI glue code adds a "monitored-battery" fwnode, wait for this */
+	ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery",
+						 NULL, 0, 0, &args);
+	if (ret) {
+		dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret);
+		return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n");
+	}
+
+	fwnode_handle_put(args.fwnode);
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = dev;
+	chip->regmap = pmic->regmap;
+
+	/*
+	 * Note cannot use devm_iio_channel_get because ACPI systems lack
+	 * the device<->channel maps which iio_channel_get will uses when passed
+	 * a non NULL device pointer.
+	 */
+	chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
+	if (IS_ERR(chip->vbat_channel)) {
+		dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
+		return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
+	}
+
+	charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN);
+	if (IS_ERR(charge_finished))
+		return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n");
+
+	ret = dc_ti_battery_hw_init(chip);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, chip);
+
+	psy_cfg.drv_data = chip;
+	chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg);
+	if (IS_ERR(chip->psy))
+		return PTR_ERR(chip->psy);
+
+	return adc_battery_helper_init(&chip->helper, chip->psy,
+				       dc_ti_battery_get_voltage_and_current_now,
+				       charge_finished);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend,
+				 adc_battery_helper_resume, NULL);
+
+static struct platform_driver dc_ti_battery_driver = {
+	.driver = {
+		.name = DEV_NAME,
+		.pm = pm_sleep_ptr(&dc_ti_battery_pm_ops),
+	},
+	.probe = dc_ti_battery_probe,
+};
+module_platform_driver(dc_ti_battery_driver);
+
+MODULE_ALIAS("platform:" DEV_NAME);
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
+MODULE_LICENSE("GPL");
-- 
2.49.0


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

* Re: [PATCH v3 1/5] power: supply: Add adc-battery-helper
  2025-07-21 12:26 ` [PATCH v3 1/5] power: supply: Add adc-battery-helper Hans de Goede
@ 2025-07-26 22:40   ` Linus Walleij
  0 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2025-07-26 22:40 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Sebastian Reichel, linux-pm

On Mon, Jul 21, 2025 at 2:26 PM Hans de Goede <hansg@kernel.org> wrote:

> The TI PMIC used on some Intel Bay/Cherry Trail systems has some builtin
> fuel-gauge functionality which just like the UG3105 fuel-gauge is not
> a full featured autonomous fuel-gauge.
>
> These fuel-gauges offer accurate current and voltage measurements but
> their coulomb-counters are intended to work together with an always on
> micro-controller monitoring the fuel-gauge.
>
> Add an adc-battery-helper offering open-circuit-voltage (ocv) and through
> that capacity estimation for devices where such limited functionality
> fuel-gauges are exposed directly to Linux.
>
> This is a copy of the existing UG3105 estimating code, generalized so that
> it can be re-used in other drivers.
>
> The next commit will replace the UG3105 driver's version of this code with
> using the adc-battery-helper.
>
> The API has been designed for easy integration into existing power-supply
> drivers. For example this functionality might also be a useful addition
> to the generic-adc-battery driver.
>
> The requirement of needing the adc_battery_helper struct to be the first
> member of a battery driver's data struct is not ideal. This is a compromise
> which is necessary to allow directly using the helper's get_property(),
> external_power_changed() and suspend()/resume() functions as power-supply /
> suspend-resume callbacks.
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>

This is a useful and beautiful helper library, approved.
This makes it simple and clear what is going on.

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Small nitpicks you can fix or ignore:

> +#define ADC_BAT_HELPER_MOV_AVG_WINDOW                          8

_SIZE

> +struct adc_battery_helper {
> +       struct power_supply *psy;
> +       struct delayed_work work;
> +       struct mutex lock;
> +       adc_battery_helper_get_func get_voltage_and_current_now;
> +       int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW];              /* micro-volt */
> +       int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW];     /* milli-ohm */
> +       int poll_count;
> +       int ocv_avg_index;
> +       int ocv_avg_uv;                                         /* micro-volt */
> +       int intern_res_poll_count;
> +       int intern_res_avg_index;
> +       int intern_res_avg_mohm;                                /* milli-ohm */
> +       int volt_uv;                                            /* micro-volt */
> +       int curr_ua;                                            /* micro-ampere */

The units are pretty evident from the name I think but if you like it
this way that is fine.

> +       int capacity;                                           /* procent */

percent

Yours,
Linus Walleij

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

* Re: [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper
  2025-07-21 12:26 ` [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper Hans de Goede
@ 2025-07-26 22:41   ` Linus Walleij
  0 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2025-07-26 22:41 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Sebastian Reichel, linux-pm

On Mon, Jul 21, 2025 at 2:26 PM Hans de Goede <hansg@kernel.org> wrote:

> Switch ug3105_battery to using the new adc-battery-helper, since the
> helper's algorithms are a copy of the replaced ug3105_battery code
> this should not cause any functional differences.
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

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

* Re: [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown
  2025-07-21 12:26 ` [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown Hans de Goede
@ 2025-07-26 22:42   ` Linus Walleij
  0 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2025-07-26 22:42 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Sebastian Reichel, linux-pm

On Mon, Jul 21, 2025 at 2:26 PM Hans de Goede <hansg@kernel.org> wrote:

> Put the fuel-gauge in standby mode when the driver is unbound and on
> system shutdown.
>
> This avoids unnecessary battery drain when the system is off.
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

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

* Re: [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver
  2025-07-21 12:26 ` [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver Hans de Goede
@ 2025-07-26 22:45   ` Linus Walleij
  0 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2025-07-26 22:45 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Sebastian Reichel, linux-pm

On Mon, Jul 21, 2025 at 2:26 PM Hans de Goede <hansg@kernel.org> wrote:

> Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail /
> Cherry Trail SoCs. One is made by X-Powers and is called the AXP288.
>
> The AXP288's builtin charger and fuel-gauge functions are already
> supported by the axp288_charger / axp288_fuel_gauge drivers.
>
> The other "Dollar Cove" PMIC is made by TI and does not have any clear TI
> denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC".
>
> The Intel Dollar Cove TI PMIC comes with a coulomb-counters with limited
> functionality which is intended to work together with an always on
> micro-controller monitoring it for fuel-gauge functionality.
>
> Most devices with the Dollar Cove TI PMIC have full-featured fuel-gauge
> functionality exposed through ACPI with the information coming from either
> the embedded-controller or a separate full-featured full-gauge IC.
>
> But some designs lack this, add a battery-monitoring driver using the
> PMIC's coulomb-counter combined with the adc-battery-helper for capacity
> estimation for these designs.
>
> Register definitions were taken from kernel/drivers/platform/x86/dc_ti_cc.c
> from the Acer A1-840 Android kernel source-code archive named:
> "App. Guide_Acer_20151221_A_A.zip"
> which is distributed by Acer from the Acer A1-840 support page:
> https://www.acer.com/us-en/support/product-support/A1-840/downloads
>
> Signed-off-by: Hans de Goede <hansg@kernel.org>

Nice, looking good!
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

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

end of thread, other threads:[~2025-07-26 22:45 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-21 12:25 [PATCH v3 0/5] power: supply: Add adc-battery-helper lib and Intel Dollar Cove TI CC battery driver Hans de Goede
2025-07-21 12:26 ` [PATCH v3 1/5] power: supply: Add adc-battery-helper Hans de Goede
2025-07-26 22:40   ` Linus Walleij
2025-07-21 12:26 ` [PATCH v3 2/5] power: supply: ug3105_battery: Switch to adc-battery-helper Hans de Goede
2025-07-26 22:41   ` Linus Walleij
2025-07-21 12:26 ` [PATCH v3 3/5] power: supply: ug3105_battery: Put FG in standby on remove and shutdown Hans de Goede
2025-07-26 22:42   ` Linus Walleij
2025-07-21 12:26 ` [PATCH v3 4/5] power: supply: adc-battery-helper: Add support for optional charge_finished GPIO Hans de Goede
2025-07-21 12:26 ` [PATCH v3 5/5] power: supply: Add new Intel Dollar Cove TI battery driver Hans de Goede
2025-07-26 22:45   ` Linus Walleij

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