File Index
Files and directories
Sign in
FILE

bq27xxx_battery_i2c.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/bq27xxx_battery_i2c.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * BQ27xxx battery monitor I2C driver
  4 *
  5 * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/
  6 *	Andrew F. Davis <afd@ti.com>
  7 */
  8
  9#include <linux/delay.h>
 10#include <linux/i2c.h>
 11#include <linux/interrupt.h>
 12#include <linux/module.h>
 13#include <linux/unaligned.h>
 14
 15#include <linux/power/bq27xxx_battery.h>
 16
 17static DEFINE_IDA(battery_id);
 18
 19static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data)
 20{
 21	struct bq27xxx_device_info *di = data;
 22
 23	bq27xxx_battery_update(di);
 24
 25	return IRQ_HANDLED;
 26}
 27
 28static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
 29				    bool single)
 30{
 31	struct i2c_client *client = to_i2c_client(di->dev);
 32	struct i2c_msg msg[2];
 33	u8 data[2];
 34	int ret;
 35	int retry = 0;
 36
 37	if (!client->adapter)
 38		return -ENODEV;
 39
 40	msg[0].addr = client->addr;
 41	msg[0].flags = 0;
 42	msg[0].buf = &reg;
 43	msg[0].len = sizeof(reg);
 44	msg[1].addr = client->addr;
 45	msg[1].flags = I2C_M_RD;
 46	msg[1].buf = data;
 47	if (single)
 48		msg[1].len = 1;
 49	else
 50		msg[1].len = 2;
 51
 52	do {
 53		ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
 54		if (ret == -EBUSY && ++retry < 3) {
 55			/* sleep 10 milliseconds when busy */
 56			usleep_range(10000, 11000);
 57			continue;
 58		}
 59		break;
 60	} while (1);
 61
 62	if (ret < 0)
 63		return ret;
 64
 65	if (!single)
 66		ret = get_unaligned_le16(data);
 67	else
 68		ret = data[0];
 69
 70	return ret;
 71}
 72
 73static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg,
 74				     int value, bool single)
 75{
 76	struct i2c_client *client = to_i2c_client(di->dev);
 77	struct i2c_msg msg;
 78	u8 data[4];
 79	int ret;
 80
 81	if (!client->adapter)
 82		return -ENODEV;
 83
 84	data[0] = reg;
 85	if (single) {
 86		data[1] = (u8) value;
 87		msg.len = 2;
 88	} else {
 89		put_unaligned_le16(value, &data[1]);
 90		msg.len = 3;
 91	}
 92
 93	msg.buf = data;
 94	msg.addr = client->addr;
 95	msg.flags = 0;
 96
 97	ret = i2c_transfer(client->adapter, &msg, 1);
 98	if (ret < 0)
 99		return ret;
100	if (ret != 1)
101		return -EINVAL;
102	return 0;
103}
104
105static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg,
106					 u8 *data, int len)
107{
108	struct i2c_client *client = to_i2c_client(di->dev);
109	int ret;
110
111	if (!client->adapter)
112		return -ENODEV;
113
114	ret = i2c_smbus_read_i2c_block_data(client, reg, len, data);
115	if (ret < 0)
116		return ret;
117	if (ret != len)
118		return -EINVAL;
119	return 0;
120}
121
122static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di,
123					  u8 reg, u8 *data, int len)
124{
125	struct i2c_client *client = to_i2c_client(di->dev);
126	struct i2c_msg msg;
127	u8 buf[33];
128	int ret;
129
130	if (!client->adapter)
131		return -ENODEV;
132
133	buf[0] = reg;
134	memcpy(&buf[1], data, len);
135
136	msg.buf = buf;
137	msg.addr = client->addr;
138	msg.flags = 0;
139	msg.len = len + 1;
140
141	ret = i2c_transfer(client->adapter, &msg, 1);
142	if (ret < 0)
143		return ret;
144	if (ret != 1)
145		return -EINVAL;
146	return 0;
147}
148
149static void bq27xxx_battery_i2c_devm_ida_free(void *data)
150{
151	int num = (long)data;
152
153	ida_free(&battery_id, num);
154}
155
156static int bq27xxx_battery_i2c_probe(struct i2c_client *client)
157{
158	const struct i2c_device_id *id = i2c_client_get_device_id(client);
159	struct bq27xxx_device_info *di;
160	int ret;
161	char *name;
162	long num;
163
164	/* Get new ID for the new battery device */
165	num = ida_alloc(&battery_id, GFP_KERNEL);
166	if (num < 0)
167		return num;
168	ret = devm_add_action_or_reset(&client->dev,
169				       bq27xxx_battery_i2c_devm_ida_free,
170				       (void *)num);
171	if (ret)
172		return ret;
173
174	name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%ld", id->name, num);
175	if (!name)
176		return -ENOMEM;
177
178	di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL);
179	if (!di)
180		return -ENOMEM;
181
182	di->dev = &client->dev;
183	di->chip = id->driver_data;
184	di->name = name;
185
186	di->bus.read = bq27xxx_battery_i2c_read;
187	di->bus.write = bq27xxx_battery_i2c_write;
188	di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read;
189	di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write;
190
191	ret = bq27xxx_battery_setup(di);
192	if (ret)
193		return ret;
194
195	/* Schedule a polling after about 1 min */
196	schedule_delayed_work(&di->work, 60 * HZ);
197
198	i2c_set_clientdata(client, di);
199
200	if (client->irq) {
201		ret = request_threaded_irq(client->irq,
202				NULL, bq27xxx_battery_irq_handler_thread,
203				IRQF_ONESHOT,
204				di->name, di);
205		if (ret) {
206			dev_err(&client->dev,
207				"Unable to register IRQ %d error %d\n",
208				client->irq, ret);
209			bq27xxx_battery_teardown(di);
210			return ret;
211		}
212	}
213
214	return 0;
215}
216
217static void bq27xxx_battery_i2c_remove(struct i2c_client *client)
218{
219	struct bq27xxx_device_info *di = i2c_get_clientdata(client);
220
221	if (client->irq)
222		free_irq(client->irq, di);
223
224	bq27xxx_battery_teardown(di);
225}
226
227static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
228	{ "bq27200", BQ27000 },
229	{ "bq27210", BQ27010 },
230	{ "bq27500", BQ2750X },
231	{ "bq27510", BQ2751X },
232	{ "bq27520", BQ2752X },
233	{ "bq27500-1", BQ27500 },
234	{ "bq27510g1", BQ27510G1 },
235	{ "bq27510g2", BQ27510G2 },
236	{ "bq27510g3", BQ27510G3 },
237	{ "bq27520g1", BQ27520G1 },
238	{ "bq27520g2", BQ27520G2 },
239	{ "bq27520g3", BQ27520G3 },
240	{ "bq27520g4", BQ27520G4 },
241	{ "bq27521", BQ27521 },
242	{ "bq27530", BQ27530 },
243	{ "bq27531", BQ27531 },
244	{ "bq27541", BQ27541 },
245	{ "bq27542", BQ27542 },
246	{ "bq27546", BQ27546 },
247	{ "bq27742", BQ27742 },
248	{ "bq27545", BQ27545 },
249	{ "bq27411", BQ27411 },
250	{ "bq27421", BQ27421 },
251	{ "bq27425", BQ27425 },
252	{ "bq27426", BQ27426 },
253	{ "bq27441", BQ27441 },
254	{ "bq27621", BQ27621 },
255	{ "bq27z561", BQ27Z561 },
256	{ "bq28z610", BQ28Z610 },
257	{ "bq34z100", BQ34Z100 },
258	{ "bq78z100", BQ78Z100 },
259	{},
260};
261MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table);
262
263#ifdef CONFIG_OF
264static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
265	{ .compatible = "ti,bq27200" },
266	{ .compatible = "ti,bq27210" },
267	{ .compatible = "ti,bq27500" },
268	{ .compatible = "ti,bq27510" },
269	{ .compatible = "ti,bq27520" },
270	{ .compatible = "ti,bq27500-1" },
271	{ .compatible = "ti,bq27510g1" },
272	{ .compatible = "ti,bq27510g2" },
273	{ .compatible = "ti,bq27510g3" },
274	{ .compatible = "ti,bq27520g1" },
275	{ .compatible = "ti,bq27520g2" },
276	{ .compatible = "ti,bq27520g3" },
277	{ .compatible = "ti,bq27520g4" },
278	{ .compatible = "ti,bq27521" },
279	{ .compatible = "ti,bq27530" },
280	{ .compatible = "ti,bq27531" },
281	{ .compatible = "ti,bq27541" },
282	{ .compatible = "ti,bq27542" },
283	{ .compatible = "ti,bq27546" },
284	{ .compatible = "ti,bq27742" },
285	{ .compatible = "ti,bq27545" },
286	{ .compatible = "ti,bq27411" },
287	{ .compatible = "ti,bq27421" },
288	{ .compatible = "ti,bq27425" },
289	{ .compatible = "ti,bq27426" },
290	{ .compatible = "ti,bq27441" },
291	{ .compatible = "ti,bq27621" },
292	{ .compatible = "ti,bq27z561" },
293	{ .compatible = "ti,bq28z610" },
294	{ .compatible = "ti,bq34z100" },
295	{ .compatible = "ti,bq78z100" },
296	{},
297};
298MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table);
299#endif
300
301static struct i2c_driver bq27xxx_battery_i2c_driver = {
302	.driver = {
303		.name = "bq27xxx-battery",
304		.of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table),
305		.pm = &bq27xxx_battery_battery_pm_ops,
306	},
307	.probe = bq27xxx_battery_i2c_probe,
308	.remove = bq27xxx_battery_i2c_remove,
309	.id_table = bq27xxx_i2c_id_table,
310};
311module_i2c_driver(bq27xxx_battery_i2c_driver);
312
313MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
314MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver");
315MODULE_LICENSE("GPL");
~/ Read-only file preview.