Skip to content

Commit b597332

Browse files
committed
power: supply: add driver for LT8491
LT8491 High Voltage Buck-Boost Battery Charge Controller with I2C Signed-off-by: John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com>
1 parent da81f92 commit b597332

File tree

3 files changed

+370
-1
lines changed

3 files changed

+370
-1
lines changed

drivers/power/supply/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,15 @@ config CHARGER_LT3651
533533
Say Y to include support for the Analog Devices (Linear Technology)
534534
LT3651 battery charger which reports its status via GPIO lines.
535535

536+
config CHARGER_LT8491
537+
tristate "Analog Devices LT8491 charger"
538+
depends on I2C
539+
help
540+
Say Y to include support for the Analog Devices (Linear Technology)
541+
LT8491 battery charge controller connected to I2C. The LT8491 is a
542+
high voltage buck-boost switching regulator battery charger
543+
controller.
544+
536545
config CHARGER_LTC4162L
537546
tristate "LTC4162-L charger"
538547
depends on I2C

drivers/power/supply/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
7272
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
7373
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
7474
obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o
75+
obj-$(CONFIG_CHARGER_LT8491) += lt8491_charger.o
7576
obj-$(CONFIG_CHARGER_LTC4162L) += ltc4162-l-charger.o
7677
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
7778
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
@@ -109,4 +110,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
109110
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
110111
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
111112
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
112-
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
113+
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o

