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