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