File Index
Files and directories
Sign in
FILE

ug3105_battery.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/ug3105_battery.c 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. 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");
~/ Read-only file preview.