Commit | Line | Data |
---|---|---|
98a27664 KM |
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 | ||
340968de | 238 | static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result) |
98a27664 KM |
239 | { |
240 | struct iio_channel *channel = pchg->chan[LP8788_VBATT]; | |
98a27664 KM |
241 | |
242 | if (!channel) | |
243 | return -EINVAL; | |
244 | ||
340968de | 245 | return iio_read_channel_processed(channel, result); |
98a27664 KM |
246 | } |
247 | ||
248 | static int lp8788_get_battery_voltage(struct lp8788_charger *pchg, | |
249 | union power_supply_propval *val) | |
250 | { | |
251 | return lp8788_get_vbatt_adc(pchg, &val->intval); | |
252 | } | |
253 | ||
254 | static int lp8788_get_battery_capacity(struct lp8788_charger *pchg, | |
255 | union power_supply_propval *val) | |
256 | { | |
257 | struct lp8788 *lp = pchg->lp; | |
258 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
259 | unsigned int max_vbatt; | |
340968de | 260 | int vbatt; |
98a27664 KM |
261 | enum lp8788_charging_state state; |
262 | u8 data; | |
263 | int ret; | |
264 | ||
265 | if (!pdata) | |
266 | return -EINVAL; | |
267 | ||
268 | max_vbatt = pdata->max_vbatt_mv; | |
269 | if (max_vbatt == 0) | |
270 | return -EINVAL; | |
271 | ||
272 | ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data); | |
273 | if (ret) | |
274 | return ret; | |
275 | ||
276 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | |
277 | ||
278 | if (state == LP8788_MAINTENANCE) { | |
279 | val->intval = LP8788_MAX_BATT_CAPACITY; | |
280 | } else { | |
281 | ret = lp8788_get_vbatt_adc(pchg, &vbatt); | |
282 | if (ret) | |
283 | return ret; | |
284 | ||
285 | val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt; | |
286 | val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY); | |
287 | } | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static int lp8788_get_battery_temperature(struct lp8788_charger *pchg, | |
293 | union power_supply_propval *val) | |
294 | { | |
295 | struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP]; | |
340968de | 296 | int result; |
98a27664 KM |
297 | int ret; |
298 | ||
299 | if (!channel) | |
300 | return -EINVAL; | |
301 | ||
340968de KM |
302 | ret = iio_read_channel_processed(channel, &result); |
303 | if (ret < 0) | |
98a27664 KM |
304 | return -EINVAL; |
305 | ||
306 | /* unit: 0.1 'C */ | |
340968de | 307 | val->intval = result * 10; |
98a27664 KM |
308 | |
309 | return 0; | |
310 | } | |
311 | ||
312 | static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg, | |
313 | union power_supply_propval *val) | |
314 | { | |
315 | u8 read; | |
316 | ||
317 | lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read); | |
318 | read &= LP8788_CHG_IBATT_M; | |
319 | val->intval = LP8788_ISEL_STEP * | |
320 | (min_t(int, read, LP8788_ISEL_MAX) + 1); | |
321 | ||
322 | return 0; | |
323 | } | |
324 | ||
325 | static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg, | |
326 | union power_supply_propval *val) | |
327 | { | |
328 | u8 read; | |
329 | ||
330 | lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read); | |
331 | read &= LP8788_CHG_VTERM_M; | |
332 | val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read; | |
333 | ||
334 | return 0; | |
335 | } | |
336 | ||
337 | static int lp8788_battery_get_property(struct power_supply *psy, | |
338 | enum power_supply_property psp, | |
339 | union power_supply_propval *val) | |
340 | { | |
341 | struct lp8788_charger *pchg = dev_get_drvdata(psy->dev->parent); | |
342 | ||
343 | switch (psp) { | |
344 | case POWER_SUPPLY_PROP_STATUS: | |
345 | return lp8788_get_battery_status(pchg, val); | |
346 | case POWER_SUPPLY_PROP_HEALTH: | |
347 | return lp8788_get_battery_health(pchg, val); | |
348 | case POWER_SUPPLY_PROP_PRESENT: | |
349 | return lp8788_get_battery_present(pchg, val); | |
350 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | |
351 | return lp8788_get_battery_voltage(pchg, val); | |
352 | case POWER_SUPPLY_PROP_CAPACITY: | |
353 | return lp8788_get_battery_capacity(pchg, val); | |
354 | case POWER_SUPPLY_PROP_TEMP: | |
355 | return lp8788_get_battery_temperature(pchg, val); | |
356 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: | |
357 | return lp8788_get_battery_charging_current(pchg, val); | |
358 | case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: | |
359 | return lp8788_get_charging_termination_voltage(pchg, val); | |
360 | default: | |
361 | return -EINVAL; | |
362 | } | |
363 | } | |
364 | ||
365 | static inline bool lp8788_is_valid_charger_register(u8 addr) | |
366 | { | |
367 | return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END; | |
368 | } | |
369 | ||
370 | static int lp8788_update_charger_params(struct lp8788_charger *pchg) | |
371 | { | |
372 | struct lp8788 *lp = pchg->lp; | |
373 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
374 | struct lp8788_chg_param *param; | |
375 | int i; | |
376 | int ret; | |
377 | ||
378 | if (!pdata || !pdata->chg_params) { | |
379 | dev_info(lp->dev, "skip updating charger parameters\n"); | |
380 | return 0; | |
381 | } | |
382 | ||
383 | /* settting charging parameters */ | |
384 | for (i = 0; i < pdata->num_chg_params; i++) { | |
385 | param = pdata->chg_params + i; | |
386 | ||
387 | if (!param) | |
388 | continue; | |
389 | ||
390 | if (lp8788_is_valid_charger_register(param->addr)) { | |
391 | ret = lp8788_write_byte(lp, param->addr, param->val); | |
392 | if (ret) | |
393 | return ret; | |
394 | } | |
395 | } | |
396 | ||
397 | return 0; | |
398 | } | |
399 | ||
400 | static int lp8788_psy_register(struct platform_device *pdev, | |
401 | struct lp8788_charger *pchg) | |
402 | { | |
403 | pchg->charger.name = LP8788_CHARGER_NAME; | |
404 | pchg->charger.type = POWER_SUPPLY_TYPE_MAINS; | |
405 | pchg->charger.properties = lp8788_charger_prop; | |
406 | pchg->charger.num_properties = ARRAY_SIZE(lp8788_charger_prop); | |
407 | pchg->charger.get_property = lp8788_charger_get_property; | |
408 | pchg->charger.supplied_to = battery_supplied_to; | |
409 | pchg->charger.num_supplicants = ARRAY_SIZE(battery_supplied_to); | |
410 | ||
411 | if (power_supply_register(&pdev->dev, &pchg->charger)) | |
412 | return -EPERM; | |
413 | ||
414 | pchg->battery.name = LP8788_BATTERY_NAME; | |
415 | pchg->battery.type = POWER_SUPPLY_TYPE_BATTERY; | |
416 | pchg->battery.properties = lp8788_battery_prop; | |
417 | pchg->battery.num_properties = ARRAY_SIZE(lp8788_battery_prop); | |
418 | pchg->battery.get_property = lp8788_battery_get_property; | |
419 | ||
420 | if (power_supply_register(&pdev->dev, &pchg->battery)) | |
421 | return -EPERM; | |
422 | ||
423 | return 0; | |
424 | } | |
425 | ||
426 | static void lp8788_psy_unregister(struct lp8788_charger *pchg) | |
427 | { | |
428 | power_supply_unregister(&pchg->battery); | |
429 | power_supply_unregister(&pchg->charger); | |
430 | } | |
431 | ||
432 | static void lp8788_charger_event(struct work_struct *work) | |
433 | { | |
434 | struct lp8788_charger *pchg = | |
435 | container_of(work, struct lp8788_charger, charger_work); | |
436 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
437 | enum lp8788_charger_event event = lp8788_is_charger_detected(pchg); | |
438 | ||
439 | pdata->charger_event(pchg->lp, event); | |
440 | } | |
441 | ||
442 | static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id) | |
443 | { | |
444 | bool found; | |
445 | int i; | |
446 | ||
447 | for (i = 0; i < pchg->num_irqs; i++) { | |
448 | if (pchg->irqs[i].virq == virq) { | |
449 | *id = pchg->irqs[i].which; | |
450 | found = true; | |
451 | break; | |
452 | } | |
453 | } | |
454 | ||
455 | return found; | |
456 | } | |
457 | ||
458 | static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr) | |
459 | { | |
460 | struct lp8788_charger *pchg = ptr; | |
461 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
462 | int id = -1; | |
463 | ||
464 | if (!lp8788_find_irq_id(pchg, virq, &id)) | |
465 | return IRQ_NONE; | |
466 | ||
467 | switch (id) { | |
468 | case LP8788_INT_CHG_INPUT_STATE: | |
469 | case LP8788_INT_CHG_STATE: | |
470 | case LP8788_INT_EOC: | |
471 | case LP8788_INT_BATT_LOW: | |
472 | case LP8788_INT_NO_BATT: | |
473 | power_supply_changed(&pchg->charger); | |
474 | power_supply_changed(&pchg->battery); | |
475 | break; | |
476 | default: | |
477 | break; | |
478 | } | |
479 | ||
480 | /* report charger dectection event if used */ | |
481 | if (!pdata) | |
482 | goto irq_handled; | |
483 | ||
484 | if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE) | |
485 | schedule_work(&pchg->charger_work); | |
486 | ||
487 | irq_handled: | |
488 | return IRQ_HANDLED; | |
489 | } | |
490 | ||
491 | static int lp8788_set_irqs(struct platform_device *pdev, | |
492 | struct lp8788_charger *pchg, const char *name) | |
493 | { | |
494 | struct resource *r; | |
495 | struct irq_domain *irqdm = pchg->lp->irqdm; | |
496 | int irq_start; | |
497 | int irq_end; | |
498 | int virq; | |
499 | int nr_irq; | |
500 | int i; | |
501 | int ret; | |
502 | ||
503 | /* no error even if no irq resource */ | |
504 | r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name); | |
505 | if (!r) | |
506 | return 0; | |
507 | ||
508 | irq_start = r->start; | |
509 | irq_end = r->end; | |
510 | ||
511 | for (i = irq_start; i <= irq_end; i++) { | |
512 | nr_irq = pchg->num_irqs; | |
513 | ||
514 | virq = irq_create_mapping(irqdm, i); | |
515 | pchg->irqs[nr_irq].virq = virq; | |
516 | pchg->irqs[nr_irq].which = i; | |
517 | pchg->num_irqs++; | |
518 | ||
519 | ret = request_threaded_irq(virq, NULL, | |
520 | lp8788_charger_irq_thread, | |
521 | 0, name, pchg); | |
522 | if (ret) | |
523 | break; | |
524 | } | |
525 | ||
526 | if (i <= irq_end) | |
527 | goto err_free_irq; | |
528 | ||
529 | return 0; | |
530 | ||
531 | err_free_irq: | |
532 | for (i = 0; i < pchg->num_irqs; i++) | |
533 | free_irq(pchg->irqs[i].virq, pchg); | |
534 | return ret; | |
535 | } | |
536 | ||
537 | static int lp8788_irq_register(struct platform_device *pdev, | |
538 | struct lp8788_charger *pchg) | |
539 | { | |
540 | struct lp8788 *lp = pchg->lp; | |
541 | const char *name[] = { | |
542 | LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ | |
543 | }; | |
544 | int i; | |
545 | int ret; | |
546 | ||
547 | INIT_WORK(&pchg->charger_work, lp8788_charger_event); | |
548 | pchg->num_irqs = 0; | |
549 | ||
550 | for (i = 0; i < ARRAY_SIZE(name); i++) { | |
551 | ret = lp8788_set_irqs(pdev, pchg, name[i]); | |
552 | if (ret) { | |
553 | dev_warn(lp->dev, "irq setup failed: %s\n", name[i]); | |
554 | return ret; | |
555 | } | |
556 | } | |
557 | ||
558 | if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) { | |
559 | dev_err(lp->dev, "invalid total number of irqs: %d\n", | |
560 | pchg->num_irqs); | |
561 | return -EINVAL; | |
562 | } | |
563 | ||
564 | ||
565 | return 0; | |
566 | } | |
567 | ||
568 | static void lp8788_irq_unregister(struct platform_device *pdev, | |
569 | struct lp8788_charger *pchg) | |
570 | { | |
571 | int i; | |
572 | int irq; | |
573 | ||
574 | for (i = 0; i < pchg->num_irqs; i++) { | |
575 | irq = pchg->irqs[i].virq; | |
576 | if (!irq) | |
577 | continue; | |
578 | ||
579 | free_irq(irq, pchg); | |
580 | } | |
581 | } | |
582 | ||
968a4783 KM |
583 | static void lp8788_setup_adc_channel(const char *consumer_name, |
584 | struct lp8788_charger *pchg) | |
98a27664 KM |
585 | { |
586 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
98a27664 | 587 | struct iio_channel *chan; |
98a27664 KM |
588 | |
589 | if (!pdata) | |
590 | return; | |
591 | ||
08d816b8 KM |
592 | /* ADC channel for battery voltage */ |
593 | chan = iio_channel_get(consumer_name, pdata->adc_vbatt); | |
594 | pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; | |
98a27664 | 595 | |
08d816b8 KM |
596 | /* ADC channel for battery temperature */ |
597 | chan = iio_channel_get(consumer_name, pdata->adc_batt_temp); | |
598 | pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; | |
98a27664 KM |
599 | } |
600 | ||
601 | static void lp8788_release_adc_channel(struct lp8788_charger *pchg) | |
602 | { | |
603 | int i; | |
604 | ||
605 | for (i = 0; i < LP8788_NUM_CHG_ADC; i++) { | |
606 | if (!pchg->chan[i]) | |
607 | continue; | |
608 | ||
609 | iio_channel_release(pchg->chan[i]); | |
610 | pchg->chan[i] = NULL; | |
611 | } | |
612 | } | |
613 | ||
614 | static ssize_t lp8788_show_charger_status(struct device *dev, | |
615 | struct device_attribute *attr, char *buf) | |
616 | { | |
617 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
618 | enum lp8788_charging_state state; | |
619 | char *desc[LP8788_MAX_CHG_STATE] = { | |
620 | [LP8788_OFF] = "CHARGER OFF", | |
621 | [LP8788_WARM_UP] = "WARM UP", | |
622 | [LP8788_LOW_INPUT] = "LOW INPUT STATE", | |
623 | [LP8788_PRECHARGE] = "CHARGING - PRECHARGE", | |
624 | [LP8788_CC] = "CHARGING - CC", | |
625 | [LP8788_CV] = "CHARGING - CV", | |
626 | [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE", | |
627 | [LP8788_BATTERY_FAULT] = "BATTERY FAULT", | |
628 | [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT", | |
629 | [LP8788_HIGH_CURRENT] = "HIGH CURRENT", | |
630 | }; | |
631 | u8 data; | |
632 | ||
633 | lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | |
634 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | |
635 | ||
636 | return scnprintf(buf, LP8788_BUF_SIZE, "%s\n", desc[state]); | |
637 | } | |
638 | ||
639 | static ssize_t lp8788_show_eoc_time(struct device *dev, | |
640 | struct device_attribute *attr, char *buf) | |
641 | { | |
642 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
643 | char *stime[] = { "400ms", "5min", "10min", "15min", | |
644 | "20min", "25min", "30min" "No timeout" }; | |
645 | u8 val; | |
646 | ||
647 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | |
648 | val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; | |
649 | ||
650 | return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Time: %s\n", | |
651 | stime[val]); | |
652 | } | |
653 | ||
654 | static ssize_t lp8788_show_eoc_level(struct device *dev, | |
655 | struct device_attribute *attr, char *buf) | |
656 | { | |
657 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
658 | char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" }; | |
659 | char *relative_level[] = { "5%", "10%", "15%", "20%" }; | |
660 | char *level; | |
661 | u8 val; | |
662 | u8 mode; | |
663 | ||
664 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | |
665 | ||
666 | mode = val & LP8788_CHG_EOC_MODE_M; | |
667 | val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; | |
668 | level = mode ? abs_level[val] : relative_level[val]; | |
669 | ||
670 | return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Level: %s\n", | |
671 | level); | |
672 | } | |
673 | ||
674 | static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); | |
675 | static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL); | |
676 | static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL); | |
677 | ||
678 | static struct attribute *lp8788_charger_attr[] = { | |
679 | &dev_attr_charger_status.attr, | |
680 | &dev_attr_eoc_time.attr, | |
681 | &dev_attr_eoc_level.attr, | |
682 | NULL, | |
683 | }; | |
684 | ||
685 | static const struct attribute_group lp8788_attr_group = { | |
686 | .attrs = lp8788_charger_attr, | |
687 | }; | |
688 | ||
c8afa640 | 689 | static int lp8788_charger_probe(struct platform_device *pdev) |
98a27664 KM |
690 | { |
691 | struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); | |
692 | struct lp8788_charger *pchg; | |
05ac539b | 693 | struct device *dev = &pdev->dev; |
98a27664 KM |
694 | int ret; |
695 | ||
05ac539b | 696 | pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL); |
98a27664 KM |
697 | if (!pchg) |
698 | return -ENOMEM; | |
699 | ||
700 | pchg->lp = lp; | |
701 | pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL; | |
702 | platform_set_drvdata(pdev, pchg); | |
703 | ||
704 | ret = lp8788_update_charger_params(pchg); | |
705 | if (ret) | |
706 | return ret; | |
707 | ||
968a4783 | 708 | lp8788_setup_adc_channel(pdev->name, pchg); |
98a27664 KM |
709 | |
710 | ret = lp8788_psy_register(pdev, pchg); | |
711 | if (ret) | |
712 | return ret; | |
713 | ||
714 | ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); | |
715 | if (ret) { | |
716 | lp8788_psy_unregister(pchg); | |
717 | return ret; | |
718 | } | |
719 | ||
720 | ret = lp8788_irq_register(pdev, pchg); | |
721 | if (ret) | |
05ac539b | 722 | dev_warn(dev, "failed to register charger irq: %d\n", ret); |
98a27664 KM |
723 | |
724 | return 0; | |
725 | } | |
726 | ||
415ec69f | 727 | static int lp8788_charger_remove(struct platform_device *pdev) |
98a27664 KM |
728 | { |
729 | struct lp8788_charger *pchg = platform_get_drvdata(pdev); | |
730 | ||
731 | flush_work(&pchg->charger_work); | |
732 | lp8788_irq_unregister(pdev, pchg); | |
733 | sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); | |
734 | lp8788_psy_unregister(pchg); | |
735 | lp8788_release_adc_channel(pchg); | |
736 | ||
737 | return 0; | |
738 | } | |
739 | ||
740 | static struct platform_driver lp8788_charger_driver = { | |
741 | .probe = lp8788_charger_probe, | |
28ea73f4 | 742 | .remove = lp8788_charger_remove, |
98a27664 KM |
743 | .driver = { |
744 | .name = LP8788_DEV_CHARGER, | |
745 | .owner = THIS_MODULE, | |
746 | }, | |
747 | }; | |
748 | module_platform_driver(lp8788_charger_driver); | |
749 | ||
750 | MODULE_DESCRIPTION("TI LP8788 Charger Driver"); | |
751 | MODULE_AUTHOR("Milo Kim"); | |
752 | MODULE_LICENSE("GPL"); | |
753 | MODULE_ALIAS("platform:lp8788-charger"); |