diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/power/tps80031-charger.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/power/tps80031-charger.c')
-rw-r--r-- | drivers/power/tps80031-charger.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/drivers/power/tps80031-charger.c b/drivers/power/tps80031-charger.c new file mode 100644 index 00000000000..93b283e0b04 --- /dev/null +++ b/drivers/power/tps80031-charger.c | |||
@@ -0,0 +1,476 @@ | |||
1 | /* | ||
2 | * drivers/power/tps80031_charger.c | ||
3 | * | ||
4 | * Battery charger driver for TI's tps80031 | ||
5 | * | ||
6 | * Copyright (c) 2011, NVIDIA Corporation. | ||
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 as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/err.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/regulator/driver.h> | ||
29 | #include <linux/regulator/machine.h> | ||
30 | #include <linux/mfd/tps80031.h> | ||
31 | #include <linux/tps80031-charger.h> | ||
32 | |||
33 | #define CONTROLLER_CTRL1 0xe1 | ||
34 | #define CONTROLLER_STAT1 0xe3 | ||
35 | #define CHARGERUSB_CTRL2 0xe9 | ||
36 | #define CHARGERUSB_CTRL3 0xea | ||
37 | #define CHARGERUSB_VOREG 0xec | ||
38 | #define CHARGERUSB_VICHRG 0xed | ||
39 | #define CHARGERUSB_CINLIMIT 0xee | ||
40 | #define CHARGERUSB_CTRLLIMIT2 0xf0 | ||
41 | #define CHARGERUSB_CTRLLIMIT1 0xef | ||
42 | #define CHARGERUSB_VICHRG_PC 0xdd | ||
43 | #define CONTROLLER_WDG 0xe2 | ||
44 | #define LINEAR_CHRG_STS 0xde | ||
45 | |||
46 | #define TPS80031_VBUS_DET BIT(2) | ||
47 | #define TPS80031_VAC_DET BIT(3) | ||
48 | |||
49 | struct tps80031_charger { | ||
50 | int max_charge_current_mA; | ||
51 | int max_charge_volt_mV; | ||
52 | struct device *dev; | ||
53 | struct regulator_dev *rdev; | ||
54 | struct regulator_desc reg_desc; | ||
55 | struct regulator_init_data reg_init_data; | ||
56 | struct tps80031_charger_platform_data *pdata; | ||
57 | int (*board_init)(void *board_data); | ||
58 | void *board_data; | ||
59 | int irq_base; | ||
60 | int watch_time_sec; | ||
61 | enum charging_states state; | ||
62 | int charging_term_current_mA; | ||
63 | charging_callback_t charger_cb; | ||
64 | void *charger_cb_data; | ||
65 | }; | ||
66 | |||
67 | static struct tps80031_charger *charger_data; | ||
68 | static uint8_t charging_current_val_code[] = { | ||
69 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0x27, | ||
70 | 0x37, 0x28, 0x38, 0x29, 0x39, 0x2A, 0x3A, 0x2B, 0x3B, 0x2C, | ||
71 | 0x3C, 0x2D, 0x3D, 0x2E, | ||
72 | }; | ||
73 | |||
74 | static int set_charge_current_limit(struct regulator_dev *rdev, | ||
75 | int min_uA, int max_uA) | ||
76 | { | ||
77 | struct tps80031_charger *charger = rdev_get_drvdata(rdev); | ||
78 | int max_vbus_current = 1500; | ||
79 | int max_charge_current = 1500; | ||
80 | int ret; | ||
81 | |||
82 | dev_info(charger->dev, "%s(): Min curr %dmA and max current %dmA\n", | ||
83 | __func__, min_uA/1000, max_uA/1000); | ||
84 | |||
85 | if (!max_uA) { | ||
86 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
87 | CONTROLLER_CTRL1, 0x0); | ||
88 | if (ret < 0) | ||
89 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
90 | __func__, CONTROLLER_CTRL1); | ||
91 | |||
92 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
93 | CONTROLLER_WDG, 0x0); | ||
94 | if (ret < 0) | ||
95 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
96 | __func__, CONTROLLER_WDG); | ||
97 | charger->state = charging_state_charging_stopped; | ||
98 | if (charger->charger_cb) | ||
99 | charger->charger_cb(charger->state, | ||
100 | charger->charger_cb_data); | ||
101 | return ret; | ||
102 | } | ||
103 | |||
104 | max_vbus_current = min(max_uA/1000, max_vbus_current); | ||
105 | max_vbus_current = max_vbus_current/50; | ||
106 | if (max_vbus_current) | ||
107 | max_vbus_current--; | ||
108 | ret = tps80031_update(charger->dev->parent, SLAVE_ID2, | ||
109 | CHARGERUSB_CINLIMIT, | ||
110 | charging_current_val_code[max_vbus_current], 0x3F); | ||
111 | if (ret < 0) { | ||
112 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
113 | __func__, CHARGERUSB_CINLIMIT); | ||
114 | return ret; | ||
115 | } | ||
116 | |||
117 | max_charge_current = min(max_uA/1000, max_charge_current); | ||
118 | if (max_charge_current <= 300) | ||
119 | max_charge_current = 0; | ||
120 | else if ((max_charge_current > 300) && (max_charge_current <= 500)) | ||
121 | max_charge_current = (max_charge_current - 300)/50; | ||
122 | else | ||
123 | max_charge_current = (max_charge_current - 500) / 100 + 4; | ||
124 | ret = tps80031_update(charger->dev->parent, SLAVE_ID2, | ||
125 | CHARGERUSB_VICHRG, (uint8_t)max_charge_current, 0xF); | ||
126 | if (ret < 0) { | ||
127 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
128 | __func__, CHARGERUSB_VICHRG); | ||
129 | return ret; | ||
130 | } | ||
131 | |||
132 | /* Enable watchdog timer */ | ||
133 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
134 | CONTROLLER_WDG, charger->watch_time_sec); | ||
135 | if (ret < 0) { | ||
136 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
137 | __func__, CONTROLLER_WDG); | ||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | /* Enable the charging */ | ||
142 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
143 | CONTROLLER_CTRL1, 0x30); | ||
144 | if (ret < 0) { | ||
145 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
146 | __func__, CONTROLLER_CTRL1); | ||
147 | return ret; | ||
148 | } | ||
149 | charger->state = charging_state_charging_in_progress; | ||
150 | if (charger->charger_cb) | ||
151 | charger->charger_cb(charger->state, | ||
152 | charger->charger_cb_data); | ||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static struct regulator_ops tegra_regulator_ops = { | ||
157 | .set_current_limit = set_charge_current_limit, | ||
158 | }; | ||
159 | |||
160 | int register_charging_state_callback(charging_callback_t cb, void *args) | ||
161 | { | ||
162 | struct tps80031_charger *charger = charger_data; | ||
163 | if (!charger_data) | ||
164 | return -ENODEV; | ||
165 | |||
166 | charger->charger_cb = cb; | ||
167 | charger->charger_cb_data = args; | ||
168 | return 0; | ||
169 | } | ||
170 | EXPORT_SYMBOL_GPL(register_charging_state_callback); | ||
171 | |||
172 | static int configure_charging_parameter(struct tps80031_charger *charger) | ||
173 | { | ||
174 | int ret; | ||
175 | int max_charge_current; | ||
176 | int max_charge_volt; | ||
177 | int term_current; | ||
178 | |||
179 | /* Disable watchdog timer */ | ||
180 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
181 | CONTROLLER_WDG, 0x0); | ||
182 | if (ret < 0) { | ||
183 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
184 | __func__, CONTROLLER_WDG); | ||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | /* Disable the charging if any */ | ||
189 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
190 | CONTROLLER_CTRL1, 0x0); | ||
191 | if (ret < 0) { | ||
192 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
193 | __func__, CONTROLLER_CTRL1); | ||
194 | return ret; | ||
195 | } | ||
196 | |||
197 | if (charger->board_init) { | ||
198 | ret = charger->board_init(charger->board_data); | ||
199 | if (ret < 0) { | ||
200 | dev_err(charger->dev, "%s(): Failed in board init\n", | ||
201 | __func__); | ||
202 | return ret; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | /* Unlock value */ | ||
207 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
208 | CHARGERUSB_CTRLLIMIT2, 0); | ||
209 | if (ret < 0) { | ||
210 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
211 | __func__, CHARGERUSB_CTRLLIMIT2); | ||
212 | return ret; | ||
213 | } | ||
214 | |||
215 | /* Set max current limit */ | ||
216 | max_charge_current = min(1500, charger->max_charge_current_mA); | ||
217 | if (max_charge_current < 100) | ||
218 | max_charge_current = 0; | ||
219 | else | ||
220 | max_charge_current = (max_charge_current - 100)/100; | ||
221 | max_charge_current &= 0xF; | ||
222 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
223 | CHARGERUSB_CTRLLIMIT2, (uint8_t)max_charge_current); | ||
224 | if (ret < 0) { | ||
225 | dev_err(charger->dev, "%s(): Failed in writing register " | ||
226 | "0x%02x\n", __func__, CHARGERUSB_CTRLLIMIT2); | ||
227 | return ret; | ||
228 | } | ||
229 | |||
230 | /* Set max voltage limit */ | ||
231 | max_charge_volt = min(4760, charger->max_charge_volt_mV); | ||
232 | max_charge_volt = max(3500, max_charge_volt); | ||
233 | max_charge_volt -= 3500; | ||
234 | max_charge_volt = max_charge_volt/20; | ||
235 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
236 | CHARGERUSB_CTRLLIMIT1, (uint8_t)max_charge_volt); | ||
237 | if (ret < 0) { | ||
238 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
239 | __func__, CHARGERUSB_CTRLLIMIT1); | ||
240 | return ret; | ||
241 | } | ||
242 | |||
243 | /* Lock value */ | ||
244 | ret = tps80031_set_bits(charger->dev->parent, SLAVE_ID2, | ||
245 | CHARGERUSB_CTRLLIMIT2, (1 << 4)); | ||
246 | if (ret < 0) { | ||
247 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
248 | __func__, CHARGERUSB_CTRLLIMIT2); | ||
249 | return ret; | ||
250 | } | ||
251 | |||
252 | /* set Pre Charge current to 400mA */ | ||
253 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, 0xDE, 0x3); | ||
254 | if (ret < 0) { | ||
255 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
256 | __func__, 0xDD); | ||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | /* set charging termination current*/ | ||
261 | if (charger->charging_term_current_mA > 400) | ||
262 | term_current = 7; | ||
263 | else | ||
264 | term_current = (charger->charging_term_current_mA - 50)/50; | ||
265 | term_current = term_current << 5; | ||
266 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
267 | CHARGERUSB_CTRL2, term_current); | ||
268 | if (ret < 0) { | ||
269 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
270 | __func__, CHARGERUSB_CTRL2); | ||
271 | return ret; | ||
272 | } | ||
273 | |||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | static irqreturn_t linch_status_isr(int irq, void *dev_id) | ||
278 | { | ||
279 | struct tps80031_charger *charger = dev_id; | ||
280 | uint8_t linch_status; | ||
281 | int ret; | ||
282 | dev_info(charger->dev, "%s() got called\n", __func__); | ||
283 | |||
284 | ret = tps80031_read(charger->dev->parent, SLAVE_ID2, | ||
285 | LINEAR_CHRG_STS, &linch_status); | ||
286 | if (ret < 0) { | ||
287 | dev_err(charger->dev, "%s(): Failed in reading register 0x%02x\n", | ||
288 | __func__, LINEAR_CHRG_STS); | ||
289 | } else { | ||
290 | dev_info(charger->dev, "%s():The status of LINEAR_CHRG_STS is 0x%02x\n", | ||
291 | __func__, linch_status); | ||
292 | if (linch_status & 0x20) { | ||
293 | charger->state = charging_state_charging_completed; | ||
294 | if (charger->charger_cb) | ||
295 | charger->charger_cb(charger->state, | ||
296 | charger->charger_cb_data); | ||
297 | } | ||
298 | } | ||
299 | |||
300 | return IRQ_HANDLED; | ||
301 | } | ||
302 | |||
303 | static irqreturn_t watchdog_expire_isr(int irq, void *dev_id) | ||
304 | { | ||
305 | struct tps80031_charger *charger = dev_id; | ||
306 | int ret; | ||
307 | |||
308 | dev_info(charger->dev, "%s()\n", __func__); | ||
309 | if (charger->state != charging_state_charging_in_progress) | ||
310 | return IRQ_HANDLED; | ||
311 | |||
312 | /* Enable watchdog timer again*/ | ||
313 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, CONTROLLER_WDG, | ||
314 | charger->watch_time_sec); | ||
315 | if (ret < 0) | ||
316 | dev_err(charger->dev, "%s(): Failed in writing register 0x%02x\n", | ||
317 | __func__, CONTROLLER_WDG); | ||
318 | |||
319 | /* Rewrite to enable the charging */ | ||
320 | if (!ret) { | ||
321 | ret = tps80031_write(charger->dev->parent, SLAVE_ID2, | ||
322 | CONTROLLER_CTRL1, 0x30); | ||
323 | if (ret < 0) | ||
324 | dev_err(charger->dev, "%s(): Failed in writing " | ||
325 | "register 0x%02x\n", | ||
326 | __func__, CONTROLLER_CTRL1); | ||
327 | } | ||
328 | return IRQ_HANDLED; | ||
329 | } | ||
330 | |||
331 | static int tps80031_charger_probe(struct platform_device *pdev) | ||
332 | { | ||
333 | int ret = 0; | ||
334 | struct device *dev = &pdev->dev; | ||
335 | struct tps80031_charger *charger; | ||
336 | struct tps80031_charger_platform_data *pdata = pdev->dev.platform_data; | ||
337 | |||
338 | dev_info(dev, "%s()\n", __func__); | ||
339 | |||
340 | if (!pdata) { | ||
341 | dev_err(dev, "%s() No platform data, exiting..\n", __func__); | ||
342 | return -ENODEV; | ||
343 | } | ||
344 | |||
345 | if (!pdata->num_consumer_supplies) { | ||
346 | dev_err(dev, "%s() No consumer supply list, exiting..\n", | ||
347 | __func__); | ||
348 | return -ENODEV; | ||
349 | } | ||
350 | |||
351 | charger = kzalloc(sizeof(*charger), GFP_KERNEL); | ||
352 | if (!charger) { | ||
353 | dev_err(dev, "failed to allocate memory status\n"); | ||
354 | return -ENOMEM; | ||
355 | } | ||
356 | |||
357 | charger->dev = &pdev->dev; | ||
358 | |||
359 | charger->max_charge_current_mA = (pdata->max_charge_current_mA) ? | ||
360 | pdata->max_charge_current_mA : 1000; | ||
361 | charger->max_charge_volt_mV = (pdata->max_charge_volt_mV) ? | ||
362 | pdata->max_charge_volt_mV : 4200; | ||
363 | charger->irq_base = pdata->irq_base; | ||
364 | charger->watch_time_sec = min(pdata->watch_time_sec, 127); | ||
365 | if (!charger->watch_time_sec) | ||
366 | charger->watch_time_sec = 127; | ||
367 | charger->charging_term_current_mA = | ||
368 | min(50, pdata->charging_term_current_mA); | ||
369 | if (charger->charging_term_current_mA < 50) | ||
370 | charger->charging_term_current_mA = 50; | ||
371 | |||
372 | charger->reg_desc.name = "vbus_charger"; | ||
373 | charger->reg_desc.id = pdata->regulator_id; | ||
374 | charger->reg_desc.ops = &tegra_regulator_ops; | ||
375 | charger->reg_desc.type = REGULATOR_CURRENT; | ||
376 | charger->reg_desc.owner = THIS_MODULE; | ||
377 | |||
378 | charger->reg_init_data.supply_regulator = NULL; | ||
379 | charger->reg_init_data.num_consumer_supplies = | ||
380 | pdata->num_consumer_supplies; | ||
381 | charger->reg_init_data.consumer_supplies = pdata->consumer_supplies; | ||
382 | charger->reg_init_data.regulator_init = NULL; | ||
383 | charger->reg_init_data.driver_data = charger; | ||
384 | charger->reg_init_data.constraints.name = "vbus_charger"; | ||
385 | charger->reg_init_data.constraints.min_uA = 0; | ||
386 | charger->reg_init_data.constraints.max_uA = | ||
387 | pdata->max_charge_current_mA * 1000; | ||
388 | charger->reg_init_data.constraints.valid_modes_mask = | ||
389 | REGULATOR_MODE_NORMAL | | ||
390 | REGULATOR_MODE_STANDBY; | ||
391 | charger->reg_init_data.constraints.valid_ops_mask = | ||
392 | REGULATOR_CHANGE_MODE | | ||
393 | REGULATOR_CHANGE_STATUS | | ||
394 | REGULATOR_CHANGE_CURRENT; | ||
395 | |||
396 | charger->board_init = pdata->board_init; | ||
397 | charger->board_data = pdata->board_data; | ||
398 | charger->state = charging_state_idle; | ||
399 | |||
400 | charger->rdev = regulator_register(&charger->reg_desc, &pdev->dev, | ||
401 | &charger->reg_init_data, charger); | ||
402 | if (IS_ERR(charger->rdev)) { | ||
403 | dev_err(&pdev->dev, "failed to register %s\n", | ||
404 | charger->reg_desc.name); | ||
405 | ret = PTR_ERR(charger->rdev); | ||
406 | goto regulator_fail; | ||
407 | } | ||
408 | |||
409 | ret = request_threaded_irq(charger->irq_base + TPS80031_INT_LINCH_GATED, | ||
410 | NULL, linch_status_isr, 0, "tps80031-linch", charger); | ||
411 | if (ret) { | ||
412 | dev_err(&pdev->dev, "Unable to register irq %d; error %d\n", | ||
413 | charger->irq_base + TPS80031_INT_LINCH_GATED, ret); | ||
414 | goto irq_linch_fail; | ||
415 | } | ||
416 | |||
417 | ret = request_threaded_irq(charger->irq_base + TPS80031_INT_FAULT_WDG, | ||
418 | NULL, watchdog_expire_isr, 0, "tps80031-wdg", charger); | ||
419 | if (ret) { | ||
420 | dev_err(&pdev->dev, "Unable to register irq %d; error %d\n", | ||
421 | charger->irq_base + TPS80031_INT_FAULT_WDG, ret); | ||
422 | goto irq_wdg_fail; | ||
423 | } | ||
424 | |||
425 | ret = configure_charging_parameter(charger); | ||
426 | if (ret) | ||
427 | goto config_fail; | ||
428 | |||
429 | dev_set_drvdata(&pdev->dev, charger); | ||
430 | charger_data = charger; | ||
431 | return ret; | ||
432 | |||
433 | config_fail: | ||
434 | free_irq(charger->irq_base + TPS80031_INT_FAULT_WDG, charger); | ||
435 | irq_wdg_fail: | ||
436 | free_irq(charger->irq_base + TPS80031_INT_LINCH_GATED, charger); | ||
437 | irq_linch_fail: | ||
438 | regulator_unregister(charger->rdev); | ||
439 | regulator_fail: | ||
440 | kfree(charger); | ||
441 | return ret; | ||
442 | } | ||
443 | |||
444 | static int tps80031_charger_remove(struct platform_device *pdev) | ||
445 | { | ||
446 | struct tps80031_charger *charger = dev_get_drvdata(&pdev->dev); | ||
447 | |||
448 | regulator_unregister(charger->rdev); | ||
449 | kfree(charger); | ||
450 | return 0; | ||
451 | } | ||
452 | |||
453 | static struct platform_driver tps80031_charger_driver = { | ||
454 | .driver = { | ||
455 | .name = "tps80031-charger", | ||
456 | .owner = THIS_MODULE, | ||
457 | }, | ||
458 | .probe = tps80031_charger_probe, | ||
459 | .remove = tps80031_charger_remove, | ||
460 | }; | ||
461 | |||
462 | static int __init tps80031_charger_init(void) | ||
463 | { | ||
464 | return platform_driver_register(&tps80031_charger_driver); | ||
465 | } | ||
466 | |||
467 | static void __exit tps80031_charger_exit(void) | ||
468 | { | ||
469 | platform_driver_unregister(&tps80031_charger_driver); | ||
470 | } | ||
471 | |||
472 | subsys_initcall(tps80031_charger_init); | ||
473 | module_exit(tps80031_charger_exit); | ||
474 | |||
475 | MODULE_LICENSE("GPL"); | ||
476 | MODULE_DESCRIPTION("tps80031 battery charger driver"); | ||