linux/drivers/power/supply/apm_power.c
<<
>>
Prefs
   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
  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 = 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
 118static 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
 194static 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
 213static 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
 283static 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
 358static 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
 366static void __exit apm_battery_exit(void)
 367{
 368        apm_get_power_status = NULL;
 369}
 370
 371module_init(apm_battery_init);
 372module_exit(apm_battery_exit);
 373
 374MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
 375MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
 376MODULE_LICENSE("GPL");
 377