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; | |
297d716f KK |
108 | struct power_supply *charger; |
109 | struct power_supply *battery; | |
98a27664 KM |
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 | { | |
297d716f | 151 | struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); |
98a27664 KM |
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 | { | |
297d716f | 340 | struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent); |
98a27664 KM |
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 | ||
297d716f KK |
400 | static const struct power_supply_desc lp8788_psy_charger_desc = { |
401 | .name = LP8788_CHARGER_NAME, | |
402 | .type = POWER_SUPPLY_TYPE_MAINS, | |
403 | .properties = lp8788_charger_prop, | |
404 | .num_properties = ARRAY_SIZE(lp8788_charger_prop), | |
405 | .get_property = lp8788_charger_get_property, | |
406 | }; | |
407 | ||
408 | static const struct power_supply_desc lp8788_psy_battery_desc = { | |
409 | .name = LP8788_BATTERY_NAME, | |
410 | .type = POWER_SUPPLY_TYPE_BATTERY, | |
411 | .properties = lp8788_battery_prop, | |
412 | .num_properties = ARRAY_SIZE(lp8788_battery_prop), | |
413 | .get_property = lp8788_battery_get_property, | |
414 | }; | |
415 | ||
98a27664 KM |
416 | static int lp8788_psy_register(struct platform_device *pdev, |
417 | struct lp8788_charger *pchg) | |
418 | { | |
2dc9215d KK |
419 | struct power_supply_config charger_cfg = {}; |
420 | ||
2dc9215d KK |
421 | charger_cfg.supplied_to = battery_supplied_to; |
422 | charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); | |
423 | ||
297d716f KK |
424 | pchg->charger = power_supply_register(&pdev->dev, |
425 | &lp8788_psy_charger_desc, | |
426 | &charger_cfg); | |
427 | if (IS_ERR(pchg->charger)) | |
98a27664 KM |
428 | return -EPERM; |
429 | ||
297d716f KK |
430 | pchg->battery = power_supply_register(&pdev->dev, |
431 | &lp8788_psy_battery_desc, NULL); | |
432 | if (IS_ERR(pchg->battery)) { | |
433 | power_supply_unregister(pchg->charger); | |
98a27664 | 434 | return -EPERM; |
a7117f81 | 435 | } |
98a27664 KM |
436 | |
437 | return 0; | |
438 | } | |
439 | ||
440 | static void lp8788_psy_unregister(struct lp8788_charger *pchg) | |
441 | { | |
297d716f KK |
442 | power_supply_unregister(pchg->battery); |
443 | power_supply_unregister(pchg->charger); | |
98a27664 KM |
444 | } |
445 | ||
446 | static void lp8788_charger_event(struct work_struct *work) | |
447 | { | |
448 | struct lp8788_charger *pchg = | |
449 | container_of(work, struct lp8788_charger, charger_work); | |
450 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
451 | enum lp8788_charger_event event = lp8788_is_charger_detected(pchg); | |
452 | ||
453 | pdata->charger_event(pchg->lp, event); | |
454 | } | |
455 | ||
456 | static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id) | |
457 | { | |
458 | bool found; | |
459 | int i; | |
460 | ||
461 | for (i = 0; i < pchg->num_irqs; i++) { | |
462 | if (pchg->irqs[i].virq == virq) { | |
463 | *id = pchg->irqs[i].which; | |
464 | found = true; | |
465 | break; | |
466 | } | |
467 | } | |
468 | ||
469 | return found; | |
470 | } | |
471 | ||
472 | static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr) | |
473 | { | |
474 | struct lp8788_charger *pchg = ptr; | |
475 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
476 | int id = -1; | |
477 | ||
478 | if (!lp8788_find_irq_id(pchg, virq, &id)) | |
479 | return IRQ_NONE; | |
480 | ||
481 | switch (id) { | |
482 | case LP8788_INT_CHG_INPUT_STATE: | |
483 | case LP8788_INT_CHG_STATE: | |
484 | case LP8788_INT_EOC: | |
485 | case LP8788_INT_BATT_LOW: | |
486 | case LP8788_INT_NO_BATT: | |
297d716f KK |
487 | power_supply_changed(pchg->charger); |
488 | power_supply_changed(pchg->battery); | |
98a27664 KM |
489 | break; |
490 | default: | |
491 | break; | |
492 | } | |
493 | ||
494 | /* report charger dectection event if used */ | |
495 | if (!pdata) | |
496 | goto irq_handled; | |
497 | ||
498 | if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE) | |
499 | schedule_work(&pchg->charger_work); | |
500 | ||
501 | irq_handled: | |
502 | return IRQ_HANDLED; | |
503 | } | |
504 | ||
505 | static int lp8788_set_irqs(struct platform_device *pdev, | |
506 | struct lp8788_charger *pchg, const char *name) | |
507 | { | |
508 | struct resource *r; | |
509 | struct irq_domain *irqdm = pchg->lp->irqdm; | |
510 | int irq_start; | |
511 | int irq_end; | |
512 | int virq; | |
513 | int nr_irq; | |
514 | int i; | |
515 | int ret; | |
516 | ||
517 | /* no error even if no irq resource */ | |
518 | r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name); | |
519 | if (!r) | |
520 | return 0; | |
521 | ||
522 | irq_start = r->start; | |
523 | irq_end = r->end; | |
524 | ||
525 | for (i = irq_start; i <= irq_end; i++) { | |
526 | nr_irq = pchg->num_irqs; | |
527 | ||
528 | virq = irq_create_mapping(irqdm, i); | |
529 | pchg->irqs[nr_irq].virq = virq; | |
530 | pchg->irqs[nr_irq].which = i; | |
531 | pchg->num_irqs++; | |
532 | ||
533 | ret = request_threaded_irq(virq, NULL, | |
534 | lp8788_charger_irq_thread, | |
535 | 0, name, pchg); | |
536 | if (ret) | |
537 | break; | |
538 | } | |
539 | ||
540 | if (i <= irq_end) | |
541 | goto err_free_irq; | |
542 | ||
543 | return 0; | |
544 | ||
545 | err_free_irq: | |
546 | for (i = 0; i < pchg->num_irqs; i++) | |
547 | free_irq(pchg->irqs[i].virq, pchg); | |
548 | return ret; | |
549 | } | |
550 | ||
551 | static int lp8788_irq_register(struct platform_device *pdev, | |
552 | struct lp8788_charger *pchg) | |
553 | { | |
98a27664 KM |
554 | const char *name[] = { |
555 | LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ | |
556 | }; | |
557 | int i; | |
558 | int ret; | |
559 | ||
560 | INIT_WORK(&pchg->charger_work, lp8788_charger_event); | |
561 | pchg->num_irqs = 0; | |
562 | ||
563 | for (i = 0; i < ARRAY_SIZE(name); i++) { | |
564 | ret = lp8788_set_irqs(pdev, pchg, name[i]); | |
565 | if (ret) { | |
d5c2b14c | 566 | dev_warn(&pdev->dev, "irq setup failed: %s\n", name[i]); |
98a27664 KM |
567 | return ret; |
568 | } | |
569 | } | |
570 | ||
571 | if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) { | |
d5c2b14c | 572 | dev_err(&pdev->dev, "invalid total number of irqs: %d\n", |
98a27664 KM |
573 | pchg->num_irqs); |
574 | return -EINVAL; | |
575 | } | |
576 | ||
577 | ||
578 | return 0; | |
579 | } | |
580 | ||
581 | static void lp8788_irq_unregister(struct platform_device *pdev, | |
582 | struct lp8788_charger *pchg) | |
583 | { | |
584 | int i; | |
585 | int irq; | |
586 | ||
587 | for (i = 0; i < pchg->num_irqs; i++) { | |
588 | irq = pchg->irqs[i].virq; | |
589 | if (!irq) | |
590 | continue; | |
591 | ||
592 | free_irq(irq, pchg); | |
593 | } | |
594 | } | |
595 | ||
5aa57f0a | 596 | static void lp8788_setup_adc_channel(struct device *dev, |
968a4783 | 597 | struct lp8788_charger *pchg) |
98a27664 KM |
598 | { |
599 | struct lp8788_charger_platform_data *pdata = pchg->pdata; | |
98a27664 | 600 | struct iio_channel *chan; |
98a27664 KM |
601 | |
602 | if (!pdata) | |
603 | return; | |
604 | ||
08d816b8 | 605 | /* ADC channel for battery voltage */ |
5aa57f0a | 606 | chan = iio_channel_get(dev, pdata->adc_vbatt); |
08d816b8 | 607 | pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; |
98a27664 | 608 | |
08d816b8 | 609 | /* ADC channel for battery temperature */ |
5aa57f0a | 610 | chan = iio_channel_get(dev, pdata->adc_batt_temp); |
08d816b8 | 611 | pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; |
98a27664 KM |
612 | } |
613 | ||
614 | static void lp8788_release_adc_channel(struct lp8788_charger *pchg) | |
615 | { | |
616 | int i; | |
617 | ||
618 | for (i = 0; i < LP8788_NUM_CHG_ADC; i++) { | |
619 | if (!pchg->chan[i]) | |
620 | continue; | |
621 | ||
622 | iio_channel_release(pchg->chan[i]); | |
623 | pchg->chan[i] = NULL; | |
624 | } | |
625 | } | |
626 | ||
627 | static ssize_t lp8788_show_charger_status(struct device *dev, | |
628 | struct device_attribute *attr, char *buf) | |
629 | { | |
630 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
631 | enum lp8788_charging_state state; | |
632 | char *desc[LP8788_MAX_CHG_STATE] = { | |
633 | [LP8788_OFF] = "CHARGER OFF", | |
634 | [LP8788_WARM_UP] = "WARM UP", | |
635 | [LP8788_LOW_INPUT] = "LOW INPUT STATE", | |
636 | [LP8788_PRECHARGE] = "CHARGING - PRECHARGE", | |
637 | [LP8788_CC] = "CHARGING - CC", | |
638 | [LP8788_CV] = "CHARGING - CV", | |
639 | [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE", | |
640 | [LP8788_BATTERY_FAULT] = "BATTERY FAULT", | |
641 | [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT", | |
642 | [LP8788_HIGH_CURRENT] = "HIGH CURRENT", | |
643 | }; | |
644 | u8 data; | |
645 | ||
646 | lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); | |
647 | state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; | |
648 | ||
4b44a1ed | 649 | return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]); |
98a27664 KM |
650 | } |
651 | ||
652 | static ssize_t lp8788_show_eoc_time(struct device *dev, | |
653 | struct device_attribute *attr, char *buf) | |
654 | { | |
655 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
656 | char *stime[] = { "400ms", "5min", "10min", "15min", | |
657 | "20min", "25min", "30min" "No timeout" }; | |
658 | u8 val; | |
659 | ||
660 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | |
661 | val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; | |
662 | ||
4b44a1ed | 663 | return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n", |
98a27664 KM |
664 | stime[val]); |
665 | } | |
666 | ||
667 | static ssize_t lp8788_show_eoc_level(struct device *dev, | |
668 | struct device_attribute *attr, char *buf) | |
669 | { | |
670 | struct lp8788_charger *pchg = dev_get_drvdata(dev); | |
671 | char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" }; | |
672 | char *relative_level[] = { "5%", "10%", "15%", "20%" }; | |
673 | char *level; | |
674 | u8 val; | |
675 | u8 mode; | |
676 | ||
677 | lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); | |
678 | ||
679 | mode = val & LP8788_CHG_EOC_MODE_M; | |
680 | val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; | |
681 | level = mode ? abs_level[val] : relative_level[val]; | |
682 | ||
4b44a1ed | 683 | return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level); |
98a27664 KM |
684 | } |
685 | ||
686 | static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); | |
687 | static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL); | |
688 | static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL); | |
689 | ||
690 | static struct attribute *lp8788_charger_attr[] = { | |
691 | &dev_attr_charger_status.attr, | |
692 | &dev_attr_eoc_time.attr, | |
693 | &dev_attr_eoc_level.attr, | |
694 | NULL, | |
695 | }; | |
696 | ||
697 | static const struct attribute_group lp8788_attr_group = { | |
698 | .attrs = lp8788_charger_attr, | |
699 | }; | |
700 | ||
c8afa640 | 701 | static int lp8788_charger_probe(struct platform_device *pdev) |
98a27664 KM |
702 | { |
703 | struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); | |
704 | struct lp8788_charger *pchg; | |
05ac539b | 705 | struct device *dev = &pdev->dev; |
98a27664 KM |
706 | int ret; |
707 | ||
05ac539b | 708 | pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL); |
98a27664 KM |
709 | if (!pchg) |
710 | return -ENOMEM; | |
711 | ||
712 | pchg->lp = lp; | |
713 | pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL; | |
714 | platform_set_drvdata(pdev, pchg); | |
715 | ||
d5c2b14c | 716 | ret = lp8788_update_charger_params(pdev, pchg); |
98a27664 KM |
717 | if (ret) |
718 | return ret; | |
719 | ||
5aa57f0a | 720 | lp8788_setup_adc_channel(&pdev->dev, pchg); |
98a27664 KM |
721 | |
722 | ret = lp8788_psy_register(pdev, pchg); | |
723 | if (ret) | |
724 | return ret; | |
725 | ||
726 | ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group); | |
727 | if (ret) { | |
728 | lp8788_psy_unregister(pchg); | |
729 | return ret; | |
730 | } | |
731 | ||
732 | ret = lp8788_irq_register(pdev, pchg); | |
733 | if (ret) | |
05ac539b | 734 | dev_warn(dev, "failed to register charger irq: %d\n", ret); |
98a27664 KM |
735 | |
736 | return 0; | |
737 | } | |
738 | ||
415ec69f | 739 | static int lp8788_charger_remove(struct platform_device *pdev) |
98a27664 KM |
740 | { |
741 | struct lp8788_charger *pchg = platform_get_drvdata(pdev); | |
742 | ||
743 | flush_work(&pchg->charger_work); | |
744 | lp8788_irq_unregister(pdev, pchg); | |
745 | sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group); | |
746 | lp8788_psy_unregister(pchg); | |
747 | lp8788_release_adc_channel(pchg); | |
748 | ||
749 | return 0; | |
750 | } | |
751 | ||
752 | static struct platform_driver lp8788_charger_driver = { | |
753 | .probe = lp8788_charger_probe, | |
28ea73f4 | 754 | .remove = lp8788_charger_remove, |
98a27664 KM |
755 | .driver = { |
756 | .name = LP8788_DEV_CHARGER, | |
98a27664 KM |
757 | }, |
758 | }; | |
759 | module_platform_driver(lp8788_charger_driver); | |
760 | ||
761 | MODULE_DESCRIPTION("TI LP8788 Charger Driver"); | |
762 | MODULE_AUTHOR("Milo Kim"); | |
763 | MODULE_LICENSE("GPL"); | |
764 | MODULE_ALIAS("platform:lp8788-charger"); |