Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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");