]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/power/pda_power.c
pda_power: various cleanups
[linux-2.6-omap-h63xx.git] / drivers / power / pda_power.c
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
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
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
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,
82 };
83
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
103 static void update_charger(void)
104 {
105         if (!pdata->set_charge)
106                 return;
107
108         if (new_ac_status > 0) {
109                 dev_dbg(dev, "charger on (AC)\n");
110                 pdata->set_charge(PDA_POWER_CHARGE_AC);
111         } else if (new_usb_status > 0) {
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         }
118 }
119
120 static void supply_timer_func(unsigned long unused)
121 {
122         if (ac_status == PDA_PSY_TO_CHANGE) {
123                 ac_status = new_ac_status;
124                 power_supply_changed(&pda_psy_ac);
125         }
126
127         if (usb_status == PDA_PSY_TO_CHANGE) {
128                 usb_status = new_usb_status;
129                 power_supply_changed(&pda_psy_usb);
130         }
131 }
132
133 static void psy_changed(void)
134 {
135         update_charger();
136
137         /*
138          * Okay, charger set. Now wait a bit before notifying supplicants,
139          * charge power should stabilize.
140          */
141         mod_timer(&supply_timer,
142                   jiffies + msecs_to_jiffies(pdata->wait_for_charger));
143 }
144
145 static void charger_timer_func(unsigned long unused)
146 {
147         update_status();
148         psy_changed();
149 }
150
151 static irqreturn_t power_changed_isr(int irq, void *power_supply)
152 {
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          */
164         mod_timer(&charger_timer,
165                   jiffies + msecs_to_jiffies(pdata->wait_for_status));
166
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
185         update_status();
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");
199
200         if (pdata->supplied_to) {
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;
205         }
206
207         if (pdata->is_ac_online) {
208                 ret = power_supply_register(&pdev->dev, &pda_psy_ac);
209                 if (ret) {
210                         dev_err(dev, "failed to register %s power supply\n",
211                                 pda_psy_ac.name);
212                         goto ac_supply_failed;
213                 }
214
215                 if (ac_irq) {
216                         ret = request_irq(ac_irq->start, power_changed_isr,
217                                           get_irq_flags(ac_irq), ac_irq->name,
218                                           &pda_psy_ac);
219                         if (ret) {
220                                 dev_err(dev, "request ac irq failed\n");
221                                 goto ac_irq_failed;
222                         }
223                 }
224         }
225
226         if (pdata->is_usb_online) {
227                 ret = power_supply_register(&pdev->dev, &pda_psy_usb);
228                 if (ret) {
229                         dev_err(dev, "failed to register %s power supply\n",
230                                 pda_psy_usb.name);
231                         goto usb_supply_failed;
232                 }
233
234                 if (usb_irq) {
235                         ret = request_irq(usb_irq->start, power_changed_isr,
236                                           get_irq_flags(usb_irq),
237                                           usb_irq->name, &pda_psy_usb);
238                         if (ret) {
239                                 dev_err(dev, "request usb irq failed\n");
240                                 goto usb_irq_failed;
241                         }
242                 }
243         }
244
245         device_init_wakeup(&pdev->dev, 1);
246
247         return 0;
248
249 usb_irq_failed:
250         if (pdata->is_usb_online)
251                 power_supply_unregister(&pda_psy_usb);
252 usb_supply_failed:
253         if (pdata->is_ac_online && ac_irq)
254                 free_irq(ac_irq->start, &pda_psy_ac);
255 ac_irq_failed:
256         if (pdata->is_ac_online)
257                 power_supply_unregister(&pda_psy_ac);
258 ac_supply_failed:
259 wrongid:
260         return ret;
261 }
262
263 static int pda_power_remove(struct platform_device *pdev)
264 {
265         if (pdata->is_usb_online && usb_irq)
266                 free_irq(usb_irq->start, &pda_psy_usb);
267         if (pdata->is_ac_online && ac_irq)
268                 free_irq(ac_irq->start, &pda_psy_ac);
269
270         del_timer_sync(&charger_timer);
271         del_timer_sync(&supply_timer);
272
273         if (pdata->is_usb_online)
274                 power_supply_unregister(&pda_psy_usb);
275         if (pdata->is_ac_online)
276                 power_supply_unregister(&pda_psy_ac);
277
278         return 0;
279 }
280
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
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,
316         .suspend = pda_power_suspend,
317         .resume = pda_power_resume,
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);
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>");