xref: /linux/drivers/power/supply/adc-battery-helper.c (revision 6a93f54333979c2948e9c1e71ea0b377b486a3f5)
1*6a93f543SHans de Goede // SPDX-License-Identifier: GPL-2.0+
2*6a93f543SHans de Goede /*
3*6a93f543SHans de Goede  * Helper for batteries with accurate current and voltage measurement, but
4*6a93f543SHans de Goede  * without temperature measurement or without a "resistance-temp-table".
5*6a93f543SHans de Goede  *
6*6a93f543SHans de Goede  * Some fuel-gauges are not full-featured autonomous fuel-gauges.
7*6a93f543SHans de Goede  * These fuel-gauges offer accurate current and voltage measurements but
8*6a93f543SHans de Goede  * their coulomb-counters are intended to work together with an always on
9*6a93f543SHans de Goede  * micro-controller monitoring the fuel-gauge.
10*6a93f543SHans de Goede  *
11*6a93f543SHans de Goede  * This adc-battery-helper code offers open-circuit-voltage (ocv) and through
12*6a93f543SHans de Goede  * that capacity estimation for devices where such limited functionality
13*6a93f543SHans de Goede  * fuel-gauges are exposed directly to Linux.
14*6a93f543SHans de Goede  *
15*6a93f543SHans de Goede  * This helper requires the hw to provide accurate battery current_now and
16*6a93f543SHans de Goede  * voltage_now measurement and this helper the provides the following properties
17*6a93f543SHans de Goede  * based on top of those readings:
18*6a93f543SHans de Goede  *
19*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_STATUS
20*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_VOLTAGE_OCV
21*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_VOLTAGE_NOW
22*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_CURRENT_NOW
23*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_CAPACITY
24*6a93f543SHans de Goede  *
25*6a93f543SHans de Goede  * As well as optional the following properties assuming an always present
26*6a93f543SHans de Goede  * system-scope battery, allowing direct use of adc_battery_helper_get_prop()
27*6a93f543SHans de Goede  * in this common case:
28*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_PRESENT
29*6a93f543SHans de Goede  *	POWER_SUPPLY_PROP_SCOPE
30*6a93f543SHans de Goede  *
31*6a93f543SHans de Goede  * Using this helper is as simple as:
32*6a93f543SHans de Goede  *
33*6a93f543SHans de Goede  * 1. Embed a struct adc_battery_helper this MUST be the first member of
34*6a93f543SHans de Goede  *    the battery driver's data struct.
35*6a93f543SHans de Goede  * 2. Use adc_battery_helper_props[] or add the above properties to
36*6a93f543SHans de Goede  *    the list of properties in power_supply_desc
37*6a93f543SHans de Goede  * 3. Call adc_battery_helper_init() after registering the power_supply and
38*6a93f543SHans de Goede  *    before returning from the probe() function
39*6a93f543SHans de Goede  * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property()
40*6a93f543SHans de Goede  *    method, or call it for the above properties.
41*6a93f543SHans de Goede  * 5. Use adc_battery_helper_external_power_changed() as the power-supply's
42*6a93f543SHans de Goede  *    external_power_changed() method or call it from that method.
43*6a93f543SHans de Goede  * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or
44*6a93f543SHans de Goede  *    call them from the driver's suspend-resume methods.
45*6a93f543SHans de Goede  *
46*6a93f543SHans de Goede  * The provided get_voltage_and_current_now() method will be called by this
47*6a93f543SHans de Goede  * helper at adc_battery_helper_init() time and later.
48*6a93f543SHans de Goede  *
49*6a93f543SHans de Goede  * Copyright (c) 2021-2025 Hans de Goede <hansg@kernel.org>
50*6a93f543SHans de Goede  */
51*6a93f543SHans de Goede 
52*6a93f543SHans de Goede #include <linux/cleanup.h>
53*6a93f543SHans de Goede #include <linux/devm-helpers.h>
54*6a93f543SHans de Goede #include <linux/mutex.h>
55*6a93f543SHans de Goede #include <linux/power_supply.h>
56*6a93f543SHans de Goede #include <linux/workqueue.h>
57*6a93f543SHans de Goede 
58*6a93f543SHans de Goede #include "adc-battery-helper.h"
59*6a93f543SHans de Goede 
60*6a93f543SHans de Goede #define MOV_AVG_WINDOW_SIZE			ADC_BAT_HELPER_MOV_AVG_WINDOW_SIZE
61*6a93f543SHans de Goede #define INIT_POLL_TIME				(5 * HZ)
62*6a93f543SHans de Goede #define POLL_TIME				(30 * HZ)
63*6a93f543SHans de Goede #define SETTLE_TIME				(1 * HZ)
64*6a93f543SHans de Goede 
65*6a93f543SHans de Goede #define INIT_POLL_COUNT				30
66*6a93f543SHans de Goede 
67*6a93f543SHans de Goede #define CURR_HYST_UA				65000
68*6a93f543SHans de Goede 
69*6a93f543SHans de Goede #define LOW_BAT_UV				3700000
70*6a93f543SHans de Goede #define FULL_BAT_HYST_UV			38000
71*6a93f543SHans de Goede 
72*6a93f543SHans de Goede #define AMBIENT_TEMP_CELSIUS			25
73*6a93f543SHans de Goede 
74*6a93f543SHans de Goede static int adc_battery_helper_get_status(struct adc_battery_helper *help)
75*6a93f543SHans de Goede {
76*6a93f543SHans de Goede 	int full_uv =
77*6a93f543SHans de Goede 		help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV;
78*6a93f543SHans de Goede 
79*6a93f543SHans de Goede 	if (help->curr_ua > CURR_HYST_UA)
80*6a93f543SHans de Goede 		return POWER_SUPPLY_STATUS_CHARGING;
81*6a93f543SHans de Goede 
82*6a93f543SHans de Goede 	if (help->curr_ua < -CURR_HYST_UA)
83*6a93f543SHans de Goede 		return POWER_SUPPLY_STATUS_DISCHARGING;
84*6a93f543SHans de Goede 
85*6a93f543SHans de Goede 	if (help->supplied && help->ocv_avg_uv > full_uv)
86*6a93f543SHans de Goede 		return POWER_SUPPLY_STATUS_FULL;
87*6a93f543SHans de Goede 
88*6a93f543SHans de Goede 	return POWER_SUPPLY_STATUS_NOT_CHARGING;
89*6a93f543SHans de Goede }
90*6a93f543SHans de Goede 
91*6a93f543SHans de Goede static void adc_battery_helper_work(struct work_struct *work)
92*6a93f543SHans de Goede {
93*6a93f543SHans de Goede 	struct adc_battery_helper *help = container_of(work, struct adc_battery_helper,
94*6a93f543SHans de Goede 						       work.work);
95*6a93f543SHans de Goede 	int i, curr_diff_ua, volt_diff_uv, res_mohm, ret, win_size;
96*6a93f543SHans de Goede 	struct device *dev = help->psy->dev.parent;
97*6a93f543SHans de Goede 	int volt_uv, prev_volt_uv = help->volt_uv;
98*6a93f543SHans de Goede 	int curr_ua, prev_curr_ua = help->curr_ua;
99*6a93f543SHans de Goede 	bool prev_supplied = help->supplied;
100*6a93f543SHans de Goede 	int prev_status = help->status;
101*6a93f543SHans de Goede 
102*6a93f543SHans de Goede 	guard(mutex)(&help->lock);
103*6a93f543SHans de Goede 
104*6a93f543SHans de Goede 	ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua);
105*6a93f543SHans de Goede 	if (ret)
106*6a93f543SHans de Goede 		goto out;
107*6a93f543SHans de Goede 
108*6a93f543SHans de Goede 	help->volt_uv = volt_uv;
109*6a93f543SHans de Goede 	help->curr_ua = curr_ua;
110*6a93f543SHans de Goede 
111*6a93f543SHans de Goede 	help->ocv_uv[help->ocv_avg_index] =
112*6a93f543SHans de Goede 		help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000;
113*6a93f543SHans de Goede 	dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n",
114*6a93f543SHans de Goede 		help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]);
115*6a93f543SHans de Goede 	help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
116*6a93f543SHans de Goede 	help->poll_count++;
117*6a93f543SHans de Goede 
118*6a93f543SHans de Goede 	help->ocv_avg_uv = 0;
119*6a93f543SHans de Goede 	win_size = min(help->poll_count, MOV_AVG_WINDOW_SIZE);
120*6a93f543SHans de Goede 	for (i = 0; i < win_size; i++)
121*6a93f543SHans de Goede 		help->ocv_avg_uv += help->ocv_uv[i];
122*6a93f543SHans de Goede 	help->ocv_avg_uv /= win_size;
123*6a93f543SHans de Goede 
124*6a93f543SHans de Goede 	help->supplied = power_supply_am_i_supplied(help->psy);
125*6a93f543SHans de Goede 	help->status = adc_battery_helper_get_status(help);
126*6a93f543SHans de Goede 	if (help->status == POWER_SUPPLY_STATUS_FULL)
127*6a93f543SHans de Goede 		help->capacity = 100;
128*6a93f543SHans de Goede 	else
129*6a93f543SHans de Goede 		help->capacity = power_supply_batinfo_ocv2cap(help->psy->battery_info,
130*6a93f543SHans de Goede 							      help->ocv_avg_uv,
131*6a93f543SHans de Goede 							      AMBIENT_TEMP_CELSIUS);
132*6a93f543SHans de Goede 
133*6a93f543SHans de Goede 	/*
134*6a93f543SHans de Goede 	 * Skip internal resistance calc on charger [un]plug and
135*6a93f543SHans de Goede 	 * when the battery is almost empty (voltage low).
136*6a93f543SHans de Goede 	 */
137*6a93f543SHans de Goede 	if (help->supplied != prev_supplied ||
138*6a93f543SHans de Goede 	    help->volt_uv < LOW_BAT_UV ||
139*6a93f543SHans de Goede 	    help->poll_count < 2)
140*6a93f543SHans de Goede 		goto out;
141*6a93f543SHans de Goede 
142*6a93f543SHans de Goede 	/*
143*6a93f543SHans de Goede 	 * Assuming that the OCV voltage does not change significantly
144*6a93f543SHans de Goede 	 * between 2 polls, then we can calculate the internal resistance
145*6a93f543SHans de Goede 	 * on a significant current change by attributing all voltage
146*6a93f543SHans de Goede 	 * change between the 2 readings to the internal resistance.
147*6a93f543SHans de Goede 	 */
148*6a93f543SHans de Goede 	curr_diff_ua = abs(help->curr_ua - prev_curr_ua);
149*6a93f543SHans de Goede 	if (curr_diff_ua < CURR_HYST_UA)
150*6a93f543SHans de Goede 		goto out;
151*6a93f543SHans de Goede 
152*6a93f543SHans de Goede 	volt_diff_uv = abs(help->volt_uv - prev_volt_uv);
153*6a93f543SHans de Goede 	res_mohm = volt_diff_uv * 1000 / curr_diff_ua;
154*6a93f543SHans de Goede 
155*6a93f543SHans de Goede 	if ((res_mohm < (help->intern_res_avg_mohm * 2 / 3)) ||
156*6a93f543SHans de Goede 	    (res_mohm > (help->intern_res_avg_mohm * 4 / 3))) {
157*6a93f543SHans de Goede 		dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res_mohm);
158*6a93f543SHans de Goede 		goto out;
159*6a93f543SHans de Goede 	}
160*6a93f543SHans de Goede 
161*6a93f543SHans de Goede 	dev_dbg(dev, "Internal resistance %d mOhm\n", res_mohm);
162*6a93f543SHans de Goede 
163*6a93f543SHans de Goede 	help->intern_res_mohm[help->intern_res_avg_index] = res_mohm;
164*6a93f543SHans de Goede 	help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW_SIZE;
165*6a93f543SHans de Goede 	help->intern_res_poll_count++;
166*6a93f543SHans de Goede 
167*6a93f543SHans de Goede 	help->intern_res_avg_mohm = 0;
168*6a93f543SHans de Goede 	win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW_SIZE);
169*6a93f543SHans de Goede 	for (i = 0; i < win_size; i++)
170*6a93f543SHans de Goede 		help->intern_res_avg_mohm += help->intern_res_mohm[i];
171*6a93f543SHans de Goede 	help->intern_res_avg_mohm /= win_size;
172*6a93f543SHans de Goede 
173*6a93f543SHans de Goede out:
174*6a93f543SHans de Goede 	queue_delayed_work(system_wq, &help->work,
175*6a93f543SHans de Goede 			   (help->poll_count <= INIT_POLL_COUNT) ?
176*6a93f543SHans de Goede 					INIT_POLL_TIME : POLL_TIME);
177*6a93f543SHans de Goede 
178*6a93f543SHans de Goede 	if (help->status != prev_status)
179*6a93f543SHans de Goede 		power_supply_changed(help->psy);
180*6a93f543SHans de Goede }
181*6a93f543SHans de Goede 
182*6a93f543SHans de Goede const enum power_supply_property adc_battery_helper_properties[] = {
183*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_STATUS,
184*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
185*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_VOLTAGE_OCV,
186*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_CURRENT_NOW,
187*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_CAPACITY,
188*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_PRESENT,
189*6a93f543SHans de Goede 	POWER_SUPPLY_PROP_SCOPE,
190*6a93f543SHans de Goede };
191*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_properties);
192*6a93f543SHans de Goede 
193*6a93f543SHans de Goede static_assert(ARRAY_SIZE(adc_battery_helper_properties) ==
194*6a93f543SHans de Goede 	      ADC_HELPER_NUM_PROPERTIES);
195*6a93f543SHans de Goede 
196*6a93f543SHans de Goede int adc_battery_helper_get_property(struct power_supply *psy,
197*6a93f543SHans de Goede 				    enum power_supply_property psp,
198*6a93f543SHans de Goede 				    union power_supply_propval *val)
199*6a93f543SHans de Goede {
200*6a93f543SHans de Goede 	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
201*6a93f543SHans de Goede 	int dummy, ret = 0;
202*6a93f543SHans de Goede 
203*6a93f543SHans de Goede 	/*
204*6a93f543SHans de Goede 	 * Avoid racing with adc_battery_helper_work() while it is updating
205*6a93f543SHans de Goede 	 * variables and avoid calling get_voltage_and_current_now() reentrantly.
206*6a93f543SHans de Goede 	 */
207*6a93f543SHans de Goede 	guard(mutex)(&help->lock);
208*6a93f543SHans de Goede 
209*6a93f543SHans de Goede 	switch (psp) {
210*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_STATUS:
211*6a93f543SHans de Goede 		val->intval = help->status;
212*6a93f543SHans de Goede 		break;
213*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
214*6a93f543SHans de Goede 		ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy);
215*6a93f543SHans de Goede 		break;
216*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
217*6a93f543SHans de Goede 		val->intval = help->ocv_avg_uv;
218*6a93f543SHans de Goede 		break;
219*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_CURRENT_NOW:
220*6a93f543SHans de Goede 		ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval);
221*6a93f543SHans de Goede 		break;
222*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_CAPACITY:
223*6a93f543SHans de Goede 		val->intval = help->capacity;
224*6a93f543SHans de Goede 		break;
225*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_PRESENT:
226*6a93f543SHans de Goede 		val->intval = 1;
227*6a93f543SHans de Goede 		break;
228*6a93f543SHans de Goede 	case POWER_SUPPLY_PROP_SCOPE:
229*6a93f543SHans de Goede 		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
230*6a93f543SHans de Goede 		break;
231*6a93f543SHans de Goede 	default:
232*6a93f543SHans de Goede 		return -EINVAL;
233*6a93f543SHans de Goede 	}
234*6a93f543SHans de Goede 
235*6a93f543SHans de Goede 	return ret;
236*6a93f543SHans de Goede }
237*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_get_property);
238*6a93f543SHans de Goede 
239*6a93f543SHans de Goede void adc_battery_helper_external_power_changed(struct power_supply *psy)
240*6a93f543SHans de Goede {
241*6a93f543SHans de Goede 	struct adc_battery_helper *help = power_supply_get_drvdata(psy);
242*6a93f543SHans de Goede 
243*6a93f543SHans de Goede 	dev_dbg(help->psy->dev.parent, "external power changed\n");
244*6a93f543SHans de Goede 	mod_delayed_work(system_wq, &help->work, SETTLE_TIME);
245*6a93f543SHans de Goede }
246*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed);
247*6a93f543SHans de Goede 
248*6a93f543SHans de Goede static void adc_battery_helper_start_work(struct adc_battery_helper *help)
249*6a93f543SHans de Goede {
250*6a93f543SHans de Goede 	help->poll_count = 0;
251*6a93f543SHans de Goede 	help->ocv_avg_index = 0;
252*6a93f543SHans de Goede 
253*6a93f543SHans de Goede 	queue_delayed_work(system_wq, &help->work, 0);
254*6a93f543SHans de Goede 	flush_delayed_work(&help->work);
255*6a93f543SHans de Goede }
256*6a93f543SHans de Goede 
257*6a93f543SHans de Goede int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy,
258*6a93f543SHans de Goede 			    adc_battery_helper_get_func get_voltage_and_current_now)
259*6a93f543SHans de Goede {
260*6a93f543SHans de Goede 	struct device *dev = psy->dev.parent;
261*6a93f543SHans de Goede 	int ret;
262*6a93f543SHans de Goede 
263*6a93f543SHans de Goede 	help->psy = psy;
264*6a93f543SHans de Goede 	help->get_voltage_and_current_now = get_voltage_and_current_now;
265*6a93f543SHans de Goede 
266*6a93f543SHans de Goede 	ret = devm_mutex_init(dev, &help->lock);
267*6a93f543SHans de Goede 	if (ret)
268*6a93f543SHans de Goede 		return ret;
269*6a93f543SHans de Goede 
270*6a93f543SHans de Goede 	ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work);
271*6a93f543SHans de Goede 	if (ret)
272*6a93f543SHans de Goede 		return ret;
273*6a93f543SHans de Goede 
274*6a93f543SHans de Goede 	if (!help->psy->battery_info ||
275*6a93f543SHans de Goede 	    help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL ||
276*6a93f543SHans de Goede 	    help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL ||
277*6a93f543SHans de Goede 	    !psy->battery_info->ocv_table[0]) {
278*6a93f543SHans de Goede 		dev_err(dev, "error required properties are missing\n");
279*6a93f543SHans de Goede 		return -ENODEV;
280*6a93f543SHans de Goede 	}
281*6a93f543SHans de Goede 
282*6a93f543SHans de Goede 	/* Use provided internal resistance as start point (in milli-ohm) */
283*6a93f543SHans de Goede 	help->intern_res_avg_mohm =
284*6a93f543SHans de Goede 		help->psy->battery_info->factory_internal_resistance_uohm / 1000;
285*6a93f543SHans de Goede 	/* Also add it to the internal resistance moving average window */
286*6a93f543SHans de Goede 	help->intern_res_mohm[0] = help->intern_res_avg_mohm;
287*6a93f543SHans de Goede 	help->intern_res_avg_index = 1;
288*6a93f543SHans de Goede 	help->intern_res_poll_count = 1;
289*6a93f543SHans de Goede 
290*6a93f543SHans de Goede 	adc_battery_helper_start_work(help);
291*6a93f543SHans de Goede 	return 0;
292*6a93f543SHans de Goede }
293*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_init);
294*6a93f543SHans de Goede 
295*6a93f543SHans de Goede int adc_battery_helper_suspend(struct device *dev)
296*6a93f543SHans de Goede {
297*6a93f543SHans de Goede 	struct adc_battery_helper *help = dev_get_drvdata(dev);
298*6a93f543SHans de Goede 
299*6a93f543SHans de Goede 	cancel_delayed_work_sync(&help->work);
300*6a93f543SHans de Goede 	return 0;
301*6a93f543SHans de Goede }
302*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_suspend);
303*6a93f543SHans de Goede 
304*6a93f543SHans de Goede int adc_battery_helper_resume(struct device *dev)
305*6a93f543SHans de Goede {
306*6a93f543SHans de Goede 	struct adc_battery_helper *help = dev_get_drvdata(dev);
307*6a93f543SHans de Goede 
308*6a93f543SHans de Goede 	adc_battery_helper_start_work(help);
309*6a93f543SHans de Goede 	return 0;
310*6a93f543SHans de Goede }
311*6a93f543SHans de Goede EXPORT_SYMBOL_GPL(adc_battery_helper_resume);
312*6a93f543SHans de Goede 
313*6a93f543SHans de Goede MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
314*6a93f543SHans de Goede MODULE_DESCRIPTION("ADC battery capacity estimation helper");
315*6a93f543SHans de Goede MODULE_LICENSE("GPL");
316