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

Configure Feed

Select the types of activity you want to include in your feed.

at v6.18-rc2 217 lines 6.5 kB view raw
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. Add coulumb counter reading, e.g. something like this: 14 * Read + reset coulomb counter every 10 polls (every 300 seconds) 15 * 16 * if ((chip->poll_count % 10) == 0) { 17 * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); 18 * if (val < 0) 19 * goto out; 20 * 21 * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, 22 * UG3105_CTRL1_RESET_COULOMB_CNT); 23 * 24 * chip->total_coulomb_count += (s16)val; 25 * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", 26 * (s16)val, chip->total_coulomb_count); 27 * } 28 * 29 * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty 30 * and remember that we did this (and clear the flag for this on susp/resume) 31 * 3. When the battery is full check if the flag that we set total_coulomb_count 32 * to when the battery was empty is set. If so we now know the capacity, 33 * not the design, but actual capacity, of the battery 34 * 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember 35 * the actual capacity of the battery over reboots 36 * 5. When we know the actual capacity at probe time, add energy_now and 37 * energy_full attributes. Guess boot + resume energy_now value based on ocv 38 * and then use total_coulomb_count to report energy_now over time, resetting 39 * things to adjust for drift when empty/full. This should give more accurate 40 * readings, esp. in the 30-70% range and allow userspace to estimate time 41 * remaining till empty/full 42 * 6. Maybe unregister + reregister the psy device when we learn the actual 43 * capacity during run-time ? 44 * 45 * The above will also require some sort of mwh_per_unit calculation. Testing 46 * has shown that an estimated 7404mWh increase of the battery's energy results 47 * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R. 48 * 49 * Copyright (C) 2021 - 2025 Hans de Goede <hansg@kernel.org> 50 */ 51 52#include <linux/module.h> 53#include <linux/slab.h> 54#include <linux/i2c.h> 55#include <linux/mod_devicetable.h> 56#include <linux/power_supply.h> 57 58#include "adc-battery-helper.h" 59 60#define UG3105_REG_MODE 0x00 61#define UG3105_REG_CTRL1 0x01 62#define UG3105_REG_COULOMB_CNT 0x02 63#define UG3105_REG_BAT_VOLT 0x08 64#define UG3105_REG_BAT_CURR 0x0c 65 66#define UG3105_MODE_STANDBY 0x00 67#define UG3105_MODE_RUN 0x10 68 69#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 70 71struct ug3105_chip { 72 /* Must be the first member see adc-battery-helper documentation */ 73 struct adc_battery_helper helper; 74 struct i2c_client *client; 75 struct power_supply *psy; 76 int uv_per_unit; 77 int ua_per_unit; 78}; 79 80static int ug3105_read_word(struct i2c_client *client, u8 reg) 81{ 82 int val; 83 84 val = i2c_smbus_read_word_data(client, reg); 85 if (val < 0) 86 dev_err(&client->dev, "Error reading reg 0x%02x\n", reg); 87 88 return val; 89} 90 91static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr) 92{ 93 struct ug3105_chip *chip = power_supply_get_drvdata(psy); 94 int ret; 95 96 ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); 97 if (ret < 0) 98 return ret; 99 100 *volt = ret * chip->uv_per_unit; 101 102 ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); 103 if (ret < 0) 104 return ret; 105 106 *curr = (s16)ret * chip->ua_per_unit; 107 return 0; 108} 109 110static const struct power_supply_desc ug3105_psy_desc = { 111 .name = "ug3105_battery", 112 .type = POWER_SUPPLY_TYPE_BATTERY, 113 .get_property = adc_battery_helper_get_property, 114 .external_power_changed = adc_battery_helper_external_power_changed, 115 .properties = adc_battery_helper_properties, 116 .num_properties = ADC_HELPER_NUM_PROPERTIES, 117}; 118 119static void ug3105_start(struct i2c_client *client) 120{ 121 i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN); 122 i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT); 123} 124 125static void ug3105_stop(struct i2c_client *client) 126{ 127 i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY); 128} 129 130static int ug3105_probe(struct i2c_client *client) 131{ 132 struct power_supply_config psy_cfg = {}; 133 struct device *dev = &client->dev; 134 u32 curr_sense_res_uohm = 10000; 135 struct ug3105_chip *chip; 136 int ret; 137 138 chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); 139 if (!chip) 140 return -ENOMEM; 141 142 chip->client = client; 143 144 ug3105_start(client); 145 146 device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); 147 148 /* 149 * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 150 * coming from somewhere for some reason (verified with a volt-meter). 151 */ 152 chip->uv_per_unit = 45000000 / 65536; 153 /* Datasheet says 8.1 uV per unit for the current ADC */ 154 chip->ua_per_unit = 8100000 / curr_sense_res_uohm; 155 156 psy_cfg.drv_data = chip; 157 chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); 158 if (IS_ERR(chip->psy)) { 159 ret = PTR_ERR(chip->psy); 160 goto stop; 161 } 162 163 ret = adc_battery_helper_init(&chip->helper, chip->psy, 164 ug3105_get_voltage_and_current_now, NULL); 165 if (ret) 166 goto stop; 167 168 i2c_set_clientdata(client, chip); 169 return 0; 170 171stop: 172 ug3105_stop(client); 173 return ret; 174} 175 176static int __maybe_unused ug3105_suspend(struct device *dev) 177{ 178 struct ug3105_chip *chip = dev_get_drvdata(dev); 179 180 adc_battery_helper_suspend(dev); 181 ug3105_stop(chip->client); 182 return 0; 183} 184 185static int __maybe_unused ug3105_resume(struct device *dev) 186{ 187 struct ug3105_chip *chip = dev_get_drvdata(dev); 188 189 ug3105_start(chip->client); 190 adc_battery_helper_resume(dev); 191 return 0; 192} 193 194static SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend, 195 ug3105_resume); 196 197static const struct i2c_device_id ug3105_id[] = { 198 { "ug3105" }, 199 { } 200}; 201MODULE_DEVICE_TABLE(i2c, ug3105_id); 202 203static struct i2c_driver ug3105_i2c_driver = { 204 .driver = { 205 .name = "ug3105", 206 .pm = &ug3105_pm_ops, 207 }, 208 .probe = ug3105_probe, 209 .remove = ug3105_stop, 210 .shutdown = ug3105_stop, 211 .id_table = ug3105_id, 212}; 213module_i2c_driver(ug3105_i2c_driver); 214 215MODULE_AUTHOR("Hans de Goede <hansg@kernel.org"); 216MODULE_DESCRIPTION("uPI uG3105 battery monitor driver"); 217MODULE_LICENSE("GPL");