|  | /* | 
|  | * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> | 
|  | * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru> | 
|  | * | 
|  | * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru> | 
|  | * | 
|  | * Use consistent with the GNU GPL is permitted, | 
|  | * provided that this copyright notice is | 
|  | * preserved in its entirety in all copies and derived works. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/power_supply.h> | 
|  | #include <linux/apm-emulation.h> | 
|  |  | 
|  |  | 
|  | #define PSY_PROP(psy, prop, val) psy->get_property(psy, \ | 
|  | POWER_SUPPLY_PROP_##prop, val) | 
|  |  | 
|  | #define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \ | 
|  | prop, val) | 
|  |  | 
|  | #define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) | 
|  |  | 
|  | static DEFINE_MUTEX(apm_mutex); | 
|  | static struct power_supply *main_battery; | 
|  |  | 
|  | enum apm_source { | 
|  | SOURCE_ENERGY, | 
|  | SOURCE_CHARGE, | 
|  | SOURCE_VOLTAGE, | 
|  | }; | 
|  |  | 
|  | struct find_bat_param { | 
|  | struct power_supply *main; | 
|  | struct power_supply *bat; | 
|  | struct power_supply *max_charge_bat; | 
|  | struct power_supply *max_energy_bat; | 
|  | union power_supply_propval full; | 
|  | int max_charge; | 
|  | int max_energy; | 
|  | }; | 
|  |  | 
|  | static int __find_main_battery(struct device *dev, void *data) | 
|  | { | 
|  | struct find_bat_param *bp = (struct find_bat_param *)data; | 
|  |  | 
|  | bp->bat = dev_get_drvdata(dev); | 
|  |  | 
|  | if (bp->bat->use_for_apm) { | 
|  | /* nice, we explicitly asked to report this battery. */ | 
|  | bp->main = bp->bat; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) || | 
|  | !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) { | 
|  | if (bp->full.intval > bp->max_charge) { | 
|  | bp->max_charge_bat = bp->bat; | 
|  | bp->max_charge = bp->full.intval; | 
|  | } | 
|  | } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) || | 
|  | !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) { | 
|  | if (bp->full.intval > bp->max_energy) { | 
|  | bp->max_energy_bat = bp->bat; | 
|  | bp->max_energy = bp->full.intval; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void find_main_battery(void) | 
|  | { | 
|  | struct find_bat_param bp; | 
|  | int error; | 
|  |  | 
|  | memset(&bp, 0, sizeof(struct find_bat_param)); | 
|  | main_battery = NULL; | 
|  | bp.main = main_battery; | 
|  |  | 
|  | error = class_for_each_device(power_supply_class, NULL, &bp, | 
|  | __find_main_battery); | 
|  | if (error) { | 
|  | main_battery = bp.main; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if ((bp.max_energy_bat && bp.max_charge_bat) && | 
|  | (bp.max_energy_bat != bp.max_charge_bat)) { | 
|  | /* try guess battery with more capacity */ | 
|  | if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN, | 
|  | &bp.full)) { | 
|  | if (bp.max_energy > bp.max_charge * bp.full.intval) | 
|  | main_battery = bp.max_energy_bat; | 
|  | else | 
|  | main_battery = bp.max_charge_bat; | 
|  | } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN, | 
|  | &bp.full)) { | 
|  | if (bp.max_charge > bp.max_energy / bp.full.intval) | 
|  | main_battery = bp.max_charge_bat; | 
|  | else | 
|  | main_battery = bp.max_energy_bat; | 
|  | } else { | 
|  | /* give up, choice any */ | 
|  | main_battery = bp.max_energy_bat; | 
|  | } | 
|  | } else if (bp.max_charge_bat) { | 
|  | main_battery = bp.max_charge_bat; | 
|  | } else if (bp.max_energy_bat) { | 
|  | main_battery = bp.max_energy_bat; | 
|  | } else { | 
|  | /* give up, try the last if any */ | 
|  | main_battery = bp.bat; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int do_calculate_time(int status, enum apm_source source) | 
|  | { | 
|  | union power_supply_propval full; | 
|  | union power_supply_propval empty; | 
|  | union power_supply_propval cur; | 
|  | union power_supply_propval I; | 
|  | enum power_supply_property full_prop; | 
|  | enum power_supply_property full_design_prop; | 
|  | enum power_supply_property empty_prop; | 
|  | enum power_supply_property empty_design_prop; | 
|  | enum power_supply_property cur_avg_prop; | 
|  | enum power_supply_property cur_now_prop; | 
|  |  | 
|  | if (MPSY_PROP(CURRENT_AVG, &I)) { | 
|  | /* if battery can't report average value, use momentary */ | 
|  | if (MPSY_PROP(CURRENT_NOW, &I)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (!I.intval) | 
|  | return 0; | 
|  |  | 
|  | switch (source) { | 
|  | case SOURCE_CHARGE: | 
|  | full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; | 
|  | full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; | 
|  | empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | 
|  | cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; | 
|  | cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; | 
|  | break; | 
|  | case SOURCE_ENERGY: | 
|  | full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; | 
|  | full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; | 
|  | empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | 
|  | cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; | 
|  | cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; | 
|  | break; | 
|  | case SOURCE_VOLTAGE: | 
|  | full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; | 
|  | full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; | 
|  | empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; | 
|  | cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; | 
|  | cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; | 
|  | break; | 
|  | default: | 
|  | printk(KERN_ERR "Unsupported source: %d\n", source); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(full_prop, &full)) { | 
|  | /* if battery can't report this property, use design value */ | 
|  | if (_MPSY_PROP(full_design_prop, &full)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(empty_prop, &empty)) { | 
|  | /* if battery can't report this property, use design value */ | 
|  | if (_MPSY_PROP(empty_design_prop, &empty)) | 
|  | empty.intval = 0; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(cur_avg_prop, &cur)) { | 
|  | /* if battery can't report average value, use momentary */ | 
|  | if (_MPSY_PROP(cur_now_prop, &cur)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (status == POWER_SUPPLY_STATUS_CHARGING) | 
|  | return ((cur.intval - full.intval) * 60L) / I.intval; | 
|  | else | 
|  | return -((cur.intval - empty.intval) * 60L) / I.intval; | 
|  | } | 
|  |  | 
|  | static int calculate_time(int status) | 
|  | { | 
|  | int time; | 
|  |  | 
|  | time = do_calculate_time(status, SOURCE_ENERGY); | 
|  | if (time != -1) | 
|  | return time; | 
|  |  | 
|  | time = do_calculate_time(status, SOURCE_CHARGE); | 
|  | if (time != -1) | 
|  | return time; | 
|  |  | 
|  | time = do_calculate_time(status, SOURCE_VOLTAGE); | 
|  | if (time != -1) | 
|  | return time; | 
|  |  | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static int calculate_capacity(enum apm_source source) | 
|  | { | 
|  | enum power_supply_property full_prop, empty_prop; | 
|  | enum power_supply_property full_design_prop, empty_design_prop; | 
|  | enum power_supply_property now_prop, avg_prop; | 
|  | union power_supply_propval empty, full, cur; | 
|  | int ret; | 
|  |  | 
|  | switch (source) { | 
|  | case SOURCE_CHARGE: | 
|  | full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; | 
|  | empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; | 
|  | full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; | 
|  | now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; | 
|  | avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; | 
|  | break; | 
|  | case SOURCE_ENERGY: | 
|  | full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; | 
|  | empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; | 
|  | full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; | 
|  | now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; | 
|  | avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; | 
|  | break; | 
|  | case SOURCE_VOLTAGE: | 
|  | full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX; | 
|  | empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN; | 
|  | full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN; | 
|  | empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN; | 
|  | now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; | 
|  | avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG; | 
|  | break; | 
|  | default: | 
|  | printk(KERN_ERR "Unsupported source: %d\n", source); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(full_prop, &full)) { | 
|  | /* if battery can't report this property, use design value */ | 
|  | if (_MPSY_PROP(full_design_prop, &full)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(avg_prop, &cur)) { | 
|  | /* if battery can't report average value, use momentary */ | 
|  | if (_MPSY_PROP(now_prop, &cur)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (_MPSY_PROP(empty_prop, &empty)) { | 
|  | /* if battery can't report this property, use design value */ | 
|  | if (_MPSY_PROP(empty_design_prop, &empty)) | 
|  | empty.intval = 0; | 
|  | } | 
|  |  | 
|  | if (full.intval - empty.intval) | 
|  | ret =  ((cur.intval - empty.intval) * 100L) / | 
|  | (full.intval - empty.intval); | 
|  | else | 
|  | return -1; | 
|  |  | 
|  | if (ret > 100) | 
|  | return 100; | 
|  | else if (ret < 0) | 
|  | return 0; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void apm_battery_apm_get_power_status(struct apm_power_info *info) | 
|  | { | 
|  | union power_supply_propval status; | 
|  | union power_supply_propval capacity, time_to_full, time_to_empty; | 
|  |  | 
|  | mutex_lock(&apm_mutex); | 
|  | find_main_battery(); | 
|  | if (!main_battery) { | 
|  | mutex_unlock(&apm_mutex); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* status */ | 
|  |  | 
|  | if (MPSY_PROP(STATUS, &status)) | 
|  | status.intval = POWER_SUPPLY_STATUS_UNKNOWN; | 
|  |  | 
|  | /* ac line status */ | 
|  |  | 
|  | if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || | 
|  | (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || | 
|  | (status.intval == POWER_SUPPLY_STATUS_FULL)) | 
|  | info->ac_line_status = APM_AC_ONLINE; | 
|  | else | 
|  | info->ac_line_status = APM_AC_OFFLINE; | 
|  |  | 
|  | /* battery life (i.e. capacity, in percents) */ | 
|  |  | 
|  | if (MPSY_PROP(CAPACITY, &capacity) == 0) { | 
|  | info->battery_life = capacity.intval; | 
|  | } else { | 
|  | /* try calculate using energy */ | 
|  | info->battery_life = calculate_capacity(SOURCE_ENERGY); | 
|  | /* if failed try calculate using charge instead */ | 
|  | if (info->battery_life == -1) | 
|  | info->battery_life = calculate_capacity(SOURCE_CHARGE); | 
|  | if (info->battery_life == -1) | 
|  | info->battery_life = calculate_capacity(SOURCE_VOLTAGE); | 
|  | } | 
|  |  | 
|  | /* charging status */ | 
|  |  | 
|  | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | 
|  | info->battery_status = APM_BATTERY_STATUS_CHARGING; | 
|  | } else { | 
|  | if (info->battery_life > 50) | 
|  | info->battery_status = APM_BATTERY_STATUS_HIGH; | 
|  | else if (info->battery_life > 5) | 
|  | info->battery_status = APM_BATTERY_STATUS_LOW; | 
|  | else | 
|  | info->battery_status = APM_BATTERY_STATUS_CRITICAL; | 
|  | } | 
|  | info->battery_flag = info->battery_status; | 
|  |  | 
|  | /* time */ | 
|  |  | 
|  | info->units = APM_UNITS_MINS; | 
|  |  | 
|  | if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { | 
|  | if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) || | 
|  | !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) | 
|  | info->time = time_to_full.intval / 60; | 
|  | else | 
|  | info->time = calculate_time(status.intval); | 
|  | } else { | 
|  | if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) || | 
|  | !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) | 
|  | info->time = time_to_empty.intval / 60; | 
|  | else | 
|  | info->time = calculate_time(status.intval); | 
|  | } | 
|  |  | 
|  | mutex_unlock(&apm_mutex); | 
|  | } | 
|  |  | 
|  | static int __init apm_battery_init(void) | 
|  | { | 
|  | printk(KERN_INFO "APM Battery Driver\n"); | 
|  |  | 
|  | apm_get_power_status = apm_battery_apm_get_power_status; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit apm_battery_exit(void) | 
|  | { | 
|  | apm_get_power_status = NULL; | 
|  | } | 
|  |  | 
|  | module_init(apm_battery_init); | 
|  | module_exit(apm_battery_exit); | 
|  |  | 
|  | MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>"); | 
|  | MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); | 
|  | MODULE_LICENSE("GPL"); |