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

power: supply: ug3105_battery: Add driver for uPI uG3105 battery monitor

Add a new battery driver for the uPI uG3105 battery monitor.

Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it
is expected to be use in combination with some always on microcontroller
reading its coulomb-counter before it can wrap (must be read every 400
seconds!).

Since Linux does not monitor coulomb-counter changes while the device is
off or suspended, the coulomb counter is not used atm.

So far this driver is only used on x86/ACPI (non devicetree) devs
(also note there is no of_match table). Therefor there is no devicetree
bindings documentation for this driver's "upisemi,rsns-microohm" property
since this is not used in actual devicetree files and the dt bindings
maintainers have requested properties with no actual dt users to
_not_ be added to the dt bindings.

The property's name has been chosen so that it should not need to be
changed if/when devicetree enumeration support gets added later, as it
mirrors "maxim,rsns-microohm" from the "maxim,max17042" bindings.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Hans de Goede and committed by
Sebastian Reichel
f059b46e 4e456230

+502
+15
drivers/power/supply/Kconfig
··· 866 866 Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, 867 867 Surface Book 3, and Surface Laptop Go. 868 868 869 + config BATTERY_UG3105 870 + tristate "uPI uG3105 battery monitor driver" 871 + depends on I2C 872 + help 873 + Battery monitor driver for the uPI uG3105 battery monitor. 874 + 875 + Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead 876 + it is expected to be use in combination with some always on 877 + microcontroller reading its coulomb-counter before it can wrap 878 + (it must be read every 400 seconds!). 879 + 880 + Since Linux does not monitor coulomb-counter changes while the 881 + device is off or suspended, the functionality of this driver is 882 + limited to reporting capacity only. 883 + 869 884 endif # POWER_SUPPLY
+1
drivers/power/supply/Makefile
··· 105 105 obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o 106 106 obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o 107 107 obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o 108 + obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
+486
drivers/power/supply/ug3105_battery.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Battery monitor driver for the uPI uG3105 battery monitor 4 + * 5 + * Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is 6 + * expected to be use in combination with some always on microcontroller reading 7 + * its coulomb-counter before it can wrap (must be read every 400 seconds!). 8 + * 9 + * Since Linux does not monitor coulomb-counter changes while the device 10 + * is off or suspended, the coulomb counter is not used atm. 11 + * 12 + * Possible improvements: 13 + * 1. Activate commented out total_coulomb_count code 14 + * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty 15 + * and remember that we did this (and clear the flag for this on susp/resume) 16 + * 3. When the battery is full check if the flag that we set total_coulomb_count 17 + * to when the battery was empty is set. If so we now know the capacity, 18 + * not the design, but actual capacity, of the battery 19 + * 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember 20 + * the actual capacity of the battery over reboots 21 + * 5. When we know the actual capacity at probe time, add energy_now and 22 + * energy_full attributes. Guess boot + resume energy_now value based on ocv 23 + * and then use total_coulomb_count to report energy_now over time, resetting 24 + * things to adjust for drift when empty/full. This should give more accurate 25 + * readings, esp. in the 30-70% range and allow userspace to estimate time 26 + * remaining till empty/full 27 + * 6. Maybe unregister + reregister the psy device when we learn the actual 28 + * capacity during run-time ? 29 + * 30 + * The above will also require some sort of mwh_per_unit calculation. Testing 31 + * has shown that an estimated 7404mWh increase of the battery's energy results 32 + * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R. 33 + * 34 + * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com> 35 + */ 36 + 37 + #include <linux/devm-helpers.h> 38 + #include <linux/module.h> 39 + #include <linux/mutex.h> 40 + #include <linux/slab.h> 41 + #include <linux/i2c.h> 42 + #include <linux/mod_devicetable.h> 43 + #include <linux/power_supply.h> 44 + #include <linux/workqueue.h> 45 + 46 + #define UG3105_MOV_AVG_WINDOW 8 47 + #define UG3105_INIT_POLL_TIME (5 * HZ) 48 + #define UG3105_POLL_TIME (30 * HZ) 49 + #define UG3105_SETTLE_TIME (1 * HZ) 50 + 51 + #define UG3105_INIT_POLL_COUNT 30 52 + 53 + #define UG3105_REG_MODE 0x00 54 + #define UG3105_REG_CTRL1 0x01 55 + #define UG3105_REG_COULOMB_CNT 0x02 56 + #define UG3105_REG_BAT_VOLT 0x08 57 + #define UG3105_REG_BAT_CURR 0x0c 58 + 59 + #define UG3105_MODE_STANDBY 0x00 60 + #define UG3105_MODE_RUN 0x10 61 + 62 + #define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 63 + 64 + #define UG3105_CURR_HYST_UA 65000 65 + 66 + #define UG3105_LOW_BAT_UV 3700000 67 + #define UG3105_FULL_BAT_HYST_UV 38000 68 + 69 + struct ug3105_chip { 70 + struct i2c_client *client; 71 + struct power_supply *psy; 72 + struct power_supply_battery_info *info; 73 + struct delayed_work work; 74 + struct mutex lock; 75 + int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */ 76 + int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */ 77 + int poll_count; 78 + int ocv_avg_index; 79 + int ocv_avg; /* micro-volt */ 80 + int intern_res_poll_count; 81 + int intern_res_avg_index; 82 + int intern_res_avg; /* milli-ohm */ 83 + int volt; /* micro-volt */ 84 + int curr; /* micro-ampere */ 85 + int total_coulomb_count; 86 + int uv_per_unit; 87 + int ua_per_unit; 88 + int status; 89 + int capacity; 90 + bool supplied; 91 + }; 92 + 93 + static int ug3105_read_word(struct i2c_client *client, u8 reg) 94 + { 95 + int val; 96 + 97 + val = i2c_smbus_read_word_data(client, reg); 98 + if (val < 0) 99 + dev_err(&client->dev, "Error reading reg 0x%02x\n", reg); 100 + 101 + return val; 102 + } 103 + 104 + static int ug3105_get_status(struct ug3105_chip *chip) 105 + { 106 + int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV; 107 + 108 + if (chip->curr > UG3105_CURR_HYST_UA) 109 + return POWER_SUPPLY_STATUS_CHARGING; 110 + 111 + if (chip->curr < -UG3105_CURR_HYST_UA) 112 + return POWER_SUPPLY_STATUS_DISCHARGING; 113 + 114 + if (chip->supplied && chip->ocv_avg > full) 115 + return POWER_SUPPLY_STATUS_FULL; 116 + 117 + return POWER_SUPPLY_STATUS_NOT_CHARGING; 118 + } 119 + 120 + static int ug3105_get_capacity(struct ug3105_chip *chip) 121 + { 122 + /* 123 + * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is 124 + * for LiPo HV (High-Voltage) bateries which can go up to 4.35V 125 + * instead of the usual 4.2V. 126 + */ 127 + static const int ocv_capacity_tbl[23] = { 128 + 3350000, 129 + 3610000, 130 + 3690000, 131 + 3710000, 132 + 3730000, 133 + 3750000, 134 + 3770000, 135 + 3786667, 136 + 3803333, 137 + 3820000, 138 + 3836667, 139 + 3853333, 140 + 3870000, 141 + 3907500, 142 + 3945000, 143 + 3982500, 144 + 4020000, 145 + 4075000, 146 + 4110000, 147 + 4150000, 148 + 4200000, 149 + 4250000, 150 + 4300000, 151 + }; 152 + int i, ocv_diff, ocv_step; 153 + 154 + if (chip->ocv_avg < ocv_capacity_tbl[0]) 155 + return 0; 156 + 157 + if (chip->status == POWER_SUPPLY_STATUS_FULL) 158 + return 100; 159 + 160 + for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { 161 + if (chip->ocv_avg > ocv_capacity_tbl[i]) 162 + continue; 163 + 164 + ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg; 165 + ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; 166 + /* scale 0-110% down to 0-100% for LiPo HV */ 167 + if (chip->info->constant_charge_voltage_max_uv >= 4300000) 168 + return (i * 500 - ocv_diff * 500 / ocv_step) / 110; 169 + else 170 + return i * 5 - ocv_diff * 5 / ocv_step; 171 + } 172 + 173 + return 100; 174 + } 175 + 176 + static void ug3105_work(struct work_struct *work) 177 + { 178 + struct ug3105_chip *chip = container_of(work, struct ug3105_chip, 179 + work.work); 180 + int i, val, curr_diff, volt_diff, res, win_size; 181 + bool prev_supplied = chip->supplied; 182 + int prev_status = chip->status; 183 + int prev_volt = chip->volt; 184 + int prev_curr = chip->curr; 185 + struct power_supply *psy; 186 + 187 + mutex_lock(&chip->lock); 188 + 189 + psy = chip->psy; 190 + if (!psy) 191 + goto out; 192 + 193 + val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); 194 + if (val < 0) 195 + goto out; 196 + chip->volt = val * chip->uv_per_unit; 197 + 198 + val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); 199 + if (val < 0) 200 + goto out; 201 + chip->curr = (s16)val * chip->ua_per_unit; 202 + 203 + chip->ocv[chip->ocv_avg_index] = 204 + chip->volt - chip->curr * chip->intern_res_avg / 1000; 205 + chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW; 206 + chip->poll_count++; 207 + 208 + /* 209 + * See possible improvements comment above. 210 + * 211 + * Read + reset coulomb counter every 10 polls (every 300 seconds) 212 + * if ((chip->poll_count % 10) == 0) { 213 + * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); 214 + * if (val < 0) 215 + * goto out; 216 + * 217 + * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, 218 + * UG3105_CTRL1_RESET_COULOMB_CNT); 219 + * 220 + * chip->total_coulomb_count += (s16)val; 221 + * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", 222 + * (s16)val, chip->total_coulomb_count); 223 + * } 224 + */ 225 + 226 + chip->ocv_avg = 0; 227 + win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW); 228 + for (i = 0; i < win_size; i++) 229 + chip->ocv_avg += chip->ocv[i]; 230 + chip->ocv_avg /= win_size; 231 + 232 + chip->supplied = power_supply_am_i_supplied(psy); 233 + chip->status = ug3105_get_status(chip); 234 + chip->capacity = ug3105_get_capacity(chip); 235 + 236 + /* 237 + * Skip internal resistance calc on charger [un]plug and 238 + * when the battery is almost empty (voltage low). 239 + */ 240 + if (chip->supplied != prev_supplied || 241 + chip->volt < UG3105_LOW_BAT_UV || 242 + chip->poll_count < 2) 243 + goto out; 244 + 245 + /* 246 + * Assuming that the OCV voltage does not change significantly 247 + * between 2 polls, then we can calculate the internal resistance 248 + * on a significant current change by attributing all voltage 249 + * change between the 2 readings to the internal resistance. 250 + */ 251 + curr_diff = abs(chip->curr - prev_curr); 252 + if (curr_diff < UG3105_CURR_HYST_UA) 253 + goto out; 254 + 255 + volt_diff = abs(chip->volt - prev_volt); 256 + res = volt_diff * 1000 / curr_diff; 257 + 258 + if ((res < (chip->intern_res_avg * 2 / 3)) || 259 + (res > (chip->intern_res_avg * 4 / 3))) { 260 + dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res); 261 + goto out; 262 + } 263 + 264 + dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res); 265 + 266 + chip->intern_res[chip->intern_res_avg_index] = res; 267 + chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW; 268 + chip->intern_res_poll_count++; 269 + 270 + chip->intern_res_avg = 0; 271 + win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW); 272 + for (i = 0; i < win_size; i++) 273 + chip->intern_res_avg += chip->intern_res[i]; 274 + chip->intern_res_avg /= win_size; 275 + 276 + out: 277 + mutex_unlock(&chip->lock); 278 + 279 + queue_delayed_work(system_wq, &chip->work, 280 + (chip->poll_count <= UG3105_INIT_POLL_COUNT) ? 281 + UG3105_INIT_POLL_TIME : UG3105_POLL_TIME); 282 + 283 + if (chip->status != prev_status && psy) 284 + power_supply_changed(psy); 285 + } 286 + 287 + static enum power_supply_property ug3105_battery_props[] = { 288 + POWER_SUPPLY_PROP_STATUS, 289 + POWER_SUPPLY_PROP_PRESENT, 290 + POWER_SUPPLY_PROP_TECHNOLOGY, 291 + POWER_SUPPLY_PROP_SCOPE, 292 + POWER_SUPPLY_PROP_VOLTAGE_NOW, 293 + POWER_SUPPLY_PROP_VOLTAGE_OCV, 294 + POWER_SUPPLY_PROP_CURRENT_NOW, 295 + POWER_SUPPLY_PROP_CAPACITY, 296 + }; 297 + 298 + static int ug3105_get_property(struct power_supply *psy, 299 + enum power_supply_property psp, 300 + union power_supply_propval *val) 301 + { 302 + struct ug3105_chip *chip = power_supply_get_drvdata(psy); 303 + int ret = 0; 304 + 305 + mutex_lock(&chip->lock); 306 + 307 + if (!chip->psy) { 308 + ret = -EAGAIN; 309 + goto out; 310 + } 311 + 312 + switch (psp) { 313 + case POWER_SUPPLY_PROP_STATUS: 314 + val->intval = chip->status; 315 + break; 316 + case POWER_SUPPLY_PROP_PRESENT: 317 + val->intval = 1; 318 + break; 319 + case POWER_SUPPLY_PROP_TECHNOLOGY: 320 + val->intval = chip->info->technology; 321 + break; 322 + case POWER_SUPPLY_PROP_SCOPE: 323 + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; 324 + break; 325 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 326 + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); 327 + if (ret < 0) 328 + break; 329 + val->intval = ret * chip->uv_per_unit; 330 + ret = 0; 331 + break; 332 + case POWER_SUPPLY_PROP_VOLTAGE_OCV: 333 + val->intval = chip->ocv_avg; 334 + break; 335 + case POWER_SUPPLY_PROP_CURRENT_NOW: 336 + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); 337 + if (ret < 0) 338 + break; 339 + val->intval = (s16)ret * chip->ua_per_unit; 340 + ret = 0; 341 + break; 342 + case POWER_SUPPLY_PROP_CAPACITY: 343 + val->intval = chip->capacity; 344 + break; 345 + default: 346 + ret = -EINVAL; 347 + } 348 + 349 + out: 350 + mutex_unlock(&chip->lock); 351 + return ret; 352 + } 353 + 354 + static void ug3105_external_power_changed(struct power_supply *psy) 355 + { 356 + struct ug3105_chip *chip = power_supply_get_drvdata(psy); 357 + 358 + dev_dbg(&chip->client->dev, "external power changed\n"); 359 + mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME); 360 + } 361 + 362 + static const struct power_supply_desc ug3105_psy_desc = { 363 + .name = "ug3105_battery", 364 + .type = POWER_SUPPLY_TYPE_BATTERY, 365 + .get_property = ug3105_get_property, 366 + .external_power_changed = ug3105_external_power_changed, 367 + .properties = ug3105_battery_props, 368 + .num_properties = ARRAY_SIZE(ug3105_battery_props), 369 + }; 370 + 371 + static void ug3105_init(struct ug3105_chip *chip) 372 + { 373 + chip->poll_count = 0; 374 + chip->ocv_avg_index = 0; 375 + chip->total_coulomb_count = 0; 376 + i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, 377 + UG3105_MODE_RUN); 378 + i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, 379 + UG3105_CTRL1_RESET_COULOMB_CNT); 380 + queue_delayed_work(system_wq, &chip->work, 0); 381 + flush_delayed_work(&chip->work); 382 + } 383 + 384 + static int ug3105_probe(struct i2c_client *client) 385 + { 386 + struct power_supply_config psy_cfg = {}; 387 + struct device *dev = &client->dev; 388 + u32 curr_sense_res_uohm = 10000; 389 + struct power_supply *psy; 390 + struct ug3105_chip *chip; 391 + int ret; 392 + 393 + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); 394 + if (!chip) 395 + return -ENOMEM; 396 + 397 + chip->client = client; 398 + mutex_init(&chip->lock); 399 + ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work); 400 + if (ret) 401 + return ret; 402 + 403 + psy_cfg.drv_data = chip; 404 + psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); 405 + if (IS_ERR(psy)) 406 + return PTR_ERR(psy); 407 + 408 + ret = power_supply_get_battery_info(psy, &chip->info); 409 + if (ret) 410 + return ret; 411 + 412 + if (chip->info->factory_internal_resistance_uohm == -EINVAL || 413 + chip->info->constant_charge_voltage_max_uv == -EINVAL) { 414 + dev_err(dev, "error required properties are missing\n"); 415 + return -ENODEV; 416 + } 417 + 418 + device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); 419 + 420 + /* 421 + * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 422 + * coming from somewhere for some reason (verified with a volt-meter). 423 + */ 424 + chip->uv_per_unit = 45000000/65536; 425 + /* Datasheet says 8.1 uV per unit for the current ADC */ 426 + chip->ua_per_unit = 8100000 / curr_sense_res_uohm; 427 + 428 + /* Use provided internal resistance as start point (in milli-ohm) */ 429 + chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000; 430 + /* Also add it to the internal resistance moving average window */ 431 + chip->intern_res[0] = chip->intern_res_avg; 432 + chip->intern_res_avg_index = 1; 433 + chip->intern_res_poll_count = 1; 434 + 435 + mutex_lock(&chip->lock); 436 + chip->psy = psy; 437 + mutex_unlock(&chip->lock); 438 + 439 + ug3105_init(chip); 440 + 441 + i2c_set_clientdata(client, chip); 442 + return 0; 443 + } 444 + 445 + static int __maybe_unused ug3105_suspend(struct device *dev) 446 + { 447 + struct ug3105_chip *chip = dev_get_drvdata(dev); 448 + 449 + cancel_delayed_work_sync(&chip->work); 450 + i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, 451 + UG3105_MODE_STANDBY); 452 + 453 + return 0; 454 + } 455 + 456 + static int __maybe_unused ug3105_resume(struct device *dev) 457 + { 458 + struct ug3105_chip *chip = dev_get_drvdata(dev); 459 + 460 + ug3105_init(chip); 461 + 462 + return 0; 463 + } 464 + 465 + static SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend, 466 + ug3105_resume); 467 + 468 + static const struct i2c_device_id ug3105_id[] = { 469 + { "ug3105" }, 470 + { } 471 + }; 472 + MODULE_DEVICE_TABLE(i2c, ug3105_id); 473 + 474 + static struct i2c_driver ug3105_i2c_driver = { 475 + .driver = { 476 + .name = "ug3105", 477 + .pm = &ug3105_pm_ops, 478 + }, 479 + .probe_new = ug3105_probe, 480 + .id_table = ug3105_id, 481 + }; 482 + module_i2c_driver(ug3105_i2c_driver); 483 + 484 + MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); 485 + MODULE_DESCRIPTION("uPI uG3105 battery monitor driver"); 486 + MODULE_LICENSE("GPL");