pda_power: various cleanups
[linux-block.git] / drivers / power / pda_power.c
CommitLineData
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
21static 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
30static struct device *dev;
31static struct pda_power_pdata *pdata;
32static struct resource *ac_irq, *usb_irq;
33static struct timer_list charger_timer;
34static struct timer_list supply_timer;
35
bfde2662
AV
36enum {
37 PDA_PSY_OFFLINE = 0,
38 PDA_PSY_ONLINE = 1,
39 PDA_PSY_TO_CHANGE,
40};
41static int new_ac_status = -1;
42static int new_usb_status = -1;
43static int ac_status = -1;
44static int usb_status = -1;
45
b2998049
AV
46static 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
65static enum power_supply_property pda_power_props[] = {
66 POWER_SUPPLY_PROP_ONLINE,
67};
68
69static char *pda_power_supplied_to[] = {
70 "main-battery",
71 "backup-battery",
72};
73
bfde2662
AV
74static 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
84static 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
94static 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
103static 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 120static 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 133static 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
145static void charger_timer_func(unsigned long unused)
146{
147 update_status();
148 psy_changed();
149}
150
5ebf6e6a 151static 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
170static 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
249usb_irq_failed:
9ef45106 250 if (pdata->is_usb_online)
bfde2662 251 power_supply_unregister(&pda_psy_usb);
9ef45106
DB
252usb_supply_failed:
253 if (pdata->is_ac_online && ac_irq)
bfde2662 254 free_irq(ac_irq->start, &pda_psy_ac);
b2998049 255ac_irq_failed:
9ef45106 256 if (pdata->is_ac_online)
bfde2662 257 power_supply_unregister(&pda_psy_ac);
9ef45106 258ac_supply_failed:
b2998049 259wrongid:
b2998049
AV
260 return ret;
261}
262
263static 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
282static 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
294static 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
310static 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
320static int __init pda_power_init(void)
321{
322 return platform_driver_register(&pda_power_pdrv);
323}
324
325static void __exit pda_power_exit(void)
326{
327 platform_driver_unregister(&pda_power_pdrv);
b2998049
AV
328}
329
330module_init(pda_power_init);
331module_exit(pda_power_exit);
332MODULE_LICENSE("GPL");
333MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");