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> |
c5ed3307 CL |
26 | #include <linux/of.h> |
27 | #include <linux/of_device.h> | |
28 | #include <linux/of_gpio.h> | |
b14a9ccc MH |
29 | #include <linux/slab.h> |
30 | #include <linux/power_supply.h> | |
31 | #include <linux/platform_device.h> | |
32 | #include <linux/power/max8903_charger.h> | |
33 | ||
34 | struct max8903_data { | |
0c3ae04b | 35 | struct max8903_pdata *pdata; |
b14a9ccc | 36 | struct device *dev; |
297d716f KK |
37 | struct power_supply *psy; |
38 | struct power_supply_desc psy_desc; | |
b14a9ccc MH |
39 | bool fault; |
40 | bool usb_in; | |
41 | bool ta_in; | |
42 | }; | |
43 | ||
44 | static enum power_supply_property max8903_charger_props[] = { | |
45 | POWER_SUPPLY_PROP_STATUS, /* Charger status output */ | |
46 | POWER_SUPPLY_PROP_ONLINE, /* External power source */ | |
47 | POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ | |
48 | }; | |
49 | ||
50 | static int max8903_get_property(struct power_supply *psy, | |
51 | enum power_supply_property psp, | |
52 | union power_supply_propval *val) | |
53 | { | |
297d716f | 54 | struct max8903_data *data = power_supply_get_drvdata(psy); |
b14a9ccc MH |
55 | |
56 | switch (psp) { | |
57 | case POWER_SUPPLY_PROP_STATUS: | |
58 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
3525e5c5 | 59 | if (gpio_is_valid(data->pdata->chg)) { |
0c3ae04b | 60 | if (gpio_get_value(data->pdata->chg) == 0) |
b14a9ccc MH |
61 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
62 | else if (data->usb_in || data->ta_in) | |
63 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
64 | else | |
65 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
66 | } | |
67 | break; | |
68 | case POWER_SUPPLY_PROP_ONLINE: | |
69 | val->intval = 0; | |
70 | if (data->usb_in || data->ta_in) | |
71 | val->intval = 1; | |
72 | break; | |
73 | case POWER_SUPPLY_PROP_HEALTH: | |
74 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
75 | if (data->fault) | |
76 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | |
77 | break; | |
78 | default: | |
79 | return -EINVAL; | |
80 | } | |
c5ed3307 | 81 | |
b14a9ccc MH |
82 | return 0; |
83 | } | |
84 | ||
85 | static irqreturn_t max8903_dcin(int irq, void *_data) | |
86 | { | |
87 | struct max8903_data *data = _data; | |
0c3ae04b | 88 | struct max8903_pdata *pdata = data->pdata; |
b14a9ccc MH |
89 | bool ta_in; |
90 | enum power_supply_type old_type; | |
91 | ||
92 | ta_in = gpio_get_value(pdata->dok) ? false : true; | |
93 | ||
94 | if (ta_in == data->ta_in) | |
95 | return IRQ_HANDLED; | |
96 | ||
97 | data->ta_in = ta_in; | |
98 | ||
99 | /* Set Current-Limit-Mode 1:DC 0:USB */ | |
3525e5c5 | 100 | if (gpio_is_valid(pdata->dcm)) |
b14a9ccc MH |
101 | gpio_set_value(pdata->dcm, ta_in ? 1 : 0); |
102 | ||
103 | /* Charger Enable / Disable (cen is negated) */ | |
3525e5c5 | 104 | if (gpio_is_valid(pdata->cen)) |
b14a9ccc MH |
105 | gpio_set_value(pdata->cen, ta_in ? 0 : |
106 | (data->usb_in ? 0 : 1)); | |
107 | ||
108 | dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? | |
109 | "Connected" : "Disconnected"); | |
110 | ||
297d716f | 111 | old_type = data->psy_desc.type; |
b14a9ccc MH |
112 | |
113 | if (data->ta_in) | |
297d716f | 114 | data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; |
b14a9ccc | 115 | else if (data->usb_in) |
297d716f | 116 | data->psy_desc.type = POWER_SUPPLY_TYPE_USB; |
b14a9ccc | 117 | else |
297d716f | 118 | data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
b14a9ccc | 119 | |
297d716f KK |
120 | if (old_type != data->psy_desc.type) |
121 | power_supply_changed(data->psy); | |
b14a9ccc MH |
122 | |
123 | return IRQ_HANDLED; | |
124 | } | |
125 | ||
126 | static irqreturn_t max8903_usbin(int irq, void *_data) | |
127 | { | |
128 | struct max8903_data *data = _data; | |
0c3ae04b | 129 | struct max8903_pdata *pdata = data->pdata; |
b14a9ccc MH |
130 | bool usb_in; |
131 | enum power_supply_type old_type; | |
132 | ||
133 | usb_in = gpio_get_value(pdata->uok) ? false : true; | |
134 | ||
135 | if (usb_in == data->usb_in) | |
136 | return IRQ_HANDLED; | |
137 | ||
138 | data->usb_in = usb_in; | |
139 | ||
140 | /* Do not touch Current-Limit-Mode */ | |
141 | ||
142 | /* Charger Enable / Disable (cen is negated) */ | |
3525e5c5 | 143 | if (gpio_is_valid(pdata->cen)) |
b14a9ccc MH |
144 | gpio_set_value(pdata->cen, usb_in ? 0 : |
145 | (data->ta_in ? 0 : 1)); | |
146 | ||
147 | dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? | |
148 | "Connected" : "Disconnected"); | |
149 | ||
297d716f | 150 | old_type = data->psy_desc.type; |
b14a9ccc MH |
151 | |
152 | if (data->ta_in) | |
297d716f | 153 | data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; |
b14a9ccc | 154 | else if (data->usb_in) |
297d716f | 155 | data->psy_desc.type = POWER_SUPPLY_TYPE_USB; |
b14a9ccc | 156 | else |
297d716f | 157 | data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
b14a9ccc | 158 | |
297d716f KK |
159 | if (old_type != data->psy_desc.type) |
160 | power_supply_changed(data->psy); | |
b14a9ccc MH |
161 | |
162 | return IRQ_HANDLED; | |
163 | } | |
164 | ||
165 | static irqreturn_t max8903_fault(int irq, void *_data) | |
166 | { | |
167 | struct max8903_data *data = _data; | |
0c3ae04b | 168 | struct max8903_pdata *pdata = data->pdata; |
b14a9ccc MH |
169 | bool fault; |
170 | ||
171 | fault = gpio_get_value(pdata->flt) ? false : true; | |
172 | ||
173 | if (fault == data->fault) | |
174 | return IRQ_HANDLED; | |
175 | ||
176 | data->fault = fault; | |
177 | ||
178 | if (fault) | |
179 | dev_err(data->dev, "Charger suffers a fault and stops.\n"); | |
180 | else | |
181 | dev_err(data->dev, "Charger recovered from a fault.\n"); | |
182 | ||
183 | return IRQ_HANDLED; | |
184 | } | |
185 | ||
c5ed3307 CL |
186 | static struct max8903_pdata *max8903_parse_dt_data(struct device *dev) |
187 | { | |
188 | struct device_node *np = dev->of_node; | |
189 | struct max8903_pdata *pdata = NULL; | |
190 | ||
191 | if (!np) | |
192 | return NULL; | |
193 | ||
194 | pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); | |
195 | if (!pdata) | |
196 | return NULL; | |
197 | ||
198 | pdata->dc_valid = false; | |
199 | pdata->usb_valid = false; | |
200 | ||
201 | pdata->cen = of_get_named_gpio(np, "cen-gpios", 0); | |
202 | if (!gpio_is_valid(pdata->cen)) | |
203 | pdata->cen = -EINVAL; | |
204 | ||
205 | pdata->chg = of_get_named_gpio(np, "chg-gpios", 0); | |
206 | if (!gpio_is_valid(pdata->chg)) | |
207 | pdata->chg = -EINVAL; | |
208 | ||
209 | pdata->flt = of_get_named_gpio(np, "flt-gpios", 0); | |
210 | if (!gpio_is_valid(pdata->flt)) | |
211 | pdata->flt = -EINVAL; | |
212 | ||
213 | pdata->usus = of_get_named_gpio(np, "usus-gpios", 0); | |
214 | if (!gpio_is_valid(pdata->usus)) | |
215 | pdata->usus = -EINVAL; | |
216 | ||
217 | pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0); | |
218 | if (!gpio_is_valid(pdata->dcm)) | |
219 | pdata->dcm = -EINVAL; | |
220 | ||
221 | pdata->dok = of_get_named_gpio(np, "dok-gpios", 0); | |
222 | if (!gpio_is_valid(pdata->dok)) | |
223 | pdata->dok = -EINVAL; | |
224 | else | |
225 | pdata->dc_valid = true; | |
226 | ||
227 | pdata->uok = of_get_named_gpio(np, "uok-gpios", 0); | |
228 | if (!gpio_is_valid(pdata->uok)) | |
229 | pdata->uok = -EINVAL; | |
230 | else | |
231 | pdata->usb_valid = true; | |
232 | ||
233 | return pdata; | |
234 | } | |
235 | ||
88a469bb | 236 | static int max8903_setup_gpios(struct platform_device *pdev) |
b14a9ccc | 237 | { |
88a469bb | 238 | struct max8903_data *data = platform_get_drvdata(pdev); |
b14a9ccc MH |
239 | struct device *dev = &pdev->dev; |
240 | struct max8903_pdata *pdata = pdev->dev.platform_data; | |
241 | int ret = 0; | |
242 | int gpio; | |
243 | int ta_in = 0; | |
244 | int usb_in = 0; | |
245 | ||
b14a9ccc | 246 | if (pdata->dc_valid) { |
3525e5c5 | 247 | if (gpio_is_valid(pdata->dok)) { |
88a469bb CL |
248 | ret = devm_gpio_request(dev, pdata->dok, |
249 | data->psy_desc.name); | |
250 | if (ret) { | |
251 | dev_err(dev, | |
252 | "Failed GPIO request for dok: %d err %d\n", | |
253 | pdata->dok, ret); | |
254 | return ret; | |
255 | } | |
256 | ||
b14a9ccc MH |
257 | gpio = pdata->dok; /* PULL_UPed Interrupt */ |
258 | ta_in = gpio_get_value(gpio) ? 0 : 1; | |
cbf9077e CL |
259 | } else { |
260 | dev_err(dev, "When DC is wired, DOK should be wired as well.\n"); | |
261 | return -EINVAL; | |
262 | } | |
263 | } | |
b14a9ccc | 264 | |
3525e5c5 CL |
265 | if (gpio_is_valid(pdata->dcm)) { |
266 | ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name); | |
267 | if (ret) { | |
268 | dev_err(dev, | |
269 | "Failed GPIO request for dcm: %d err %d\n", | |
270 | pdata->dcm, ret); | |
271 | return ret; | |
b14a9ccc | 272 | } |
3525e5c5 CL |
273 | |
274 | gpio = pdata->dcm; /* Output */ | |
275 | gpio_set_value(gpio, ta_in); | |
b14a9ccc MH |
276 | } |
277 | ||
278 | if (pdata->usb_valid) { | |
3525e5c5 | 279 | if (gpio_is_valid(pdata->uok)) { |
88a469bb CL |
280 | ret = devm_gpio_request(dev, pdata->uok, |
281 | data->psy_desc.name); | |
282 | if (ret) { | |
283 | dev_err(dev, | |
284 | "Failed GPIO request for uok: %d err %d\n", | |
285 | pdata->uok, ret); | |
286 | return ret; | |
287 | } | |
288 | ||
b14a9ccc MH |
289 | gpio = pdata->uok; |
290 | usb_in = gpio_get_value(gpio) ? 0 : 1; | |
291 | } else { | |
292 | dev_err(dev, "When USB is wired, UOK should be wired." | |
293 | "as well.\n"); | |
0df2deea | 294 | return -EINVAL; |
b14a9ccc MH |
295 | } |
296 | } | |
297 | ||
3525e5c5 CL |
298 | if (gpio_is_valid(pdata->cen)) { |
299 | ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name); | |
300 | if (ret) { | |
301 | dev_err(dev, | |
302 | "Failed GPIO request for cen: %d err %d\n", | |
303 | pdata->cen, ret); | |
304 | return ret; | |
b14a9ccc | 305 | } |
3525e5c5 CL |
306 | |
307 | gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); | |
b14a9ccc MH |
308 | } |
309 | ||
3525e5c5 CL |
310 | if (gpio_is_valid(pdata->chg)) { |
311 | ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name); | |
312 | if (ret) { | |
313 | dev_err(dev, | |
314 | "Failed GPIO request for chg: %d err %d\n", | |
315 | pdata->chg, ret); | |
316 | return ret; | |
b14a9ccc MH |
317 | } |
318 | } | |
319 | ||
3525e5c5 CL |
320 | if (gpio_is_valid(pdata->flt)) { |
321 | ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name); | |
322 | if (ret) { | |
323 | dev_err(dev, | |
324 | "Failed GPIO request for flt: %d err %d\n", | |
325 | pdata->flt, ret); | |
326 | return ret; | |
b14a9ccc MH |
327 | } |
328 | } | |
329 | ||
3525e5c5 CL |
330 | if (gpio_is_valid(pdata->usus)) { |
331 | ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name); | |
332 | if (ret) { | |
333 | dev_err(dev, | |
334 | "Failed GPIO request for usus: %d err %d\n", | |
335 | pdata->usus, ret); | |
336 | return ret; | |
b14a9ccc MH |
337 | } |
338 | } | |
339 | ||
340 | data->fault = false; | |
341 | data->ta_in = ta_in; | |
342 | data->usb_in = usb_in; | |
343 | ||
88a469bb CL |
344 | return 0; |
345 | } | |
346 | ||
347 | static int max8903_probe(struct platform_device *pdev) | |
348 | { | |
349 | struct max8903_data *data; | |
350 | struct device *dev = &pdev->dev; | |
351 | struct max8903_pdata *pdata = pdev->dev.platform_data; | |
352 | struct power_supply_config psy_cfg = {}; | |
353 | int ret = 0; | |
354 | ||
88a469bb | 355 | data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); |
e6518a43 | 356 | if (!data) |
88a469bb | 357 | return -ENOMEM; |
88a469bb | 358 | |
c5ed3307 CL |
359 | if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node) |
360 | pdata = max8903_parse_dt_data(dev); | |
361 | ||
362 | if (!pdata) { | |
363 | dev_err(dev, "No platform data.\n"); | |
364 | return -EINVAL; | |
365 | } | |
366 | ||
367 | pdev->dev.platform_data = pdata; | |
368 | data->pdata = pdata; | |
88a469bb CL |
369 | data->dev = dev; |
370 | platform_set_drvdata(pdev, data); | |
371 | ||
372 | if (pdata->dc_valid == false && pdata->usb_valid == false) { | |
373 | dev_err(dev, "No valid power sources.\n"); | |
374 | return -EINVAL; | |
375 | } | |
376 | ||
377 | ret = max8903_setup_gpios(pdev); | |
378 | if (ret) | |
379 | return ret; | |
380 | ||
297d716f | 381 | data->psy_desc.name = "max8903_charger"; |
88a469bb CL |
382 | data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : |
383 | ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : | |
b14a9ccc | 384 | POWER_SUPPLY_TYPE_BATTERY); |
297d716f KK |
385 | data->psy_desc.get_property = max8903_get_property; |
386 | data->psy_desc.properties = max8903_charger_props; | |
387 | data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); | |
b14a9ccc | 388 | |
c5ed3307 | 389 | psy_cfg.of_node = dev->of_node; |
297d716f KK |
390 | psy_cfg.drv_data = data; |
391 | ||
0df2deea | 392 | data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg); |
297d716f | 393 | if (IS_ERR(data->psy)) { |
b14a9ccc | 394 | dev_err(dev, "failed: power supply register.\n"); |
0df2deea | 395 | return PTR_ERR(data->psy); |
b14a9ccc MH |
396 | } |
397 | ||
398 | if (pdata->dc_valid) { | |
0df2deea | 399 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok), |
d5fdfedc SS |
400 | NULL, max8903_dcin, |
401 | IRQF_TRIGGER_FALLING | | |
402 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
403 | "MAX8903 DC IN", data); | |
b14a9ccc MH |
404 | if (ret) { |
405 | dev_err(dev, "Cannot request irq %d for DC (%d)\n", | |
406 | gpio_to_irq(pdata->dok), ret); | |
0df2deea | 407 | return ret; |
b14a9ccc MH |
408 | } |
409 | } | |
410 | ||
411 | if (pdata->usb_valid) { | |
0df2deea | 412 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok), |
d5fdfedc SS |
413 | NULL, max8903_usbin, |
414 | IRQF_TRIGGER_FALLING | | |
415 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
416 | "MAX8903 USB IN", data); | |
b14a9ccc MH |
417 | if (ret) { |
418 | dev_err(dev, "Cannot request irq %d for USB (%d)\n", | |
419 | gpio_to_irq(pdata->uok), ret); | |
0df2deea | 420 | return ret; |
b14a9ccc MH |
421 | } |
422 | } | |
423 | ||
3525e5c5 | 424 | if (gpio_is_valid(pdata->flt)) { |
0df2deea | 425 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt), |
d5fdfedc SS |
426 | NULL, max8903_fault, |
427 | IRQF_TRIGGER_FALLING | | |
428 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
429 | "MAX8903 Fault", data); | |
b14a9ccc MH |
430 | if (ret) { |
431 | dev_err(dev, "Cannot request irq %d for Fault (%d)\n", | |
432 | gpio_to_irq(pdata->flt), ret); | |
0df2deea | 433 | return ret; |
b14a9ccc MH |
434 | } |
435 | } | |
436 | ||
b14a9ccc MH |
437 | return 0; |
438 | } | |
439 | ||
c5ed3307 CL |
440 | static const struct of_device_id max8903_match_ids[] = { |
441 | { .compatible = "maxim,max8903", }, | |
442 | { /* sentinel */ } | |
443 | }; | |
444 | MODULE_DEVICE_TABLE(of, max8903_match_ids); | |
445 | ||
b14a9ccc MH |
446 | static struct platform_driver max8903_driver = { |
447 | .probe = max8903_probe, | |
b14a9ccc MH |
448 | .driver = { |
449 | .name = "max8903-charger", | |
c5ed3307 | 450 | .of_match_table = max8903_match_ids |
b14a9ccc MH |
451 | }, |
452 | }; | |
453 | ||
300bac7f | 454 | module_platform_driver(max8903_driver); |
b14a9ccc MH |
455 | |
456 | MODULE_LICENSE("GPL"); | |
457 | MODULE_DESCRIPTION("MAX8903 Charger Driver"); | |
458 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | |
bd19c756 | 459 | MODULE_ALIAS("platform:max8903-charger"); |