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