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 | 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"); | |
0df2deea | 204 | return -EINVAL; |
b14a9ccc MH |
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"); | |
0df2deea | 218 | return -EINVAL; |
b14a9ccc MH |
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"); | |
0df2deea | 226 | return -EINVAL; |
b14a9ccc MH |
227 | } |
228 | } | |
229 | } | |
230 | ||
231 | if (pdata->usb_valid) { | |
232 | if (pdata->uok && gpio_is_valid(pdata->uok)) { | |
233 | gpio = pdata->uok; | |
234 | usb_in = gpio_get_value(gpio) ? 0 : 1; | |
235 | } else { | |
236 | dev_err(dev, "When USB is wired, UOK should be wired." | |
237 | "as well.\n"); | |
0df2deea | 238 | return -EINVAL; |
b14a9ccc MH |
239 | } |
240 | } | |
241 | ||
242 | if (pdata->cen) { | |
243 | if (gpio_is_valid(pdata->cen)) { | |
244 | gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); | |
245 | } else { | |
246 | dev_err(dev, "Invalid pin: cen.\n"); | |
0df2deea | 247 | return -EINVAL; |
b14a9ccc MH |
248 | } |
249 | } | |
250 | ||
251 | if (pdata->chg) { | |
252 | if (!gpio_is_valid(pdata->chg)) { | |
253 | dev_err(dev, "Invalid pin: chg.\n"); | |
0df2deea | 254 | return -EINVAL; |
b14a9ccc MH |
255 | } |
256 | } | |
257 | ||
258 | if (pdata->flt) { | |
259 | if (!gpio_is_valid(pdata->flt)) { | |
260 | dev_err(dev, "Invalid pin: flt.\n"); | |
0df2deea | 261 | return -EINVAL; |
b14a9ccc MH |
262 | } |
263 | } | |
264 | ||
265 | if (pdata->usus) { | |
266 | if (!gpio_is_valid(pdata->usus)) { | |
267 | dev_err(dev, "Invalid pin: usus.\n"); | |
0df2deea | 268 | return -EINVAL; |
b14a9ccc MH |
269 | } |
270 | } | |
271 | ||
272 | data->fault = false; | |
273 | data->ta_in = ta_in; | |
274 | data->usb_in = usb_in; | |
275 | ||
297d716f KK |
276 | data->psy_desc.name = "max8903_charger"; |
277 | data->psy_desc.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : | |
b14a9ccc MH |
278 | ((usb_in) ? POWER_SUPPLY_TYPE_USB : |
279 | POWER_SUPPLY_TYPE_BATTERY); | |
297d716f KK |
280 | data->psy_desc.get_property = max8903_get_property; |
281 | data->psy_desc.properties = max8903_charger_props; | |
282 | data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); | |
b14a9ccc | 283 | |
297d716f KK |
284 | psy_cfg.drv_data = data; |
285 | ||
0df2deea | 286 | data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg); |
297d716f | 287 | if (IS_ERR(data->psy)) { |
b14a9ccc | 288 | dev_err(dev, "failed: power supply register.\n"); |
0df2deea | 289 | return PTR_ERR(data->psy); |
b14a9ccc MH |
290 | } |
291 | ||
292 | if (pdata->dc_valid) { | |
0df2deea | 293 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok), |
d5fdfedc SS |
294 | NULL, max8903_dcin, |
295 | IRQF_TRIGGER_FALLING | | |
296 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
297 | "MAX8903 DC IN", data); | |
b14a9ccc MH |
298 | if (ret) { |
299 | dev_err(dev, "Cannot request irq %d for DC (%d)\n", | |
300 | gpio_to_irq(pdata->dok), ret); | |
0df2deea | 301 | return ret; |
b14a9ccc MH |
302 | } |
303 | } | |
304 | ||
305 | if (pdata->usb_valid) { | |
0df2deea | 306 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok), |
d5fdfedc SS |
307 | NULL, max8903_usbin, |
308 | IRQF_TRIGGER_FALLING | | |
309 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
310 | "MAX8903 USB IN", data); | |
b14a9ccc MH |
311 | if (ret) { |
312 | dev_err(dev, "Cannot request irq %d for USB (%d)\n", | |
313 | gpio_to_irq(pdata->uok), ret); | |
0df2deea | 314 | return ret; |
b14a9ccc MH |
315 | } |
316 | } | |
317 | ||
318 | if (pdata->flt) { | |
0df2deea | 319 | ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt), |
d5fdfedc SS |
320 | NULL, max8903_fault, |
321 | IRQF_TRIGGER_FALLING | | |
322 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | |
323 | "MAX8903 Fault", data); | |
b14a9ccc MH |
324 | if (ret) { |
325 | dev_err(dev, "Cannot request irq %d for Fault (%d)\n", | |
326 | gpio_to_irq(pdata->flt), ret); | |
0df2deea | 327 | return ret; |
b14a9ccc MH |
328 | } |
329 | } | |
330 | ||
b14a9ccc MH |
331 | return 0; |
332 | } | |
333 | ||
334 | static struct platform_driver max8903_driver = { | |
335 | .probe = max8903_probe, | |
b14a9ccc MH |
336 | .driver = { |
337 | .name = "max8903-charger", | |
b14a9ccc MH |
338 | }, |
339 | }; | |
340 | ||
300bac7f | 341 | module_platform_driver(max8903_driver); |
b14a9ccc MH |
342 | |
343 | MODULE_LICENSE("GPL"); | |
344 | MODULE_DESCRIPTION("MAX8903 Charger Driver"); | |
345 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | |
bd19c756 | 346 | MODULE_ALIAS("platform:max8903-charger"); |