Commit | Line | Data |
---|---|---|
b2998049 AV |
1 | /* |
2 | * Common power driver for PDAs and phones with one or two external | |
3 | * power supplies (AC/USB) connected to main and backup batteries, | |
4 | * and optional builtin charger. | |
5 | * | |
6 | * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/power_supply.h> | |
17 | #include <linux/pda_power.h> | |
18 | #include <linux/timer.h> | |
19 | #include <linux/jiffies.h> | |
20 | ||
21 | static inline unsigned int get_irq_flags(struct resource *res) | |
22 | { | |
23 | unsigned int flags = IRQF_DISABLED | IRQF_SHARED; | |
24 | ||
25 | flags |= res->flags & IRQF_TRIGGER_MASK; | |
26 | ||
27 | return flags; | |
28 | } | |
29 | ||
30 | static struct device *dev; | |
31 | static struct pda_power_pdata *pdata; | |
32 | static struct resource *ac_irq, *usb_irq; | |
33 | static struct timer_list charger_timer; | |
34 | static struct timer_list supply_timer; | |
35 | ||
bfde2662 AV |
36 | enum { |
37 | PDA_PSY_OFFLINE = 0, | |
38 | PDA_PSY_ONLINE = 1, | |
39 | PDA_PSY_TO_CHANGE, | |
40 | }; | |
41 | static int new_ac_status = -1; | |
42 | static int new_usb_status = -1; | |
43 | static int ac_status = -1; | |
44 | static int usb_status = -1; | |
45 | ||
b2998049 AV |
46 | static int pda_power_get_property(struct power_supply *psy, |
47 | enum power_supply_property psp, | |
48 | union power_supply_propval *val) | |
49 | { | |
50 | switch (psp) { | |
51 | case POWER_SUPPLY_PROP_ONLINE: | |
52 | if (psy->type == POWER_SUPPLY_TYPE_MAINS) | |
53 | val->intval = pdata->is_ac_online ? | |
54 | pdata->is_ac_online() : 0; | |
55 | else | |
56 | val->intval = pdata->is_usb_online ? | |
57 | pdata->is_usb_online() : 0; | |
58 | break; | |
59 | default: | |
60 | return -EINVAL; | |
61 | } | |
62 | return 0; | |
63 | } | |
64 | ||
65 | static enum power_supply_property pda_power_props[] = { | |
66 | POWER_SUPPLY_PROP_ONLINE, | |
67 | }; | |
68 | ||
69 | static char *pda_power_supplied_to[] = { | |
70 | "main-battery", | |
71 | "backup-battery", | |
72 | }; | |
73 | ||
bfde2662 AV |
74 | static struct power_supply pda_psy_ac = { |
75 | .name = "ac", | |
76 | .type = POWER_SUPPLY_TYPE_MAINS, | |
77 | .supplied_to = pda_power_supplied_to, | |
78 | .num_supplicants = ARRAY_SIZE(pda_power_supplied_to), | |
79 | .properties = pda_power_props, | |
80 | .num_properties = ARRAY_SIZE(pda_power_props), | |
81 | .get_property = pda_power_get_property, | |
b2998049 AV |
82 | }; |
83 | ||
bfde2662 AV |
84 | static struct power_supply pda_psy_usb = { |
85 | .name = "usb", | |
86 | .type = POWER_SUPPLY_TYPE_USB, | |
87 | .supplied_to = pda_power_supplied_to, | |
88 | .num_supplicants = ARRAY_SIZE(pda_power_supplied_to), | |
89 | .properties = pda_power_props, | |
90 | .num_properties = ARRAY_SIZE(pda_power_props), | |
91 | .get_property = pda_power_get_property, | |
92 | }; | |
93 | ||
94 | static void update_status(void) | |
95 | { | |
96 | if (pdata->is_ac_online) | |
97 | new_ac_status = !!pdata->is_ac_online(); | |
98 | ||
99 | if (pdata->is_usb_online) | |
100 | new_usb_status = !!pdata->is_usb_online(); | |
101 | } | |
102 | ||
b2998049 AV |
103 | static void update_charger(void) |
104 | { | |
105 | if (!pdata->set_charge) | |
106 | return; | |
107 | ||
bfde2662 | 108 | if (new_ac_status > 0) { |
b2998049 AV |
109 | dev_dbg(dev, "charger on (AC)\n"); |
110 | pdata->set_charge(PDA_POWER_CHARGE_AC); | |
bfde2662 | 111 | } else if (new_usb_status > 0) { |
b2998049 AV |
112 | dev_dbg(dev, "charger on (USB)\n"); |
113 | pdata->set_charge(PDA_POWER_CHARGE_USB); | |
114 | } else { | |
115 | dev_dbg(dev, "charger off\n"); | |
116 | pdata->set_charge(0); | |
117 | } | |
b2998049 AV |
118 | } |
119 | ||
bfde2662 | 120 | static void supply_timer_func(unsigned long unused) |
b2998049 | 121 | { |
bfde2662 AV |
122 | if (ac_status == PDA_PSY_TO_CHANGE) { |
123 | ac_status = new_ac_status; | |
124 | power_supply_changed(&pda_psy_ac); | |
125 | } | |
5ebf6e6a | 126 | |
bfde2662 AV |
127 | if (usb_status == PDA_PSY_TO_CHANGE) { |
128 | usb_status = new_usb_status; | |
129 | power_supply_changed(&pda_psy_usb); | |
130 | } | |
b2998049 AV |
131 | } |
132 | ||
bfde2662 | 133 | static void psy_changed(void) |
b2998049 AV |
134 | { |
135 | update_charger(); | |
136 | ||
bfde2662 AV |
137 | /* |
138 | * Okay, charger set. Now wait a bit before notifying supplicants, | |
139 | * charge power should stabilize. | |
140 | */ | |
b2998049 AV |
141 | mod_timer(&supply_timer, |
142 | jiffies + msecs_to_jiffies(pdata->wait_for_charger)); | |
b2998049 AV |
143 | } |
144 | ||
bfde2662 AV |
145 | static void charger_timer_func(unsigned long unused) |
146 | { | |
147 | update_status(); | |
148 | psy_changed(); | |
149 | } | |
150 | ||
5ebf6e6a | 151 | static irqreturn_t power_changed_isr(int irq, void *power_supply) |
b2998049 | 152 | { |
bfde2662 AV |
153 | if (power_supply == &pda_psy_ac) |
154 | ac_status = PDA_PSY_TO_CHANGE; | |
155 | else if (power_supply == &pda_psy_usb) | |
156 | usb_status = PDA_PSY_TO_CHANGE; | |
157 | else | |
158 | return IRQ_NONE; | |
159 | ||
160 | /* | |
161 | * Wait a bit before reading ac/usb line status and setting charger, | |
162 | * because ac/usb status readings may lag from irq. | |
163 | */ | |
b2998049 AV |
164 | mod_timer(&charger_timer, |
165 | jiffies + msecs_to_jiffies(pdata->wait_for_status)); | |
bfde2662 | 166 | |
b2998049 AV |
167 | return IRQ_HANDLED; |
168 | } | |
169 | ||
170 | static int pda_power_probe(struct platform_device *pdev) | |
171 | { | |
172 | int ret = 0; | |
173 | ||
174 | dev = &pdev->dev; | |
175 | ||
176 | if (pdev->id != -1) { | |
177 | dev_err(dev, "it's meaningless to register several " | |
178 | "pda_powers; use id = -1\n"); | |
179 | ret = -EINVAL; | |
180 | goto wrongid; | |
181 | } | |
182 | ||
183 | pdata = pdev->dev.platform_data; | |
184 | ||
bfde2662 | 185 | update_status(); |
b2998049 AV |
186 | update_charger(); |
187 | ||
188 | if (!pdata->wait_for_status) | |
189 | pdata->wait_for_status = 500; | |
190 | ||
191 | if (!pdata->wait_for_charger) | |
192 | pdata->wait_for_charger = 500; | |
193 | ||
194 | setup_timer(&charger_timer, charger_timer_func, 0); | |
195 | setup_timer(&supply_timer, supply_timer_func, 0); | |
196 | ||
197 | ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); | |
198 | usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); | |
b2998049 AV |
199 | |
200 | if (pdata->supplied_to) { | |
bfde2662 AV |
201 | pda_psy_ac.supplied_to = pdata->supplied_to; |
202 | pda_psy_ac.num_supplicants = pdata->num_supplicants; | |
203 | pda_psy_usb.supplied_to = pdata->supplied_to; | |
204 | pda_psy_usb.num_supplicants = pdata->num_supplicants; | |
b2998049 AV |
205 | } |
206 | ||
9ef45106 | 207 | if (pdata->is_ac_online) { |
bfde2662 | 208 | ret = power_supply_register(&pdev->dev, &pda_psy_ac); |
9ef45106 DB |
209 | if (ret) { |
210 | dev_err(dev, "failed to register %s power supply\n", | |
bfde2662 | 211 | pda_psy_ac.name); |
9ef45106 DB |
212 | goto ac_supply_failed; |
213 | } | |
b2998049 | 214 | |
9ef45106 DB |
215 | if (ac_irq) { |
216 | ret = request_irq(ac_irq->start, power_changed_isr, | |
217 | get_irq_flags(ac_irq), ac_irq->name, | |
bfde2662 | 218 | &pda_psy_ac); |
9ef45106 DB |
219 | if (ret) { |
220 | dev_err(dev, "request ac irq failed\n"); | |
221 | goto ac_irq_failed; | |
222 | } | |
223 | } | |
b2998049 AV |
224 | } |
225 | ||
9ef45106 | 226 | if (pdata->is_usb_online) { |
bfde2662 | 227 | ret = power_supply_register(&pdev->dev, &pda_psy_usb); |
b2998049 | 228 | if (ret) { |
9ef45106 | 229 | dev_err(dev, "failed to register %s power supply\n", |
bfde2662 | 230 | pda_psy_usb.name); |
9ef45106 | 231 | goto usb_supply_failed; |
b2998049 | 232 | } |
b2998049 | 233 | |
9ef45106 DB |
234 | if (usb_irq) { |
235 | ret = request_irq(usb_irq->start, power_changed_isr, | |
236 | get_irq_flags(usb_irq), | |
bfde2662 | 237 | usb_irq->name, &pda_psy_usb); |
9ef45106 DB |
238 | if (ret) { |
239 | dev_err(dev, "request usb irq failed\n"); | |
240 | goto usb_irq_failed; | |
241 | } | |
b2998049 AV |
242 | } |
243 | } | |
244 | ||
8f8e9b38 DB |
245 | device_init_wakeup(&pdev->dev, 1); |
246 | ||
9ef45106 | 247 | return 0; |
b2998049 AV |
248 | |
249 | usb_irq_failed: | |
9ef45106 | 250 | if (pdata->is_usb_online) |
bfde2662 | 251 | power_supply_unregister(&pda_psy_usb); |
9ef45106 DB |
252 | usb_supply_failed: |
253 | if (pdata->is_ac_online && ac_irq) | |
bfde2662 | 254 | free_irq(ac_irq->start, &pda_psy_ac); |
b2998049 | 255 | ac_irq_failed: |
9ef45106 | 256 | if (pdata->is_ac_online) |
bfde2662 | 257 | power_supply_unregister(&pda_psy_ac); |
9ef45106 | 258 | ac_supply_failed: |
b2998049 | 259 | wrongid: |
b2998049 AV |
260 | return ret; |
261 | } | |
262 | ||
263 | static int pda_power_remove(struct platform_device *pdev) | |
264 | { | |
9ef45106 | 265 | if (pdata->is_usb_online && usb_irq) |
bfde2662 | 266 | free_irq(usb_irq->start, &pda_psy_usb); |
9ef45106 | 267 | if (pdata->is_ac_online && ac_irq) |
bfde2662 AV |
268 | free_irq(ac_irq->start, &pda_psy_ac); |
269 | ||
b2998049 AV |
270 | del_timer_sync(&charger_timer); |
271 | del_timer_sync(&supply_timer); | |
bfde2662 | 272 | |
9ef45106 | 273 | if (pdata->is_usb_online) |
bfde2662 | 274 | power_supply_unregister(&pda_psy_usb); |
9ef45106 | 275 | if (pdata->is_ac_online) |
bfde2662 AV |
276 | power_supply_unregister(&pda_psy_ac); |
277 | ||
b2998049 AV |
278 | return 0; |
279 | } | |
280 | ||
8f8e9b38 DB |
281 | #ifdef CONFIG_PM |
282 | static int pda_power_suspend(struct platform_device *pdev, pm_message_t state) | |
283 | { | |
284 | if (device_may_wakeup(&pdev->dev)) { | |
285 | if (ac_irq) | |
286 | enable_irq_wake(ac_irq->start); | |
287 | if (usb_irq) | |
288 | enable_irq_wake(usb_irq->start); | |
289 | } | |
290 | ||
291 | return 0; | |
292 | } | |
293 | ||
294 | static int pda_power_resume(struct platform_device *pdev) | |
295 | { | |
296 | if (device_may_wakeup(&pdev->dev)) { | |
297 | if (usb_irq) | |
298 | disable_irq_wake(usb_irq->start); | |
299 | if (ac_irq) | |
300 | disable_irq_wake(ac_irq->start); | |
301 | } | |
302 | ||
303 | return 0; | |
304 | } | |
305 | #else | |
306 | #define pda_power_suspend NULL | |
307 | #define pda_power_resume NULL | |
308 | #endif /* CONFIG_PM */ | |
309 | ||
b2998049 AV |
310 | static struct platform_driver pda_power_pdrv = { |
311 | .driver = { | |
312 | .name = "pda-power", | |
313 | }, | |
314 | .probe = pda_power_probe, | |
315 | .remove = pda_power_remove, | |
8f8e9b38 DB |
316 | .suspend = pda_power_suspend, |
317 | .resume = pda_power_resume, | |
b2998049 AV |
318 | }; |
319 | ||
320 | static int __init pda_power_init(void) | |
321 | { | |
322 | return platform_driver_register(&pda_power_pdrv); | |
323 | } | |
324 | ||
325 | static void __exit pda_power_exit(void) | |
326 | { | |
327 | platform_driver_unregister(&pda_power_pdrv); | |
b2998049 AV |
328 | } |
329 | ||
330 | module_init(pda_power_init); | |
331 | module_exit(pda_power_exit); | |
332 | MODULE_LICENSE("GPL"); | |
333 | MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>"); |