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