Blame view

drivers/power/supply/bq27xxx_battery_i2c.c 6.82 KB
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
1
  /*
5d9e01b31   Andrew F. Davis   power_supply: bq2...
2
   * BQ27xxx battery monitor I2C driver
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   *
   * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
   *	Andrew F. Davis <afd@ti.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   *
   * This program is distributed "as is" WITHOUT ANY WARRANTY of any
   * kind, whether express or implied; without even the implied warranty
   * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   */
  
  #include <linux/i2c.h>
  #include <linux/interrupt.h>
  #include <linux/module.h>
  #include <asm/unaligned.h>
  
  #include <linux/power/bq27xxx_battery.h>
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
23
24
  static DEFINE_IDR(battery_id);
  static DEFINE_MUTEX(battery_mutex);
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data)
  {
  	struct bq27xxx_device_info *di = data;
  
  	bq27xxx_battery_update(di);
  
  	return IRQ_HANDLED;
  }
  
  static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
  				    bool single)
  {
  	struct i2c_client *client = to_i2c_client(di->dev);
  	struct i2c_msg msg[2];
14073f661   Matt Ranostay   power: supply: bq...
39
  	u8 data[2];
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
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
  	int ret;
  
  	if (!client->adapter)
  		return -ENODEV;
  
  	msg[0].addr = client->addr;
  	msg[0].flags = 0;
  	msg[0].buf = &reg;
  	msg[0].len = sizeof(reg);
  	msg[1].addr = client->addr;
  	msg[1].flags = I2C_M_RD;
  	msg[1].buf = data;
  	if (single)
  		msg[1].len = 1;
  	else
  		msg[1].len = 2;
  
  	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
  	if (ret < 0)
  		return ret;
  
  	if (!single)
  		ret = get_unaligned_le16(data);
  	else
  		ret = data[0];
  
  	return ret;
  }
14073f661   Matt Ranostay   power: supply: bq...
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
  static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg,
  				     int value, bool single)
  {
  	struct i2c_client *client = to_i2c_client(di->dev);
  	struct i2c_msg msg;
  	u8 data[4];
  	int ret;
  
  	if (!client->adapter)
  		return -ENODEV;
  
  	data[0] = reg;
  	if (single) {
  		data[1] = (u8) value;
  		msg.len = 2;
  	} else {
  		put_unaligned_le16(value, &data[1]);
  		msg.len = 3;
  	}
  
  	msg.buf = data;
  	msg.addr = client->addr;
  	msg.flags = 0;
  
  	ret = i2c_transfer(client->adapter, &msg, 1);
  	if (ret < 0)
  		return ret;
  	if (ret != 1)
  		return -EINVAL;
  	return 0;
  }
  
  static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg,
  					 u8 *data, int len)
  {
  	struct i2c_client *client = to_i2c_client(di->dev);
  	int ret;
  
  	if (!client->adapter)
  		return -ENODEV;
  
  	ret = i2c_smbus_read_i2c_block_data(client, reg, len, data);
  	if (ret < 0)
  		return ret;
  	if (ret != len)
  		return -EINVAL;
  	return 0;
  }
  
  static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di,
  					  u8 reg, u8 *data, int len)
  {
  	struct i2c_client *client = to_i2c_client(di->dev);
  	struct i2c_msg msg;
  	u8 buf[33];
  	int ret;
  
  	if (!client->adapter)
  		return -ENODEV;
  
  	buf[0] = reg;
  	memcpy(&buf[1], data, len);
  
  	msg.buf = buf;
  	msg.addr = client->addr;
  	msg.flags = 0;
  	msg.len = len + 1;
  
  	ret = i2c_transfer(client->adapter, &msg, 1);
  	if (ret < 0)
  		return ret;
  	if (ret != 1)
  		return -EINVAL;
  	return 0;
  }
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
143
144
145
146
147
  static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
  				     const struct i2c_device_id *id)
  {
  	struct bq27xxx_device_info *di;
  	int ret;
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
148
149
150
151
152
153
154
155
156
157
158
159
160
  	char *name;
  	int num;
  
  	/* Get new ID for the new battery device */
  	mutex_lock(&battery_mutex);
  	num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL);
  	mutex_unlock(&battery_mutex);
  	if (num < 0)
  		return num;
  
  	name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num);
  	if (!name)
  		goto err_mem;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
161
162
163
  
  	di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL);
  	if (!di)
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
164
  		goto err_mem;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
165

9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
166
  	di->id = num;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
167
168
  	di->dev = &client->dev;
  	di->chip = id->driver_data;
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
169
  	di->name = name;
14073f661   Matt Ranostay   power: supply: bq...
170

703df6c09   Andrew F. Davis   power: bq27xxx_ba...
171
  	di->bus.read = bq27xxx_battery_i2c_read;
14073f661   Matt Ranostay   power: supply: bq...
172
173
174
  	di->bus.write = bq27xxx_battery_i2c_write;
  	di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read;
  	di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
