blob: bbf3ee10da04ad433027963d9831379827cb715f [file] [log] [blame]
Anton Vorontsov3788ec92007-05-04 00:43:24 +04001/*
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
24static struct power_supply *main_battery;
25
26static void find_main_battery(void)
27{
28 struct device *dev;
Anton Vorontsovd3853762007-10-05 01:05:00 +040029 struct power_supply *bat = NULL;
30 struct power_supply *max_charge_bat = NULL;
31 struct power_supply *max_energy_bat = NULL;
Anton Vorontsov3788ec92007-05-04 00:43:24 +040032 union power_supply_propval full;
33 int max_charge = 0;
Anton Vorontsovd3853762007-10-05 01:05:00 +040034 int max_energy = 0;
Anton Vorontsov3788ec92007-05-04 00:43:24 +040035
36 main_battery = NULL;
Anton Vorontsovd3853762007-10-05 01:05:00 +040037
Anton Vorontsov3788ec92007-05-04 00:43:24 +040038 list_for_each_entry(dev, &power_supply_class->devices, node) {
39 bat = dev_get_drvdata(dev);
Anton Vorontsovd3853762007-10-05 01:05:00 +040040
41 if (bat->use_for_apm) {
42 /* nice, we explicitly asked to report this battery. */
43 main_battery = bat;
44 return;
Anton Vorontsov3788ec92007-05-04 00:43:24 +040045 }
46
Anton Vorontsovd3853762007-10-05 01:05:00 +040047 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 }
Anton Vorontsov3788ec92007-05-04 00:43:24 +040060 }
Anton Vorontsovd3853762007-10-05 01:05:00 +040061
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 }
Anton Vorontsov3788ec92007-05-04 00:43:24 +040088}
89
Anton Vorontsov2a721df2007-10-05 01:05:00 +040090static int calculate_time(int status, int using_charge)
Anton Vorontsov3788ec92007-05-04 00:43:24 +040091{
Anton Vorontsov2a721df2007-10-05 01:05:00 +040092 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;
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400102
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
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400109 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
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400143 if (status == POWER_SUPPLY_STATUS_CHARGING)
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400144 return ((cur.intval - full.intval) * 60L) / I.intval;
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400145 else
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400146 return -((cur.intval - empty.intval) * 60L) / I.intval;
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400147}
148
149static 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
205static 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) {
Anton Vorontsovcd1ebcc2007-10-05 01:04:59 +0400262 if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) ||
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400263 !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) {
Anton Vorontsovcd1ebcc2007-10-05 01:04:59 +0400264 info->time = time_to_full.intval / 60;
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400265 } else {
266 info->time = calculate_time(status.intval, 0);
267 if (info->time == -1)
268 info->time = calculate_time(status.intval, 1);
269 }
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400270 } else {
Anton Vorontsovcd1ebcc2007-10-05 01:04:59 +0400271 if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) ||
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400272 !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) {
Anton Vorontsovcd1ebcc2007-10-05 01:04:59 +0400273 info->time = time_to_empty.intval / 60;
Anton Vorontsov2a721df2007-10-05 01:05:00 +0400274 } else {
275 info->time = calculate_time(status.intval, 0);
276 if (info->time == -1)
277 info->time = calculate_time(status.intval, 1);
278 }
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400279 }
280
281 up(&power_supply_class->sem);
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400282}
283
284static 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
292static void __exit apm_battery_exit(void)
293{
294 apm_get_power_status = NULL;
Anton Vorontsov3788ec92007-05-04 00:43:24 +0400295}
296
297module_init(apm_battery_init);
298module_exit(apm_battery_exit);
299
300MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
301MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
302MODULE_LICENSE("GPL");