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