]>
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 <[email protected]> | |
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 | 33 | struct device *dev; |
297d716f KK |
34 | struct power_supply *psy; |
35 | struct power_supply_desc psy_desc; | |
b14a9ccc MH |
36 | bool fault; |
37 | bool usb_in; | |
38 | bool ta_in; | |
39 | }; | |
40 | ||
41 | static enum power_supply_property max8903_charger_props[] = { | |
42 | POWER_SUPPLY_PROP_STATUS, /* Charger status output */ | |
43 | POWER_SUPPLY_PROP_ONLINE, /* External power source */ | |
44 | POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ | |
45 | }; | |
46 | ||
47 | static int max8903_get_property(struct power_supply *psy, | |
48 | enum power_supply_property psp, | |
49 | union power_supply_propval *val) | |
50 | { | |
297d716f | 51 | struct max8903_data *data = power_supply_get_drvdata(psy); |
b14a9ccc MH |
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 | ||
297d716f | 107 | old_type = data->psy_desc.type; |
b14a9ccc MH |
108 | |
109 | if (data->ta_in) | |
297d716f | 110 | data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; |
b14a9ccc | 111 | else if (data->usb_in) |
297d716f | 112 | data->psy_desc.type = POWER_SUPPLY_TYPE_USB; |
b14a9ccc | 113 | else |
297d716f | 114 | data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
b14a9ccc | 115 | |
297d716f KK |
116 | if (old_type != data->psy_desc.type) |
117 | power_supply_changed(data->psy); | |
b14a9ccc MH |
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 | ||
297d716f | 146 | old_type = data->psy_desc.type; |
b14a9ccc MH |
147 | |
148 | if (data->ta_in) | |
297d716f | 149 | data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; |
b14a9ccc | 150 | else if (data->usb_in) |
297d716f | 151 | data->psy_desc.type = POWER_SUPPLY_TYPE_USB; |
b14a9ccc | 152 | else |
297d716f | 153 | data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
b14a9ccc | 154 | |
297d716f KK |
155 | if (old_type != data->psy_desc.type) |
156 | power_supply_changed(data->psy); | |
b14a9ccc MH |
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; | |
297d716f | 187 | struct power_supply_config psy_cfg = {}; |
b14a9ccc MH |
188 | int ret = 0; |
189 | int gpio; | |
190 | int ta_in = 0; | |
191 | int usb_in = 0; | |
192 | ||
f3f66b3e | 193 | data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); |
b14a9ccc MH |
194 | if (data == NULL) { |
195 | dev_err(dev, "Cannot allocate memory.\n"); | |
196 | return -ENOMEM; | |
197 | } | |
464f29a2 | 198 | memcpy(&data->pdata, pdata, sizeof(struct max8903_pdata)); |
b14a9ccc MH |
199 | data->dev = dev; |
200 | platform_set_drvdata(pdev, data); | |
201 | ||
202 | if (pdata->dc_valid == false && pdata->usb_valid == false) { | |
203 | dev_err(dev, "No valid power sources.\n"); | |
204 | ret = -EINVAL; | |
205 | goto err; | |
206 | } | |
207 | ||
208 | if (pdata->dc_valid) { | |
209 | if (pdata->dok && gpio_is_valid(pdata->dok) && | |
210 | pdata->dcm && gpio_is_valid(pdata->dcm)) { | |
211 | gpio = pdata->dok; /* PULL_UPed Interrupt */ | |
212 | ta_in = gpio_get_value(gpio) ? 0 : 1; | |
213 | ||
214 | gpio = pdata->dcm; /* Output */ | |
215 | gpio_set_value(gpio, ta_in); | |
216 | } else { | |
217 | dev_err(dev, "When DC is wired, DOK and DCM should" | |
218 | " be wired as well.\n"); | |
219 | ret = -EINVAL; | |
220 | goto err; | |
221 | } | |
222 | } else { | |
223 | if (pdata->dcm) { | |
224 | if (gpio_is_valid(pdata->dcm)) | |
225 | gpio_set_value(pdata->dcm, 0); | |
226 | else { | |
227 | dev_err(dev, "Invalid pin: dcm.\n"); | |
228 | ret = -EINVAL; | |
229 | goto err; | |
230 | } | |
231 | } | |
232 | } | |
233 | ||
234 | if (pdata->usb_valid) { | |
235 | if (pdata->uok && gpio_is_valid(pdata->uok)) { | |
236 | gpio = pdata->uok; | |
237 | usb_in = gpio_get_value(gpio) ? 0 : 1; | |
238 | } else { | |
239 | dev_err(dev, "When USB is wired, UOK should be wired." | |
240 | "as well.\n"); | |
241 | ret = -EINVAL; | |
242 | goto err; | |
243 | } | |
244 | } | |
245 | ||
246 | if (pdata->cen) { | |
247 | if (gpio_is_valid(pdata->cen)) { | |
248 | gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); | |
249 | } else { | |
250 | dev_err(dev, "Invalid pin: cen.\n"); | |
251 | ret = -EINVAL; | |
252 | goto err; | |
253 | } | |
254 | } | |
255 | ||
256 | if (pdata->chg) { | |
257 | if (!gpio_is_valid(pdata->chg)) { | |
258 | dev_err(dev, "Invalid pin: chg.\n"); | |
259 | ret = -EINVAL; | |
260 | goto err; | |
261 | } | |
262 | } | |
263 | ||
264 | if (pdata->flt) { | |
265 | if (!gpio_is_valid(pdata->flt)) { | |
266 | dev_err(dev, "Invalid pin: flt.\n"); | |
267 | ret = -EINVAL; | |
268 | goto err; | |
269 | } | |
270 | } | |
271 | ||
272 | if (pdata->usus) { | |
273 | if (!gpio_is_valid(pdata->usus)) { | |
274 | dev_err(dev, "Invalid pin: usus.\n"); | |
275 | ret = -EINVAL; | |
276 | goto err; | |
277 | } | |
278 | } | |
279 | ||
280 | data->fault = false; | |
281 | data->ta_in = ta_in; | |
282 | data->usb_in = usb_in; | |
283 | ||
297d716f KK |
284 | data->psy_desc.name = "max8903_charger"; |
285 | data->psy_desc.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : | |
b14a9ccc MH |
286 | ((usb_in) ? POWER_SUPPLY_TYPE_USB : |
287 | POWER_SUPPLY_TYPE_BATTERY); | |
297d716f KK |
288 | data->psy_desc.get_property = max8903_get_property; |
289 | data->psy_desc.properties = max8903_charger_props; | |
290 | data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); | |
b14a9ccc | 291 | |
297d716f KK |
292 | psy_cfg.drv_data = data; |
293 | ||
294 | data->psy = power_supply_register(dev, &data->psy_desc, &psy_cfg); | |
295 | if (IS_ERR(data->psy)) { | |
b14a9ccc | 296 | dev_err(dev, "failed: power supply register.\n"); |
297d716f | 297 | ret = PTR_ERR(data->psy); |
b14a9ccc MH |
298 | goto err; |
299 | } | |
300 | ||
301 | if (pdata->dc_valid) { | |
302 | ret = request_threaded_irq(gpio_to_irq(pdata->dok), | |
303 | NULL, max8903_dcin, | |
304 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
305 | "MAX8903 DC IN", data); | |
306 | if (ret) { | |
307 | dev_err(dev, "Cannot request irq %d for DC (%d)\n", | |
308 | gpio_to_irq(pdata->dok), ret); | |
309 | goto err_psy; | |
310 | } | |
311 | } | |
312 | ||
313 | if (pdata->usb_valid) { | |
314 | ret = request_threaded_irq(gpio_to_irq(pdata->uok), | |
315 | NULL, max8903_usbin, | |
316 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
317 | "MAX8903 USB IN", data); | |
318 | if (ret) { | |
319 | dev_err(dev, "Cannot request irq %d for USB (%d)\n", | |
320 | gpio_to_irq(pdata->uok), ret); | |
321 | goto err_dc_irq; | |
322 | } | |
323 | } | |
324 | ||
325 | if (pdata->flt) { | |
326 | ret = request_threaded_irq(gpio_to_irq(pdata->flt), | |
327 | NULL, max8903_fault, | |
328 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | |
329 | "MAX8903 Fault", data); | |
330 | if (ret) { | |
331 | dev_err(dev, "Cannot request irq %d for Fault (%d)\n", | |
332 | gpio_to_irq(pdata->flt), ret); | |
333 | goto err_usb_irq; | |
334 | } | |
335 | } | |
336 | ||
337 | return 0; | |
338 | ||
339 | err_usb_irq: | |
340 | if (pdata->usb_valid) | |
341 | free_irq(gpio_to_irq(pdata->uok), data); | |
342 | err_dc_irq: | |
343 | if (pdata->dc_valid) | |
344 | free_irq(gpio_to_irq(pdata->dok), data); | |
345 | err_psy: | |
297d716f | 346 | power_supply_unregister(data->psy); |
b14a9ccc | 347 | err: |
b14a9ccc MH |
348 | return ret; |
349 | } | |
350 | ||
415ec69f | 351 | static int max8903_remove(struct platform_device *pdev) |
b14a9ccc MH |
352 | { |
353 | struct max8903_data *data = platform_get_drvdata(pdev); | |
354 | ||
355 | if (data) { | |
464f29a2 | 356 | struct max8903_pdata *pdata = &data->pdata; |
b14a9ccc MH |
357 | |
358 | if (pdata->flt) | |
359 | free_irq(gpio_to_irq(pdata->flt), data); | |
360 | if (pdata->usb_valid) | |
361 | free_irq(gpio_to_irq(pdata->uok), data); | |
362 | if (pdata->dc_valid) | |
363 | free_irq(gpio_to_irq(pdata->dok), data); | |
297d716f | 364 | power_supply_unregister(data->psy); |
b14a9ccc MH |
365 | } |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
370 | static struct platform_driver max8903_driver = { | |
371 | .probe = max8903_probe, | |
28ea73f4 | 372 | .remove = max8903_remove, |
b14a9ccc MH |
373 | .driver = { |
374 | .name = "max8903-charger", | |
b14a9ccc MH |
375 | }, |
376 | }; | |
377 | ||
300bac7f | 378 | module_platform_driver(max8903_driver); |
b14a9ccc MH |
379 | |
380 | MODULE_LICENSE("GPL"); | |
381 | MODULE_DESCRIPTION("MAX8903 Charger Driver"); | |
382 | MODULE_AUTHOR("MyungJoo Ham <[email protected]>"); | |
bd19c756 | 383 | MODULE_ALIAS("platform:max8903-charger"); |