Commit | Line | Data |
---|---|---|
3788ec93 AV |
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> | |
51990e82 | 13 | #include <linux/device.h> |
3788ec93 AV |
14 | #include <linux/power_supply.h> |
15 | #include <linux/apm-emulation.h> | |
16 | ||
dffd28a1 | 17 | |
75599d36 | 18 | #define PSY_PROP(psy, prop, val) (power_supply_get_property(psy, \ |
c84cad3d | 19 | POWER_SUPPLY_PROP_##prop, val)) |
3788ec93 | 20 | |
75599d36 | 21 | #define _MPSY_PROP(prop, val) (power_supply_get_property(main_battery, \ |
c84cad3d | 22 | prop, val)) |
3788ec93 AV |
23 | |
24 | #define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) | |
25 | ||
dffd28a1 | 26 | static DEFINE_MUTEX(apm_mutex); |
3788ec93 AV |
27 | static struct power_supply *main_battery; |
28 | ||
dffd28a1 DB |
29 | enum apm_source { |
30 | SOURCE_ENERGY, | |
31 | SOURCE_CHARGE, | |
32 | SOURCE_VOLTAGE, | |
33 | }; | |
34 | ||
443cad92 DY |
35 | struct find_bat_param { |
36 | struct power_supply *main; | |
37 | struct power_supply *bat; | |
38 | struct power_supply *max_charge_bat; | |
39 | struct power_supply *max_energy_bat; | |
3788ec93 | 40 | union power_supply_propval full; |
443cad92 DY |
41 | int max_charge; |
42 | int max_energy; | |
43 | }; | |
3788ec93 | 44 | |
443cad92 DY |
45 | static int __find_main_battery(struct device *dev, void *data) |
46 | { | |
47 | struct find_bat_param *bp = (struct find_bat_param *)data; | |
d385376f | 48 | |
443cad92 | 49 | bp->bat = dev_get_drvdata(dev); |
d385376f | 50 | |
297d716f | 51 | if (bp->bat->desc->use_for_apm) { |
443cad92 DY |
52 | /* nice, we explicitly asked to report this battery. */ |
53 | bp->main = bp->bat; | |
54 | return 1; | |
55 | } | |
d385376f | 56 | |
443cad92 DY |
57 | if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || |
58 | !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { | |
59 | if (bp->full.intval > bp->max_charge) { | |
60 | bp->max_charge_bat = bp->bat; | |
61 | bp->max_charge = bp->full.intval; | |
62 | } | |
63 | } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || | |
64 | !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { | |
65 | if (bp->full.intval > bp->max_energy) { | |
66 | bp->max_energy_bat = bp->bat; | |
67 | bp->max_energy = bp->full.intval; | |
3788ec93 | 68 | } |
d385376f | 69 | } |
443cad92 DY |
70 | return 0; |
71 | } | |
72 | ||
73 | static void find_main_battery(void) | |
74 | { | |
75 | struct find_bat_param bp; | |
76 | int error; | |
77 | ||
78 | memset(&bp, 0, sizeof(struct find_bat_param)); | |
79 | main_battery = NULL; | |
80 | bp.main = main_battery; | |
81 | ||
68ade097 | 82 | error = power_supply_for_each_device(&bp, __find_main_battery); |
443cad92 DY |
83 | if (error) { |
84 | main_battery = bp.main; | |
85 | return; | |
86 | } | |
3788ec93 | 87 | |
443cad92 DY |
88 | if ((bp.max_energy_bat && bp.max_charge_bat) && |
89 | (bp.max_energy_bat != bp.max_charge_bat)) { | |
d385376f | 90 | /* try guess battery with more capacity */ |
443cad92 DY |
91 | if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, |
92 | &bp.full)) { | |
93 | if (bp.max_energy > bp.max_charge * bp.full.intval) | |
94 | main_battery = bp.max_energy_bat; | |
d385376f | 95 | else |
443cad92 DY |
96 | main_battery = bp.max_charge_bat; |
97 | } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, | |
98 | &bp.full)) { | |
99 | if (bp.max_charge > bp.max_energy / bp.full.intval) | |
100 | main_battery = bp.max_charge_bat; | |
d385376f | 101 | else |
443cad92 | 102 | main_battery = bp.max_energy_bat; |
d385376f AV |
103 | } else { |
104 | /* give up, choice any */ | |
443cad92 | 105 | main_battery = bp.max_energy_bat; |
d385376f | 106 | } |
443cad92 DY |
107 | } else if (bp.max_charge_bat) { |
108 | main_battery = bp.max_charge_bat; | |
109 | } else if (bp.max_energy_bat) { | |
110 | main_battery = bp.max_energy_bat; | |
d385376f AV |
111 | } else { |
112 | /* give up, try the last if any */ | |
443cad92 | 113 | main_battery = bp.bat; |
3788ec93 | 114 | } |
3788ec93 AV |
115 | } |
116 | ||
dffd28a1 | 117 | static int do_calculate_time(int status, enum apm_source source) |
3788ec93 | 118 | { |
2a721dfc AV |
119 | union power_supply_propval full; |
120 | union power_supply_propval empty; | |
121 | union power_supply_propval cur; | |
122 | union power_supply_propval I; | |
123 | enum power_supply_property full_prop; | |
124 | enum power_supply_property full_design_prop; | |
125 | enum power_supply_property empty_prop; | |
126 | enum power_supply_property empty_design_prop; | |
127 | enum power_supply_property cur_avg_prop; | |
128 | enum power_supply_property cur_now_prop; | |
3788ec93 | 129 | |
2a721dfc AV |
130 | if (MPSY_PROP(CURRENT_AVG, &I)) { |
131 | /* if battery can't report average value, use momentary */ | |
132 | if (MPSY_PROP(CURRENT_NOW, &I)) | |
3788ec93 AV |
133 | return -1; |
134 | } | |
135 | ||
e91926e9 AV |
136 | if (!I.intval) |
137 | return 0; | |
138 | ||
dffd28a1 DB |
139 | switch (source) { |
140 | case SOURCE_CHARGE: | |
2a721dfc AV |
141 | full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; |
142 | full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; | |
143 | empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | |
144 | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | |
145 | cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; | |
146 | cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; | |
dffd28a1 DB |
147 | break; |
148 | case SOURCE_ENERGY: | |
2a721dfc AV |
149 | full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; |
150 | full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; | |
151 | empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; | |
152 | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | |
153 | cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; | |
154 | cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; | |
dffd28a1 DB |
155 | break; |
156 | case SOURCE_VOLTAGE: | |
157 | full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; | |
158 | full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; | |
159 | empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; | |
160 | empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; | |
161 | cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; | |
162 | cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; | |
163 | break; | |
164 | default: | |
165 | printk(KERN_ERR "Unsupported source: %d\n", source); | |
166 | return -1; | |
3788ec93 AV |
167 | } |
168 | ||
2a721dfc AV |
169 | if (_MPSY_PROP(full_prop, &full)) { |
170 | /* if battery can't report this property, use design value */ | |
171 | if (_MPSY_PROP(full_design_prop, &full)) | |
3788ec93 AV |
172 | return -1; |
173 | } | |
174 | ||
2a721dfc AV |
175 | if (_MPSY_PROP(empty_prop, &empty)) { |
176 | /* if battery can't report this property, use design value */ | |
177 | if (_MPSY_PROP(empty_design_prop, &empty)) | |
178 | empty.intval = 0; | |
179 | } | |
180 | ||
181 | if (_MPSY_PROP(cur_avg_prop, &cur)) { | |
3788ec93 | 182 | /* if battery can't report average value, use momentary */ |
2a721dfc | 183 | if (_MPSY_PROP(cur_now_prop, &cur)) |
3788ec93 AV |
184 | return -1; |
185 | } | |
186 | ||
187 | if (status == POWER_SUPPLY_STATUS_CHARGING) | |
2a721dfc | 188 | return ((cur.intval - full.intval) * 60L) / I.intval; |
3788ec93 | 189 | else |
2a721dfc | 190 | return -((cur.intval - empty.intval) * 60L) / I.intval; |
3788ec93 AV |
191 | } |
192 | ||
dffd28a1 DB |
193 | static int calculate_time(int status) |
194 | { | |
195 | int time; | |
196 | ||
197 | time = do_calculate_time(status, SOURCE_ENERGY); | |
198 | if (time != -1) | |
199 | return time; | |
200 | ||
201 | time = do_calculate_time(status, SOURCE_CHARGE); | |
202 | if (time != -1) | |
203 | return time; | |
204 | ||
205 | time = do_calculate_time(status, SOURCE_VOLTAGE); | |
206 | if (time != -1) | |
207 | return time; | |
208 | ||
209 | return -1; | |
210 | } | |
211 | ||
212 | static int calculate_capacity(enum apm_source source) | |
3788ec93 AV |
213 | { |
214 | enum power_supply_property full_prop, empty_prop; | |
215 | enum power_supply_property full_design_prop, empty_design_prop; | |
216 | enum power_supply_property now_prop, avg_prop; | |
217 | union power_supply_propval empty, full, cur; | |
218 | int ret; | |
219 | ||
dffd28a1 DB |
220 | switch (source) { |
221 | case SOURCE_CHARGE: | |
3788ec93 AV |
222 | full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; |
223 | empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | |
224 | full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; | |
225 | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; | |
226 | now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; | |
227 | avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; | |
dffd28a1 DB |
228 | break; |
229 | case SOURCE_ENERGY: | |
3788ec93 AV |
230 | full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; |
231 | empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; | |
232 | full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; | |
233 | empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; | |
234 | now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; | |
235 | avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; | |
1d220334 | 236 | break; |
dffd28a1 DB |
237 | case SOURCE_VOLTAGE: |
238 | full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; | |
239 | empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; | |
240 | full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; | |
241 | empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; | |
242 | now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; | |
243 | avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; | |
244 | break; | |
245 | default: | |
246 | printk(KERN_ERR "Unsupported source: %d\n", source); | |
247 | return -1; | |
3788ec93 AV |
248 | } |
249 | ||
250 | if (_MPSY_PROP(full_prop, &full)) { | |
251 | /* if battery can't report this property, use design value */ | |
252 | if (_MPSY_PROP(full_design_prop, &full)) | |
253 | return -1; | |
254 | } | |
255 | ||
256 | if (_MPSY_PROP(avg_prop, &cur)) { | |
257 | /* if battery can't report average value, use momentary */ | |
258 | if (_MPSY_PROP(now_prop, &cur)) | |
259 | return -1; | |
260 | } | |
261 | ||
262 | if (_MPSY_PROP(empty_prop, &empty)) { | |
263 | /* if battery can't report this property, use design value */ | |
264 | if (_MPSY_PROP(empty_design_prop, &empty)) | |
265 | empty.intval = 0; | |
266 | } | |
267 | ||
268 | if (full.intval - empty.intval) | |
269 | ret = ((cur.intval - empty.intval) * 100L) / | |
270 | (full.intval - empty.intval); | |
271 | else | |
272 | return -1; | |
273 | ||
274 | if (ret > 100) | |
275 | return 100; | |
276 | else if (ret < 0) | |
277 | return 0; | |
278 | ||
279 | return ret; | |
280 | } | |
281 | ||
282 | static void apm_battery_apm_get_power_status(struct apm_power_info *info) | |
283 | { | |
284 | union power_supply_propval status; | |
285 | union power_supply_propval capacity, time_to_full, time_to_empty; | |
286 | ||
443cad92 | 287 | mutex_lock(&apm_mutex); |
3788ec93 AV |
288 | find_main_battery(); |
289 | if (!main_battery) { | |
443cad92 | 290 | mutex_unlock(&apm_mutex); |
3788ec93 AV |
291 | return; |
292 | } | |
293 | ||
294 | /* status */ | |
295 | ||
296 | if (MPSY_PROP(STATUS, &status)) | |
297 | status.intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
298 | ||
299 | /* ac line status */ | |
300 | ||
301 | if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || | |
302 | (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || | |
303 | (status.intval == POWER_SUPPLY_STATUS_FULL)) | |
304 | info->ac_line_status = APM_AC_ONLINE; | |
305 | else | |
306 | info->ac_line_status = APM_AC_OFFLINE; | |
307 | ||
308 | /* battery life (i.e. capacity, in percents) */ | |
309 | ||
310 | if (MPSY_PROP(CAPACITY, &capacity) == 0) { | |
311 | info->battery_life = capacity.intval; | |
312 | } else { | |
313 | /* try calculate using energy */ | |
dffd28a1 | 314 | info->battery_life = calculate_capacity(SOURCE_ENERGY); |
3788ec93 AV |
315 | /* if failed try calculate using charge instead */ |
316 | if (info->battery_life == -1) | |
dffd28a1 DB |
317 | info->battery_life = calculate_capacity(SOURCE_CHARGE); |
318 | if (info->battery_life == -1) | |
319 | info->battery_life = calculate_capacity(SOURCE_VOLTAGE); | |
3788ec93 AV |
320 | } |
321 | ||
322 | /* charging status */ | |
323 | ||
324 | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | |
325 | info->battery_status = APM_BATTERY_STATUS_CHARGING; | |
326 | } else { | |
327 | if (info->battery_life > 50) | |
328 | info->battery_status = APM_BATTERY_STATUS_HIGH; | |
329 | else if (info->battery_life > 5) | |
330 | info->battery_status = APM_BATTERY_STATUS_LOW; | |
331 | else | |
332 | info->battery_status = APM_BATTERY_STATUS_CRITICAL; | |
333 | } | |
334 | info->battery_flag = info->battery_status; | |
335 | ||
336 | /* time */ | |
337 | ||
338 | info->units = APM_UNITS_MINS; | |
339 | ||
340 | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | |
cd1ebcc0 | 341 | if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || |
dffd28a1 | 342 | !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) |
cd1ebcc0 | 343 | info->time = time_to_full.intval / 60; |
dffd28a1 DB |
344 | else |
345 | info->time = calculate_time(status.intval); | |
3788ec93 | 346 | } else { |
cd1ebcc0 | 347 | if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || |
dffd28a1 | 348 | !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) |
cd1ebcc0 | 349 | info->time = time_to_empty.intval / 60; |
dffd28a1 DB |
350 | else |
351 | info->time = calculate_time(status.intval); | |
3788ec93 AV |
352 | } |
353 | ||
443cad92 | 354 | mutex_unlock(&apm_mutex); |
3788ec93 AV |
355 | } |
356 | ||
357 | static int __init apm_battery_init(void) | |
358 | { | |
359 | printk(KERN_INFO "APM Battery Driver\n"); | |
360 | ||
361 | apm_get_power_status = apm_battery_apm_get_power_status; | |
362 | return 0; | |
363 | } | |
364 | ||
365 | static void __exit apm_battery_exit(void) | |
366 | { | |
367 | apm_get_power_status = NULL; | |
3788ec93 AV |
368 | } |
369 | ||
370 | module_init(apm_battery_init); | |
371 | module_exit(apm_battery_exit); | |
372 | ||
373 | MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>"); | |
374 | MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); | |
375 | MODULE_LICENSE("GPL"); |