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