xref: /linux/drivers/power/supply/adc-battery-helper.c (revision 4f38da1f027ea2c9f01bb71daa7a299c191b6940)
16a93f543SHans de Goede // SPDX-License-Identifier: GPL-2.0+
26a93f543SHans de Goede /*
36a93f543SHans de Goede  * Helper for batteries with accurate current and voltage measurement, but
46a93f543SHans de Goede  * without temperature measurement or without a "resistance-temp-table".
56a93f543SHans de Goede  *
66a93f543SHans de Goede  * Some fuel-gauges are not full-featured autonomous fuel-gauges.
76a93f543SHans de Goede  * These fuel-gauges offer accurate current and voltage measurements but
86a93f543SHans de Goede  * their coulomb-counters are intended to work together with an always on
96a93f543SHans de Goede  * micro-controller monitoring the fuel-gauge.
106a93f543SHans de Goede  *
116a93f543SHans de Goede  * This adc-battery-helper code offers open-circuit-voltage (ocv) and through
126a93f543SHans de Goede  * that capacity estimation for devices where such limited functionality
136a93f543SHans de Goede  * fuel-gauges are exposed directly to Linux.
146a93f543SHans de Goede  *
156a93f543SHans de Goede  * This helper requires the hw to provide accurate battery current_now and
166a93f543SHans de Goede  * voltage_now measurement and this helper the provides the following properties
176a93f543SHans de Goede  * based on top of those readings:
186a93f543SHans de Goede  *
196a93f543SHans de Goede  *	POWER_SUPPLY_PROP_STATUS
206a93f543SHans de Goede  *	POWER_SUPPLY_PROP_VOLTAGE_OCV
216a93f543SHans de Goede  *	POWER_SUPPLY_PROP_VOLTAGE_NOW
226a93f543SHans de Goede  *	POWER_SUPPLY_PROP_CURRENT_NOW
236a93f543SHans de Goede  *	POWER_SUPPLY_PROP_CAPACITY
246a93f543SHans de Goede  *
256a93f543SHans de Goede  * As well as optional the following properties assuming an always present
266a93f543SHans de Goede  * system-scope battery, allowing direct use of adc_battery_helper_get_prop()
276a93f543SHans de Goede  * in this common case:
286a93f543SHans de Goede  *	POWER_SUPPLY_PROP_PRESENT
296a93f543SHans de Goede  *	POWER_SUPPLY_PROP_SCOPE
306a93f543SHans de Goede  *
316a93f543SHans de Goede  * Using this helper is as simple as:
326a93f543SHans de Goede  *
336a93f543SHans de Goede  * 1. Embed a struct adc_battery_helper this MUST be the first member of
346a93f543SHans de Goede  *    the battery driver's data struct.
356a93f543SHans de Goede  * 2. Use adc_battery_helper_props[] or add the above properties to
366a93f543SHans de Goede  *    the list of properties in power_supply_desc
376a93f543SHans de Goede  * 3. Call adc_battery_helper_init() after registering the power_supply and
386a93f543SHans de Goede  *    before returning from the probe() function
396a93f543SHans de Goede  * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property()
406a93f543SHans de Goede  *    method, or call it for the above properties.
416a93f543SHans de Goede  * 5. Use adc_battery_helper_external_power_changed() as the power-supply's
426a93f543SHans de Goede  *    external_power_changed() method or call it from that method.
436a93f543SHans de Goede  * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or
446a93f543SHans de Goede  *    call them from the driver's suspend-resume methods.
456a93f543SHans de Goede  *
466a93f543SHans de Goede  * The provided get_voltage_and_current_now() method will be called by this
476a93f543SHans de Goede  * helper at adc_battery_helper_init() time and later.
486a93f543SHans de Goede  *
496a93f543SHans de Goede  * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
506a93f543SHans de Goede  */
516a93f543SHans de Goede 
526a93f543SHans de Goede #include <linux/cleanup.h>
536a93f543SHans de Goede #include <linux/devm-helpers.h>
54926b1443SHans de Goede #include <linux/gpio/consumer.h>
556a93f543SHans de Goede #include <linux/mutex.h>
566a93f543SHans de Goede #include <linux/power_supply.h>
576a93f543SHans de Goede #include <linux/workqueue.h>
586a93f543SHans de Goede 
596a93f543SHans de Goede #include "adc-battery-helper.h"
606a93f543SHans de Goede 
616a93f543SHans de Goede #define MOV_AVG_WINDOW_SIZE			ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE
626a93f543SHans de Goede #define INIT_POLL_TIME				(5 * HZ)
636a93f543SHans de Goede #define POLL_TIME				(30 * HZ)
646a93f543SHans de Goede #define SETTLE_TIME				(1 * HZ)
656a93f543SHans de Goede 
666a93f543SHans de Goede #define INIT_POLL_COUNT				30
676a93f543SHans de Goede 
686a93f543SHans de Goede #define CURR_HYST_UA				65000
696a93f543SHans de Goede 
706a93f543SHans de Goede #define LOW_BAT_UV				3700000
716a93f543SHans de Goede #define FULL_BAT_HYST_UV			38000
726a93f543SHans de Goede 
736a93f543SHans de Goede #define AMBIENT_TEMP_CELSIUS			25
746a93f543SHans de Goede 
756a93f543SHans de Goede static int adc_battery_helper_get_status(struct adc_battery_helper *help)
766a93f543SHans de Goede {
776a93f543SHans de Goede 	int full_uv =
786a93f543SHans de Goede 		help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV;
796a93f543SHans de Goede 
806a93f543SHans de Goede 	if (help->curr_ua > CURR_HYST_UA)
816a93f543SHans de Goede 		return POWER_SUPPLY_STATUS_CHARGING;
826a93f543SHans de Goede 
836a93f543SHans de Goede 	if (help->curr_ua < -CURR_HYST_UA)
846a93f543SHans de Goede 		return POWER_SUPPLY_STATUS_DISCHARGING;
856a93f543SHans de Goede 
86926b1443SHans de Goede 	if (help->supplied) {
87926b1443SHans de Goede 		bool full;
88926b1443SHans de Goede 
89926b1443SHans de Goede 		if (help->charge_finished)
90926b1443SHans de Goede 			full = gpiod_get_value_cansleep(help->charge_finished);
91926b1443SHans de Goede 		else
92926b1443SHans de Goede 			full = help->ocv_avg_uv > full_uv;
93926b1443SHans de Goede 
94926b1443SHans de Goede 		if (full)
956a93f543SHans de Goede 			return POWER_SUPPLY_STATUS_FULL;
96926b1443SHans de Goede 	}
976a93f543SHans de Goede 
986a93f543SHans de Goede 	return POWER_SUPPLY_STATUS_NOT_CHARGING;
996a93f543SHans de Goede }
1006a93f543SHans de Goede 
1016a93f543SHans de Goede static void adc_battery_helper_work(struct work_struct *work)
1026a93f543SHans de Goede {
1036a93f543SHans de Goede 	struct adc_battery_helper *help = container_of(work, struct adc_battery_helper,
1046a93f543SHans de Goede 						       work.work);
1056a93f543SHans de Goede 	int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size;
1066a93f543SHans de Goede 	struct device *dev = help->psy->dev.parent;
1076a93f543SHans de Goede 	int volt_uv, prev_volt_uv = help->volt_uv;
1086a93f543SHans de Goede 	int curr_ua, prev_curr_ua = help->curr_ua;
1096a93f543SHans de Goede 	bool prev_supplied = help->supplied;
1106a93f543SHans de Goede 	int prev_status = help->status;
1116a93f543SHans de Goede 
1126a93f543SHans de Goede 	guard(mutex)(&help->lock);
1136a93f543SHans de Goede 
1146a93f543SHans de Goede 	ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua);
1156a93f543SHans de Goede 	if (ret)
1166a93f543SHans de Goede 		goto out;
1176a93f543SHans de Goede 
1186a93f543SHans de Goede 	help->volt_uv = volt_uv;
1196a93f543SHans de Goede 	help->curr_ua = curr_ua;
1206a93f543SHans de Goede 
1216a93f543SHans de Goede 	help->ocv_uv[help->ocv_avg_index] =
1226a93f543SHans de Goede 		help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000;
1236a93f543SHans de Goede 	dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n",
1246a93f543SHans de Goede 		help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]);
1256a93f543SHans de Goede 	help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
1266a93f543SHans de Goede 	help->poll_count++;
1276a93f543SHans de Goede 
1286a93f543SHans de Goede 	help->ocv_avg_uv = 0;
1296a93f543SHans de Goede 	win_size = min(help->poll_count, MOV_AVG_WINDOW_SIZE);
1306a93f543SHans de Goede 	for (i = 0; i < win_size; i++)
1316a93f543SHans de Goede 		help->ocv_avg_uv += help->ocv_uv[i];
1326a93f543SHans de Goede 	help->ocv_avg_uv /= win_size;
1336a93f543SHans de Goede 
1346a93f543SHans de Goede 	help->supplied = power_supply_am_i_supplied(help->psy);
1356a93f543SHans de Goede 	help->status = adc_battery_helper_get_status(help);
1366a93f543SHans de Goede 	if (help->status == POWER_SUPPLY_STATUS_FULL)
1376a93f543SHans de Goede 		help->capacity = 100;
1386a93f543SHans de Goede 	else
1396a93f543SHans de Goede 		help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info,
1406a93f543SHans de Goede 							      help->ocv_avg_uv,
1416a93f543SHans de Goede 							      AMBIENT_TEMP_CELSIUS);
1426a93f543SHans de Goede 
1436a93f543SHans de Goede 	/*
1446a93f543SHans de Goede 	 * Skip internal resistance calc on charger [un]plug and
1456a93f543SHans de Goede 	 * when the battery is almost empty (voltage low).
1466a93f543SHans de Goede 	 */
1476a93f543SHans de Goede 	if (help->supplied != prev_supplied ||
1486a93f543SHans de Goede 	    help->volt_uv < LOW_BAT_UV ||
1496a93f543SHans de Goede 	    help->poll_count < 2)
1506a93f543SHans de Goede 		goto out;
1516a93f543SHans de Goede 
1526a93f543SHans de Goede 	/*
1536a93f543SHans de Goede 	 * Assuming that the OCV voltage does not change significantly
1546a93f543SHans de Goede 	 * between 2 polls, then we can calculate the internal resistance
1556a93f543SHans de Goede 	 * on a significant current change by attributing all voltage
1566a93f543SHans de Goede 	 * change between the 2 readings to the internal resistance.
1576a93f543SHans de Goede 	 */
1586a93f543SHans de Goede 	curr_diff_ua = abs(help->curr_ua - prev_curr_ua);
1596a93f543SHans de Goede 	if (curr_diff_ua < CURR_HYST_UA)
1606a93f543SHans de Goede 		goto out;
1616a93f543SHans de Goede 
1626a93f543SHans de Goede 	volt_diff_uv = abs(help->volt_uv - prev_volt_uv);
1636a93f543SHans de Goede 	res_mohm = volt_diff_uv * 1000 / curr_diff_ua;
1646a93f543SHans de Goede 
1656a93f543SHans de Goede 	if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) ||
1666a93f543SHans de Goede 	    (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) {
1676a93f543SHans de Goede 		dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm);
1686a93f543SHans de Goede 		goto out;
1696a93f543SHans de Goede 	}
1706a93f543SHans de Goede 
1716a93f543SHans de Goede 	dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm);
1726a93f543SHans de Goede 
1736a93f543SHans de Goede 	help->intern_res_mohm[help->intern_res_avg_index] = res_mohm;
1746a93f543SHans de Goede 	help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
1756a93f543SHans de Goede 	help->intern_res_poll_count++;
1766a93f543SHans de Goede 
1776a93f543SHans de Goede 	help->intern_res_avg_mohm = 0;
1786a93f543SHans de Goede 	win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW_SIZE);
1796a93f543SHans de Goede 	for (i = 0; i < win_size; i++)
1806a93f543SHans de Goede 		help->intern_res_avg_mohm += help->intern_res_mohm[i];
1816a93f543SHans de Goede 	help->intern_res_avg_mohm /= win_size;
1826a93f543SHans de Goede 
1836a93f543SHans de Goede out:
184*c4a7748bSMarco Crivellari 	queue_delayed_work(system_percpu_wq, &help->work,
1856a93f543SHans de Goede 			   (help->poll_count <= INIT_POLL_COUNT) ?
1866a93f543SHans de Goede 					INIT_POLL_TIME : POLL_TIME);
1876a93f543SHans de Goede 
1886a93f543SHans de Goede 	if (help->status != prev_status)
1896a93f543SHans de Goede 		power_supply_changed(help->psy);
1906a93f543SHans de Goede }
1916a93f543SHans de Goede 
1926a93f543SHans de Goede const enum power_supply_property adc_battery_helper_properties[] = {
1936a93f543SHans de Goede 	POWER_SUPPLY_PROP_STATUS,
1946a93f543SHans de Goede 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
1956a93f543SHans de Goede 	POWER_SUPPLY_PROP_VOLTAGE_OCV,
1966a93f543SHans de Goede 	POWER_SUPPLY_PROP_CURRENT_NOW,
1976a93f543SHans de Goede 	POWER_SUPPLY_PROP_CAPACITY,
1986a93f543SHans de Goede 	POWER_SUPPLY_PROP_PRESENT,
1996a93f543SHans de Goede 	POWER_SUPPLY_PROP_SCOPE,
2006a93f543SHans de Goede };
2016a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_properties);
2026a93f543SHans de Goede 
2036a93f543SHans de Goede static_assert(ARRAY_SIZE(adc_battery_helper_properties) ==
2046a93f543SHans de Goede 	      ADC_HELPER_NUM_PROPERTIES);
2056a93f543SHans de Goede 
2066a93f543SHans de Goede int adc_battery_helper_get_property(struct power_supply *psy,
2076a93f543SHans de Goede 				    enum power_supply_property psp,
2086a93f543SHans de Goede 				    union power_supply_propval *val)
2096a93f543SHans de Goede {
2106a93f543SHans de Goede 	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
2116a93f543SHans de Goede 	int dummy, ret = 0;
2126a93f543SHans de Goede 
2136a93f543SHans de Goede 	/*
2146a93f543SHans de Goede 	 * Avoid racing with adc_battery_helper_work() while it is updating
2156a93f543SHans de Goede 	 * variables and avoid calling get_voltage_and_current_now() reentrantly.
2166a93f543SHans de Goede 	 */
2176a93f543SHans de Goede 	guard(mutex)(&help->lock);
2186a93f543SHans de Goede 
2196a93f543SHans de Goede 	switch (psp) {
2206a93f543SHans de Goede 	case POWER_SUPPLY_PROP_STATUS:
2216a93f543SHans de Goede 		val->intval = help->status;
2226a93f543SHans de Goede 		break;
2236a93f543SHans de Goede 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
2246a93f543SHans de Goede 		ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy);
2256a93f543SHans de Goede 		break;
2266a93f543SHans de Goede 	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
2276a93f543SHans de Goede 		val->intval = help->ocv_avg_uv;
2286a93f543SHans de Goede 		break;
2296a93f543SHans de Goede 	case POWER_SUPPLY_PROP_CURRENT_NOW:
2306a93f543SHans de Goede 		ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval);
2316a93f543SHans de Goede 		break;
2326a93f543SHans de Goede 	case POWER_SUPPLY_PROP_CAPACITY:
2336a93f543SHans de Goede 		val->intval = help->capacity;
2346a93f543SHans de Goede 		break;
2356a93f543SHans de Goede 	case POWER_SUPPLY_PROP_PRESENT:
2366a93f543SHans de Goede 		val->intval = 1;
2376a93f543SHans de Goede 		break;
2386a93f543SHans de Goede 	case POWER_SUPPLY_PROP_SCOPE:
2396a93f543SHans de Goede 		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
2406a93f543SHans de Goede 		break;
2416a93f543SHans de Goede 	default:
2426a93f543SHans de Goede 		return -EINVAL;
2436a93f543SHans de Goede 	}
2446a93f543SHans de Goede 
2456a93f543SHans de Goede 	return ret;
2466a93f543SHans de Goede }
2476a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_get_property);
2486a93f543SHans de Goede 
2496a93f543SHans de Goede void adc_battery_helper_external_power_changed(struct power_supply *psy)
2506a93f543SHans de Goede {
2516a93f543SHans de Goede 	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
2526a93f543SHans de Goede 
2536a93f543SHans de Goede 	dev_dbg(help->psy->dev.parent, "external power changed\n");
254*c4a7748bSMarco Crivellari 	mod_delayed_work(system_percpu_wq, &help->work, SETTLE_TIME);
2556a93f543SHans de Goede }
2566a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed);
2576a93f543SHans de Goede 
2586a93f543SHans de Goede static void adc_battery_helper_start_work(struct adc_battery_helper *help)
2596a93f543SHans de Goede {
2606a93f543SHans de Goede 	help->poll_count = 0;
2616a93f543SHans de Goede 	help->ocv_avg_index = 0;
2626a93f543SHans de Goede 
263*c4a7748bSMarco Crivellari 	queue_delayed_work(system_percpu_wq, &help->work, 0);
2646a93f543SHans de Goede 	flush_delayed_work(&help->work);
2656a93f543SHans de Goede }
2666a93f543SHans de Goede 
2676a93f543SHans de Goede int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
268926b1443SHans de Goede 			    adc_battery_helper_get_func get_voltage_and_current_now,
269926b1443SHans de Goede 			    struct gpio_desc *charge_finished_gpio)
2706a93f543SHans de Goede {
2716a93f543SHans de Goede 	struct device *dev = psy->dev.parent;
2726a93f543SHans de Goede 	int ret;
2736a93f543SHans de Goede 
2746a93f543SHans de Goede 	help->psy = psy;
2756a93f543SHans de Goede 	help->get_voltage_and_current_now = get_voltage_and_current_now;
276926b1443SHans de Goede 	help->charge_finished = charge_finished_gpio;
2776a93f543SHans de Goede 
2786a93f543SHans de Goede 	ret = devm_mutex_init(dev, &help->lock);
2796a93f543SHans de Goede 	if (ret)
2806a93f543SHans de Goede 		return ret;
2816a93f543SHans de Goede 
2826a93f543SHans de Goede 	ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work);
2836a93f543SHans de Goede 	if (ret)
2846a93f543SHans de Goede 		return ret;
2856a93f543SHans de Goede 
2866a93f543SHans de Goede 	if (!help->psy->battery_info ||
2876a93f543SHans de Goede 	    help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
2886a93f543SHans de Goede 	    help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
2896a93f543SHans de Goede 	    !psy->battery_info->ocv_table[0]) {
2906a93f543SHans de Goede 		dev_err(dev, "error required properties are missing\n");
2916a93f543SHans de Goede 		return -ENODEV;
2926a93f543SHans de Goede 	}
2936a93f543SHans de Goede 
2946a93f543SHans de Goede 	/* Use provided internal resistance as start point (in milli-ohm) */
2956a93f543SHans de Goede 	help->intern_res_avg_mohm =
2966a93f543SHans de Goede 		help->psy->battery_info->factory_internal_resistance_uohm / 1000;
2976a93f543SHans de Goede 	/* Also add it to the internal resistance moving average window */
2986a93f543SHans de Goede 	help->intern_res_mohm[0] = help->intern_res_avg_mohm;
2996a93f543SHans de Goede 	help->intern_res_avg_index = 1;
3006a93f543SHans de Goede 	help->intern_res_poll_count = 1;
3016a93f543SHans de Goede 
3026a93f543SHans de Goede 	adc_battery_helper_start_work(help);
3036a93f543SHans de Goede 	return 0;
3046a93f543SHans de Goede }
3056a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_init);
3066a93f543SHans de Goede 
3076a93f543SHans de Goede int adc_battery_helper_suspend(struct device *dev)
3086a93f543SHans de Goede {
3096a93f543SHans de Goede 	struct adc_battery_helper *help = dev_get_drvdata(dev);
3106a93f543SHans de Goede 
3116a93f543SHans de Goede 	cancel_delayed_work_sync(&help->work);
3126a93f543SHans de Goede 	return 0;
3136a93f543SHans de Goede }
3146a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_suspend);
3156a93f543SHans de Goede 
3166a93f543SHans de Goede int adc_battery_helper_resume(struct device *dev)
3176a93f543SHans de Goede {
3186a93f543SHans de Goede 	struct adc_battery_helper *help = dev_get_drvdata(dev);
3196a93f543SHans de Goede 
3206a93f543SHans de Goede 	adc_battery_helper_start_work(help);
3216a93f543SHans de Goede 	return 0;
3226a93f543SHans de Goede }
3236a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_resume);
3246a93f543SHans de Goede 
3256a93f543SHans de Goede MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
3266a93f543SHans de Goede MODULE_DESCRIPTION("ADC battery capacity estimation helper");
3276a93f543SHans de Goede MODULE_LICENSE("GPL");
328