diff options
Diffstat (limited to 'drivers/power/pda_power.c')
-rw-r--r-- | drivers/power/pda_power.c | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c new file mode 100644 index 000000000000..4e1eb040e148 --- /dev/null +++ b/drivers/power/pda_power.c | |||
@@ -0,0 +1,261 @@ | |||
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 | static int pda_power_get_property(struct power_supply *psy, | ||
37 | enum power_supply_property psp, | ||
38 | union power_supply_propval *val) | ||
39 | { | ||
40 | switch (psp) { | ||
41 | case POWER_SUPPLY_PROP_ONLINE: | ||
42 | if (psy->type == POWER_SUPPLY_TYPE_MAINS) | ||
43 | val->intval = pdata->is_ac_online ? | ||
44 | pdata->is_ac_online() : 0; | ||
45 | else | ||
46 | val->intval = pdata->is_usb_online ? | ||
47 | pdata->is_usb_online() : 0; | ||
48 | break; | ||
49 | default: | ||
50 | return -EINVAL; | ||
51 | } | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static enum power_supply_property pda_power_props[] = { | ||
56 | POWER_SUPPLY_PROP_ONLINE, | ||
57 | }; | ||
58 | |||
59 | static char *pda_power_supplied_to[] = { | ||
60 | "main-battery", | ||
61 | "backup-battery", | ||
62 | }; | ||
63 | |||
64 | static struct power_supply pda_power_supplies[] = { | ||
65 | { | ||
66 | .name = "ac", | ||
67 | .type = POWER_SUPPLY_TYPE_MAINS, | ||
68 | .supplied_to = pda_power_supplied_to, | ||
69 | .num_supplicants = ARRAY_SIZE(pda_power_supplied_to), | ||
70 | .properties = pda_power_props, | ||
71 | .num_properties = ARRAY_SIZE(pda_power_props), | ||
72 | .get_property = pda_power_get_property, | ||
73 | }, | ||
74 | { | ||
75 | .name = "usb", | ||
76 | .type = POWER_SUPPLY_TYPE_USB, | ||
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 | |||
85 | static void update_charger(void) | ||
86 | { | ||
87 | if (!pdata->set_charge) | ||
88 | return; | ||
89 | |||
90 | if (pdata->is_ac_online && pdata->is_ac_online()) { | ||
91 | dev_dbg(dev, "charger on (AC)\n"); | ||
92 | pdata->set_charge(PDA_POWER_CHARGE_AC); | ||
93 | } else if (pdata->is_usb_online && pdata->is_usb_online()) { | ||
94 | dev_dbg(dev, "charger on (USB)\n"); | ||
95 | pdata->set_charge(PDA_POWER_CHARGE_USB); | ||
96 | } else { | ||
97 | dev_dbg(dev, "charger off\n"); | ||
98 | pdata->set_charge(0); | ||
99 | } | ||
100 | |||
101 | return; | ||
102 | } | ||
103 | |||
104 | static void supply_timer_func(unsigned long irq) | ||
105 | { | ||
106 | if (ac_irq && irq == ac_irq->start) | ||
107 | power_supply_changed(&pda_power_supplies[0]); | ||
108 | else if (usb_irq && irq == usb_irq->start) | ||
109 | power_supply_changed(&pda_power_supplies[1]); | ||
110 | return; | ||
111 | } | ||
112 | |||
113 | static void charger_timer_func(unsigned long irq) | ||
114 | { | ||
115 | update_charger(); | ||
116 | |||
117 | /* Okay, charger set. Now wait a bit before notifying supplicants, | ||
118 | * charge power should stabilize. */ | ||
119 | supply_timer.data = irq; | ||
120 | mod_timer(&supply_timer, | ||
121 | jiffies + msecs_to_jiffies(pdata->wait_for_charger)); | ||
122 | return; | ||
123 | } | ||
124 | |||
125 | static irqreturn_t power_changed_isr(int irq, void *unused) | ||
126 | { | ||
127 | /* Wait a bit before reading ac/usb line status and setting charger, | ||
128 | * because ac/usb status readings may lag from irq. */ | ||
129 | charger_timer.data = irq; | ||
130 | mod_timer(&charger_timer, | ||
131 | jiffies + msecs_to_jiffies(pdata->wait_for_status)); | ||
132 | return IRQ_HANDLED; | ||
133 | } | ||
134 | |||
135 | static int pda_power_probe(struct platform_device *pdev) | ||
136 | { | ||
137 | int ret = 0; | ||
138 | |||
139 | dev = &pdev->dev; | ||
140 | |||
141 | if (pdev->id != -1) { | ||
142 | dev_err(dev, "it's meaningless to register several " | ||
143 | "pda_powers; use id = -1\n"); | ||
144 | ret = -EINVAL; | ||
145 | goto wrongid; | ||
146 | } | ||
147 | |||
148 | pdata = pdev->dev.platform_data; | ||
149 | |||
150 | update_charger(); | ||
151 | |||
152 | if (!pdata->wait_for_status) | ||
153 | pdata->wait_for_status = 500; | ||
154 | |||
155 | if (!pdata->wait_for_charger) | ||
156 | pdata->wait_for_charger = 500; | ||
157 | |||
158 | setup_timer(&charger_timer, charger_timer_func, 0); | ||
159 | setup_timer(&supply_timer, supply_timer_func, 0); | ||
160 | |||
161 | ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac"); | ||
162 | usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb"); | ||
163 | if (!ac_irq && !usb_irq) { | ||
164 | dev_err(dev, "no ac/usb irq specified\n"); | ||
165 | ret = -ENODEV; | ||
166 | goto noirqs; | ||
167 | } | ||
168 | |||
169 | if (pdata->supplied_to) { | ||
170 | pda_power_supplies[0].supplied_to = pdata->supplied_to; | ||
171 | pda_power_supplies[1].supplied_to = pdata->supplied_to; | ||
172 | pda_power_supplies[0].num_supplicants = pdata->num_supplicants; | ||
173 | pda_power_supplies[1].num_supplicants = pdata->num_supplicants; | ||
174 | } | ||
175 | |||
176 | ret = power_supply_register(&pdev->dev, &pda_power_supplies[0]); | ||
177 | if (ret) { | ||
178 | dev_err(dev, "failed to register %s power supply\n", | ||
179 | pda_power_supplies[0].name); | ||
180 | goto supply0_failed; | ||
181 | } | ||
182 | |||
183 | ret = power_supply_register(&pdev->dev, &pda_power_supplies[1]); | ||
184 | if (ret) { | ||
185 | dev_err(dev, "failed to register %s power supply\n", | ||
186 | pda_power_supplies[1].name); | ||
187 | goto supply1_failed; | ||
188 | } | ||
189 | |||
190 | if (ac_irq) { | ||
191 | ret = request_irq(ac_irq->start, power_changed_isr, | ||
192 | get_irq_flags(ac_irq), ac_irq->name, | ||
193 | &pda_power_supplies[0]); | ||
194 | if (ret) { | ||
195 | dev_err(dev, "request ac irq failed\n"); | ||
196 | goto ac_irq_failed; | ||
197 | } | ||
198 | } | ||
199 | |||
200 | if (usb_irq) { | ||
201 | ret = request_irq(usb_irq->start, power_changed_isr, | ||
202 | get_irq_flags(usb_irq), usb_irq->name, | ||
203 | &pda_power_supplies[1]); | ||
204 | if (ret) { | ||
205 | dev_err(dev, "request usb irq failed\n"); | ||
206 | goto usb_irq_failed; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | goto success; | ||
211 | |||
212 | usb_irq_failed: | ||
213 | if (ac_irq) | ||
214 | free_irq(ac_irq->start, &pda_power_supplies[0]); | ||
215 | ac_irq_failed: | ||
216 | power_supply_unregister(&pda_power_supplies[1]); | ||
217 | supply1_failed: | ||
218 | power_supply_unregister(&pda_power_supplies[0]); | ||
219 | supply0_failed: | ||
220 | noirqs: | ||
221 | wrongid: | ||
222 | success: | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | static int pda_power_remove(struct platform_device *pdev) | ||
227 | { | ||
228 | if (usb_irq) | ||
229 | free_irq(usb_irq->start, &pda_power_supplies[1]); | ||
230 | if (ac_irq) | ||
231 | free_irq(ac_irq->start, &pda_power_supplies[0]); | ||
232 | del_timer_sync(&charger_timer); | ||
233 | del_timer_sync(&supply_timer); | ||
234 | power_supply_unregister(&pda_power_supplies[1]); | ||
235 | power_supply_unregister(&pda_power_supplies[0]); | ||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static struct platform_driver pda_power_pdrv = { | ||
240 | .driver = { | ||
241 | .name = "pda-power", | ||
242 | }, | ||
243 | .probe = pda_power_probe, | ||
244 | .remove = pda_power_remove, | ||
245 | }; | ||
246 | |||
247 | static int __init pda_power_init(void) | ||
248 | { | ||
249 | return platform_driver_register(&pda_power_pdrv); | ||
250 | } | ||
251 | |||
252 | static void __exit pda_power_exit(void) | ||
253 | { | ||
254 | platform_driver_unregister(&pda_power_pdrv); | ||
255 | return; | ||
256 | } | ||
257 | |||
258 | module_init(pda_power_init); | ||
259 | module_exit(pda_power_exit); | ||
260 | MODULE_LICENSE("GPL"); | ||
261 | MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>"); | ||