]>
Commit | Line | Data |
---|---|---|
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"); |