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