drivers/power/supply/lt8491_charger.c

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Analog Devices LT8491 Battery Charger
4+
*
5+
* Copyright 2024 Analog Devices Inc.
6+
*/
7+
8+
#include <linux/bitfield.h>
9+
#include <linux/device.h>
10+
#include <linux/errno.h>
11+
#include <linux/i2c.h>
12+
#include <linux/mod_devicetable.h>
13+
#include <linux/module.h>
14+
#include <linux/power_supply.h>
15+
16+
#define LT8491_TELE_TBAT_REG 0x0
17+
#define LT8491_TELE_POUT_REG 0x2
18+
#define LT8491_TELE_PIN_REG 0x4
19+
#define LT8491_TELE_EFF_REG 0x6
20+
#define LT8491_TELE_IOUT_REG 0x8
21+
#define LT8491_TELE_IIN_REG 0xA
22+
#define LT8491_TELE_VBAT_REG 0xC
23+
#define LT8491_TELE_VIN_REG 0xE
24+
#define LT8491_TELE_VINR_REG 0x10
25+
#define LT8491_STAT_CHARGER_REG 0x12
26+
#define LT8491_CTRL_UPDATE_TELEM_REG 0x26
27+
28+
#define LT8491_CFG_RSENSE1_REG 0x28
29+
#define LT8491_CFG_RIMON_OUT_REG 0x2A
30+
#define LT8491_CFG_RSENSE2_REG 0x2C
31+
#define LT8491_CFG_RDACO_REG 0x2E
32+
#define LT8491_CFG_RFBOUT1_REG 0x30
33+
#define LT8491_CFG_RFBOUT2_REG 0x32
34+
#define LT8491_CFG_RDACI_REG 0x34
35+
#define LT8491_CFG_RFBIN2_REG 0x36
36+
#define LT8491_CFG_RFBIN1_REG 0x38
37+
#define LT8491_CFG_TBAT_MIN_REG 0x40
38+
#define LT8491_CFG_TBAT_MAX_REG 0x41
39+
#define LT8491_MFR_DATA1_LSB_REG 0x5C
40+
41+
#define LT8491_CHARGING_MASK BIT(2)
42+
43+
#define LT8491_MFR_DATA_LEN 0x3
44+
45+
struct lt8491_info {
46+
struct i2c_client *client;
47+
struct power_supply *psp;
48+
};
49+
50+
static s32 lt8491_update_telemetry(struct lt8491_info *info)
51+
{
52+
return i2c_smbus_write_byte_data(info->client, LT8491_CTRL_UPDATE_TELEM_REG, 0xAA);
53+
}
54+
55+
static int lt8491_read_serial_number(struct lt8491_info *info, char *strval)
56+
{
57+
int i, ret;
58+
u32 serial_number[LT8491_MFR_DATA_LEN];
59+
60+
for (i = 0; i < LT8491_MFR_DATA_LEN; i++) {
61+
serial_number[i] = i2c_smbus_read_word_data(info->client, LT8491_MFR_DATA1_LSB_REG + i * 2);
62+
if (serial_number[i] < 0)
63+
return serial_number[i];
64+
}
65+
66+
ret = sprintf(strval, "%04x%04x%04x", serial_number[0], serial_number[1], serial_number[2]);
67+
if (ret < 0)
68+
return ret;
69+
70+
return 0;
71+
}
72+
73+
static int lt8491_get_property(struct power_supply *psy,
74+
enum power_supply_property psp,
75+
union power_supply_propval *val)
76+
{
77+
struct lt8491_info *info = power_supply_get_drvdata(psy);
78+
char *strval;
79+
s16 ret;
80+
81+
switch (psp) {
82+
case POWER_SUPPLY_PROP_STATUS:
83+
ret = i2c_smbus_read_byte_data(info->client, LT8491_STAT_CHARGER_REG);
84+
if (ret < 0)
85+
return ret;
86+
87+
val->intval = FIELD_GET(LT8491_CHARGING_MASK, ret) ?
88+
POWER_SUPPLY_STATUS_CHARGING :
89+
POWER_SUPPLY_STATUS_NOT_CHARGING;
90+
91+
return 0;
92+
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
93+
ret = lt8491_update_telemetry(info);
94+
if (ret)
95+
return ret;
96+
97+
ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_VBAT_REG);
98+
if (ret < 0)
99+
return ret;
100+
101+
val->intval = ret * 10000;
102+
103+
return 0;
104+
case POWER_SUPPLY_PROP_CURRENT_NOW:
105+
ret = lt8491_update_telemetry(info);
106+
if (ret)
107+
return ret;
108+
109+
ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_IOUT_REG);
110+
if (ret < 0)
111+
return ret;
112+
113+
val->intval = ret;
114+
115+
return 0;
116+
case POWER_SUPPLY_PROP_POWER_NOW:
117+
ret = lt8491_update_telemetry(info);
118+
if (ret)
119+
return ret;
120+
121+
ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_POUT_REG);
122+
if (ret < 0)
123+
return ret;
124+
125+
val->intval = ret * 10000;
126+
127+
return 0;
128+
case POWER_SUPPLY_PROP_TEMP:
129+
ret = lt8491_update_telemetry(info);
130+
if (ret)
131+
return ret;
132+
133+
ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_TBAT_REG);
134+
if (ret < 0)
135+
return ret;
136+
137+
val->intval = ret;
138+
139+
return 0;
140+
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
141+
ret = i2c_smbus_read_byte_data(info->client, LT8491_CFG_TBAT_MIN_REG);
142+
if (ret < 0)
143+
return ret;
144+
145+
val->intval = ret * 10;
146+
147+
return 0;
148+
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
149+
ret = i2c_smbus_read_byte_data(info->client, LT8491_CFG_TBAT_MAX_REG);
150+
if (ret < 0)
151+
return ret;
152+
153+
val->intval = ret * 10;
154+
155+
return 0;
156+
case POWER_SUPPLY_PROP_MODEL_NAME:
157+
val->strval = "lt8491";
158+
159+
return 0;
160+
case POWER_SUPPLY_PROP_MANUFACTURER:
161+
val->strval = "Linear Technology Corporation";
162+
163+
return 0;
164+
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
165+
ret = lt8491_read_serial_number(info, strval);
166+
if (ret)
167+
return ret;
168+
169+
val->strval = strval;
170+
171+
return 0;
172+
default:
173+
return -EINVAL;
174+
}
175+
}
176+
177+
static int lt8491_set_property(struct power_supply *psy,
178+
enum power_supply_property psp,
179+
const union power_supply_propval *val)
180+
{
181+
struct lt8491_info *info = power_supply_get_drvdata(psy);
182+
183+
switch (psp) {
184+
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
185+
return i2c_smbus_write_byte_data(info->client,
186+
LT8491_CFG_TBAT_MIN_REG,
187+
val->intval / 10);
188+
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
189+
return i2c_smbus_write_byte_data(info->client,
190+
LT8491_CFG_TBAT_MAX_REG,
191+
val->intval / 10);
192+
default:
193+
return -EINVAL;
194+
}
195+
}
196+
197+
static int lt8491_property_is_writeable(struct power_supply *psy,
198+
enum power_supply_property psp)
199+
{
200+
switch (psp) {
201+
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
202+
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
203+
return 1;
204+
default:
205+
return 0;
206+
}
207+
}
208+
209+
static enum power_supply_property lt8491_properties[] = {
210+
POWER_SUPPLY_PROP_STATUS,
211+
POWER_SUPPLY_PROP_VOLTAGE_NOW,
212+
POWER_SUPPLY_PROP_CURRENT_NOW,
213+
POWER_SUPPLY_PROP_POWER_NOW,
214+
POWER_SUPPLY_PROP_TEMP,
215+
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
216+
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
217+
POWER_SUPPLY_PROP_MODEL_NAME,
218+
POWER_SUPPLY_PROP_MANUFACTURER,
219+
POWER_SUPPLY_PROP_SERIAL_NUMBER,
220+
};
221+
222+
static const struct power_supply_desc lt8491_desc = {
223+
.name = "lt8491",
224+
.type = POWER_SUPPLY_TYPE_BATTERY,
225+
.properties = lt8491_properties,
226+
.num_properties = ARRAY_SIZE(lt8491_properties),
227+
.get_property = lt8491_get_property,
228+
.set_property = lt8491_set_property,
229+
.property_is_writeable = lt8491_property_is_writeable,
230+
};
231+
232+
static int lt8491_configure_resistor(struct i2c_client *client,
233+
const char *propname, int divider,
234+
unsigned int reg)
235+
{
236+
struct lt8491_info *info = i2c_get_clientdata(client);
237+
struct device *dev = &client->dev;
238+
int ret;
239+
u32 val;
240+
241+
ret = device_property_read_u32(dev, propname, &val);
242+
if (ret)
243+
return dev_err_probe(dev, ret, "Missing %s property.\n", propname);
244+
245+
ret = i2c_smbus_write_word_data(info->client, reg, val / divider);
246+
if (ret)
247+
return ret;
248+
249+
return val;
250+
}
251+
252+
static int lt8491_configure_telemetry(struct i2c_client *client)
253+
{
254+
int ret;
255+
256+
ret = lt8491_configure_resistor(client, "adi,rsense1-micro-ohms", 10,
257+
LT8491_CFG_RSENSE1_REG);
258+
if (ret < 0)
259+
return ret;
260+
261+
ret = lt8491_configure_resistor(client, "adi,rimon-out-ohms", 10,
262+
LT8491_CFG_RIMON_OUT_REG);
263+
if (ret < 0)
264+
return ret;
265+
266+
ret = lt8491_configure_resistor(client, "adi,rsense2-micro-ohms", 10,
267+
LT8491_CFG_RSENSE2_REG);
268+
if (ret < 0)
269+
return ret;
270+
271+
ret = lt8491_configure_resistor(client, "adi,rdaco-ohms", 10,
272+
LT8491_CFG_RDACO_REG);
273+
if (ret < 0)
274+
return ret;
275+
276+
ret = lt8491_configure_resistor(client, "adi,rfbout1-ohms", 100,
277+
LT8491_CFG_RFBOUT1_REG);
278+
if (ret < 0)
279+
return ret;
280+
281+
ret = lt8491_configure_resistor(client, "adi,rfbout2-ohms", 10,
282+
LT8491_CFG_RFBOUT2_REG);
283+
if (ret < 0)
284+
return ret;
285+
286+
ret = lt8491_configure_resistor(client, "adi,rdaci-ohms", 10,
287+
LT8491_CFG_RDACI_REG);
288+
if (ret < 0)
289+
return ret;
290+
291+
ret = lt8491_configure_resistor(client, "adi,rfbin2-ohms", 10,
292+
LT8491_CFG_RFBIN2_REG);
293+
if (ret < 0)
294+
return ret;
295+
296+
ret = lt8491_configure_resistor(client, "adi,rfbin1-ohms", 100,
297+
LT8491_CFG_RFBIN1_REG);
298+
if (ret < 0)
299+
return ret;
300+
301+
return 0;
302+
}
303+
304+
static int lt8491_probe(struct i2c_client *client)
305+
{
306+
struct device *dev = &client->dev;
307+
struct lt8491_info *info;
308+
struct power_supply_config psy_cfg = {};
309+
int ret;
310+
311+
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
312+
I2C_FUNC_SMBUS_READ_WORD_DATA))
313+
return -EOPNOTSUPP;
314+
315+
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
316+
if (!info)
317+
return -ENOMEM;
318+
319+
i2c_set_clientdata(client, info);
320+
info->client = client;
321+
psy_cfg.drv_data = info;
322+
323+
ret = lt8491_configure_telemetry(client);
324+
if (ret)
325+
return ret;
326+
327+
info->psp = power_supply_register(dev, &lt8491_desc, &psy_cfg);
328+
if (IS_ERR(info->psp))
329+
return dev_err_probe(dev, PTR_ERR(info->psp),
330+
"Failed to register power supply.\n");
331+
332+
return 0;
333+
}
334+
335+
static const struct i2c_device_id lt8491_id[] = {
336+
{ "lt8491", 0 },
337+
{ }
338+
};
339+
MODULE_DEVICE_TABLE(i2c, lt8491_id);
340+
341+
static const struct of_device_id lt8491_of_match[] = {
342+
{ .compatible = "adi,lt8491" },
343+
{ }
344+
};
345+
MODULE_DEVICE_TABLE(of, lt8491_of_match);
346+
347+
static struct i2c_driver lt8491_driver = {
348+
.driver = {
349+
.name = "lt8491",
350+
.of_match_table = lt8491_of_match,
351+
},
352+
.probe_new = lt8491_probe,
353+
.id_table = lt8491_id,
354+
};
355+
module_i2c_driver(lt8491_driver);
356+
357+
MODULE_AUTHOR("John Erasmus Mari Geronimo <johnerasmusmari.geronimo@analog.com");
358+
MODULE_DESCRIPTION("LT8491 battery charger");
359+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)