3928e7cdddc13331c5176491df1f7891896cc4fb
[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)
91 {
92         union power_supply_propval charge_full, charge_empty;
93         union power_supply_propval charge, I;
94
95         if (MPSY_PROP(CHARGE_FULL, &charge_full)) {
96                 /* if battery can't report this property, use design value */
97                 if (MPSY_PROP(CHARGE_FULL_DESIGN, &charge_full))
98                         return -1;
99         }
100
101         if (MPSY_PROP(CHARGE_EMPTY, &charge_empty)) {
102                 /* if battery can't report this property, use design value */
103                 if (MPSY_PROP(CHARGE_EMPTY_DESIGN, &charge_empty))
104                         charge_empty.intval = 0;
105         }
106
107         if (MPSY_PROP(CHARGE_AVG, &charge)) {
108                 /* if battery can't report average value, use momentary */
109                 if (MPSY_PROP(CHARGE_NOW, &charge))
110                         return -1;
111         }
112
113         if (MPSY_PROP(CURRENT_AVG, &I)) {
114                 /* if battery can't report average value, use momentary */
115                 if (MPSY_PROP(CURRENT_NOW, &I))
116                         return -1;
117         }
118
119         if (status == POWER_SUPPLY_STATUS_CHARGING)
120                 return ((charge.intval - charge_full.intval) * 60L) /
121                        I.intval;
122         else
123                 return -((charge.intval - charge_empty.intval) * 60L) /
124                         I.intval;
125 }
126
127 static int calculate_capacity(int using_charge)
128 {
129         enum power_supply_property full_prop, empty_prop;
130         enum power_supply_property full_design_prop, empty_design_prop;
131         enum power_supply_property now_prop, avg_prop;
132         union power_supply_propval empty, full, cur;
133         int ret;
134
135         if (using_charge) {
136                 full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
137                 empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
138                 full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
139                 empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
140                 now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
141                 avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
142         } else {
143                 full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
144                 empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
145                 full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
146                 empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
147                 now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
148                 avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
149         }
150
151         if (_MPSY_PROP(full_prop, &full)) {
152                 /* if battery can't report this property, use design value */
153                 if (_MPSY_PROP(full_design_prop, &full))
154                         return -1;
155         }
156
157         if (_MPSY_PROP(avg_prop, &cur)) {
158                 /* if battery can't report average value, use momentary */
159                 if (_MPSY_PROP(now_prop, &cur))
160                         return -1;
161         }
162
163         if (_MPSY_PROP(empty_prop, &empty)) {
164                 /* if battery can't report this property, use design value */
165                 if (_MPSY_PROP(empty_design_prop, &empty))
166                         empty.intval = 0;
167         }
168
169         if (full.intval - empty.intval)
170                 ret =  ((cur.intval - empty.intval) * 100L) /
171                        (full.intval - empty.intval);
172         else
173                 return -1;
174
175         if (ret > 100)
176                 return 100;
177         else if (ret < 0)
178                 return 0;
179
180         return ret;
181 }
182
183 static void apm_battery_apm_get_power_status(struct apm_power_info *info)
184 {
185         union power_supply_propval status;
186         union power_supply_propval capacity, time_to_full, time_to_empty;
187
188         down(&power_supply_class->sem);
189         find_main_battery();
190         if (!main_battery) {
191                 up(&power_supply_class->sem);
192                 return;
193         }
194
195         /* status */
196
197         if (MPSY_PROP(STATUS, &status))
198                 status.intval = POWER_SUPPLY_STATUS_UNKNOWN;
199
200         /* ac line status */
201
202         if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) ||
203             (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) ||
204             (status.intval == POWER_SUPPLY_STATUS_FULL))
205                 info->ac_line_status = APM_AC_ONLINE;
206         else
207                 info->ac_line_status = APM_AC_OFFLINE;
208
209         /* battery life (i.e. capacity, in percents) */
210
211         if (MPSY_PROP(CAPACITY, &capacity) == 0) {
212                 info->battery_life = capacity.intval;
213         } else {
214                 /* try calculate using energy */
215                 info->battery_life = calculate_capacity(0);
216                 /* if failed try calculate using charge instead */
217                 if (info->battery_life == -1)
218                         info->battery_life = calculate_capacity(1);
219         }
220
221         /* charging status */
222
223         if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
224                 info->battery_status = APM_BATTERY_STATUS_CHARGING;
225         } else {
226                 if (info->battery_life > 50)
227                         info->battery_status = APM_BATTERY_STATUS_HIGH;
228                 else if (info->battery_life > 5)
229                         info->battery_status = APM_BATTERY_STATUS_LOW;
230                 else
231                         info->battery_status = APM_BATTERY_STATUS_CRITICAL;
232         }
233         info->battery_flag = info->battery_status;
234
235         /* time */
236
237         info->units = APM_UNITS_MINS;
238
239         if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
240                 if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) ||
241                                 !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full))
242                         info->time = time_to_full.intval / 60;
243                 else
244                         info->time = calculate_time(status.intval);
245         } else {
246                 if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) ||
247                                 !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty))
248                         info->time = time_to_empty.intval / 60;
249                 else
250                         info->time = calculate_time(status.intval);
251         }
252
253         up(&power_supply_class->sem);
254 }
255
256 static int __init apm_battery_init(void)
257 {
258         printk(KERN_INFO "APM Battery Driver\n");
259
260         apm_get_power_status = apm_battery_apm_get_power_status;
261         return 0;
262 }
263
264 static void __exit apm_battery_exit(void)
265 {
266         apm_get_power_status = NULL;
267 }
268
269 module_init(apm_battery_init);
270 module_exit(apm_battery_exit);
271
272 MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
273 MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
274 MODULE_LICENSE("GPL");