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