diff options
author | Woogyom Kim <milo.kim@nsc.com> | 2012-01-03 23:27:43 -0500 |
---|---|---|
committer | Anton Vorontsov <cbouatmailru@gmail.com> | 2012-01-03 23:29:43 -0500 |
commit | 2165c8a45bf2fba49f54fb81a50914f883bd14df (patch) | |
tree | 42de787666eff2b261012ed50d4a4aa46cea8d63 /drivers/power/lp8727_charger.c | |
parent | ad3d13eee78ec44194bf919a37e2f711e53cbdf0 (diff) |
power_supply: Add LP8727 charger driver
National Semiconductor LP8727 is the battery charger with Micro/Mini
USB interface. This IC includes below functions:
- I2C interface for accessing user registers
- Single input Li-Ion battery charger
- Charger input ID detection from Micro/Mini USB
- Multiplexing switches on USB, UART
Signed-off-by: Woogyom Kim <milo.kim@ti.com>
Signed-off-by: Anton Vorontsov <cbouatmailru@gmail.com>
Diffstat (limited to 'drivers/power/lp8727_charger.c')
-rw-r--r-- | drivers/power/lp8727_charger.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c new file mode 100644 index 000000000000..2a649e07ddde --- /dev/null +++ b/drivers/power/lp8727_charger.c | |||
@@ -0,0 +1,491 @@ | |||
1 | /* | ||
2 | * Driver for LP8727 Micro/Mini USB IC with intergrated charger | ||
3 | * | ||
4 | * Copyright (C) 2011 National Semiconductor | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/slab.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/i2c.h> | ||
16 | #include <linux/power_supply.h> | ||
17 | #include <linux/lp8727.h> | ||
18 | |||
19 | #define DEBOUNCE_MSEC 270 | ||
20 | |||
21 | /* Registers */ | ||
22 | #define CTRL1 0x1 | ||
23 | #define CTRL2 0x2 | ||
24 | #define SWCTRL 0x3 | ||
25 | #define INT1 0x4 | ||
26 | #define INT2 0x5 | ||
27 | #define STATUS1 0x6 | ||
28 | #define STATUS2 0x7 | ||
29 | #define CHGCTRL2 0x9 | ||
30 | |||
31 | /* CTRL1 register */ | ||
32 | #define CP_EN (1 << 0) | ||
33 | #define ADC_EN (1 << 1) | ||
34 | #define ID200_EN (1 << 4) | ||
35 | |||
36 | /* CTRL2 register */ | ||
37 | #define CHGDET_EN (1 << 1) | ||
38 | #define INT_EN (1 << 6) | ||
39 | |||
40 | /* SWCTRL register */ | ||
41 | #define SW_DM1_DM (0x0 << 0) | ||
42 | #define SW_DM1_U1 (0x1 << 0) | ||
43 | #define SW_DM1_HiZ (0x7 << 0) | ||
44 | #define SW_DP2_DP (0x0 << 3) | ||
45 | #define SW_DP2_U2 (0x1 << 3) | ||
46 | #define SW_DP2_HiZ (0x7 << 3) | ||
47 | |||
48 | /* INT1 register */ | ||
49 | #define IDNO (0xF << 0) | ||
50 | #define VBUS (1 << 4) | ||
51 | |||
52 | /* STATUS1 register */ | ||
53 | #define CHGSTAT (3 << 4) | ||
54 | #define CHPORT (1 << 6) | ||
55 | #define DCPORT (1 << 7) | ||
56 | |||
57 | /* STATUS2 register */ | ||
58 | #define TEMP_STAT (3 << 5) | ||
59 | |||
60 | enum lp8727_dev_id { | ||
61 | ID_NONE, | ||
62 | ID_TA, | ||
63 | ID_DEDICATED_CHG, | ||
64 | ID_USB_CHG, | ||
65 | ID_USB_DS, | ||
66 | ID_MAX, | ||
67 | }; | ||
68 | |||
69 | enum lp8727_chg_stat { | ||
70 | PRECHG, | ||
71 | CC, | ||
72 | CV, | ||
73 | EOC, | ||
74 | }; | ||
75 | |||
76 | struct lp8727_psy { | ||
77 | struct power_supply ac; | ||
78 | struct power_supply usb; | ||
79 | struct power_supply batt; | ||
80 | }; | ||
81 | |||
82 | struct lp8727_chg { | ||
83 | struct device *dev; | ||
84 | struct i2c_client *client; | ||
85 | struct mutex xfer_lock; | ||
86 | struct delayed_work work; | ||
87 | struct workqueue_struct *irqthread; | ||
88 | struct lp8727_platform_data *pdata; | ||
89 | struct lp8727_psy *psy; | ||
90 | struct lp8727_chg_param *chg_parm; | ||
91 | enum lp8727_dev_id devid; | ||
92 | }; | ||
93 | |||
94 | static int lp8727_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 * data, u8 len) | ||
95 | { | ||
96 | s32 ret; | ||
97 | |||
98 | mutex_lock(&pchg->xfer_lock); | ||
99 | ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data); | ||
100 | mutex_unlock(&pchg->xfer_lock); | ||
101 | |||
102 | return (ret != len) ? -EIO : 0; | ||
103 | } | ||
104 | |||
105 | static int lp8727_i2c_write(struct lp8727_chg *pchg, u8 reg, u8 * data, u8 len) | ||
106 | { | ||
107 | s32 ret; | ||
108 | |||
109 | mutex_lock(&pchg->xfer_lock); | ||
110 | ret = i2c_smbus_write_i2c_block_data(pchg->client, reg, len, data); | ||
111 | mutex_unlock(&pchg->xfer_lock); | ||
112 | |||
113 | return ret; | ||
114 | } | ||
115 | |||
116 | static inline int lp8727_i2c_read_byte(struct lp8727_chg *pchg, u8 reg, | ||
117 | u8 * data) | ||
118 | { | ||
119 | return lp8727_i2c_read(pchg, reg, data, 1); | ||
120 | } | ||
121 | |||
122 | static inline int lp8727_i2c_write_byte(struct lp8727_chg *pchg, u8 reg, | ||
123 | u8 * data) | ||
124 | { | ||
125 | return lp8727_i2c_write(pchg, reg, data, 1); | ||
126 | } | ||
127 | |||
128 | static int lp8727_is_charger_attached(const char *name, int id) | ||
129 | { | ||
130 | if (name) { | ||
131 | if (!strcmp(name, "ac")) | ||
132 | return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0; | ||
133 | else if (!strcmp(name, "usb")) | ||
134 | return (id == ID_USB_CHG) ? 1 : 0; | ||
135 | } | ||
136 | |||
137 | return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0; | ||
138 | } | ||
139 | |||
140 | static void lp8727_init_device(struct lp8727_chg *pchg) | ||
141 | { | ||
142 | u8 val; | ||
143 | |||
144 | val = ID200_EN | ADC_EN | CP_EN; | ||
145 | if (lp8727_i2c_write_byte(pchg, CTRL1, &val)) | ||
146 | dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL1); | ||
147 | |||
148 | val = INT_EN | CHGDET_EN; | ||
149 | if (lp8727_i2c_write_byte(pchg, CTRL2, &val)) | ||
150 | dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL2); | ||
151 | } | ||
152 | |||
153 | static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg) | ||
154 | { | ||
155 | u8 val; | ||
156 | (void)lp8727_i2c_read_byte(pchg, STATUS1, &val); | ||
157 | return (val & DCPORT); | ||
158 | } | ||
159 | |||
160 | static int lp8727_is_usb_charger(struct lp8727_chg *pchg) | ||
161 | { | ||
162 | u8 val; | ||
163 | (void)lp8727_i2c_read_byte(pchg, STATUS1, &val); | ||
164 | return (val & CHPORT); | ||
165 | } | ||
166 | |||
167 | static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) | ||
168 | { | ||
169 | u8 val = sw; | ||
170 | (void)lp8727_i2c_write_byte(pchg, SWCTRL, &val); | ||
171 | } | ||
172 | |||
173 | static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) | ||
174 | { | ||
175 | u8 devid = ID_NONE; | ||
176 | u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ; | ||
177 | |||
178 | switch (id) { | ||
179 | case 0x5: | ||
180 | devid = ID_TA; | ||
181 | pchg->chg_parm = &pchg->pdata->ac; | ||
182 | break; | ||
183 | case 0xB: | ||
184 | if (lp8727_is_dedicated_charger(pchg)) { | ||
185 | pchg->chg_parm = &pchg->pdata->ac; | ||
186 | devid = ID_DEDICATED_CHG; | ||
187 | } else if (lp8727_is_usb_charger(pchg)) { | ||
188 | pchg->chg_parm = &pchg->pdata->usb; | ||
189 | devid = ID_USB_CHG; | ||
190 | swctrl = SW_DM1_DM | SW_DP2_DP; | ||
191 | } else if (vbusin) { | ||
192 | devid = ID_USB_DS; | ||
193 | swctrl = SW_DM1_DM | SW_DP2_DP; | ||
194 | } | ||
195 | break; | ||
196 | default: | ||
197 | devid = ID_NONE; | ||
198 | pchg->chg_parm = NULL; | ||
199 | break; | ||
200 | } | ||
201 | |||
202 | pchg->devid = devid; | ||
203 | lp8727_ctrl_switch(pchg, swctrl); | ||
204 | } | ||
205 | |||
206 | static void lp8727_enable_chgdet(struct lp8727_chg *pchg) | ||
207 | { | ||
208 | u8 val; | ||
209 | |||
210 | (void)lp8727_i2c_read_byte(pchg, CTRL2, &val); | ||
211 | val |= CHGDET_EN; | ||
212 | (void)lp8727_i2c_write_byte(pchg, CTRL2, &val); | ||
213 | } | ||
214 | |||
215 | static void lp8727_delayed_func(struct work_struct *_work) | ||
216 | { | ||
217 | u8 intstat[2], idno, vbus; | ||
218 | struct lp8727_chg *pchg = | ||
219 | container_of(_work, struct lp8727_chg, work.work); | ||
220 | |||
221 | if (lp8727_i2c_read(pchg, INT1, intstat, 2)) { | ||
222 | dev_err(pchg->dev, "can not read INT registers\n"); | ||
223 | return; | ||
224 | } | ||
225 | |||
226 | idno = intstat[0] & IDNO; | ||
227 | vbus = intstat[0] & VBUS; | ||
228 | |||
229 | lp8727_id_detection(pchg, idno, vbus); | ||
230 | lp8727_enable_chgdet(pchg); | ||
231 | |||
232 | power_supply_changed(&pchg->psy->ac); | ||
233 | power_supply_changed(&pchg->psy->usb); | ||
234 | power_supply_changed(&pchg->psy->batt); | ||
235 | } | ||
236 | |||
237 | static irqreturn_t lp8727_isr_func(int irq, void *ptr) | ||
238 | { | ||
239 | struct lp8727_chg *pchg = ptr; | ||
240 | unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC); | ||
241 | |||
242 | queue_delayed_work(pchg->irqthread, &pchg->work, delay); | ||
243 | |||
244 | return IRQ_HANDLED; | ||
245 | } | ||
246 | |||
247 | static void lp8727_intr_config(struct lp8727_chg *pchg) | ||
248 | { | ||
249 | INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); | ||
250 | |||
251 | pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd"); | ||
252 | if (!pchg->irqthread) | ||
253 | dev_err(pchg->dev, "can not create thread for lp8727\n"); | ||
254 | |||
255 | if (request_threaded_irq(pchg->client->irq, | ||
256 | NULL, | ||
257 | lp8727_isr_func, | ||
258 | IRQF_TRIGGER_FALLING, "lp8727_irq", pchg)) { | ||
259 | dev_err(pchg->dev, "lp8727 irq can not be registered\n"); | ||
260 | } | ||
261 | } | ||
262 | |||
263 | static enum power_supply_property lp8727_charger_prop[] = { | ||
264 | POWER_SUPPLY_PROP_ONLINE, | ||
265 | }; | ||
266 | |||
267 | static enum power_supply_property lp8727_battery_prop[] = { | ||
268 | POWER_SUPPLY_PROP_STATUS, | ||
269 | POWER_SUPPLY_PROP_HEALTH, | ||
270 | POWER_SUPPLY_PROP_PRESENT, | ||
271 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | ||
272 | POWER_SUPPLY_PROP_CAPACITY, | ||
273 | POWER_SUPPLY_PROP_TEMP, | ||
274 | }; | ||
275 | |||
276 | static char *battery_supplied_to[] = { | ||
277 | "main_batt", | ||
278 | }; | ||
279 | |||
280 | static int lp8727_charger_get_property(struct power_supply *psy, | ||
281 | enum power_supply_property psp, | ||
282 | union power_supply_propval *val) | ||
283 | { | ||
284 | struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | ||
285 | |||
286 | if (psp == POWER_SUPPLY_PROP_ONLINE) { | ||
287 | val->intval = lp8727_is_charger_attached(psy->name, | ||
288 | pchg->devid); | ||
289 | } | ||
290 | |||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | static int lp8727_battery_get_property(struct power_supply *psy, | ||
295 | enum power_supply_property psp, | ||
296 | union power_supply_propval *val) | ||
297 | { | ||
298 | struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | ||
299 | u8 read; | ||
300 | |||
301 | switch (psp) { | ||
302 | case POWER_SUPPLY_PROP_STATUS: | ||
303 | if (lp8727_is_charger_attached(psy->name, pchg->devid)) { | ||
304 | (void)lp8727_i2c_read_byte(pchg, STATUS1, &read); | ||
305 | if (((read & CHGSTAT) >> 4) == EOC) | ||
306 | val->intval = POWER_SUPPLY_STATUS_FULL; | ||
307 | else | ||
308 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | ||
309 | } else { | ||
310 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | ||
311 | } | ||
312 | break; | ||
313 | case POWER_SUPPLY_PROP_HEALTH: | ||
314 | (void)lp8727_i2c_read_byte(pchg, STATUS2, &read); | ||
315 | read = (read & TEMP_STAT) >> 5; | ||
316 | if (read >= 0x1 && read <= 0x3) | ||
317 | val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; | ||
318 | else | ||
319 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | ||
320 | break; | ||
321 | case POWER_SUPPLY_PROP_PRESENT: | ||
322 | if (pchg->pdata->get_batt_present) | ||
323 | val->intval = pchg->pdata->get_batt_present(); | ||
324 | break; | ||
325 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
326 | if (pchg->pdata->get_batt_level) | ||
327 | val->intval = pchg->pdata->get_batt_level(); | ||
328 | break; | ||
329 | case POWER_SUPPLY_PROP_CAPACITY: | ||
330 | if (pchg->pdata->get_batt_capacity) | ||
331 | val->intval = pchg->pdata->get_batt_capacity(); | ||
332 | break; | ||
333 | case POWER_SUPPLY_PROP_TEMP: | ||
334 | if (pchg->pdata->get_batt_temp) | ||
335 | val->intval = pchg->pdata->get_batt_temp(); | ||
336 | break; | ||
337 | default: | ||
338 | break; | ||
339 | } | ||
340 | |||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | static void lp8727_charger_changed(struct power_supply *psy) | ||
345 | { | ||
346 | struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); | ||
347 | u8 val; | ||
348 | u8 eoc_level, ichg; | ||
349 | |||
350 | if (lp8727_is_charger_attached(psy->name, pchg->devid)) { | ||
351 | if (pchg->chg_parm) { | ||
352 | eoc_level = pchg->chg_parm->eoc_level; | ||
353 | ichg = pchg->chg_parm->ichg; | ||
354 | val = (ichg << 4) | eoc_level; | ||
355 | (void)lp8727_i2c_write_byte(pchg, CHGCTRL2, &val); | ||
356 | } | ||
357 | } | ||
358 | } | ||
359 | |||
360 | static int lp8727_register_psy(struct lp8727_chg *pchg) | ||
361 | { | ||
362 | struct lp8727_psy *psy; | ||
363 | |||
364 | psy = kzalloc(sizeof(*psy), GFP_KERNEL); | ||
365 | if (!psy) | ||
366 | goto err_mem; | ||
367 | |||
368 | pchg->psy = psy; | ||
369 | |||
370 | psy->ac.name = "ac"; | ||
371 | psy->ac.type = POWER_SUPPLY_TYPE_MAINS; | ||
372 | psy->ac.properties = lp8727_charger_prop; | ||
373 | psy->ac.num_properties = ARRAY_SIZE(lp8727_charger_prop); | ||
374 | psy->ac.get_property = lp8727_charger_get_property; | ||
375 | psy->ac.supplied_to = battery_supplied_to; | ||
376 | psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to); | ||
377 | |||
378 | if (power_supply_register(pchg->dev, &psy->ac)) | ||
379 | goto err_psy; | ||
380 | |||
381 | psy->usb.name = "usb"; | ||
382 | psy->usb.type = POWER_SUPPLY_TYPE_USB; | ||
383 | psy->usb.properties = lp8727_charger_prop; | ||
384 | psy->usb.num_properties = ARRAY_SIZE(lp8727_charger_prop); | ||
385 | psy->usb.get_property = lp8727_charger_get_property; | ||
386 | psy->usb.supplied_to = battery_supplied_to; | ||
387 | psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to); | ||
388 | |||
389 | if (power_supply_register(pchg->dev, &psy->usb)) | ||
390 | goto err_psy; | ||
391 | |||
392 | psy->batt.name = "main_batt"; | ||
393 | psy->batt.type = POWER_SUPPLY_TYPE_BATTERY; | ||
394 | psy->batt.properties = lp8727_battery_prop; | ||
395 | psy->batt.num_properties = ARRAY_SIZE(lp8727_battery_prop); | ||
396 | psy->batt.get_property = lp8727_battery_get_property; | ||
397 | psy->batt.external_power_changed = lp8727_charger_changed; | ||
398 | |||
399 | if (power_supply_register(pchg->dev, &psy->batt)) | ||
400 | goto err_psy; | ||
401 | |||
402 | return 0; | ||
403 | |||
404 | err_mem: | ||
405 | return -ENOMEM; | ||
406 | err_psy: | ||
407 | kfree(psy); | ||
408 | return -EPERM; | ||
409 | } | ||
410 | |||
411 | static void lp8727_unregister_psy(struct lp8727_chg *pchg) | ||
412 | { | ||
413 | struct lp8727_psy *psy = pchg->psy; | ||
414 | |||
415 | if (psy) { | ||
416 | power_supply_unregister(&psy->ac); | ||
417 | power_supply_unregister(&psy->usb); | ||
418 | power_supply_unregister(&psy->batt); | ||
419 | kfree(psy); | ||
420 | } | ||
421 | } | ||
422 | |||
423 | static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) | ||
424 | { | ||
425 | struct lp8727_chg *pchg; | ||
426 | int ret; | ||
427 | |||
428 | pchg = kzalloc(sizeof(*pchg), GFP_KERNEL); | ||
429 | if (!pchg) | ||
430 | return -ENOMEM; | ||
431 | |||
432 | pchg->client = cl; | ||
433 | pchg->dev = &cl->dev; | ||
434 | pchg->pdata = cl->dev.platform_data; | ||
435 | i2c_set_clientdata(cl, pchg); | ||
436 | |||
437 | mutex_init(&pchg->xfer_lock); | ||
438 | |||
439 | lp8727_init_device(pchg); | ||
440 | lp8727_intr_config(pchg); | ||
441 | |||
442 | ret = lp8727_register_psy(pchg); | ||
443 | if (ret) { | ||
444 | dev_err(pchg->dev, | ||
445 | "can not register power supplies. err=%d", ret); | ||
446 | } | ||
447 | |||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | static int __devexit lp8727_remove(struct i2c_client *cl) | ||
452 | { | ||
453 | struct lp8727_chg *pchg = i2c_get_clientdata(cl); | ||
454 | |||
455 | lp8727_unregister_psy(pchg); | ||
456 | free_irq(pchg->client->irq, pchg); | ||
457 | flush_workqueue(pchg->irqthread); | ||
458 | destroy_workqueue(pchg->irqthread); | ||
459 | kfree(pchg); | ||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | static const struct i2c_device_id lp8727_ids[] = { | ||
464 | {"lp8727", 0}, | ||
465 | }; | ||
466 | |||
467 | static struct i2c_driver lp8727_driver = { | ||
468 | .driver = { | ||
469 | .name = "lp8727", | ||
470 | }, | ||
471 | .probe = lp8727_probe, | ||
472 | .remove = __devexit_p(lp8727_remove), | ||
473 | .id_table = lp8727_ids, | ||
474 | }; | ||
475 | |||
476 | static int __init lp8727_init(void) | ||
477 | { | ||
478 | return i2c_add_driver(&lp8727_driver); | ||
479 | } | ||
480 | |||
481 | static void __exit lp8727_chg_exit(void) | ||
482 | { | ||
483 | i2c_del_driver(&lp8727_driver); | ||
484 | } | ||
485 | |||
486 | module_init(lp8727_init); | ||
487 | module_exit(lp8727_chg_exit); | ||
488 | |||
489 | MODULE_DESCRIPTION("National Semiconductor LP8727 charger driver"); | ||
490 | MODULE_AUTHOR("Woogyom Kim <milo.kim@nsc.com>"); | ||
491 | MODULE_LICENSE("GPL"); | ||