2007-05-04 00:43:24 +04:00
/*
* 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>
2012-01-22 20:23:42 +04:00
# include <linux/device.h>
2007-05-04 00:43:24 +04:00
# include <linux/power_supply.h>
# include <linux/apm-emulation.h>
2008-01-13 02:39:16 +03:00
2011-05-29 21:53:12 +04:00
# define PSY_PROP(psy, prop, val) (psy->get_property(psy, \
POWER_SUPPLY_PROP_ # # prop , val ) )
2007-05-04 00:43:24 +04:00
2011-05-29 21:53:12 +04:00
# define _MPSY_PROP(prop, val) (main_battery->get_property(main_battery, \
prop , val ) )
2007-05-04 00:43:24 +04:00
# define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val)
2008-01-13 02:39:16 +03:00
static DEFINE_MUTEX ( apm_mutex ) ;
2007-05-04 00:43:24 +04:00
static struct power_supply * main_battery ;
2008-01-13 02:39:16 +03:00
enum apm_source {
SOURCE_ENERGY ,
SOURCE_CHARGE ,
SOURCE_VOLTAGE ,
} ;
2008-01-22 08:58:22 +03:00
struct find_bat_param {
struct power_supply * main ;
struct power_supply * bat ;
struct power_supply * max_charge_bat ;
struct power_supply * max_energy_bat ;
2007-05-04 00:43:24 +04:00
union power_supply_propval full ;
2008-01-22 08:58:22 +03:00
int max_charge ;
int max_energy ;
} ;
2007-05-04 00:43:24 +04:00
2008-01-22 08:58:22 +03:00
static int __find_main_battery ( struct device * dev , void * data )
{
struct find_bat_param * bp = ( struct find_bat_param * ) data ;
2007-10-05 01:05:00 +04:00
2008-01-22 08:58:22 +03:00
bp - > bat = dev_get_drvdata ( dev ) ;
2007-10-05 01:05:00 +04:00
2008-01-22 08:58:22 +03:00
if ( bp - > bat - > use_for_apm ) {
/* nice, we explicitly asked to report this battery. */
bp - > main = bp - > bat ;
return 1 ;
}
2007-10-05 01:05:00 +04:00
2008-01-22 08:58:22 +03:00
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 ;
2007-05-04 00:43:24 +04:00
}
2007-10-05 01:05:00 +04:00
}
2008-01-22 08:58:22 +03:00
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 ;
2008-05-23 01:21:08 +04:00
error = class_for_each_device ( power_supply_class , NULL , & bp ,
2008-01-22 08:58:22 +03:00
__find_main_battery ) ;
if ( error ) {
main_battery = bp . main ;
return ;
}
2007-05-04 00:43:24 +04:00
2008-01-22 08:58:22 +03:00
if ( ( bp . max_energy_bat & & bp . max_charge_bat ) & &
( bp . max_energy_bat ! = bp . max_charge_bat ) ) {
2007-10-05 01:05:00 +04:00
/* try guess battery with more capacity */
2008-01-22 08:58:22 +03:00
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 ;
2007-10-05 01:05:00 +04:00
else
2008-01-22 08:58:22 +03:00
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 ;
2007-10-05 01:05:00 +04:00
else
2008-01-22 08:58:22 +03:00
main_battery = bp . max_energy_bat ;
2007-10-05 01:05:00 +04:00
} else {
/* give up, choice any */
2008-01-22 08:58:22 +03:00
main_battery = bp . max_energy_bat ;
2007-10-05 01:05:00 +04:00
}
2008-01-22 08:58:22 +03:00
} else if ( bp . max_charge_bat ) {
main_battery = bp . max_charge_bat ;
} else if ( bp . max_energy_bat ) {
main_battery = bp . max_energy_bat ;
2007-10-05 01:05:00 +04:00
} else {
/* give up, try the last if any */
2008-01-22 08:58:22 +03:00
main_battery = bp . bat ;
2007-05-04 00:43:24 +04:00
}
}
2008-01-13 02:39:16 +03:00
static int do_calculate_time ( int status , enum apm_source source )
2007-05-04 00:43:24 +04:00
{
2007-10-05 01:05:00 +04:00
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 ;
2007-05-04 00:43:24 +04:00
2007-10-05 01:05:00 +04:00
if ( MPSY_PROP ( CURRENT_AVG , & I ) ) {
/* if battery can't report average value, use momentary */
if ( MPSY_PROP ( CURRENT_NOW , & I ) )
2007-05-04 00:43:24 +04:00
return - 1 ;
}
2008-01-13 02:44:54 +03:00
if ( ! I . intval )
return 0 ;
2008-01-13 02:39:16 +03:00
switch ( source ) {
case SOURCE_CHARGE :
2007-10-05 01:05:00 +04:00
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 ;
2008-01-13 02:39:16 +03:00
break ;
case SOURCE_ENERGY :
2007-10-05 01:05:00 +04:00
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 ;
2008-01-13 02:39:16 +03:00
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 ;
2007-05-04 00:43:24 +04:00
}
2007-10-05 01:05:00 +04:00
if ( _MPSY_PROP ( full_prop , & full ) ) {
/* if battery can't report this property, use design value */
if ( _MPSY_PROP ( full_design_prop , & full ) )
2007-05-04 00:43:24 +04:00
return - 1 ;
}
2007-10-05 01:05:00 +04:00
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 ) ) {
2007-05-04 00:43:24 +04:00
/* if battery can't report average value, use momentary */
2007-10-05 01:05:00 +04:00
if ( _MPSY_PROP ( cur_now_prop , & cur ) )
2007-05-04 00:43:24 +04:00
return - 1 ;
}
if ( status = = POWER_SUPPLY_STATUS_CHARGING )
2007-10-05 01:05:00 +04:00
return ( ( cur . intval - full . intval ) * 60L ) / I . intval ;
2007-05-04 00:43:24 +04:00
else
2007-10-05 01:05:00 +04:00
return - ( ( cur . intval - empty . intval ) * 60L ) / I . intval ;
2007-05-04 00:43:24 +04:00
}
2008-01-13 02:39:16 +03:00
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 )
2007-05-04 00:43:24 +04:00
{
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 ;
2008-01-13 02:39:16 +03:00
switch ( source ) {
case SOURCE_CHARGE :
2007-05-04 00:43:24 +04:00
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 ;
2008-01-13 02:39:16 +03:00
break ;
case SOURCE_ENERGY :
2007-05-04 00:43:24 +04:00
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 ;
2010-09-08 00:10:26 +04:00
break ;
2008-01-13 02:39:16 +03:00
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 ;
2007-05-04 00:43:24 +04:00
}
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 ;
2008-01-22 08:58:22 +03:00
mutex_lock ( & apm_mutex ) ;
2007-05-04 00:43:24 +04:00
find_main_battery ( ) ;
if ( ! main_battery ) {
2008-01-22 08:58:22 +03:00
mutex_unlock ( & apm_mutex ) ;
2007-05-04 00:43:24 +04:00
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 */
2008-01-13 02:39:16 +03:00
info - > battery_life = calculate_capacity ( SOURCE_ENERGY ) ;
2007-05-04 00:43:24 +04:00
/* if failed try calculate using charge instead */
if ( info - > battery_life = = - 1 )
2008-01-13 02:39:16 +03:00
info - > battery_life = calculate_capacity ( SOURCE_CHARGE ) ;
if ( info - > battery_life = = - 1 )
info - > battery_life = calculate_capacity ( SOURCE_VOLTAGE ) ;
2007-05-04 00:43:24 +04:00
}
/* 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 ) {
2007-10-05 01:04:59 +04:00
if ( ! MPSY_PROP ( TIME_TO_FULL_AVG , & time_to_full ) | |
2008-01-13 02:39:16 +03:00
! MPSY_PROP ( TIME_TO_FULL_NOW , & time_to_full ) )
2007-10-05 01:04:59 +04:00
info - > time = time_to_full . intval / 60 ;
2008-01-13 02:39:16 +03:00
else
info - > time = calculate_time ( status . intval ) ;
2007-05-04 00:43:24 +04:00
} else {
2007-10-05 01:04:59 +04:00
if ( ! MPSY_PROP ( TIME_TO_EMPTY_AVG , & time_to_empty ) | |
2008-01-13 02:39:16 +03:00
! MPSY_PROP ( TIME_TO_EMPTY_NOW , & time_to_empty ) )
2007-10-05 01:04:59 +04:00
info - > time = time_to_empty . intval / 60 ;
2008-01-13 02:39:16 +03:00
else
info - > time = calculate_time ( status . intval ) ;
2007-05-04 00:43:24 +04:00
}
2008-01-22 08:58:22 +03:00
mutex_unlock ( & apm_mutex ) ;
2007-05-04 00:43:24 +04:00
}
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 " ) ;