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/device.h> 14 #include <linux/power_supply.h> 15 #include <linux/apm-emulation.h> 16 17 18 #define PSY_PROP(psy, prop, val) (power_supply_get_property(psy, \ 19 POWER_SUPPLY_PROP_##prop, val)) 20 21 #define _MPSY_PROP(prop, val) (power_supply_get_property(main_battery, \ 22 prop, val)) 23 24 #define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) 25 26 static DEFINE_MUTEX(apm_mutex); 27 static struct power_supply *main_battery; 28 29 enum apm_source { 30 SOURCE_ENERGY, 31 SOURCE_CHARGE, 32 SOURCE_VOLTAGE, 33 }; 34 35 struct find_bat_param { 36 struct power_supply *main; 37 struct power_supply *bat; 38 struct power_supply *max_charge_bat; 39 struct power_supply *max_energy_bat; 40 union power_supply_propval full; 41 int max_charge; 42 int max_energy; 43 }; 44 45 static int __find_main_battery(struct device *dev, void *data) 46 { 47 struct find_bat_param *bp = (struct find_bat_param *)data; 48 49 bp->bat = dev_get_drvdata(dev); 50 51 if (bp->bat->desc->use_for_apm) { 52 /* nice, we explicitly asked to report this battery. */ 53 bp->main = bp->bat; 54 return 1; 55 } 56 57 if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || 58 !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { 59 if (bp->full.intval > bp->max_charge) { 60 bp->max_charge_bat = bp->bat; 61 bp->max_charge = bp->full.intval; 62 } 63 } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || 64 !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { 65 if (bp->full.intval > bp->max_energy) { 66 bp->max_energy_bat = bp->bat; 67 bp->max_energy = bp->full.intval; 68 } 69 } 70 return 0; 71 } 72 73 static void find_main_battery(void) 74 { 75 struct find_bat_param bp; 76 int error; 77 78 memset(&bp, 0, sizeof(struct find_bat_param)); 79 main_battery = NULL; 80 bp.main = main_battery; 81 82 error = class_for_each_device(power_supply_class, NULL, &bp, 83 __find_main_battery); 84 if (error) { 85 main_battery = bp.main; 86 return; 87 } 88 89 if ((bp.max_energy_bat && bp.max_charge_bat) && 90 (bp.max_energy_bat != bp.max_charge_bat)) { 91 /* try guess battery with more capacity */ 92 if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, 93 &bp.full)) { 94 if (bp.max_energy > bp.max_charge * bp.full.intval) 95 main_battery = bp.max_energy_bat; 96 else 97 main_battery = bp.max_charge_bat; 98 } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, 99 &bp.full)) { 100 if (bp.max_charge > bp.max_energy / bp.full.intval) 101 main_battery = bp.max_charge_bat; 102 else 103 main_battery = bp.max_energy_bat; 104 } else { 105 /* give up, choice any */ 106 main_battery = bp.max_energy_bat; 107 } 108 } else if (bp.max_charge_bat) { 109 main_battery = bp.max_charge_bat; 110 } else if (bp.max_energy_bat) { 111 main_battery = bp.max_energy_bat; 112 } else { 113 /* give up, try the last if any */ 114 main_battery = bp.bat; 115 } 116 } 117 118 static int do_calculate_time(int status, enum apm_source source) 119 { 120 union power_supply_propval full; 121 union power_supply_propval empty; 122 union power_supply_propval cur; 123 union power_supply_propval I; 124 enum power_supply_property full_prop; 125 enum power_supply_property full_design_prop; 126 enum power_supply_property empty_prop; 127 enum power_supply_property empty_design_prop; 128 enum power_supply_property cur_avg_prop; 129 enum power_supply_property cur_now_prop; 130 131 if (MPSY_PROP(CURRENT_AVG, &I)) { 132 /* if battery can't report average value, use momentary */ 133 if (MPSY_PROP(CURRENT_NOW, &I)) 134 return -1; 135 } 136 137 if (!I.intval) 138 return 0; 139 140 switch (source) { 141 case SOURCE_CHARGE: 142 full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; 143 full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; 144 empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; 145 empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; 146 cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; 147 cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; 148 break; 149 case SOURCE_ENERGY: 150 full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; 151 full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; 152 empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; 153 empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; 154 cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; 155 cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; 156 break; 157 case SOURCE_VOLTAGE: 158 full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; 159 full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; 160 empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; 161 empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; 162 cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; 163 cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; 164 break; 165 default: 166 printk(KERN_ERR "Unsupported source: %d\n", source); 167 return -1; 168 } 169 170 if (_MPSY_PROP(full_prop, &full)) { 171 /* if battery can't report this property, use design value */ 172 if (_MPSY_PROP(full_design_prop, &full)) 173 return -1; 174 } 175 176 if (_MPSY_PROP(empty_prop, &empty)) { 177 /* if battery can't report this property, use design value */ 178 if (_MPSY_PROP(empty_design_prop, &empty)) 179 empty.intval = 0; 180 } 181 182 if (_MPSY_PROP(cur_avg_prop, &cur)) { 183 /* if battery can't report average value, use momentary */ 184 if (_MPSY_PROP(cur_now_prop, &cur)) 185 return -1; 186 } 187 188 if (status == POWER_SUPPLY_STATUS_CHARGING) 189 return ((cur.intval - full.intval) * 60L) / I.intval; 190 else 191 return -((cur.intval - empty.intval) * 60L) / I.intval; 192 } 193 194 static int calculate_time(int status) 195 { 196 int time; 197 198 time = do_calculate_time(status, SOURCE_ENERGY); 199 if (time != -1) 200 return time; 201 202 time = do_calculate_time(status, SOURCE_CHARGE); 203 if (time != -1) 204 return time; 205 206 time = do_calculate_time(status, SOURCE_VOLTAGE); 207 if (time != -1) 208 return time; 209 210 return -1; 211 } 212 213 static int calculate_capacity(enum apm_source source) 214 { 215 enum power_supply_property full_prop, empty_prop; 216 enum power_supply_property full_design_prop, empty_design_prop; 217 enum power_supply_property now_prop, avg_prop; 218 union power_supply_propval empty, full, cur; 219 int ret; 220 221 switch (source) { 222 case SOURCE_CHARGE: 223 full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; 224 empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; 225 full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; 226 empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; 227 now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; 228 avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; 229 break; 230 case SOURCE_ENERGY: 231 full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; 232 empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; 233 full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; 234 empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; 235 now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; 236 avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; 237 break; 238 case SOURCE_VOLTAGE: 239 full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; 240 empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; 241 full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; 242 empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; 243 now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; 244 avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; 245 break; 246 default: 247 printk(KERN_ERR "Unsupported source: %d\n", source); 248 return -1; 249 } 250 251 if (_MPSY_PROP(full_prop, &full)) { 252 /* if battery can't report this property, use design value */ 253 if (_MPSY_PROP(full_design_prop, &full)) 254 return -1; 255 } 256 257 if (_MPSY_PROP(avg_prop, &cur)) { 258 /* if battery can't report average value, use momentary */ 259 if (_MPSY_PROP(now_prop, &cur)) 260 return -1; 261 } 262 263 if (_MPSY_PROP(empty_prop, &empty)) { 264 /* if battery can't report this property, use design value */ 265 if (_MPSY_PROP(empty_design_prop, &empty)) 266 empty.intval = 0; 267 } 268 269 if (full.intval - empty.intval) 270 ret = ((cur.intval - empty.intval) * 100L) / 271 (full.intval - empty.intval); 272 else 273 return -1; 274 275 if (ret > 100) 276 return 100; 277 else if (ret < 0) 278 return 0; 279 280 return ret; 281 } 282 283 static void apm_battery_apm_get_power_status(struct apm_power_info *info) 284 { 285 union power_supply_propval status; 286 union power_supply_propval capacity, time_to_full, time_to_empty; 287 288 mutex_lock(&apm_mutex); 289 find_main_battery(); 290 if (!main_battery) { 291 mutex_unlock(&apm_mutex); 292 return; 293 } 294 295 /* status */ 296 297 if (MPSY_PROP(STATUS, &status)) 298 status.intval = POWER_SUPPLY_STATUS_UNKNOWN; 299 300 /* ac line status */ 301 302 if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || 303 (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || 304 (status.intval == POWER_SUPPLY_STATUS_FULL)) 305 info->ac_line_status = APM_AC_ONLINE; 306 else 307 info->ac_line_status = APM_AC_OFFLINE; 308 309 /* battery life (i.e. capacity, in percents) */ 310 311 if (MPSY_PROP(CAPACITY, &capacity) == 0) { 312 info->battery_life = capacity.intval; 313 } else { 314 /* try calculate using energy */ 315 info->battery_life = calculate_capacity(SOURCE_ENERGY); 316 /* if failed try calculate using charge instead */ 317 if (info->battery_life == -1) 318 info->battery_life = calculate_capacity(SOURCE_CHARGE); 319 if (info->battery_life == -1) 320 info->battery_life = calculate_capacity(SOURCE_VOLTAGE); 321 } 322 323 /* charging status */ 324 325 if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { 326 info->battery_status = APM_BATTERY_STATUS_CHARGING; 327 } else { 328 if (info->battery_life > 50) 329 info->battery_status = APM_BATTERY_STATUS_HIGH; 330 else if (info->battery_life > 5) 331 info->battery_status = APM_BATTERY_STATUS_LOW; 332 else 333 info->battery_status = APM_BATTERY_STATUS_CRITICAL; 334 } 335 info->battery_flag = info->battery_status; 336 337 /* time */ 338 339 info->units = APM_UNITS_MINS; 340 341 if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { 342 if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || 343 !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) 344 info->time = time_to_full.intval / 60; 345 else 346 info->time = calculate_time(status.intval); 347 } else { 348 if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || 349 !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) 350 info->time = time_to_empty.intval / 60; 351 else 352 info->time = calculate_time(status.intval); 353 } 354 355 mutex_unlock(&apm_mutex); 356 } 357 358 static int __init apm_battery_init(void) 359 { 360 printk(KERN_INFO "APM Battery Driver\n"); 361 362 apm_get_power_status = apm_battery_apm_get_power_status; 363 return 0; 364 } 365 366 static void __exit apm_battery_exit(void) 367 { 368 apm_get_power_status = NULL; 369 } 370 371 module_init(apm_battery_init); 372 module_exit(apm_battery_exit); 373 374 MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>"); 375 MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); 376 MODULE_LICENSE("GPL"); 377