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> | |
7e6d62db | 25 | #include <linux/module.h> |
b14a9ccc MH |
26 | #include <linux/slab.h> |
27 | #include <linux/power_supply.h> | |
28 | #include <linux/platform_device.h> | |
29 | #include <linux/power/max8903_charger.h> | |
30 | ||
31 | struct max8903_data { | |
464f29a2 | 32 | struct max8903_pdata pdata; |
b14a9ccc MH |
33 | struct device *dev; |
34 | struct power_supply psy; | |
35 | bool fault; | |
36 | bool usb_in; | |
37 | bool ta_in; | |
38 | }; | |
39 | ||
40 | static enum power_supply_property max8903_charger_props[] = { | |
41 | POWER_SUPPLY_PROP_STATUS, /* Charger status output */ | |
42 | POWER_SUPPLY_PROP_ONLINE, /* External power source */ | |
43 | POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ | |
44 | }; | |
45 | ||
46 | static int max8903_get_property(struct power_supply *psy, | |
47 | enum power_supply_property psp, | |
48 | union power_supply_propval *val) | |
49 | { | |
50 | struct max8903_data *data = container_of(psy, | |
51 | struct max8903_data, psy); | |
52 | ||
53 | switch (psp) { | |
54 | case POWER_SUPPLY_PROP_STATUS: | |
55 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
464f29a2 MH |
56 | if (data->pdata.chg) { |
57 | if (gpio_get_value(data->pdata.chg) == 0) | |
b14a9ccc MH |
58 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
59 | else if (data->usb_in || data->ta_in) | |
60 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
61 | else | |
62 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
63 | } | |
64 | break; | |
65 | case POWER_SUPPLY_PROP_ONLINE: | |
66 | val->intval = 0; | |
67 | if (data->usb_in || data->ta_in) | |
68 | val->intval = 1; | |
69 | break; | |
70 | case POWER_SUPPLY_PROP_HEALTH: | |
71 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
72 | if (data->fault) | |
73 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | |
74 | break; | |
75 | default: | |
76 | return -EINVAL; | |
77 | } | |
78 | return 0; | |
79 | } | |
80 | ||
81 | static irqreturn_t max8903_dcin(int irq, void *_data) | |
82 | { | |
83 | struct max8903_data *data = _data; | |
464f29a2 | 84 | struct max8903_pdata *pdata = &data->pdata; |
b14a9ccc MH |
85 | bool ta_in; |
86 | enum power_supply_type old_type; | |
87 | ||
88 | ta_in = gpio_get_value(pdata->dok) ? false : true; | |
89 | ||
90 | if (ta_in == data->ta_in) | |
91 | return IRQ_HANDLED; | |
92 | ||
93 | data->ta_in = ta_in; | |
94 | ||
95 | /* Set Current-Limit-Mode 1:DC 0:USB */ | |
96 | if (pdata->dcm) | |
97 | gpio_set_value(pdata->dcm, ta_in ? 1 : 0); | |
98 | ||
99 | /* Charger Enable / Disable (cen is negated) */ | |
100 | if (pdata->cen) | |
101 | gpio_set_value(pdata->cen, ta_in ? 0 : | |
102 | (data->usb_in ? 0 : 1)); | |
103 | ||
104 | dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? | |
105 | "Connected" : "Disconnected"); | |
106 | ||
107 | old_type = data->psy.type; | |
108 | ||
109 | if (data->ta_in) | |
110 | data->psy.type = POWER_SUPPLY_TYPE_MAINS; | |
111 | else if (data->usb_in) | |
112 | data->psy.type = POWER_SUPPLY_TYPE_USB; | |
113 | else | |
114 | data->psy.type = POWER_SUPPLY_TYPE_BATTERY; | |
115 | ||
116 | if (old_type != data->psy.type) | |
117 | power_supply_changed(&data->psy); | |
118 | ||
119 | return IRQ_HANDLED; | |
120 | } | |
121 | ||
122 | static irqreturn_t max8903_usbin(int irq, void *_data) | |
123 | { | |
124 | struct max8903_data *data = _data; | |
464f29a2 | 125 | struct max8903_pdata *pdata = &data->pdata; |
b14a9ccc MH |
126 | bool usb_in; |
127 | enum power_supply_type old_type; | |
128 | ||
129 | usb_in = gpio_get_value(pdata->uok) ? false : true; | |
130 | ||
131 | if (usb_in == data->usb_in) | |
132 | return IRQ_HANDLED; | |
133 | ||
134 | data->usb_in = usb_in; | |
135 | ||
136 | /* Do not touch Current-Limit-Mode */ | |
137 | ||
138 | /* Charger Enable / Disable (cen is negated) */ | |
139 | if (pdata->cen) | |
140 | gpio_set_value(pdata->cen, usb_in ? 0 : | |
141 | (data->ta_in ? 0 : 1)); | |
142 | ||
143 | dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? | |
144 | "Connected" : "Disconnected"); | |
145 | ||
146 | old_type = data->psy.type; | |
147 | ||
148 | if (data->ta_in) | |
149 | data->psy.type = POWER_SUPPLY_TYPE_MAINS; | |
150 | else if (data->usb_in) | |
151 | data->psy.type = POWER_SUPPLY_TYPE_USB; | |
152 | else | |
153 | data->psy.type = POWER_SUPPLY_TYPE_BATTERY; | |
154 | ||
155 | if (old_type != data->psy.type) | |
156 | power_supply_changed(&data->psy); | |
157 | ||
158 | return IRQ_HANDLED; | |
159 | } | |
160 | ||
161 | static irqreturn_t max8903_fault(int irq, void *_data) | |
162 | { | |
163 | struct max8903_data *data = _data; | |
464f29a2 | 164 | struct max8903_pdata *pdata = &data->pdata; |
b14a9ccc MH |
165 | bool fault; |
166 | ||
167 | fault = gpio_get_value(pdata->flt) ? false : true; | |
168 | ||
169 | if (fault == data->fault) | |
170 | return IRQ_HANDLED; | |
171 | ||
172 | data->fault = fault; | |
173 | ||
174 | if (fault) | |
175 | dev_err(data->dev, "Charger suffers a fault and stops.\n"); | |
176 | else | |
177 | dev_err(data->dev, "Charger recovered from a fault.\n"); | |
178 | ||
179 | return IRQ_HANDLED; | |
180 | } | |
181 | ||
c8afa640 | 182 | static int max8903_probe(struct platform_device *pdev) |
b14a9ccc MH |
183 | { |
184 | struct max8903_data *data; | |
185 | struct device *dev = &pdev->dev; | |
186 | struct max8903_pdata *pdata = pdev->dev.platform_data; | |
187 | int ret = 0; | |
188 | int gpio; | |
189 | int ta_in = 0; | |
190 | int usb_in = 0; | |
191 | ||
f3f66b3e | 192 | data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); |
b14a9ccc MH |
193 | if (data == NULL) { |
194 | dev_err(dev, "Cannot allocate memory.\n"); | |
195 | return -ENOMEM; | |
196 | } | |
464f29a2 | 197 | memcpy(&data->pdata, pdata, sizeof(struct max8903_pdata)); |
b14a9ccc MH |
198 | data->dev = dev; |
199 | platform_set_drvdata(pdev, data); | |
200 | ||
201 | if (pdata->dc_valid == false && pdata->usb_valid == false) { | |
202 | dev_err(dev, "No valid power sources.\n"); | |
203 | ret = -EINVAL; | |
204 | goto err; | |
205 | } | |
206 | ||
207 | if (pdata->dc_valid) { | |
208 | if (pdata->dok && gpio_is_valid(pdata->dok) && | |
209 | pdata->dcm && gpio_is_valid(pdata->dcm)) { | |
210 | gpio = pdata->dok; /* PULL_UPed Interrupt */ | |
211 | ta_in = gpio_get_value(gpio) ? 0 : 1; | |
212 | ||
213 | gpio = pdata->dcm; /* Output */ | |
214 | gpio_set_value(gpio, ta_in); | |
215 | } else { | |
216 | dev_err(dev, "When DC is wired, DOK and DCM should" | |
217 | " be wired as well.\n"); | |
218 | ret = -EINVAL; | |
219 | goto err; | |
220 | } | |
221 | } else { | |
222 | if (pdata->dcm) { | |
223 | if (gpio_is_valid(pdata->dcm)) | |
224 | gpio_set_value(pdata->dcm, 0); | |
225 | else { | |
226 | dev_err(dev, "Invalid pin: dcm.\n"); | |
227 | ret = -EINVAL; | |
228 | goto err; | |
229 | } | |
230 | } | |
231 | } | |
232 | ||
233 | if (pdata->usb_valid) { | |
234 | if (pdata->uok && gpio_is_valid(pdata->uok)) { | |
235 | gpio = pdata->uok; | |
236 | usb_in = gpio_get_value(gpio) ? 0 : 1; | |
237 | } else { | |
238 | dev_err(dev, "When USB is wired, UOK should be wired." | |
239 | "as well.\n"); | |
240 | ret = -EINVAL; | |
241 | goto err; | |
242 | } | |
243 | } | |
244 | ||
245 | if (pdata->cen) { | |
246 | if (gpio_is_valid(pdata->cen)) { | |
247 | gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); | |
248 | } else { | |
249 | dev_err(dev, "Invalid pin: cen.\n"); | |
250 | ret = -EINVAL; | |
251 | goto err; | |
252 | } | |
253 | } | |
254 | ||
255 | if (pdata->chg) { | |
256 | if (!gpio_is_valid(pdata->chg)) { | |
257 | dev_err(dev, "Invalid pin: chg.\n"); | |
258 | ret = -EINVAL; | |
259 | goto err; | |
260 | } | |
261 | } | |
262 | ||
263 | if (pdata->flt) { | |
264 | if (!gpio_is_valid(pdata->flt)) { | |
265 | dev_err(dev, "Invalid pin: flt.\n"); | |
266 | ret = -EINVAL; | |
267 | goto err; | |
268 | } | |
269 | } | |
270 | ||
271 | if (pdata->usus) { | |
272 | if (!gpio_is_valid(pdata->usus)) { | |
273 | dev_err(dev, "Invalid pin: usus.\n"); | |
274 | ret = -EINVAL; | |
275 | goto err; | |
276 | } | |
277 | } | |
278 | ||
279 | data->fault = false; | |
280 | data->ta_in = ta_in; | |
281 | data->usb_in = usb_in; | |
282 | ||
283 | data->psy.name = "max8903_charger"; | |
284 | data->psy.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : | |
285 | ((usb_in) ? POWER_SUPPLY_TYPE_USB : | |
286 | POWER_SUPPLY_TYPE_BATTERY); | |
287 | data->psy.get_property = max8903_get_property; | |
288 | data->psy.properties = max8903_charger_props; | |
289 | data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); | |
290 | ||
291 | ret = power_supply_register(dev, &data->psy); | |
292 | if (ret) { | |
293 | dev_err(dev, "failed: power supply register.\n"); | |
294 | goto err; | |
295 | } | |
296 | ||
297 | if (pdata->dc_valid) { | |
298 | ret = request_threaded_irq(gpio_to_irq(pdata->dok), | |
299 | NULL, max8903_dcin, | |
300 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
301 | "MAX8903 DC IN", data); | |
302 | if (ret) { | |
303 | dev_err(dev, "Cannot request irq %d for DC (%d)\n", | |
304 | gpio_to_irq(pdata->dok), ret); | |
305 | goto err_psy; | |
306 | } | |
307 | } | |
308 | ||
309 | if (pdata->usb_valid) { | |
310 | ret = request_threaded_irq(gpio_to_irq(pdata->uok), | |
311 | NULL, max8903_usbin, | |
312 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
313 | "MAX8903 USB IN", data); | |
314 | if (ret) { | |
315 | dev_err(dev, "Cannot request irq %d for USB (%d)\n", | |
316 | gpio_to_irq(pdata->uok), ret); | |
317 | goto err_dc_irq; | |
318 | } | |
319 | } | |
320 | ||
321 | if (pdata->flt) { | |
322 | ret = request_threaded_irq(gpio_to_irq(pdata->flt), | |
323 | NULL, max8903_fault, | |
324 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
325 | "MAX8903 Fault", data); | |
326 | if (ret) { | |
327 | dev_err(dev, "Cannot request irq %d for Fault (%d)\n", | |
328 | gpio_to_irq(pdata->flt), ret); | |
329 | goto err_usb_irq; | |
330 | } | |
331 | } | |
332 | ||
333 | return 0; | |
334 | ||
335 | err_usb_irq: | |
336 | if (pdata->usb_valid) | |
337 | free_irq(gpio_to_irq(pdata->uok), data); | |
338 | err_dc_irq: | |
339 | if (pdata->dc_valid) | |
340 | free_irq(gpio_to_irq(pdata->dok), data); | |
341 | err_psy: | |
342 | power_supply_unregister(&data->psy); | |
343 | err: | |
b14a9ccc MH |
344 | return ret; |
345 | } | |
346 | ||
415ec69f | 347 | static int max8903_remove(struct platform_device *pdev) |
b14a9ccc MH |
348 | { |
349 | struct max8903_data *data = platform_get_drvdata(pdev); | |
350 | ||
351 | if (data) { | |
464f29a2 | 352 | struct max8903_pdata *pdata = &data->pdata; |
b14a9ccc MH |
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); | |
b14a9ccc MH |
361 | } |
362 | ||
363 | return 0; | |
364 | } | |
365 | ||
366 | static struct platform_driver max8903_driver = { | |
367 | .probe = max8903_probe, | |
28ea73f4 | 368 | .remove = max8903_remove, |
b14a9ccc MH |
369 | .driver = { |
370 | .name = "max8903-charger", | |
371 | .owner = THIS_MODULE, | |
372 | }, | |
373 | }; | |
374 | ||
300bac7f | 375 | module_platform_driver(max8903_driver); |
b14a9ccc MH |
376 | |
377 | MODULE_LICENSE("GPL"); | |
378 | MODULE_DESCRIPTION("MAX8903 Charger Driver"); | |
379 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | |
bd19c756 | 380 | MODULE_ALIAS("platform:max8903-charger"); |