Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[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, *batm;
30         union power_supply_propval full;
31         int max_charge = 0;
32
33         main_battery = NULL;
34         batm = NULL;
35         list_for_each_entry(dev, &power_supply_class->devices, node) {
36                 bat = dev_get_drvdata(dev);
37                 /* If none of battery devices cantains 'use_for_apm' flag,
38                    choice one with maximum design charge */
39                 if (!PSY_PROP(bat, CHARGE_FULL_DESIGN, &full)) {
40                         if (full.intval > max_charge) {
41                                 batm = bat;
42                                 max_charge = full.intval;
43                         }
44                 }
45
46                 if (bat->use_for_apm)
47                         main_battery = bat;
48         }
49         if (!main_battery)
50                 main_battery = batm;
51
52         return;
53 }
54
55 static int calculate_time(int status)
56 {
57         union power_supply_propval charge_full, charge_empty;
58         union power_supply_propval charge, I;
59
60         if (MPSY_PROP(CHARGE_FULL, &charge_full)) {
61                 /* if battery can't report this property, use design value */
62                 if (MPSY_PROP(CHARGE_FULL_DESIGN, &charge_full))
63                         return -1;
64         }
65
66         if (MPSY_PROP(CHARGE_EMPTY, &charge_empty)) {
67                 /* if battery can't report this property, use design value */
68                 if (MPSY_PROP(CHARGE_EMPTY_DESIGN, &charge_empty))
69                         charge_empty.intval = 0;
70         }
71
72         if (MPSY_PROP(CHARGE_AVG, &charge)) {
73                 /* if battery can't report average value, use momentary */
74                 if (MPSY_PROP(CHARGE_NOW, &charge))
75                         return -1;
76         }
77
78         if (MPSY_PROP(CURRENT_AVG, &I)) {
79                 /* if battery can't report average value, use momentary */
80                 if (MPSY_PROP(CURRENT_NOW, &I))
81                         return -1;
82         }
83
84         if (status == POWER_SUPPLY_STATUS_CHARGING)
85                 return ((charge.intval - charge_full.intval) * 60L) /
86                        I.intval;
87         else
88                 return -((charge.intval - charge_empty.intval) * 60L) /
89                         I.intval;
90 }
91
92 static int calculate_capacity(int using_charge)
93 {
94         enum power_supply_property full_prop, empty_prop;
95         enum power_supply_property full_design_prop, empty_design_prop;
96         enum power_supply_property now_prop, avg_prop;
97         union power_supply_propval empty, full, cur;
98         int ret;
99
100         if (using_charge) {
101                 full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
102                 empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
103                 full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
104                 empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
105                 now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
106                 avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
107         } else {
108                 full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
109                 empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
110                 full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
111                 empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
112                 now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
113                 avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
114         }
115
116         if (_MPSY_PROP(full_prop, &full)) {
117                 /* if battery can't report this property, use design value */
118                 if (_MPSY_PROP(full_design_prop, &full))
119                         return -1;
120         }
121
122         if (_MPSY_PROP(avg_prop, &cur)) {
123                 /* if battery can't report average value, use momentary */
124                 if (_MPSY_PROP(now_prop, &cur))
125                         return -1;
126         }
127
128         if (_MPSY_PROP(empty_prop, &empty)) {
129                 /* if battery can't report this property, use design value */
130                 if (_MPSY_PROP(empty_design_prop, &empty))
131                         empty.intval = 0;
132         }
133
134         if (full.intval - empty.intval)
135                 ret =  ((cur.intval - empty.intval) * 100L) /
136                        (full.intval - empty.intval);
137         else
138                 return -1;
139
140         if (ret > 100)
141                 return 100;
142         else if (ret < 0)
143                 return 0;
144
145         return ret;
146 }
147
148 static void apm_battery_apm_get_power_status(struct apm_power_info *info)
149 {
150         union power_supply_propval status;
151         union power_supply_propval capacity, time_to_full, time_to_empty;
152
153         down(&power_supply_class->sem);
154         find_main_battery();
155         if (!main_battery) {
156                 up(&power_supply_class->sem);
157                 return;
158         }
159
160         /* status */
161
162         if (MPSY_PROP(STATUS, &status))
163                 status.intval = POWER_SUPPLY_STATUS_UNKNOWN;
164
165         /* ac line status */
166
167         if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) ||
168             (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) ||
169             (status.intval == POWER_SUPPLY_STATUS_FULL))
170                 info->ac_line_status = APM_AC_ONLINE;
171         else
172                 info->ac_line_status = APM_AC_OFFLINE;
173
174         /* battery life (i.e. capacity, in percents) */
175
176         if (MPSY_PROP(CAPACITY, &capacity) == 0) {
177                 info->battery_life = capacity.intval;
178         } else {
179                 /* try calculate using energy */
180                 info->battery_life = calculate_capacity(0);
181                 /* if failed try calculate using charge instead */
182                 if (info->battery_life == -1)
183                         info->battery_life = calculate_capacity(1);
184         }
185
186         /* charging status */
187
188         if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
189                 info->battery_status = APM_BATTERY_STATUS_CHARGING;
190         } else {
191                 if (info->battery_life > 50)
192                         info->battery_status = APM_BATTERY_STATUS_HIGH;
193                 else if (info->battery_life > 5)
194                         info->battery_status = APM_BATTERY_STATUS_LOW;
195                 else
196                         info->battery_status = APM_BATTERY_STATUS_CRITICAL;
197         }
198         info->battery_flag = info->battery_status;
199
200         /* time */
201
202         info->units = APM_UNITS_MINS;
203
204         if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
205                 if (MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full)) {
206                         if (MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full))
207                                 info->time = calculate_time(status.intval);
208                         else
209                                 info->time = time_to_full.intval / 60;
210                 }
211         } else {
212                 if (MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty)) {
213                         if (MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty))
214                                 info->time = calculate_time(status.intval);
215                         else
216                                 info->time = time_to_empty.intval / 60;
217                 }
218         }
219
220         up(&power_supply_class->sem);
221         return;
222 }
223
224 static int __init apm_battery_init(void)
225 {
226         printk(KERN_INFO "APM Battery Driver\n");
227
228         apm_get_power_status = apm_battery_apm_get_power_status;
229         return 0;
230 }
231
232 static void __exit apm_battery_exit(void)
233 {
234         apm_get_power_status = NULL;
235         return;
236 }
237
238 module_init(apm_battery_init);
239 module_exit(apm_battery_exit);
240
241 MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
242 MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
243 MODULE_LICENSE("GPL");