Commit | Line | Data |
---|---|---|
b14a9ccc MH |
1 | /* |
2 | * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver | |
3 | * | |
4 | * Copyright (C) 2011 Samsung Electronics | |
5 | * MyungJoo Ham <myungjoo.ham@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
20 | * | |
21 | */ | |
22 | ||
23 | #include <linux/gpio.h> | |
24 | #include <linux/interrupt.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/power_supply.h> | |
27 | #include <linux/platform_device.h> | |
28 | #include <linux/power/max8903_charger.h> | |
29 | ||
30 | struct max8903_data { | |
31 | struct max8903_pdata *pdata; | |
32 | struct device *dev; | |
33 | struct power_supply psy; | |
34 | bool fault; | |
35 | bool usb_in; | |
36 | bool ta_in; | |
37 | }; | |
38 | ||
39 | static enum power_supply_property max8903_charger_props[] = { | |
40 | POWER_SUPPLY_PROP_STATUS, /* Charger status output */ | |
41 | POWER_SUPPLY_PROP_ONLINE, /* External power source */ | |
42 | POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ | |
43 | }; | |
44 | ||
45 | static int max8903_get_property(struct power_supply *psy, | |
46 | enum power_supply_property psp, | |
47 | union power_supply_propval *val) | |
48 | { | |
49 | struct max8903_data *data = container_of(psy, | |
50 | struct max8903_data, psy); | |
51 | ||
52 | switch (psp) { | |
53 | case POWER_SUPPLY_PROP_STATUS: | |
54 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
55 | if (data->pdata->chg) { | |
56 | if (gpio_get_value(data->pdata->chg) == 0) | |
57 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | |
58 | else if (data->usb_in || data->ta_in) | |
59 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
60 | else | |
61 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
62 | } | |
63 | break; | |
64 | case POWER_SUPPLY_PROP_ONLINE: | |
65 | val->intval = 0; | |
66 | if (data->usb_in || data->ta_in) | |
67 | val->intval = 1; | |
68 | break; | |
69 | case POWER_SUPPLY_PROP_HEALTH: | |
70 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
71 | if (data->fault) | |
72 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | |
73 | break; | |
74 | default: | |
75 | return -EINVAL; | |
76 | } | |
77 | return 0; | |
78 | } | |
79 | ||
80 | static irqreturn_t max8903_dcin(int irq, void *_data) | |
81 | { | |
82 | struct max8903_data *data = _data; | |
83 | struct max8903_pdata *pdata = data->pdata; | |
84 | bool ta_in; | |
85 | enum power_supply_type old_type; | |
86 | ||
87 | ta_in = gpio_get_value(pdata->dok) ? false : true; | |
88 | ||
89 | if (ta_in == data->ta_in) | |
90 | return IRQ_HANDLED; | |
91 | ||
92 | data->ta_in = ta_in; | |
93 | ||
94 | /* Set Current-Limit-Mode 1:DC 0:USB */ | |
95 | if (pdata->dcm) | |
96 | gpio_set_value(pdata->dcm, ta_in ? 1 : 0); | |
97 | ||
98 | /* Charger Enable / Disable (cen is negated) */ | |
99 | if (pdata->cen) | |
100 | gpio_set_value(pdata->cen, ta_in ? 0 : | |
101 | (data->usb_in ? 0 : 1)); | |
102 | ||
103 | dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? | |
104 | "Connected" : "Disconnected"); | |
105 | ||
106 | old_type = data->psy.type; | |
107 | ||
108 | if (data->ta_in) | |
109 | data->psy.type = POWER_SUPPLY_TYPE_MAINS; | |
110 | else if (data->usb_in) | |
111 | data->psy.type = POWER_SUPPLY_TYPE_USB; | |
112 | else | |
113 | data->psy.type = POWER_SUPPLY_TYPE_BATTERY; | |
114 | ||
115 | if (old_type != data->psy.type) | |
116 | power_supply_changed(&data->psy); | |
117 | ||
118 | return IRQ_HANDLED; | |
119 | } | |
120 | ||
121 | static irqreturn_t max8903_usbin(int irq, void *_data) | |
122 | { | |
123 | struct max8903_data *data = _data; | |
124 | struct max8903_pdata *pdata = data->pdata; | |
125 | bool usb_in; | |
126 | enum power_supply_type old_type; | |
127 | ||
128 | usb_in = gpio_get_value(pdata->uok) ? false : true; | |
129 | ||
130 | if (usb_in == data->usb_in) | |
131 | return IRQ_HANDLED; | |
132 | ||
133 | data->usb_in = usb_in; | |
134 | ||
135 | /* Do not touch Current-Limit-Mode */ | |
136 | ||
137 | /* Charger Enable / Disable (cen is negated) */ | |
138 | if (pdata->cen) | |
139 | gpio_set_value(pdata->cen, usb_in ? 0 : | |
140 | (data->ta_in ? 0 : 1)); | |
141 | ||
142 | dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? | |
143 | "Connected" : "Disconnected"); | |
144 | ||
145 | old_type = data->psy.type; | |
146 | ||
147 | if (data->ta_in) | |
148 | data->psy.type = POWER_SUPPLY_TYPE_MAINS; | |
149 | else if (data->usb_in) | |
150 | data->psy.type = POWER_SUPPLY_TYPE_USB; | |
151 | else | |
152 | data->psy.type = POWER_SUPPLY_TYPE_BATTERY; | |
153 | ||
154 | if (old_type != data->psy.type) | |
155 | power_supply_changed(&data->psy); | |
156 | ||
157 | return IRQ_HANDLED; | |
158 | } | |
159 | ||
160 | static irqreturn_t max8903_fault(int irq, void *_data) | |
161 | { | |
162 | struct max8903_data *data = _data; | |
163 | struct max8903_pdata *pdata = data->pdata; | |
164 | bool fault; | |
165 | ||
166 | fault = gpio_get_value(pdata->flt) ? false : true; | |
167 | ||
168 | if (fault == data->fault) | |
169 | return IRQ_HANDLED; | |
170 | ||
171 | data->fault = fault; | |
172 | ||
173 | if (fault) | |
174 | dev_err(data->dev, "Charger suffers a fault and stops.\n"); | |
175 | else | |
176 | dev_err(data->dev, "Charger recovered from a fault.\n"); | |
177 | ||
178 | return IRQ_HANDLED; | |
179 | } | |
180 | ||
181 | static __devinit int max8903_probe(struct platform_device *pdev) | |
182 | { | |
183 | struct max8903_data *data; | |
184 | struct device *dev = &pdev->dev; | |
185 | struct max8903_pdata *pdata = pdev->dev.platform_data; | |
186 | int ret = 0; | |
187 | int gpio; | |
188 | int ta_in = 0; | |
189 | int usb_in = 0; | |
190 | ||
191 | data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); | |
192 | if (data == NULL) { | |
193 | dev_err(dev, "Cannot allocate memory.\n"); | |
194 | return -ENOMEM; | |
195 | } | |
196 | data->pdata = pdata; | |
197 | data->dev = dev; | |
198 | platform_set_drvdata(pdev, data); | |
199 | ||
200 | if (pdata->dc_valid == false && pdata->usb_valid == false) { | |
201 | dev_err(dev, "No valid power sources.\n"); | |
202 | ret = -EINVAL; | |
203 | goto err; | |
204 | } | |
205 | ||
206 | if (pdata->dc_valid) { | |
207 | if (pdata->dok && gpio_is_valid(pdata->dok) && | |
208 | pdata->dcm && gpio_is_valid(pdata->dcm)) { | |
209 | gpio = pdata->dok; /* PULL_UPed Interrupt */ | |
210 | ta_in = gpio_get_value(gpio) ? 0 : 1; | |
211 | ||
212 | gpio = pdata->dcm; /* Output */ | |
213 | gpio_set_value(gpio, ta_in); | |
214 | } else { | |
215 | dev_err(dev, "When DC is wired, DOK and DCM should" | |
216 | " be wired as well.\n"); | |
217 | ret = -EINVAL; | |
218 | goto err; | |
219 | } | |
220 | } else { | |
221 | if (pdata->dcm) { | |
222 | if (gpio_is_valid(pdata->dcm)) | |
223 | gpio_set_value(pdata->dcm, 0); | |
224 | else { | |
225 | dev_err(dev, "Invalid pin: dcm.\n"); | |
226 | ret = -EINVAL; | |
227 | goto err; | |
228 | } | |
229 | } | |
230 | } | |
231 | ||
232 | if (pdata->usb_valid) { | |
233 | if (pdata->uok && gpio_is_valid(pdata->uok)) { | |
234 | gpio = pdata->uok; | |
235 | usb_in = gpio_get_value(gpio) ? 0 : 1; | |
236 | } else { | |
237 | dev_err(dev, "When USB is wired, UOK should be wired." | |
238 | "as well.\n"); | |
239 | ret = -EINVAL; | |
240 | goto err; | |
241 | } | |
242 | } | |
243 | ||
244 | if (pdata->cen) { | |
245 | if (gpio_is_valid(pdata->cen)) { | |
246 | gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); | |
247 | } else { | |
248 | dev_err(dev, "Invalid pin: cen.\n"); | |
249 | ret = -EINVAL; | |
250 | goto err; | |
251 | } | |
252 | } | |
253 | ||
254 | if (pdata->chg) { | |
255 | if (!gpio_is_valid(pdata->chg)) { | |
256 | dev_err(dev, "Invalid pin: chg.\n"); | |
257 | ret = -EINVAL; | |
258 | goto err; | |
259 | } | |
260 | } | |
261 | ||
262 | if (pdata->flt) { | |
263 | if (!gpio_is_valid(pdata->flt)) { | |
264 | dev_err(dev, "Invalid pin: flt.\n"); | |
265 | ret = -EINVAL; | |
266 | goto err; | |
267 | } | |
268 | } | |
269 | ||
270 | if (pdata->usus) { | |
271 | if (!gpio_is_valid(pdata->usus)) { | |
272 | dev_err(dev, "Invalid pin: usus.\n"); | |
273 | ret = -EINVAL; | |
274 | goto err; | |
275 | } | |
276 | } | |
277 | ||
278 | data->fault = false; | |
279 | data->ta_in = ta_in; | |
280 | data->usb_in = usb_in; | |
281 | ||
282 | data->psy.name = "max8903_charger"; | |
283 | data->psy.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : | |
284 | ((usb_in) ? POWER_SUPPLY_TYPE_USB : | |
285 | POWER_SUPPLY_TYPE_BATTERY); | |
286 | data->psy.get_property = max8903_get_property; | |
287 | data->psy.properties = max8903_charger_props; | |
288 | data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); | |
289 | ||
290 | ret = power_supply_register(dev, &data->psy); | |
291 | if (ret) { | |
292 | dev_err(dev, "failed: power supply register.\n"); | |
293 | goto err; | |
294 | } | |
295 | ||
296 | if (pdata->dc_valid) { | |
297 | ret = request_threaded_irq(gpio_to_irq(pdata->dok), | |
298 | NULL, max8903_dcin, | |
299 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
300 | "MAX8903 DC IN", data); | |
301 | if (ret) { | |
302 | dev_err(dev, "Cannot request irq %d for DC (%d)\n", | |
303 | gpio_to_irq(pdata->dok), ret); | |
304 | goto err_psy; | |
305 | } | |
306 | } | |
307 | ||
308 | if (pdata->usb_valid) { | |
309 | ret = request_threaded_irq(gpio_to_irq(pdata->uok), | |
310 | NULL, max8903_usbin, | |
311 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
312 | "MAX8903 USB IN", data); | |
313 | if (ret) { | |
314 | dev_err(dev, "Cannot request irq %d for USB (%d)\n", | |
315 | gpio_to_irq(pdata->uok), ret); | |
316 | goto err_dc_irq; | |
317 | } | |
318 | } | |
319 | ||
320 | if (pdata->flt) { | |
321 | ret = request_threaded_irq(gpio_to_irq(pdata->flt), | |
322 | NULL, max8903_fault, | |
323 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
324 | "MAX8903 Fault", data); | |
325 | if (ret) { | |
326 | dev_err(dev, "Cannot request irq %d for Fault (%d)\n", | |
327 | gpio_to_irq(pdata->flt), ret); | |
328 | goto err_usb_irq; | |
329 | } | |
330 | } | |
331 | ||
332 | return 0; | |
333 | ||
334 | err_usb_irq: | |
335 | if (pdata->usb_valid) | |
336 | free_irq(gpio_to_irq(pdata->uok), data); | |
337 | err_dc_irq: | |
338 | if (pdata->dc_valid) | |
339 | free_irq(gpio_to_irq(pdata->dok), data); | |
340 | err_psy: | |
341 | power_supply_unregister(&data->psy); | |
342 | err: | |
343 | kfree(data); | |
344 | return ret; | |
345 | } | |
346 | ||
347 | static __devexit int max8903_remove(struct platform_device *pdev) | |
348 | { | |
349 | struct max8903_data *data = platform_get_drvdata(pdev); | |
350 | ||
351 | if (data) { | |
352 | struct max8903_pdata *pdata = data->pdata; | |
353 | ||
354 | if (pdata->flt) | |
355 | free_irq(gpio_to_irq(pdata->flt), data); | |
356 | if (pdata->usb_valid) | |
357 | free_irq(gpio_to_irq(pdata->uok), data); | |
358 | if (pdata->dc_valid) | |
359 | free_irq(gpio_to_irq(pdata->dok), data); | |
360 | power_supply_unregister(&data->psy); | |
361 | kfree(data); | |
362 | } | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | static struct platform_driver max8903_driver = { | |
368 | .probe = max8903_probe, | |
369 | .remove = __devexit_p(max8903_remove), | |
370 | .driver = { | |
371 | .name = "max8903-charger", | |
372 | .owner = THIS_MODULE, | |
373 | }, | |
374 | }; | |
375 | ||
376 | static int __init max8903_init(void) | |
377 | { | |
378 | return platform_driver_register(&max8903_driver); | |
379 | } | |
380 | module_init(max8903_init); | |
381 | ||
382 | static void __exit max8903_exit(void) | |
383 | { | |
384 | platform_driver_unregister(&max8903_driver); | |
385 | } | |
386 | module_exit(max8903_exit); | |
387 | ||
388 | MODULE_LICENSE("GPL"); | |
389 | MODULE_DESCRIPTION("MAX8903 Charger Driver"); | |
390 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | |
391 | MODULE_ALIAS("max8903-charger"); |