Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power: supply: axp288_fuel_gauge: Refresh all registers in one go

The I2C-bus to the XPower AXP288 is shared between the Linux kernel and
the SoCs P-Unit. The P-Unit has a semaphore which the kernel must "lock"
before it may use the bus and while the kernel holds the semaphore the CPU
and GPU power-states must not be changed otherwise the system will freeze.

This is a complex process, which is quite expensive. This is all done by
iosf_mbi_block_punit_i2c_access(). To ensure that no unguarded I2C-bus
accesses happen, iosf_mbi_block_punit_i2c_access() gets called by the
I2C-bus-driver for every I2C transfer. Because this is so expensive it
is allowed to call iosf_mbi_block_punit_i2c_access() in a nested
fashion, so that higher-level code which does multiple I2C-transfers can
call it once for a group of transfers, turning the calls done by the
I2C-bus-driver into no-ops.

Userspace power-supply API users typically will read all provided
properties in one go, refreshing the last read values when
power_supply_changed() is called by the driver and/or periodically
(e.g. every 2 minutes).

The reading of all properties in one go causes the P-Unit semaphore
to quickly be taken and released multiple times in a row. Certain
PMIC registers like AXP20X_FG_RES are even used in multiple properties
so they get read multiple times, leading to a P-Unit take + release
each time the register is read.

As already mentioned the taking of the P-Unit semaphore is a quite
expensive operation and it has also been reported that the
"hammering" of the P-Unit semaphore done by the axp288_fuel_gauge
driver can even cause stability issues with the system as a whole.

Switch over to a scheme where the axp288_fuel_gauge driver keeps
a local copy of all the registers which it uses for properties
and make it only refresh its copy of the registers if the values
are older then 1 minute; or when a fuel-gauge interrupt has
triggered since the last read.

This not only reduces the amount of reads, it also makes the code
do all the reads in one go, rather then reading specific registers
based on which property is being queried. This allows calling
iosf_mbi_block_punit_i2c_access() once before doing all the reads,
so that we now only take the P-Unit semaphore once per update.

Tested-by: Andrejus Basovas <cpp@gcc.lt>
Signed-off-by: Andrejus Basovas <cpp@gcc.lt>
Co-developed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Andrejus Basovas and committed by
Sebastian Reichel
394088f0 c371d449

