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