apm_power: calculate to_full/to_empty time using energy
[pandora-kernel.git] / drivers / power / apm_power.c
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 = NULL;
30         struct power_supply *max_charge_bat = NULL;
31         struct power_supply *max_energy_bat = NULL;
32         union power_supply_propval full;
33         int max_charge = 0;
34         int max_energy = 0;
35
36         main_battery = NULL;
37
38         list_for_each_entry(dev, &power_supply_class->devices, node) {
39                 bat = dev_get_drvdata(dev);
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)) {
49                         if (full.intval > max_charge) {
50                                 max_charge_bat = bat;
51                                 max_charge = full.intval;
52                         }
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                         }
59                 }
60         }
61
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;
87         }
88 }
89
90 static int calculate_time(int status, int using_charge)
91 {
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;
102
103         if (MPSY_PROP(CURRENT_AVG, &I)) {
104                 /* if battery can't report average value, use momentary */
105                 if (MPSY_PROP(CURRENT_NOW, &I))
106                         return -1;
107         }
108
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;
123         }
124
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))
128                         return -1;
129         }
130
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)) {
138                 /* if battery can't report average value, use momentary */
139                 if (_MPSY_PROP(cur_now_prop, &cur))
140                         return -1;
141         }
142
143         if (status == POWER_SUPPLY_STATUS_CHARGING)
144                 return ((cur.intval - full.intval) * 60L) / I.intval;
145         else
146                 return -((cur.intval - empty.intval) * 60L) / I.intval;
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) {
262                 if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) ||
263                                 !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) {
264                         info->time = time_to_full.intval / 60;
265                 } else {
266                         info->time = calculate_time(status.intval, 0);
267                         if (info->time == -1)
268                                 info->time = calculate_time(status.intval, 1);
269                 }
270         } else {
271                 if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) ||
272                               !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) {
273                         info->time = time_to_empty.intval / 60;
274                 } else {
275                         info->time = calculate_time(status.intval, 0);
276                         if (info->time == -1)
277                                 info->time = calculate_time(status.intval, 1);
278                 }
279         }
280
281         up(&power_supply_class->sem);
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;
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");