+107 -98
+1 -1
drivers/power/supply/Kconfig
··· 358 358 359 359 config AXP288_FUEL_GAUGE 360 360 tristate "X-Powers AXP288 Fuel Gauge" 361 - depends on MFD_AXP20X && IIO 361 + depends on MFD_AXP20X && IIO && IOSF_MBI 362 362 help 363 363 Say yes here to have support for X-Power power management IC (PMIC) 364 364 Fuel Gauge. The device provides battery statistics and status
+106 -97
drivers/power/supply/axp288_fuel_gauge.c
··· 2 2 /* 3 3 * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver 4 4 * 5 - * Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com> 5 + * Copyright (C) 2020-2021 Andrejus Basovas <xxx@yyy.tld> 6 + * Copyright (C) 2016-2021 Hans de Goede <hdegoede@redhat.com> 6 7 * Copyright (C) 2014 Intel Corporation 7 8 * 8 9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ··· 21 20 #include <linux/power_supply.h> 22 21 #include <linux/iio/consumer.h> 23 22 #include <asm/unaligned.h> 23 + #include <asm/iosf_mbi.h> 24 24 25 25 #define PS_STAT_VBUS_TRIGGER (1 << 0) 26 26 #define PS_STAT_BAT_CHRG_DIR (1 << 2) ··· 86 84 #define PROP_VOLT(a) ((a) * 1000) 87 85 #define PROP_CURR(a) ((a) * 1000) 88 86 87 + #define AXP288_REG_UPDATE_INTERVAL (60 * HZ) 89 88 #define AXP288_FG_INTR_NUM 6 90 89 enum { 91 90 QWBTU_IRQ = 0, ··· 117 114 int pwr_op; 118 115 int low_cap; 119 116 struct dentry *debug_file; 117 + 118 + char valid; /* zero until following fields are valid */ 119 + unsigned long last_updated; /* in jiffies */ 120 + 121 + int pwr_stat; 122 + int fg_res; 123 + int bat_volt; 124 + int d_curr; 125 + int c_curr; 126 + int ocv; 127 + int fg_cc_mtr1; 128 + int fg_des_cap1; 120 129 }; 121 130 122 131 static enum power_supply_property fuel_gauge_props[] = { ··· 207 192 return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f); 208 193 } 209 194 195 + static int fuel_gauge_update_registers(struct axp288_fg_info *info) 196 + { 197 + int ret; 198 + 199 + if (info->valid && time_before(jiffies, info->last_updated + AXP288_REG_UPDATE_INTERVAL)) 200 + return 0; 201 + 202 + dev_dbg(info->dev, "Fuel Gauge updating register values...\n"); 203 + 204 + ret = iosf_mbi_block_punit_i2c_access(); 205 + if (ret < 0) 206 + return ret; 207 + 208 + ret = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); 209 + if (ret < 0) 210 + goto out; 211 + info->pwr_stat = ret; 212 + 213 + ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 214 + if (ret < 0) 215 + goto out; 216 + info->fg_res = ret; 217 + 218 + ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &info->bat_volt); 219 + if (ret < 0) 220 + goto out; 221 + 222 + if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) { 223 + info->d_curr = 0; 224 + ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr); 225 + if (ret < 0) 226 + goto out; 227 + } else { 228 + info->c_curr = 0; 229 + ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &info->d_curr); 230 + if (ret < 0) 231 + goto out; 232 + } 233 + 234 + ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); 235 + if (ret < 0) 236 + goto out; 237 + info->ocv = ret; 238 + 239 + ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); 240 + if (ret < 0) 241 + goto out; 242 + info->fg_cc_mtr1 = ret; 243 + 244 + ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG); 245 + if (ret < 0) 246 + goto out; 247 + info->fg_des_cap1 = ret; 248 + 249 + info->last_updated = jiffies; 250 + info->valid = 1; 251 + ret = 0; 252 + out: 253 + iosf_mbi_unblock_punit_i2c_access(); 254 + return ret; 255 + } 256 + 210 257 static void fuel_gauge_get_status(struct axp288_fg_info *info) 211 258 { 212 - int pwr_stat, fg_res, curr, ret; 213 - 214 - pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); 215 - if (pwr_stat < 0) { 216 - dev_err(info->dev, "PWR STAT read failed: %d\n", pwr_stat); 217 - return; 218 - } 259 + int pwr_stat = info->pwr_stat; 260 + int fg_res = info->fg_res; 261 + int curr = info->d_curr; 219 262 220 263 /* Report full if Vbus is valid and the reported capacity is 100% */ 221 264 if (!(pwr_stat & PS_STAT_VBUS_VALID)) 222 265 goto not_full; 223 266 224 - fg_res = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 225 - if (fg_res < 0) { 226 - dev_err(info->dev, "FG RES read failed: %d\n", fg_res); 227 - return; 228 - } 229 267 if (!(fg_res & FG_REP_CAP_VALID)) 230 268 goto not_full; 231 269 ··· 296 228 if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR)) 297 229 goto not_full; 298 230 299 - ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &curr); 300 - if (ret < 0) { 301 - dev_err(info->dev, "FG get current failed: %d\n", ret); 302 - return; 303 - } 304 231 if (curr == 0) { 305 232 info->status = POWER_SUPPLY_STATUS_FULL; 306 233 return; ··· 308 245 info->status = POWER_SUPPLY_STATUS_DISCHARGING; 309 246 } 310 247 311 - static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) 312 - { 313 - int ret = 0, raw_val; 314 - 315 - ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &raw_val); 316 - if (ret < 0) 317 - goto vbatt_read_fail; 318 - 319 - *vbatt = VOLTAGE_FROM_ADC(raw_val); 320 - vbatt_read_fail: 321 - return ret; 322 - } 323 - 324 - static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) 325 - { 326 - int ret, discharge; 327 - 328 - /* First check discharge current, so that we do only 1 read on bat. */ 329 - ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &discharge); 330 - if (ret < 0) 331 - return ret; 332 - 333 - if (discharge > 0) { 334 - *cur = -1 * discharge; 335 - return 0; 336 - } 337 - 338 - return iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], cur); 339 - } 340 - 341 - static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv) 342 - { 343 - int ret; 344 - 345 - ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); 346 - if (ret >= 0) 347 - *vocv = VOLTAGE_FROM_ADC(ret); 348 - 349 - return ret; 350 - } 351 - 352 248 static int fuel_gauge_battery_health(struct axp288_fg_info *info) 353 249 { 354 - int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN; 355 - 356 - ret = fuel_gauge_get_vocv(info, &vocv); 357 - if (ret < 0) 358 - goto health_read_fail; 250 + int vocv = VOLTAGE_FROM_ADC(info->ocv); 251 + int health = POWER_SUPPLY_HEALTH_UNKNOWN; 359 252 360 253 if (vocv > info->max_volt) 361 254 health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 362 255 else 363 256 health = POWER_SUPPLY_HEALTH_GOOD; 364 257 365 - health_read_fail: 366 258 return health; 367 259 } 368 260 ··· 326 308 union power_supply_propval *val) 327 309 { 328 310 struct axp288_fg_info *info = power_supply_get_drvdata(ps); 329 - int ret = 0, value; 311 + int ret, value; 330 312 331 313 mutex_lock(&info->lock); 314 + 315 + ret = fuel_gauge_update_registers(info); 316 + if (ret < 0) 317 + goto out; 318 + 332 319 switch (prop) { 333 320 case POWER_SUPPLY_PROP_STATUS: 334 321 fuel_gauge_get_status(info); ··· 343 320 val->intval = fuel_gauge_battery_health(info); 344 321 break; 345 322 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 346 - ret = fuel_gauge_get_vbatt(info, &value); 347 - if (ret < 0) 348 - goto fuel_gauge_read_err; 323 + value = VOLTAGE_FROM_ADC(info->bat_volt); 349 324 val->intval = PROP_VOLT(value); 350 325 break; 351 326 case POWER_SUPPLY_PROP_VOLTAGE_OCV: 352 - ret = fuel_gauge_get_vocv(info, &value); 353 - if (ret < 0) 354 - goto fuel_gauge_read_err; 327 + value = VOLTAGE_FROM_ADC(info->ocv); 355 328 val->intval = PROP_VOLT(value); 356 329 break; 357 330 case POWER_SUPPLY_PROP_CURRENT_NOW: 358 - ret = fuel_gauge_get_current(info, &value); 359 - if (ret < 0) 360 - goto fuel_gauge_read_err; 331 + if (info->d_curr > 0) 332 + value = -1 * info->d_curr; 333 + else 334 + value = info->c_curr; 335 + 361 336 val->intval = PROP_CURR(value); 362 337 break; 363 338 case POWER_SUPPLY_PROP_PRESENT: ··· 365 344 val->intval = 0; 366 345 break; 367 346 case POWER_SUPPLY_PROP_CAPACITY: 368 - ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 369 - if (ret < 0) 370 - goto fuel_gauge_read_err; 371 - 372 - if (!(ret & FG_REP_CAP_VALID)) 347 + if (!(info->fg_res & FG_REP_CAP_VALID)) 373 348 dev_err(info->dev, "capacity measurement not valid\n"); 374 - val->intval = (ret & FG_REP_CAP_VAL_MASK); 349 + val->intval = (info->fg_res & FG_REP_CAP_VAL_MASK); 375 350 break; 376 351 case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 377 352 val->intval = (info->low_cap & 0x0f); ··· 376 359 val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 377 360 break; 378 361 case POWER_SUPPLY_PROP_CHARGE_NOW: 379 - ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); 380 - if (ret < 0) 381 - goto fuel_gauge_read_err; 382 - 383 - val->intval = ret * FG_DES_CAP_RES_LSB; 362 + val->intval = info->fg_cc_mtr1 * FG_DES_CAP_RES_LSB; 384 363 break; 385 364 case POWER_SUPPLY_PROP_CHARGE_FULL: 386 - ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG); 387 - if (ret < 0) 388 - goto fuel_gauge_read_err; 389 - 390 - val->intval = ret * FG_DES_CAP_RES_LSB; 365 + val->intval = info->fg_des_cap1 * FG_DES_CAP_RES_LSB; 391 366 break; 392 367 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 393 368 val->intval = PROP_VOLT(info->max_volt); 394 369 break; 395 370 default: 396 - mutex_unlock(&info->lock); 397 - return -EINVAL; 371 + ret = -EINVAL; 398 372 } 399 373 400 - mutex_unlock(&info->lock); 401 - return 0; 402 - 403 - fuel_gauge_read_err: 374 + out: 404 375 mutex_unlock(&info->lock); 405 376 return ret; 406 377 } ··· 477 472 dev_warn(info->dev, "Spurious Interrupt!!!\n"); 478 473 } 479 474 475 + info->valid = 0; /* Force updating of the cached registers */ 476 + 480 477 power_supply_changed(info->bat); 481 478 return IRQ_HANDLED; 482 479 } ··· 487 480 { 488 481 struct axp288_fg_info *info = power_supply_get_drvdata(psy); 489 482 483 + info->valid = 0; /* Force updating of the cached registers */ 490 484 power_supply_changed(info->bat); 491 485 } 492 486 ··· 645 637 info->regmap = axp20x->regmap; 646 638 info->regmap_irqc = axp20x->regmap_irqc; 647 639 info->status = POWER_SUPPLY_STATUS_UNKNOWN; 640 + info->valid = 0; 648 641 649 642 platform_set_drvdata(pdev, info); 650 643