]>
Commit | Line | Data |
---|---|---|
bfdb46ce RM |
1 | /* |
2 | * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC | |
3 | * | |
4 | * Copyright (C) 2009 Bluewater Systems Ltd | |
5 | * | |
6 | * Author: Ryan Mallon <ryan@bluewatersys.com> | |
7 | * | |
9b9ade6b YV |
8 | * DS2786 added by Yulia Vilensky <vilensky@compulab.co.il> |
9 | * | |
bfdb46ce RM |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | */ | |
15 | ||
16 | #include <linux/kernel.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/types.h> | |
19 | #include <linux/errno.h> | |
20 | #include <linux/swab.h> | |
21 | #include <linux/i2c.h> | |
22 | #include <linux/idr.h> | |
23 | #include <linux/power_supply.h> | |
5a0e3ad6 | 24 | #include <linux/slab.h> |
9b9ade6b | 25 | #include <linux/ds2782_battery.h> |
bfdb46ce RM |
26 | |
27 | #define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */ | |
28 | ||
9b9ade6b YV |
29 | #define DS278x_REG_VOLT_MSB 0x0c |
30 | #define DS278x_REG_TEMP_MSB 0x0a | |
31 | #define DS278x_REG_CURRENT_MSB 0x0e | |
bfdb46ce RM |
32 | |
33 | /* EEPROM Block */ | |
34 | #define DS2782_REG_RSNSP 0x69 /* Sense resistor value */ | |
35 | ||
36 | /* Current unit measurement in uA for a 1 milli-ohm sense resistor */ | |
37 | #define DS2782_CURRENT_UNITS 1563 | |
38 | ||
9b9ade6b YV |
39 | #define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */ |
40 | ||
41 | #define DS2786_CURRENT_UNITS 25 | |
42 | ||
43 | struct ds278x_info; | |
44 | ||
45 | struct ds278x_battery_ops { | |
eb9650d6 | 46 | int (*get_battery_current)(struct ds278x_info *info, int *current_uA); |
353f867b RM |
47 | int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV); |
48 | int (*get_battery_capacity)(struct ds278x_info *info, int *capacity); | |
9b9ade6b YV |
49 | }; |
50 | ||
51 | #define to_ds278x_info(x) container_of(x, struct ds278x_info, battery) | |
bfdb46ce | 52 | |
9b9ade6b | 53 | struct ds278x_info { |
bfdb46ce RM |
54 | struct i2c_client *client; |
55 | struct power_supply battery; | |
9b9ade6b | 56 | struct ds278x_battery_ops *ops; |
bfdb46ce | 57 | int id; |
9b9ade6b | 58 | int rsns; |
bfdb46ce RM |
59 | }; |
60 | ||
61 | static DEFINE_IDR(battery_id); | |
62 | static DEFINE_MUTEX(battery_lock); | |
63 | ||
9b9ade6b | 64 | static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val) |
bfdb46ce RM |
65 | { |
66 | int ret; | |
67 | ||
68 | ret = i2c_smbus_read_byte_data(info->client, reg); | |
69 | if (ret < 0) { | |
70 | dev_err(&info->client->dev, "register read failed\n"); | |
71 | return ret; | |
72 | } | |
73 | ||
74 | *val = ret; | |
75 | return 0; | |
76 | } | |
77 | ||
9b9ade6b | 78 | static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb, |
bfdb46ce RM |
79 | s16 *val) |
80 | { | |
81 | int ret; | |
82 | ||
83 | ret = swab16(i2c_smbus_read_word_data(info->client, reg_msb)); | |
84 | if (ret < 0) { | |
85 | dev_err(&info->client->dev, "register read failed\n"); | |
86 | return ret; | |
87 | } | |
88 | ||
89 | *val = ret; | |
90 | return 0; | |
91 | } | |
92 | ||
9b9ade6b | 93 | static int ds278x_get_temp(struct ds278x_info *info, int *temp) |
bfdb46ce RM |
94 | { |
95 | s16 raw; | |
96 | int err; | |
97 | ||
98 | /* | |
99 | * Temperature is measured in units of 0.125 degrees celcius, the | |
100 | * power_supply class measures temperature in tenths of degrees | |
101 | * celsius. The temperature value is stored as a 10 bit number, plus | |
102 | * sign in the upper bits of a 16 bit register. | |
103 | */ | |
9b9ade6b | 104 | err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw); |
bfdb46ce RM |
105 | if (err) |
106 | return err; | |
107 | *temp = ((raw / 32) * 125) / 100; | |
108 | return 0; | |
109 | } | |
110 | ||
9b9ade6b | 111 | static int ds2782_get_current(struct ds278x_info *info, int *current_uA) |
bfdb46ce RM |
112 | { |
113 | int sense_res; | |
114 | int err; | |
115 | u8 sense_res_raw; | |
116 | s16 raw; | |
117 | ||
118 | /* | |
119 | * The units of measurement for current are dependent on the value of | |
120 | * the sense resistor. | |
121 | */ | |
9b9ade6b | 122 | err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw); |
bfdb46ce RM |
123 | if (err) |
124 | return err; | |
125 | if (sense_res_raw == 0) { | |
126 | dev_err(&info->client->dev, "sense resistor value is 0\n"); | |
127 | return -ENXIO; | |
128 | } | |
129 | sense_res = 1000 / sense_res_raw; | |
130 | ||
131 | dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n", | |
132 | sense_res); | |
9b9ade6b | 133 | err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); |
bfdb46ce RM |
134 | if (err) |
135 | return err; | |
136 | *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res); | |
137 | return 0; | |
138 | } | |
139 | ||
353f867b | 140 | static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV) |
bfdb46ce RM |
141 | { |
142 | s16 raw; | |
143 | int err; | |
144 | ||
145 | /* | |
146 | * Voltage is measured in units of 4.88mV. The voltage is stored as | |
147 | * a 10-bit number plus sign, in the upper bits of a 16-bit register | |
148 | */ | |
9b9ade6b | 149 | err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); |
bfdb46ce RM |
150 | if (err) |
151 | return err; | |
353f867b | 152 | *voltage_uV = (raw / 32) * 4800; |
bfdb46ce RM |
153 | return 0; |
154 | } | |
155 | ||
9b9ade6b | 156 | static int ds2782_get_capacity(struct ds278x_info *info, int *capacity) |
bfdb46ce RM |
157 | { |
158 | int err; | |
159 | u8 raw; | |
160 | ||
9b9ade6b | 161 | err = ds278x_read_reg(info, DS2782_REG_RARC, &raw); |
bfdb46ce RM |
162 | if (err) |
163 | return err; | |
164 | *capacity = raw; | |
2d31757c | 165 | return 0; |
bfdb46ce RM |
166 | } |
167 | ||
9b9ade6b YV |
168 | static int ds2786_get_current(struct ds278x_info *info, int *current_uA) |
169 | { | |
170 | int err; | |
171 | s16 raw; | |
172 | ||
173 | err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw); | |
174 | if (err) | |
175 | return err; | |
176 | *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns); | |
177 | return 0; | |
178 | } | |
179 | ||
353f867b | 180 | static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) |
9b9ade6b YV |
181 | { |
182 | s16 raw; | |
183 | int err; | |
184 | ||
185 | /* | |
186 | * Voltage is measured in units of 1.22mV. The voltage is stored as | |
187 | * a 10-bit number plus sign, in the upper bits of a 16-bit register | |
188 | */ | |
189 | err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); | |
190 | if (err) | |
191 | return err; | |
353f867b | 192 | *voltage_uV = (raw / 8) * 1220; |
9b9ade6b YV |
193 | return 0; |
194 | } | |
195 | ||
196 | static int ds2786_get_capacity(struct ds278x_info *info, int *capacity) | |
197 | { | |
198 | int err; | |
199 | u8 raw; | |
200 | ||
201 | err = ds278x_read_reg(info, DS2786_REG_RARC, &raw); | |
202 | if (err) | |
203 | return err; | |
204 | /* Relative capacity is displayed with resolution 0.5 % */ | |
205 | *capacity = raw/2 ; | |
206 | return 0; | |
207 | } | |
208 | ||
209 | static int ds278x_get_status(struct ds278x_info *info, int *status) | |
bfdb46ce RM |
210 | { |
211 | int err; | |
212 | int current_uA; | |
213 | int capacity; | |
214 | ||
eb9650d6 | 215 | err = info->ops->get_battery_current(info, ¤t_uA); |
bfdb46ce RM |
216 | if (err) |
217 | return err; | |
218 | ||
eb9650d6 | 219 | err = info->ops->get_battery_capacity(info, &capacity); |
bfdb46ce RM |
220 | if (err) |
221 | return err; | |
222 | ||
223 | if (capacity == 100) | |
224 | *status = POWER_SUPPLY_STATUS_FULL; | |
225 | else if (current_uA == 0) | |
226 | *status = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
227 | else if (current_uA < 0) | |
228 | *status = POWER_SUPPLY_STATUS_DISCHARGING; | |
229 | else | |
230 | *status = POWER_SUPPLY_STATUS_CHARGING; | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
9b9ade6b | 235 | static int ds278x_battery_get_property(struct power_supply *psy, |
bfdb46ce RM |
236 | enum power_supply_property prop, |
237 | union power_supply_propval *val) | |
238 | { | |
9b9ade6b | 239 | struct ds278x_info *info = to_ds278x_info(psy); |
bfdb46ce RM |
240 | int ret; |
241 | ||
242 | switch (prop) { | |
243 | case POWER_SUPPLY_PROP_STATUS: | |
9b9ade6b | 244 | ret = ds278x_get_status(info, &val->intval); |
bfdb46ce RM |
245 | break; |
246 | ||
247 | case POWER_SUPPLY_PROP_CAPACITY: | |
eb9650d6 | 248 | ret = info->ops->get_battery_capacity(info, &val->intval); |
bfdb46ce RM |
249 | break; |
250 | ||
251 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | |
eb9650d6 | 252 | ret = info->ops->get_battery_voltage(info, &val->intval); |
bfdb46ce RM |
253 | break; |
254 | ||
255 | case POWER_SUPPLY_PROP_CURRENT_NOW: | |
eb9650d6 | 256 | ret = info->ops->get_battery_current(info, &val->intval); |
bfdb46ce RM |
257 | break; |
258 | ||
259 | case POWER_SUPPLY_PROP_TEMP: | |
9b9ade6b | 260 | ret = ds278x_get_temp(info, &val->intval); |
bfdb46ce RM |
261 | break; |
262 | ||
263 | default: | |
264 | ret = -EINVAL; | |
265 | } | |
266 | ||
267 | return ret; | |
268 | } | |
269 | ||
9b9ade6b | 270 | static enum power_supply_property ds278x_battery_props[] = { |
bfdb46ce RM |
271 | POWER_SUPPLY_PROP_STATUS, |
272 | POWER_SUPPLY_PROP_CAPACITY, | |
273 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | |
274 | POWER_SUPPLY_PROP_CURRENT_NOW, | |
275 | POWER_SUPPLY_PROP_TEMP, | |
276 | }; | |
277 | ||
9b9ade6b | 278 | static void ds278x_power_supply_init(struct power_supply *battery) |
bfdb46ce RM |
279 | { |
280 | battery->type = POWER_SUPPLY_TYPE_BATTERY; | |
9b9ade6b YV |
281 | battery->properties = ds278x_battery_props; |
282 | battery->num_properties = ARRAY_SIZE(ds278x_battery_props); | |
283 | battery->get_property = ds278x_battery_get_property; | |
bfdb46ce RM |
284 | battery->external_power_changed = NULL; |
285 | } | |
286 | ||
9b9ade6b | 287 | static int ds278x_battery_remove(struct i2c_client *client) |
bfdb46ce | 288 | { |
9b9ade6b | 289 | struct ds278x_info *info = i2c_get_clientdata(client); |
bfdb46ce RM |
290 | |
291 | power_supply_unregister(&info->battery); | |
292 | kfree(info->battery.name); | |
293 | ||
294 | mutex_lock(&battery_lock); | |
295 | idr_remove(&battery_id, info->id); | |
296 | mutex_unlock(&battery_lock); | |
297 | ||
bfdb46ce RM |
298 | kfree(info); |
299 | return 0; | |
300 | } | |
301 | ||
ab6cc8f9 AV |
302 | enum ds278x_num_id { |
303 | DS2782 = 0, | |
304 | DS2786, | |
305 | }; | |
306 | ||
9b9ade6b | 307 | static struct ds278x_battery_ops ds278x_ops[] = { |
ab6cc8f9 | 308 | [DS2782] = { |
eb9650d6 PH |
309 | .get_battery_current = ds2782_get_current, |
310 | .get_battery_voltage = ds2782_get_voltage, | |
311 | .get_battery_capacity = ds2782_get_capacity, | |
9b9ade6b | 312 | }, |
ab6cc8f9 | 313 | [DS2786] = { |
eb9650d6 PH |
314 | .get_battery_current = ds2786_get_current, |
315 | .get_battery_voltage = ds2786_get_voltage, | |
316 | .get_battery_capacity = ds2786_get_capacity, | |
9b9ade6b YV |
317 | } |
318 | }; | |
319 | ||
320 | static int ds278x_battery_probe(struct i2c_client *client, | |
bfdb46ce RM |
321 | const struct i2c_device_id *id) |
322 | { | |
9b9ade6b YV |
323 | struct ds278x_platform_data *pdata = client->dev.platform_data; |
324 | struct ds278x_info *info; | |
bfdb46ce RM |
325 | int ret; |
326 | int num; | |
327 | ||
9b9ade6b YV |
328 | /* |
329 | * ds2786 should have the sense resistor value set | |
330 | * in the platform data | |
331 | */ | |
ab6cc8f9 | 332 | if (id->driver_data == DS2786 && !pdata) { |
9b9ade6b YV |
333 | dev_err(&client->dev, "missing platform data for ds2786\n"); |
334 | return -EINVAL; | |
335 | } | |
336 | ||
bfdb46ce RM |
337 | /* Get an ID for this battery */ |
338 | ret = idr_pre_get(&battery_id, GFP_KERNEL); | |
339 | if (ret == 0) { | |
340 | ret = -ENOMEM; | |
341 | goto fail_id; | |
342 | } | |
343 | ||
344 | mutex_lock(&battery_lock); | |
345 | ret = idr_get_new(&battery_id, client, &num); | |
346 | mutex_unlock(&battery_lock); | |
347 | if (ret < 0) | |
348 | goto fail_id; | |
349 | ||
350 | info = kzalloc(sizeof(*info), GFP_KERNEL); | |
351 | if (!info) { | |
352 | ret = -ENOMEM; | |
353 | goto fail_info; | |
354 | } | |
355 | ||
9b9ade6b | 356 | info->battery.name = kasprintf(GFP_KERNEL, "%s-%d", client->name, num); |
bfdb46ce RM |
357 | if (!info->battery.name) { |
358 | ret = -ENOMEM; | |
359 | goto fail_name; | |
360 | } | |
361 | ||
ab6cc8f9 | 362 | if (id->driver_data == DS2786) |
9b9ade6b YV |
363 | info->rsns = pdata->rsns; |
364 | ||
bfdb46ce RM |
365 | i2c_set_clientdata(client, info); |
366 | info->client = client; | |
9b9ade6b YV |
367 | info->id = num; |
368 | info->ops = &ds278x_ops[id->driver_data]; | |
369 | ds278x_power_supply_init(&info->battery); | |
bfdb46ce RM |
370 | |
371 | ret = power_supply_register(&client->dev, &info->battery); | |
372 | if (ret) { | |
373 | dev_err(&client->dev, "failed to register battery\n"); | |
374 | goto fail_register; | |
375 | } | |
376 | ||
377 | return 0; | |
378 | ||
379 | fail_register: | |
380 | kfree(info->battery.name); | |
381 | fail_name: | |
bfdb46ce RM |
382 | kfree(info); |
383 | fail_info: | |
384 | mutex_lock(&battery_lock); | |
385 | idr_remove(&battery_id, num); | |
386 | mutex_unlock(&battery_lock); | |
387 | fail_id: | |
388 | return ret; | |
389 | } | |
390 | ||
9b9ade6b | 391 | static const struct i2c_device_id ds278x_id[] = { |
ab6cc8f9 AV |
392 | {"ds2782", DS2782}, |
393 | {"ds2786", DS2786}, | |
bfdb46ce RM |
394 | {}, |
395 | }; | |
84ab16f5 | 396 | MODULE_DEVICE_TABLE(i2c, ds278x_id); |
bfdb46ce | 397 | |
9b9ade6b | 398 | static struct i2c_driver ds278x_battery_driver = { |
bfdb46ce RM |
399 | .driver = { |
400 | .name = "ds2782-battery", | |
401 | }, | |
9b9ade6b YV |
402 | .probe = ds278x_battery_probe, |
403 | .remove = ds278x_battery_remove, | |
404 | .id_table = ds278x_id, | |
bfdb46ce RM |
405 | }; |
406 | ||
9b9ade6b | 407 | static int __init ds278x_init(void) |
bfdb46ce | 408 | { |
9b9ade6b | 409 | return i2c_add_driver(&ds278x_battery_driver); |
bfdb46ce | 410 | } |
9b9ade6b | 411 | module_init(ds278x_init); |
bfdb46ce | 412 | |
9b9ade6b | 413 | static void __exit ds278x_exit(void) |
bfdb46ce | 414 | { |
9b9ade6b | 415 | i2c_del_driver(&ds278x_battery_driver); |
bfdb46ce | 416 | } |
9b9ade6b | 417 | module_exit(ds278x_exit); |
bfdb46ce RM |
418 | |
419 | MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>"); | |
420 | MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver"); | |
421 | MODULE_LICENSE("GPL"); |