diff options
-rw-r--r-- | drivers/power/Kconfig | 7 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/apm_power.c | 243 |
3 files changed, 251 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index cc70644db947..791fa0cc45fe 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig | |||
@@ -21,4 +21,11 @@ config PDA_POWER | |||
21 | one or two external power supplies (AC/USB) connected to main and | 21 | one or two external power supplies (AC/USB) connected to main and |
22 | backup batteries, and optional builtin charger. | 22 | backup batteries, and optional builtin charger. |
23 | 23 | ||
24 | config APM_POWER | ||
25 | tristate "APM emulation for class batteries" | ||
26 | depends on APM_EMULATION | ||
27 | help | ||
28 | Say Y here to enable support APM status emulation using | ||
29 | battery class devices. | ||
30 | |||
24 | endif # POWER_SUPPLY | 31 | endif # POWER_SUPPLY |
diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 1ff4b6806381..7779cebcb3ae 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile | |||
@@ -15,3 +15,4 @@ endif | |||
15 | obj-$(CONFIG_POWER_SUPPLY) += power_supply.o | 15 | obj-$(CONFIG_POWER_SUPPLY) += power_supply.o |
16 | 16 | ||
17 | obj-$(CONFIG_PDA_POWER) += pda_power.o | 17 | obj-$(CONFIG_PDA_POWER) += pda_power.o |
18 | obj-$(CONFIG_APM_POWER) += apm_power.o | ||
diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c new file mode 100644 index 000000000000..042bd950d036 --- /dev/null +++ b/drivers/power/apm_power.c | |||
@@ -0,0 +1,243 @@ | |||
1 | /* | ||
2 | * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> | ||
3 | * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru> | ||
4 | * | ||
5 | * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru> | ||
6 | * | ||
7 | * Use consistent with the GNU GPL is permitted, | ||
8 | * provided that this copyright notice is | ||
9 | * preserved in its entirety in all copies and derived works. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/power_supply.h> | ||
14 | #include <linux/apm-emulation.h> | ||
15 | |||
16 | #define PSY_PROP(psy, prop, val) psy->get_property(psy, \ | ||
17 | POWER_SUPPLY_PROP_##prop, val) | ||
18 | |||
19 | #define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \ | ||
20 | prop, val) | ||
21 | |||
22 | #define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) | ||
23 | |||
24 | static struct power_supply *main_battery; | ||
25 | |||
26 | static void find_main_battery(void) | ||
27 | { | ||
28 | struct device *dev; | ||
29 | struct power_supply *bat, *batm; | ||
30 | union power_supply_propval full; | ||
31 | int max_charge = 0; | ||
32 | |||
33 | main_battery = NULL; | ||
34 | batm = NULL; | ||
35 | list_for_each_entry(dev, &power_supply_class->devices, node) { | ||
36 | bat = dev_get_drvdata(dev); | ||
37 | /* If none of battery devices cantains 'use_for_apm' flag, | ||
38 | choice one with maximum design charge */ | ||
39 | if (!PSY_PROP(bat, CHARGE_FULL_DESIGN, &full)) { | ||
40 | if (full.intval > max_charge) { | ||
41 | batm = bat; | ||
42 | max_charge = full.intval; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | if (bat->use_for_apm) | ||
47 | main_battery = bat; | ||
48 | } | ||
49 | if (!main_battery) | ||
50 | main_battery = batm; | ||
51 | |||
52 | return; | ||
53 | } | ||
54 | |||
55 | static int calculate_time(int status) | ||
56 | { | ||
57 | union power_supply_propval charge_full, charge_empty; | ||
58 | union power_supply_propval charge, I; | ||
59 | |||
60 | if (MPSY_PROP(CHARGE_FULL, &charge_full)) { | ||
61 | /* if battery can't report this property, use design value */ | ||
62 | if (MPSY_PROP(CHARGE_FULL_DESIGN, &charge_full)) | ||
63 | return -1; | ||
64 | } | ||
65 | |||
66 | if (MPSY_PROP(CHARGE_EMPTY, &charge_empty)) { | ||
67 | /* if battery can't report this property, use design value */ | ||
68 | if (MPSY_PROP(CHARGE_EMPTY_DESIGN, &charge_empty)) | ||
69 | charge_empty.intval = 0; | ||
70 | } | ||
71 | |||
72 | if (MPSY_PROP(CHARGE_AVG, &charge)) { | ||
73 | /* if battery can't report average value, use momentary */ | ||
74 | if (MPSY_PROP(CHARGE_NOW, &charge)) | ||
75 | return -1; | ||
76 | } | ||
77 | |||
78 | if (MPSY_PROP(CURRENT_AVG, &I)) { | ||
79 | /* if battery can't report average value, use momentary */ | ||
80 | if (MPSY_PROP(CURRENT_NOW, &I)) | ||
81 | return -1; | ||
82 | } | ||
83 | |||
84 | if (status == POWER_SUPPLY_STATUS_CHARGING) | ||
85 | return ((charge.intval - charge_full.intval) * 60L) / | ||
86 | I.intval; | ||
87 | else | ||
88 | return -((charge.intval - charge_empty.intval) * 60L) / | ||
89 | I.intval; | ||
90 | } | ||
91 | |||
92 | static int calculate_capacity(int using_charge) | ||
93 | { | ||
94 | enum power_supply_property full_prop, empty_prop; | ||
95 | enum power_supply_property full_design_prop, empty_design_prop; | ||
96 | enum power_supply_property now_prop, avg_prop; | ||
97 | union power_supply_propval empty, full, cur; | ||
98 | int ret; | ||
99 | |||
100 | if (using_charge) { | ||
101 | full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; | ||
102 | empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | ||
103 | full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; | ||
104 | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; | ||
105 | now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; | ||
106 | avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; | ||
107 | } else { | ||
108 | full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; | ||
109 | empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; | ||
110 | full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; | ||
111 | empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; | ||
112 | now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; | ||
113 | avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; | ||
114 | } | ||
115 | |||
116 | if (_MPSY_PROP(full_prop, &full)) { | ||
117 | /* if battery can't report this property, use design value */ | ||
118 | if (_MPSY_PROP(full_design_prop, &full)) | ||
119 | return -1; | ||
120 | } | ||
121 | |||
122 | if (_MPSY_PROP(avg_prop, &cur)) { | ||
123 | /* if battery can't report average value, use momentary */ | ||
124 | if (_MPSY_PROP(now_prop, &cur)) | ||
125 | return -1; | ||
126 | } | ||
127 | |||
128 | if (_MPSY_PROP(empty_prop, &empty)) { | ||
129 | /* if battery can't report this property, use design value */ | ||
130 | if (_MPSY_PROP(empty_design_prop, &empty)) | ||
131 | empty.intval = 0; | ||
132 | } | ||
133 | |||
134 | if (full.intval - empty.intval) | ||
135 | ret = ((cur.intval - empty.intval) * 100L) / | ||
136 | (full.intval - empty.intval); | ||
137 | else | ||
138 | return -1; | ||
139 | |||
140 | if (ret > 100) | ||
141 | return 100; | ||
142 | else if (ret < 0) | ||
143 | return 0; | ||
144 | |||
145 | return ret; | ||
146 | } | ||
147 | |||
148 | static void apm_battery_apm_get_power_status(struct apm_power_info *info) | ||
149 | { | ||
150 | union power_supply_propval status; | ||
151 | union power_supply_propval capacity, time_to_full, time_to_empty; | ||
152 | |||
153 | down(&power_supply_class->sem); | ||
154 | find_main_battery(); | ||
155 | if (!main_battery) { | ||
156 | up(&power_supply_class->sem); | ||
157 | return; | ||
158 | } | ||
159 | |||
160 | /* status */ | ||
161 | |||
162 | if (MPSY_PROP(STATUS, &status)) | ||
163 | status.intval = POWER_SUPPLY_STATUS_UNKNOWN; | ||
164 | |||
165 | /* ac line status */ | ||
166 | |||
167 | if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || | ||
168 | (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || | ||
169 | (status.intval == POWER_SUPPLY_STATUS_FULL)) | ||
170 | info->ac_line_status = APM_AC_ONLINE; | ||
171 | else | ||
172 | info->ac_line_status = APM_AC_OFFLINE; | ||
173 | |||
174 | /* battery life (i.e. capacity, in percents) */ | ||
175 | |||
176 | if (MPSY_PROP(CAPACITY, &capacity) == 0) { | ||
177 | info->battery_life = capacity.intval; | ||
178 | } else { | ||
179 | /* try calculate using energy */ | ||
180 | info->battery_life = calculate_capacity(0); | ||
181 | /* if failed try calculate using charge instead */ | ||
182 | if (info->battery_life == -1) | ||
183 | info->battery_life = calculate_capacity(1); | ||
184 | } | ||
185 | |||
186 | /* charging status */ | ||
187 | |||
188 | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | ||
189 | info->battery_status = APM_BATTERY_STATUS_CHARGING; | ||
190 | } else { | ||
191 | if (info->battery_life > 50) | ||
192 | info->battery_status = APM_BATTERY_STATUS_HIGH; | ||
193 | else if (info->battery_life > 5) | ||
194 | info->battery_status = APM_BATTERY_STATUS_LOW; | ||
195 | else | ||
196 | info->battery_status = APM_BATTERY_STATUS_CRITICAL; | ||
197 | } | ||
198 | info->battery_flag = info->battery_status; | ||
199 | |||
200 | /* time */ | ||
201 | |||
202 | info->units = APM_UNITS_MINS; | ||
203 | |||
204 | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | ||
205 | if (MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full)) { | ||
206 | if (MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) | ||
207 | info->time = calculate_time(status.intval); | ||
208 | else | ||
209 | info->time = time_to_full.intval / 60; | ||
210 | } | ||
211 | } else { | ||
212 | if (MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty)) { | ||
213 | if (MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) | ||
214 | info->time = calculate_time(status.intval); | ||
215 | else | ||
216 | info->time = time_to_empty.intval / 60; | ||
217 | } | ||
218 | } | ||
219 | |||
220 | up(&power_supply_class->sem); | ||
221 | return; | ||
222 | } | ||
223 | |||
224 | static int __init apm_battery_init(void) | ||
225 | { | ||
226 | printk(KERN_INFO "APM Battery Driver\n"); | ||
227 | |||
228 | apm_get_power_status = apm_battery_apm_get_power_status; | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | static void __exit apm_battery_exit(void) | ||
233 | { | ||
234 | apm_get_power_status = NULL; | ||
235 | return; | ||
236 | } | ||
237 | |||
238 | module_init(apm_battery_init); | ||
239 | module_exit(apm_battery_exit); | ||
240 | |||
241 | MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>"); | ||
242 | MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); | ||
243 | MODULE_LICENSE("GPL"); | ||