Skip to content

Commit 2a7a9ee

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 <[email protected]>
1 parent 62596c7 commit 2a7a9ee

File tree

3 files changed

+381
-0
lines changed

3 files changed

+381
-0
lines changed

drivers/power/supply/Kconfig

+9
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

+1
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

drivers/power/supply/lt8491_charger.c

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

0 commit comments

Comments
 (0)