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