diff options
Diffstat (limited to 'drivers/power/lp8788-charger.c')
-rw-r--r-- | drivers/power/lp8788-charger.c | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c new file mode 100644 index 00000000000..e852d12cd07 --- /dev/null +++ b/drivers/power/lp8788-charger.c | |||
@@ -0,0 +1,795 @@ | |||
1 | /* | ||
2 | * TI LP8788 MFD - battery charger driver | ||
3 | * | ||
4 | * Copyright 2012 Texas Instruments | ||
5 | * | ||
6 | * Author: Milo(Woogyom) Kim <milo.kim@ti.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | * | ||
12 | */ | ||
13 | |||
14 | #include <linux/err.h> | ||
15 | #include <linux/iio/consumer.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/irqdomain.h> | ||
18 | #include <linux/mfd/lp8788.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/platform_device.h> | ||
21 | #include <linux/power_supply.h> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/workqueue.h> | ||
24 | |||
25 | /* register address */ | ||
26 | #define LP8788_CHG_STATUS 0x07 | ||
27 | #define LP8788_CHG_IDCIN 0x13 | ||
28 | #define LP8788_CHG_IBATT 0x14 | ||
29 | #define LP8788_CHG_VTERM 0x15 | ||
30 | #define LP8788_CHG_EOC 0x16 | ||
31 | |||
32 | /* mask/shift bits */ | ||
33 | #define LP8788_CHG_INPUT_STATE_M 0x03 /* Addr 07h */ | ||
34 | #define LP8788_CHG_STATE_M 0x3C | ||
35 | #define LP8788_CHG_STATE_S 2 | ||
36 | #define LP8788_NO_BATT_M BIT(6) | ||
37 | #define LP8788_BAD_BATT_M BIT(7) | ||
38 | #define LP8788_CHG_IBATT_M 0x1F /* Addr 14h */ | ||
39 | #define LP8788_CHG_VTERM_M 0x0F /* Addr 15h */ | ||
40 | #define LP8788_CHG_EOC_LEVEL_M 0x30 /* Addr 16h */ | ||
41 | #define LP8788_CHG_EOC_LEVEL_S 4 | ||
42 | #define LP8788_CHG_EOC_TIME_M 0x0E | ||
43 | #define LP8788_CHG_EOC_TIME_S 1 | ||
44 | #define LP8788_CHG_EOC_MODE_M BIT(0) | ||
45 | |||
46 | #define LP8788_CHARGER_NAME "charger" | ||
47 | #define LP8788_BATTERY_NAME "main_batt" | ||
48 | |||
49 | #define LP8788_CHG_START 0x11 | ||
50 | #define LP8788_CHG_END 0x1C | ||
51 | |||
52 | #define LP8788_BUF_SIZE 40 | ||
53 | #define LP8788_ISEL_MAX 23 | ||
54 | #define LP8788_ISEL_STEP 50 | ||
55 | #define LP8788_VTERM_MIN 4100 | ||
56 | #define LP8788_VTERM_STEP 25 | ||
57 | #define LP8788_MAX_BATT_CAPACITY 100 | ||
58 | #define LP8788_MAX_CHG_IRQS 11 | ||
59 | |||
60 | enum lp8788_charging_state { | ||
61 | LP8788_OFF, | ||
62 | LP8788_WARM_UP, | ||
63 | LP8788_LOW_INPUT = 0x3, | ||
64 | LP8788_PRECHARGE, | ||
65 | LP8788_CC, | ||
66 | LP8788_CV, | ||
67 | LP8788_MAINTENANCE, | ||
68 | LP8788_BATTERY_FAULT, | ||
69 | LP8788_SYSTEM_SUPPORT = 0xC, | ||
70 | LP8788_HIGH_CURRENT = 0xF, | ||
71 | LP8788_MAX_CHG_STATE, | ||
72 | }; | ||
73 | |||
74 | enum lp8788_charger_adc_sel { | ||
75 | LP8788_VBATT, | ||
76 | LP8788_BATT_TEMP, | ||
77 | LP8788_NUM_CHG_ADC, | ||
78 | }; | ||
79 | |||
80 | enum lp8788_charger_input_state { | ||
81 | LP8788_SYSTEM_SUPPLY = 1, | ||
82 | LP8788_FULL_FUNCTION, | ||
83 | }; | ||
84 | |||
85 | /* | ||
86 | * struct lp8788_chg_irq | ||
87 | * @which : lp8788 interrupt id | ||
88 | * @virq : Linux IRQ number from irq_domain | ||
89 | */ | ||
90 | struct lp8788_chg_irq { | ||
91 | enum lp8788_int_id which; | ||
92 | int virq; | ||
93 | }; | ||
94 | |||
95 | /* | ||
96 | * struct lp8788_charger | ||
97 | * @lp : used for accessing the registers of mfd lp8788 device | ||
98 | * @charger : power supply driver for the battery charger | ||
99 | * @battery : power supply driver for the battery | ||
100 | * @charger_work : work queue for charger input interrupts | ||
101 | * @chan : iio channels for getting adc values | ||
102 | * eg) battery voltage, capacity and temperature | ||
103 | * @irqs : charger dedicated interrupts | ||
104 | * @num_irqs : total numbers of charger interrupts | ||
105 | * @pdata : charger platform specific data | ||
106 | */ | ||
107 | struct lp8788_charger { | ||
108 | struct lp8788 *lp; | ||
109 | struct power_supply charger; | ||
110 | struct power_supply battery; | ||
111 | struct work_struct charger_work; | ||
112 | struct iio_channel *chan[LP8788_NUM_CHG_ADC]; | ||
113 | struct lp8788_chg_irq irqs[LP8788_MAX_CHG_IRQS]; | ||
114 | int num_irqs; | ||
115 | struct lp8788_charger_platform_data *pdata; | ||
116 | }; | ||
117 | |||
118 | static char *battery_supplied_to[] = { | ||
119 | LP8788_BATTERY_NAME, | ||
120 | }; | ||
121 | |||
122 | static enum power_supply_property lp8788_charger_prop[] = { | ||
123 | POWER_SUPPLY_PROP_ONLINE, | ||
124 | POWER_SUPPLY_PROP_CURRENT_MAX, | ||
125 | }; | ||
126 | |||
127 | static enum power_supply_property lp8788_battery_prop[] = { | ||
128 | POWER_SUPPLY_PROP_STATUS, | ||
129 | POWER_SUPPLY_PROP_HEALTH, | ||
130 | POWER_SUPPLY_PROP_PRESENT, | ||
131 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | ||
132 | POWER_SUPPLY_PROP_CAPACITY, | ||
133 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, | ||
134 | POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, | ||
135 | POWER_SUPPLY_PROP_TEMP, | ||
136 | }; | ||
137 | |||
138 | static bool lp8788_is_charger_detected(struct lp8788_charger *pchg) | ||
139 | { | ||
140 | u8 data; | ||
141 | |||
142 | lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | ||
143 | data &= LP8788_CHG_INPUT_STATE_M; | ||
144 | |||
145 | return data == LP8788_SYSTEM_SUPPLY || data == LP8788_FULL_FUNCTION; | ||
146 | } | ||
147 | |||
148 | static int lp8788_charger_get_property(struct power_supply *psy, | ||
149 | enum power_supply_property psp, | ||
150 | union power_supply_propval *val) | ||
151 | { | ||
152 | struct lp8788_charger *pchg = dev_get_drvdata(psy->dev->parent); | ||
153 | u8 read; | ||
154 | |||
155 | switch (psp) { | ||
156 | case POWER_SUPPLY_PROP_ONLINE: | ||
157 | val->intval = lp8788_is_charger_detected(pchg); | ||
158 | break; | ||
159 | case POWER_SUPPLY_PROP_CURRENT_MAX: | ||
160 | lp8788_read_byte(pchg->lp, LP8788_CHG_IDCIN, &read); | ||
161 | val->intval = LP8788_ISEL_STEP * | ||
162 | (min_t(int, read, LP8788_ISEL_MAX) + 1); | ||
163 | break; | ||
164 | default: | ||
165 | return -EINVAL; | ||
166 | } | ||
167 | |||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | static int lp8788_get_battery_status(struct lp8788_charger *pchg, | ||
172 | union power_supply_propval *val) | ||
173 | { | ||
174 | enum lp8788_charging_state state; | ||
175 | u8 data; | ||
176 | int ret; | ||
177 | |||
178 | ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | ||
179 | if (ret) | ||
180 | return ret; | ||
181 | |||
182 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | ||
183 | switch (state) { | ||
184 | case LP8788_OFF: | ||
185 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | ||
186 | break; | ||
187 | case LP8788_PRECHARGE: | ||
188 | case LP8788_CC: | ||
189 | case LP8788_CV: | ||
190 | case LP8788_HIGH_CURRENT: | ||
191 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | ||
192 | break; | ||
193 | case LP8788_MAINTENANCE: | ||
194 | val->intval = POWER_SUPPLY_STATUS_FULL; | ||
195 | break; | ||
196 | default: | ||
197 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | ||
198 | break; | ||
199 | } | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int lp8788_get_battery_health(struct lp8788_charger *pchg, | ||
205 | union power_supply_propval *val) | ||
206 | { | ||
207 | u8 data; | ||
208 | int ret; | ||
209 | |||
210 | ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | ||
211 | if (ret) | ||
212 | return ret; | ||
213 | |||
214 | if (data & LP8788_NO_BATT_M) | ||
215 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | ||
216 | else if (data & LP8788_BAD_BATT_M) | ||
217 | val->intval = POWER_SUPPLY_HEALTH_DEAD; | ||
218 | else | ||
219 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | ||
220 | |||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | static int lp8788_get_battery_present(struct lp8788_charger *pchg, | ||
225 | union power_supply_propval *val) | ||
226 | { | ||
227 | u8 data; | ||
228 | int ret; | ||
229 | |||
230 | ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | ||
231 | if (ret) | ||
232 | return ret; | ||
233 | |||
234 | val->intval = !(data & LP8788_NO_BATT_M); | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, | ||
239 | unsigned int *result) | ||
240 | { | ||
241 | struct iio_channel *channel = pchg->chan[LP8788_VBATT]; | ||
242 | int scaleint; | ||
243 | int scalepart; | ||
244 | int ret; | ||
245 | |||
246 | if (!channel) | ||
247 | return -EINVAL; | ||
248 | |||
249 | ret = iio_read_channel_scale(channel, &scaleint, &scalepart); | ||
250 | if (ret != IIO_VAL_INT_PLUS_MICRO) | ||
251 | return -EINVAL; | ||
252 | |||
253 | /* unit: mV */ | ||
254 | *result = (scaleint + scalepart * 1000000) / 1000; | ||
255 | |||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | static int lp8788_get_battery_voltage(struct lp8788_charger *pchg, | ||
260 | union power_supply_propval *val) | ||
261 | { | ||
262 | return lp8788_get_vbatt_adc(pchg, &val->intval); | ||
263 | } | ||
264 | |||
265 | static int lp8788_get_battery_capacity(struct lp8788_charger *pchg, | ||
266 | union power_supply_propval *val) | ||
267 | { | ||
268 | struct lp8788 *lp = pchg->lp; | ||
269 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | ||
270 | unsigned int max_vbatt; | ||
271 | unsigned int vbatt; | ||
272 | enum lp8788_charging_state state; | ||
273 | u8 data; | ||
274 | int ret; | ||
275 | |||
276 | if (!pdata) | ||
277 | return -EINVAL; | ||
278 | |||
279 | max_vbatt = pdata->max_vbatt_mv; | ||
280 | if (max_vbatt == 0) | ||
281 | return -EINVAL; | ||
282 | |||
283 | ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data); | ||
284 | if (ret) | ||
285 | return ret; | ||
286 | |||
287 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | ||
288 | |||
289 | if (state == LP8788_MAINTENANCE) { | ||
290 | val->intval = LP8788_MAX_BATT_CAPACITY; | ||
291 | } else { | ||
292 | ret = lp8788_get_vbatt_adc(pchg, &vbatt); | ||
293 | if (ret) | ||
294 | return ret; | ||
295 | |||
296 | val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt; | ||
297 | val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY); | ||
298 | } | ||
299 | |||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | static int lp8788_get_battery_temperature(struct lp8788_charger *pchg, | ||
304 | union power_supply_propval *val) | ||
305 | { | ||
306 | struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP]; | ||
307 | int scaleint; | ||
308 | int scalepart; | ||
309 | int ret; | ||
310 | |||
311 | if (!channel) | ||
312 | return -EINVAL; | ||
313 | |||
314 | ret = iio_read_channel_scale(channel, &scaleint, &scalepart); | ||
315 | if (ret != IIO_VAL_INT_PLUS_MICRO) | ||
316 | return -EINVAL; | ||
317 | |||
318 | /* unit: 0.1 'C */ | ||
319 | val->intval = (scaleint + scalepart * 1000000) / 100; | ||
320 | |||
321 | return 0; | ||
322 | } | ||
323 | |||
324 | static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg, | ||
325 | union power_supply_propval *val) | ||
326 | { | ||
327 | u8 read; | ||
328 | |||
329 | lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read); | ||
330 | read &= LP8788_CHG_IBATT_M; | ||
331 | val->intval = LP8788_ISEL_STEP * | ||
332 | (min_t(int, read, LP8788_ISEL_MAX) + 1); | ||
333 | |||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg, | ||
338 | union power_supply_propval *val) | ||
339 | { | ||
340 | u8 read; | ||
341 | |||
342 | lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read); | ||
343 | read &= LP8788_CHG_VTERM_M; | ||
344 | val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read; | ||
345 | |||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | static int lp8788_battery_get_property(struct power_supply *psy, | ||
350 | enum power_supply_property psp, | ||
351 | union power_supply_propval *val) | ||
352 | { | ||
353 | struct lp8788_charger *pchg = dev_get_drvdata(psy->dev->parent); | ||
354 | |||
355 | switch (psp) { | ||
356 | case POWER_SUPPLY_PROP_STATUS: | ||
357 | return lp8788_get_battery_status(pchg, val); | ||
358 | case POWER_SUPPLY_PROP_HEALTH: | ||
359 | return lp8788_get_battery_health(pchg, val); | ||
360 | case POWER_SUPPLY_PROP_PRESENT: | ||
361 | return lp8788_get_battery_present(pchg, val); | ||
362 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
363 | return lp8788_get_battery_voltage(pchg, val); | ||
364 | case POWER_SUPPLY_PROP_CAPACITY: | ||
365 | return lp8788_get_battery_capacity(pchg, val); | ||
366 | case POWER_SUPPLY_PROP_TEMP: | ||
367 | return lp8788_get_battery_temperature(pchg, val); | ||
368 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | ||
369 | return lp8788_get_battery_charging_current(pchg, val); | ||
370 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: | ||
371 | return lp8788_get_charging_termination_voltage(pchg, val); | ||
372 | default: | ||
373 | return -EINVAL; | ||
374 | } | ||
375 | } | ||
376 | |||
377 | static inline bool lp8788_is_valid_charger_register(u8 addr) | ||
378 | { | ||
379 | return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END; | ||
380 | } | ||
381 | |||
382 | static int lp8788_update_charger_params(struct lp8788_charger *pchg) | ||
383 | { | ||
384 | struct lp8788 *lp = pchg->lp; | ||
385 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | ||
386 | struct lp8788_chg_param *param; | ||
387 | int i; | ||
388 | int ret; | ||
389 | |||
390 | if (!pdata || !pdata->chg_params) { | ||
391 | dev_info(lp->dev, "skip updating charger parameters\n"); | ||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | /* settting charging parameters */ | ||
396 | for (i = 0; i < pdata->num_chg_params; i++) { | ||
397 | param = pdata->chg_params + i; | ||
398 | |||
399 | if (!param) | ||
400 | continue; | ||
401 | |||
402 | if (lp8788_is_valid_charger_register(param->addr)) { | ||
403 | ret = lp8788_write_byte(lp, param->addr, param->val); | ||
404 | if (ret) | ||
405 | return ret; | ||
406 | } | ||
407 | } | ||
408 | |||
409 | return 0; | ||
410 | } | ||
411 | |||
412 | static int lp8788_psy_register(struct platform_device *pdev, | ||
413 | struct lp8788_charger *pchg) | ||
414 | { | ||
415 | pchg->charger.name = LP8788_CHARGER_NAME; | ||
416 | pchg->charger.type = POWER_SUPPLY_TYPE_MAINS; | ||
417 | pchg->charger.properties = lp8788_charger_prop; | ||
418 | pchg->charger.num_properties = ARRAY_SIZE(lp8788_charger_prop); | ||
419 | pchg->charger.get_property = lp8788_charger_get_property; | ||
420 | pchg->charger.supplied_to = battery_supplied_to; | ||
421 | pchg->charger.num_supplicants = ARRAY_SIZE(battery_supplied_to); | ||
422 | |||
423 | if (power_supply_register(&pdev->dev, &pchg->charger)) | ||
424 | return -EPERM; | ||
425 | |||
426 | pchg->battery.name = LP8788_BATTERY_NAME; | ||
427 | pchg->battery.type = POWER_SUPPLY_TYPE_BATTERY; | ||
428 | pchg->battery.properties = lp8788_battery_prop; | ||
429 | pchg->battery.num_properties = ARRAY_SIZE(lp8788_battery_prop); | ||
430 | pchg->battery.get_property = lp8788_battery_get_property; | ||
431 | |||
432 | if (power_supply_register(&pdev->dev, &pchg->battery)) | ||
433 | return -EPERM; | ||
434 | |||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | static void lp8788_psy_unregister(struct lp8788_charger *pchg) | ||
439 | { | ||
440 | power_supply_unregister(&pchg->battery); | ||
441 | power_supply_unregister(&pchg->charger); | ||
442 | } | ||
443 | |||
444 | static void lp8788_charger_event(struct work_struct *work) | ||
445 | { | ||
446 | struct lp8788_charger *pchg = | ||
447 | container_of(work, struct lp8788_charger, charger_work); | ||
448 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | ||
449 | enum lp8788_charger_event event = lp8788_is_charger_detected(pchg); | ||
450 | |||
451 | pdata->charger_event(pchg->lp, event); | ||
452 | } | ||
453 | |||
454 | static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id) | ||
455 | { | ||
456 | bool found; | ||
457 | int i; | ||
458 | |||
459 | for (i = 0; i < pchg->num_irqs; i++) { | ||
460 | if (pchg->irqs[i].virq == virq) { | ||
461 | *id = pchg->irqs[i].which; | ||
462 | found = true; | ||
463 | break; | ||
464 | } | ||
465 | } | ||
466 | |||
467 | return found; | ||
468 | } | ||
469 | |||
470 | static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr) | ||
471 | { | ||
472 | struct lp8788_charger *pchg = ptr; | ||
473 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | ||
474 | int id = -1; | ||
475 | |||
476 | if (!lp8788_find_irq_id(pchg, virq, &id)) | ||
477 | return IRQ_NONE; | ||
478 | |||
479 | switch (id) { | ||
480 | case LP8788_INT_CHG_INPUT_STATE: | ||
481 | case LP8788_INT_CHG_STATE: | ||
482 | case LP8788_INT_EOC: | ||
483 | case LP8788_INT_BATT_LOW: | ||
484 | case LP8788_INT_NO_BATT: | ||
485 | power_supply_changed(&pchg->charger); | ||
486 | power_supply_changed(&pchg->battery); | ||
487 | break; | ||
488 | default: | ||
489 | break; | ||
490 | } | ||
491 | |||
492 | /* report charger dectection event if used */ | ||
493 | if (!pdata) | ||
494 | goto irq_handled; | ||
495 | |||
496 | if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE) | ||
497 | schedule_work(&pchg->charger_work); | ||
498 | |||
499 | irq_handled: | ||
500 | return IRQ_HANDLED; | ||
501 | } | ||
502 | |||
503 | static int lp8788_set_irqs(struct platform_device *pdev, | ||
504 | struct lp8788_charger *pchg, const char *name) | ||
505 | { | ||
506 | struct resource *r; | ||
507 | struct irq_domain *irqdm = pchg->lp->irqdm; | ||
508 | int irq_start; | ||
509 | int irq_end; | ||
510 | int virq; | ||
511 | int nr_irq; | ||
512 | int i; | ||
513 | int ret; | ||
514 | |||
515 | /* no error even if no irq resource */ | ||
516 | r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name); | ||
517 | if (!r) | ||
518 | return 0; | ||
519 | |||
520 | irq_start = r->start; | ||
521 | irq_end = r->end; | ||
522 | |||
523 | for (i = irq_start; i <= irq_end; i++) { | ||
524 | nr_irq = pchg->num_irqs; | ||
525 | |||
526 | virq = irq_create_mapping(irqdm, i); | ||
527 | pchg->irqs[nr_irq].virq = virq; | ||
528 | pchg->irqs[nr_irq].which = i; | ||
529 | pchg->num_irqs++; | ||
530 | |||
531 | ret = request_threaded_irq(virq, NULL, | ||
532 | lp8788_charger_irq_thread, | ||
533 | 0, name, pchg); | ||
534 | if (ret) | ||
535 | break; | ||
536 | } | ||
537 | |||
538 | if (i <= irq_end) | ||
539 | goto err_free_irq; | ||
540 | |||
541 | return 0; | ||
542 | |||
543 | err_free_irq: | ||
544 | for (i = 0; i < pchg->num_irqs; i++) | ||
545 | free_irq(pchg->irqs[i].virq, pchg); | ||
546 | return ret; | ||
547 | } | ||
548 | |||
549 | static int lp8788_irq_register(struct platform_device *pdev, | ||
550 | struct lp8788_charger *pchg) | ||
551 | { | ||
552 | struct lp8788 *lp = pchg->lp; | ||
553 | const char *name[] = { | ||
554 | LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ | ||
555 | }; | ||
556 | int i; | ||
557 | int ret; | ||
558 | |||
559 | INIT_WORK(&pchg->charger_work, lp8788_charger_event); | ||
560 | pchg->num_irqs = 0; | ||
561 | |||
562 | for (i = 0; i < ARRAY_SIZE(name); i++) { | ||
563 | ret = lp8788_set_irqs(pdev, pchg, name[i]); | ||
564 | if (ret) { | ||
565 | dev_warn(lp->dev, "irq setup failed: %s\n", name[i]); | ||
566 | return ret; | ||
567 | } | ||
568 | } | ||
569 | |||
570 | if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) { | ||
571 | dev_err(lp->dev, "invalid total number of irqs: %d\n", | ||
572 | pchg->num_irqs); | ||
573 | return -EINVAL; | ||
574 | } | ||
575 | |||
576 | |||
577 | return 0; | ||
578 | } | ||
579 | |||
580 | static void lp8788_irq_unregister(struct platform_device *pdev, | ||
581 | struct lp8788_charger *pchg) | ||
582 | { | ||
583 | int i; | ||
584 | int irq; | ||
585 | |||
586 | for (i = 0; i < pchg->num_irqs; i++) { | ||
587 | irq = pchg->irqs[i].virq; | ||
588 | if (!irq) | ||
589 | continue; | ||
590 | |||
591 | free_irq(irq, pchg); | ||
592 | } | ||
593 | } | ||
594 | |||
595 | static void lp8788_setup_adc_channel(struct lp8788_charger *pchg) | ||
596 | { | ||
597 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | ||
598 | struct device *dev = pchg->lp->dev; | ||
599 | struct iio_channel *chan; | ||
600 | enum lp8788_adc_id id; | ||
601 | const char *chan_name[LPADC_MAX] = { | ||
602 | [LPADC_VBATT_5P5] = "vbatt-5p5", | ||
603 | [LPADC_VBATT_6P0] = "vbatt-6p0", | ||
604 | [LPADC_VBATT_5P0] = "vbatt-5p0", | ||
605 | [LPADC_ADC1] = "adc1", | ||
606 | [LPADC_ADC2] = "adc2", | ||
607 | [LPADC_ADC3] = "adc3", | ||
608 | [LPADC_ADC4] = "adc4", | ||
609 | }; | ||
610 | |||
611 | if (!pdata) | ||
612 | return; | ||
613 | |||
614 | id = pdata->vbatt_adc; | ||
615 | switch (id) { | ||
616 | case LPADC_VBATT_5P5: | ||
617 | case LPADC_VBATT_6P0: | ||
618 | case LPADC_VBATT_5P0: | ||
619 | chan = iio_channel_get(NULL, chan_name[id]); | ||
620 | pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; | ||
621 | break; | ||
622 | default: | ||
623 | dev_err(dev, "invalid ADC id for VBATT: %d\n", id); | ||
624 | pchg->chan[LP8788_VBATT] = NULL; | ||
625 | break; | ||
626 | } | ||
627 | |||
628 | id = pdata->batt_temp_adc; | ||
629 | switch (id) { | ||
630 | case LPADC_ADC1: | ||
631 | case LPADC_ADC2: | ||
632 | case LPADC_ADC3: | ||
633 | case LPADC_ADC4: | ||
634 | chan = iio_channel_get(NULL, chan_name[id]); | ||
635 | pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; | ||
636 | break; | ||
637 | default: | ||
638 | dev_err(dev, "invalid ADC id for BATT_TEMP : %d\n", id); | ||
639 | pchg->chan[LP8788_BATT_TEMP] = NULL; | ||
640 | break; | ||
641 | } | ||
642 | } | ||
643 | |||
644 | static void lp8788_release_adc_channel(struct lp8788_charger *pchg) | ||
645 | { | ||
646 | int i; | ||
647 | |||
648 | for (i = 0; i < LP8788_NUM_CHG_ADC; i++) { | ||
649 | if (!pchg->chan[i]) | ||
650 | continue; | ||
651 | |||
652 | iio_channel_release(pchg->chan[i]); | ||
653 | pchg->chan[i] = NULL; | ||
654 | } | ||
655 | } | ||
656 | |||
657 | static ssize_t lp8788_show_charger_status(struct device *dev, | ||
658 | struct device_attribute *attr, char *buf) | ||
659 | { | ||
660 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | ||
661 | enum lp8788_charging_state state; | ||
662 | char *desc[LP8788_MAX_CHG_STATE] = { | ||
663 | [LP8788_OFF] = "CHARGER OFF", | ||
664 | [LP8788_WARM_UP] = "WARM UP", | ||
665 | [LP8788_LOW_INPUT] = "LOW INPUT STATE", | ||
666 | [LP8788_PRECHARGE] = "CHARGING - PRECHARGE", | ||
667 | [LP8788_CC] = "CHARGING - CC", | ||
668 | [LP8788_CV] = "CHARGING - CV", | ||
669 | [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE", | ||
670 | [LP8788_BATTERY_FAULT] = "BATTERY FAULT", | ||
671 | [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT", | ||
672 | [LP8788_HIGH_CURRENT] = "HIGH CURRENT", | ||
673 | }; | ||
674 | u8 data; | ||
675 | |||
676 | lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | ||
677 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | ||
678 | |||
679 | return scnprintf(buf, LP8788_BUF_SIZE, "%s\n", desc[state]); | ||
680 | } | ||
681 | |||
682 | static ssize_t lp8788_show_eoc_time(struct device *dev, | ||
683 | struct device_attribute *attr, char *buf) | ||
684 | { | ||
685 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | ||
686 | char *stime[] = { "400ms", "5min", "10min", "15min", | ||
687 | "20min", "25min", "30min" "No timeout" }; | ||
688 | u8 val; | ||
689 | |||
690 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | ||
691 | val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; | ||
692 | |||
693 | return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Time: %s\n", | ||
694 | stime[val]); | ||
695 | } | ||
696 | |||
697 | static ssize_t lp8788_show_eoc_level(struct device *dev, | ||
698 | struct device_attribute *attr, char *buf) | ||
699 | { | ||
700 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | ||
701 | char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" }; | ||
702 | char *relative_level[] = { "5%", "10%", "15%", "20%" }; | ||
703 | char *level; | ||
704 | u8 val; | ||
705 | u8 mode; | ||
706 | |||
707 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | ||
708 | |||
709 | mode = val & LP8788_CHG_EOC_MODE_M; | ||
710 | val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; | ||
711 | level = mode ? abs_level[val] : relative_level[val]; | ||
712 | |||
713 | return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Level: %s\n", | ||
714 | level); | ||
715 | } | ||
716 | |||
717 | static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); | ||
718 | static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL); | ||
719 | static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL); | ||
720 | |||
721 | static struct attribute *lp8788_charger_attr[] = { | ||
722 | &dev_attr_charger_status.attr, | ||
723 | &dev_attr_eoc_time.attr, | ||
724 | &dev_attr_eoc_level.attr, | ||
725 | NULL, | ||
726 | }; | ||
727 | |||
728 | static const struct attribute_group lp8788_attr_group = { | ||
729 | .attrs = lp8788_charger_attr, | ||
730 | }; | ||
731 | |||
732 | static __devinit int lp8788_charger_probe(struct platform_device *pdev) | ||
733 | { | ||
734 | struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); | ||
735 | struct lp8788_charger *pchg; | ||
736 | int ret; | ||
737 | |||
738 | pchg = devm_kzalloc(lp->dev, sizeof(struct lp8788_charger), GFP_KERNEL); | ||
739 | if (!pchg) | ||
740 | return -ENOMEM; | ||
741 | |||
742 | pchg->lp = lp; | ||
743 | pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL; | ||
744 | platform_set_drvdata(pdev, pchg); | ||
745 | |||
746 | ret = lp8788_update_charger_params(pchg); | ||
747 | if (ret) | ||
748 | return ret; | ||
749 | |||
750 | lp8788_setup_adc_channel(pchg); | ||
751 | |||
752 | ret = lp8788_psy_register(pdev, pchg); | ||
753 | if (ret) | ||
754 | return ret; | ||
755 | |||
756 | ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); | ||
757 | if (ret) { | ||
758 | lp8788_psy_unregister(pchg); | ||
759 | return ret; | ||
760 | } | ||
761 | |||
762 | ret = lp8788_irq_register(pdev, pchg); | ||
763 | if (ret) | ||
764 | dev_warn(lp->dev, "failed to register charger irq: %d\n", ret); | ||
765 | |||
766 | return 0; | ||
767 | } | ||
768 | |||
769 | static int __devexit lp8788_charger_remove(struct platform_device *pdev) | ||
770 | { | ||
771 | struct lp8788_charger *pchg = platform_get_drvdata(pdev); | ||
772 | |||
773 | flush_work(&pchg->charger_work); | ||
774 | lp8788_irq_unregister(pdev, pchg); | ||
775 | sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); | ||
776 | lp8788_psy_unregister(pchg); | ||
777 | lp8788_release_adc_channel(pchg); | ||
778 | |||
779 | return 0; | ||
780 | } | ||
781 | |||
782 | static struct platform_driver lp8788_charger_driver = { | ||
783 | .probe = lp8788_charger_probe, | ||
784 | .remove = __devexit_p(lp8788_charger_remove), | ||
785 | .driver = { | ||
786 | .name = LP8788_DEV_CHARGER, | ||
787 | .owner = THIS_MODULE, | ||
788 | }, | ||
789 | }; | ||
790 | module_platform_driver(lp8788_charger_driver); | ||
791 | |||
792 | MODULE_DESCRIPTION("TI LP8788 Charger Driver"); | ||
793 | MODULE_AUTHOR("Milo Kim"); | ||
794 | MODULE_LICENSE("GPL"); | ||
795 | MODULE_ALIAS("platform:lp8788-charger"); | ||