175
176
177
  
  	ret = bq27xxx_battery_setup(di);
  	if (ret)
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
178
  		goto err_failed;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
  
  	/* Schedule a polling after about 1 min */
  	schedule_delayed_work(&di->work, 60 * HZ);
  
  	i2c_set_clientdata(client, di);
  
  	if (client->irq) {
  		ret = devm_request_threaded_irq(&client->dev, client->irq,
  				NULL, bq27xxx_battery_irq_handler_thread,
  				IRQF_ONESHOT,
  				di->name, di);
  		if (ret) {
  			dev_err(&client->dev,
  				"Unable to register IRQ %d error %d
  ",
  				client->irq, ret);
  			return ret;
  		}
  	}
  
  	return 0;
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
200
201
202
203
204
205
206
207
208
209
  
  err_mem:
  	ret = -ENOMEM;
  
  err_failed:
  	mutex_lock(&battery_mutex);
  	idr_remove(&battery_id, num);
  	mutex_unlock(&battery_mutex);
  
  	return ret;
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
210
211
212
213
214
215
216
  }
  
  static int bq27xxx_battery_i2c_remove(struct i2c_client *client)
  {
  	struct bq27xxx_device_info *di = i2c_get_clientdata(client);
  
  	bq27xxx_battery_teardown(di);
9aafabc7f   Ivaylo Dimitrov   power: bq27xxx_ba...
217
218
219
  	mutex_lock(&battery_mutex);
  	idr_remove(&battery_id, di->id);
  	mutex_unlock(&battery_mutex);
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
220
221
222
223
224
225
  	return 0;
  }
  
  static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
  	{ "bq27200", BQ27000 },
  	{ "bq27210", BQ27010 },
818e3012c   Chris Lapa   power: supply: bq...
226
  	{ "bq27500", BQ2750X },
6da6e4bdd   Chris Lapa   power: supply: bq...
227
  	{ "bq27510", BQ2751X },
3a731c641   Liam Breck   power: supply: bq...
228
  	{ "bq27520", BQ2752X },
32833635b   Chris Lapa   power: supply: bq...
229
  	{ "bq27500-1", BQ27500 },
bd28177f3   Chris Lapa   power: supply: bq...
230
  	{ "bq27510g1", BQ27510G1 },
698a2bf5f   Chris Lapa   power: supply: bq...
231
  	{ "bq27510g2", BQ27510G2 },
71375aa7d   Chris Lapa   power: supply: bq...
232
  	{ "bq27510g3", BQ27510G3 },
68f2a813e   Chris Lapa   power: supply: bq...
233
  	{ "bq27520g1", BQ27520G1 },
a5deb9a93   Chris Lapa   power: supply: bq...
234
  	{ "bq27520g2", BQ27520G2 },
825e915ba   Chris Lapa   power: supply: bq...
235
  	{ "bq27520g3", BQ27520G3 },
8835cae5f   Chris Lapa   power: supply: bq...
236
  	{ "bq27520g4", BQ27520G4 },
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
237
  	{ "bq27530", BQ27530 },
3a731c641   Liam Breck   power: supply: bq...
238
  	{ "bq27531", BQ27531 },
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
239
  	{ "bq27541", BQ27541 },
3a731c641   Liam Breck   power: supply: bq...
240
241
242
  	{ "bq27542", BQ27542 },
  	{ "bq27546", BQ27546 },
  	{ "bq27742", BQ27742 },
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
243
244
  	{ "bq27545", BQ27545 },
  	{ "bq27421", BQ27421 },
3a731c641   Liam Breck   power: supply: bq...
245
246
247
  	{ "bq27425", BQ27425 },
  	{ "bq27441", BQ27441 },
  	{ "bq27621", BQ27621 },
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
248
249
250
  	{},
  };
  MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table);
7df3a7468   Pali Rohár   power_supply: bq2...
251
252
253
254
255
256
257
  #ifdef CONFIG_OF
  static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
  	{ .compatible = "ti,bq27200" },
  	{ .compatible = "ti,bq27210" },
  	{ .compatible = "ti,bq27500" },
  	{ .compatible = "ti,bq27510" },
  	{ .compatible = "ti,bq27520" },
32833635b   Chris Lapa   power: supply: bq...
258
  	{ .compatible = "ti,bq27500-1" },
bd28177f3   Chris Lapa   power: supply: bq...
259
  	{ .compatible = "ti,bq27510g1" },
698a2bf5f   Chris Lapa   power: supply: bq...
260
  	{ .compatible = "ti,bq27510g2" },
71375aa7d   Chris Lapa   power: supply: bq...
261
  	{ .compatible = "ti,bq27510g3" },
68f2a813e   Chris Lapa   power: supply: bq...
262
  	{ .compatible = "ti,bq27520g1" },
a5deb9a93   Chris Lapa   power: supply: bq...
263
  	{ .compatible = "ti,bq27520g2" },
825e915ba   Chris Lapa   power: supply: bq...
264
  	{ .compatible = "ti,bq27520g3" },
8835cae5f   Chris Lapa   power: supply: bq...
265
  	{ .compatible = "ti,bq27520g4" },
7df3a7468   Pali Rohár   power_supply: bq2...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
  	{ .compatible = "ti,bq27530" },
  	{ .compatible = "ti,bq27531" },
  	{ .compatible = "ti,bq27541" },
  	{ .compatible = "ti,bq27542" },
  	{ .compatible = "ti,bq27546" },
  	{ .compatible = "ti,bq27742" },
  	{ .compatible = "ti,bq27545" },
  	{ .compatible = "ti,bq27421" },
  	{ .compatible = "ti,bq27425" },
  	{ .compatible = "ti,bq27441" },
  	{ .compatible = "ti,bq27621" },
  	{},
  };
  MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table);
  #endif
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
281
282
283
  static struct i2c_driver bq27xxx_battery_i2c_driver = {
  	.driver = {
  		.name = "bq27xxx-battery",
7df3a7468   Pali Rohár   power_supply: bq2...
284
  		.of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table),
703df6c09   Andrew F. Davis   power: bq27xxx_ba...
285
286
287
288
289
290
291
292
293
294
  	},
  	.probe = bq27xxx_battery_i2c_probe,
  	.remove = bq27xxx_battery_i2c_remove,
  	.id_table = bq27xxx_i2c_id_table,
  };
  module_i2c_driver(bq27xxx_battery_i2c_driver);
  
  MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
  MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver");
  MODULE_LICENSE("GPL");