2011-12-27 13:47:48 +04:00
/*
* Copyright ( C ) 2011 Samsung Electronics Co . , Ltd .
* MyungJoo Ham < myungjoo . ham @ samsung . com >
*
* This driver enables to monitor battery health and control charger
* during suspend - to - mem .
* Charger manager depends on other devices . register this later than
* the depending devices .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
* */
2013-06-07 05:25:12 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2011-12-27 13:47:48 +04:00
# include <linux/io.h>
# include <linux/module.h>
# include <linux/irq.h>
# include <linux/interrupt.h>
# include <linux/rtc.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <linux/platform_device.h>
# include <linux/power/charger-manager.h>
# include <linux/regulator/consumer.h>
2012-09-21 13:49:37 +04:00
# include <linux/sysfs.h>
2013-12-18 10:42:35 +04:00
# include <linux/of.h>
2013-12-18 10:42:34 +04:00
# include <linux/thermal.h>
/*
* Default termperature threshold for charging .
* Every temperature units are in tenth of centigrade .
*/
# define CM_DEFAULT_RECHARGE_TEMP_DIFF 50
# define CM_DEFAULT_CHARGE_TEMP_MAX 500
2011-12-27 13:47:48 +04:00
2012-05-05 17:26:47 +04:00
static const char * const default_event_names [ ] = {
[ CM_EVENT_UNKNOWN ] = " Unknown " ,
[ CM_EVENT_BATT_FULL ] = " Battery Full " ,
[ CM_EVENT_BATT_IN ] = " Battery Inserted " ,
[ CM_EVENT_BATT_OUT ] = " Battery Pulled Out " ,
2013-12-18 10:42:34 +04:00
[ CM_EVENT_BATT_OVERHEAT ] = " Battery Overheat " ,
[ CM_EVENT_BATT_COLD ] = " Battery Cold " ,
2012-05-05 17:26:47 +04:00
[ CM_EVENT_EXT_PWR_IN_OUT ] = " External Power Attach/Detach " ,
[ CM_EVENT_CHG_START_STOP ] = " Charging Start/Stop " ,
[ CM_EVENT_OTHERS ] = " Other battery events "
} ;
2011-12-27 13:47:48 +04:00
/*
* Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
* without any delays .
*/
# define CM_JIFFIES_SMALL (2)
/* If y is valid (> 0) and smaller than x, do x = y */
# define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x))
/*
* Regard CM_RTC_SMALL ( sec ) is small enough to ignore error in invoking
* rtc alarm . It should be 2 or larger
*/
# define CM_RTC_SMALL (2)
# define UEVENT_BUF_SIZE 32
static LIST_HEAD ( cm_list ) ;
static DEFINE_MUTEX ( cm_list_mtx ) ;
/* About in-suspend (suspend-again) monitoring */
static struct rtc_device * rtc_dev ;
/*
* Backup RTC alarm
* Save the wakeup alarm before entering suspend - to - RAM
*/
static struct rtc_wkalrm rtc_wkalarm_save ;
/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */
static unsigned long rtc_wkalarm_save_time ;
static bool cm_suspended ;
static bool cm_rtc_set ;
static unsigned long cm_suspend_duration_ms ;
2012-05-05 17:24:10 +04:00
/* About normal (not suspended) monitoring */
static unsigned long polling_jiffy = ULONG_MAX ; /* ULONG_MAX: no polling */
static unsigned long next_polling ; /* Next appointed polling time */
static struct workqueue_struct * cm_wq ; /* init at driver add */
static struct delayed_work cm_monitor_work ; /* init at driver add */
2011-12-27 13:47:48 +04:00
/* Global charger-manager description */
static struct charger_global_desc * g_desc ; /* init with setup_charger_manager */
/**
* is_batt_present - See if the battery presents in place .
* @ cm : the Charger Manager representing the battery .
*/
static bool is_batt_present ( struct charger_manager * cm )
{
union power_supply_propval val ;
bool present = false ;
int i , ret ;
switch ( cm - > desc - > battery_present ) {
2012-05-05 17:24:10 +04:00
case CM_BATTERY_PRESENT :
present = true ;
break ;
case CM_NO_BATTERY :
break ;
2011-12-27 13:47:48 +04:00
case CM_FUEL_GAUGE :
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_PRESENT , & val ) ;
if ( ret = = 0 & & val . intval )
present = true ;
break ;
case CM_CHARGER_STAT :
for ( i = 0 ; cm - > charger_stat [ i ] ; i + + ) {
ret = cm - > charger_stat [ i ] - > get_property (
cm - > charger_stat [ i ] ,
POWER_SUPPLY_PROP_PRESENT , & val ) ;
if ( ret = = 0 & & val . intval ) {
present = true ;
break ;
}
}
break ;
}
return present ;
}
/**
* is_ext_pwr_online - See if an external power source is attached to charge
* @ cm : the Charger Manager representing the battery .
*
* Returns true if at least one of the chargers of the battery has an external
* power source attached to charge the battery regardless of whether it is
* actually charging or not .
*/
static bool is_ext_pwr_online ( struct charger_manager * cm )
{
union power_supply_propval val ;
bool online = false ;
int i , ret ;
/* If at least one of them has one, it's yes. */
for ( i = 0 ; cm - > charger_stat [ i ] ; i + + ) {
ret = cm - > charger_stat [ i ] - > get_property (
cm - > charger_stat [ i ] ,
POWER_SUPPLY_PROP_ONLINE , & val ) ;
if ( ret = = 0 & & val . intval ) {
online = true ;
break ;
}
}
return online ;
}
2011-12-27 13:47:49 +04:00
/**
* get_batt_uV - Get the voltage level of the battery
* @ cm : the Charger Manager representing the battery .
* @ uV : the voltage level returned .
*
* Returns 0 if there is no error .
* Returns a negative value on error .
*/
static int get_batt_uV ( struct charger_manager * cm , int * uV )
{
union power_supply_propval val ;
int ret ;
2012-01-12 08:56:35 +04:00
if ( ! cm - > fuel_gauge )
2011-12-27 13:47:49 +04:00
return - ENODEV ;
2012-01-12 08:56:35 +04:00
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_VOLTAGE_NOW , & val ) ;
2011-12-27 13:47:49 +04:00
if ( ret )
return ret ;
* uV = val . intval ;
return 0 ;
}
2011-12-27 13:47:48 +04:00
/**
* is_charging - Returns true if the battery is being charged .
* @ cm : the Charger Manager representing the battery .
*/
static bool is_charging ( struct charger_manager * cm )
{
int i , ret ;
bool charging = false ;
union power_supply_propval val ;
/* If there is no battery, it cannot be charged */
if ( ! is_batt_present ( cm ) )
return false ;
/* If at least one of the charger is charging, return yes */
for ( i = 0 ; cm - > charger_stat [ i ] ; i + + ) {
/* 1. The charger sholuld not be DISABLED */
if ( cm - > emergency_stop )
continue ;
if ( ! cm - > charger_enabled )
continue ;
/* 2. The charger should be online (ext-power) */
ret = cm - > charger_stat [ i ] - > get_property (
cm - > charger_stat [ i ] ,
POWER_SUPPLY_PROP_ONLINE , & val ) ;
if ( ret ) {
2013-06-07 05:25:12 +04:00
dev_warn ( cm - > dev , " Cannot read ONLINE value from %s \n " ,
cm - > desc - > psy_charger_stat [ i ] ) ;
2011-12-27 13:47:48 +04:00
continue ;
}
if ( val . intval = = 0 )
continue ;
/*
* 3. The charger should not be FULL , DISCHARGING ,
* or NOT_CHARGING .
*/
ret = cm - > charger_stat [ i ] - > get_property (
cm - > charger_stat [ i ] ,
POWER_SUPPLY_PROP_STATUS , & val ) ;
if ( ret ) {
2013-06-07 05:25:12 +04:00
dev_warn ( cm - > dev , " Cannot read STATUS value from %s \n " ,
cm - > desc - > psy_charger_stat [ i ] ) ;
2011-12-27 13:47:48 +04:00
continue ;
}
if ( val . intval = = POWER_SUPPLY_STATUS_FULL | |
val . intval = = POWER_SUPPLY_STATUS_DISCHARGING | |
val . intval = = POWER_SUPPLY_STATUS_NOT_CHARGING )
continue ;
/* Then, this is charging. */
charging = true ;
break ;
}
return charging ;
}
2012-08-21 12:06:52 +04:00
/**
* is_full_charged - Returns true if the battery is fully charged .
* @ cm : the Charger Manager representing the battery .
*/
static bool is_full_charged ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
union power_supply_propval val ;
int ret = 0 ;
int uV ;
/* If there is no battery, it cannot be charged */
2012-11-22 11:53:46 +04:00
if ( ! is_batt_present ( cm ) )
return false ;
2012-08-21 12:06:52 +04:00
if ( cm - > fuel_gauge & & desc - > fullbatt_full_capacity > 0 ) {
2012-11-22 11:53:46 +04:00
val . intval = 0 ;
2012-08-21 12:06:52 +04:00
/* Not full if capacity of fuel gauge isn't full */
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CHARGE_FULL , & val ) ;
2012-11-22 11:53:46 +04:00
if ( ! ret & & val . intval > desc - > fullbatt_full_capacity )
return true ;
2012-08-21 12:06:52 +04:00
}
/* Full, if it's over the fullbatt voltage */
if ( desc - > fullbatt_uV > 0 ) {
ret = get_batt_uV ( cm , & uV ) ;
2012-11-22 11:53:46 +04:00
if ( ! ret & & uV > = desc - > fullbatt_uV )
return true ;
2012-08-21 12:06:52 +04:00
}
/* Full, if the capacity is more than fullbatt_soc */
if ( cm - > fuel_gauge & & desc - > fullbatt_soc > 0 ) {
2012-11-22 11:53:46 +04:00
val . intval = 0 ;
2012-08-21 12:06:52 +04:00
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CAPACITY , & val ) ;
2012-11-22 11:53:46 +04:00
if ( ! ret & & val . intval > = desc - > fullbatt_soc )
return true ;
2012-08-21 12:06:52 +04:00
}
2012-11-22 11:53:46 +04:00
return false ;
2012-08-21 12:06:52 +04:00
}
2011-12-27 13:47:48 +04:00
/**
* is_polling_required - Return true if need to continue polling for this CM .
* @ cm : the Charger Manager representing the battery .
*/
static bool is_polling_required ( struct charger_manager * cm )
{
switch ( cm - > desc - > polling_mode ) {
case CM_POLL_DISABLE :
return false ;
case CM_POLL_ALWAYS :
return true ;
case CM_POLL_EXTERNAL_POWER_ONLY :
return is_ext_pwr_online ( cm ) ;
case CM_POLL_CHARGING_ONLY :
return is_charging ( cm ) ;
default :
dev_warn ( cm - > dev , " Incorrect polling_mode (%d) \n " ,
2013-06-07 05:25:12 +04:00
cm - > desc - > polling_mode ) ;
2011-12-27 13:47:48 +04:00
}
return false ;
}
/**
* try_charger_enable - Enable / Disable chargers altogether
* @ cm : the Charger Manager representing the battery .
* @ enable : true : enable / false : disable
*
* Note that Charger Manager keeps the charger enabled regardless whether
* the charger is charging or not ( because battery is full or no external
* power source exists ) except when CM needs to disable chargers forcibly
* bacause of emergency causes ; when the battery is overheated or too cold .
*/
static int try_charger_enable ( struct charger_manager * cm , bool enable )
{
int err = 0 , i ;
struct charger_desc * desc = cm - > desc ;
/* Ignore if it's redundent command */
2012-01-12 08:56:35 +04:00
if ( enable = = cm - > charger_enabled )
2011-12-27 13:47:48 +04:00
return 0 ;
if ( enable ) {
if ( cm - > emergency_stop )
return - EAGAIN ;
2012-09-21 08:20:05 +04:00
/*
* Save start time of charging to limit
* maximum possible charging time .
*/
cm - > charging_start_time = ktime_to_ms ( ktime_get ( ) ) ;
cm - > charging_end_time = 0 ;
2012-07-27 09:01:34 +04:00
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
2012-09-21 13:49:37 +04:00
if ( desc - > charger_regulators [ i ] . externally_control )
continue ;
2012-07-27 09:01:34 +04:00
err = regulator_enable ( desc - > charger_regulators [ i ] . consumer ) ;
if ( err < 0 ) {
2013-06-07 05:25:12 +04:00
dev_warn ( cm - > dev , " Cannot enable %s regulator \n " ,
desc - > charger_regulators [ i ] . regulator_name ) ;
2012-07-27 09:01:34 +04:00
}
}
2011-12-27 13:47:48 +04:00
} else {
2012-09-21 08:20:05 +04:00
/*
* Save end time of charging to maintain fully charged state
* of battery after full - batt .
*/
cm - > charging_start_time = 0 ;
cm - > charging_end_time = ktime_to_ms ( ktime_get ( ) ) ;
2012-07-27 09:01:34 +04:00
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
2012-09-21 13:49:37 +04:00
if ( desc - > charger_regulators [ i ] . externally_control )
continue ;
2012-07-27 09:01:34 +04:00
err = regulator_disable ( desc - > charger_regulators [ i ] . consumer ) ;
if ( err < 0 ) {
2013-06-07 05:25:12 +04:00
dev_warn ( cm - > dev , " Cannot disable %s regulator \n " ,
desc - > charger_regulators [ i ] . regulator_name ) ;
2012-07-27 09:01:34 +04:00
}
}
2011-12-27 13:47:48 +04:00
/*
* Abnormal battery state - Stop charging forcibly ,
* even if charger was enabled at the other places
*/
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
if ( regulator_is_enabled (
desc - > charger_regulators [ i ] . consumer ) ) {
regulator_force_disable (
desc - > charger_regulators [ i ] . consumer ) ;
2013-06-07 05:25:12 +04:00
dev_warn ( cm - > dev , " Disable regulator(%s) forcibly \n " ,
desc - > charger_regulators [ i ] . regulator_name ) ;
2011-12-27 13:47:48 +04:00
}
}
}
if ( ! err )
cm - > charger_enabled = enable ;
return err ;
}
2012-05-05 17:24:10 +04:00
/**
* try_charger_restart - Restart charging .
* @ cm : the Charger Manager representing the battery .
*
* Restart charging by turning off and on the charger .
*/
static int try_charger_restart ( struct charger_manager * cm )
{
int err ;
if ( cm - > emergency_stop )
return - EAGAIN ;
err = try_charger_enable ( cm , false ) ;
if ( err )
return err ;
return try_charger_enable ( cm , true ) ;
}
2011-12-27 13:47:48 +04:00
/**
* uevent_notify - Let users know something has changed .
* @ cm : the Charger Manager representing the battery .
* @ event : the event string .
*
* If @ event is null , it implies that uevent_notify is called
* by resume function . When called in the resume function , cm_suspended
* should be already reset to false in order to let uevent_notify
* notify the recent event during the suspend to users . While
* suspended , uevent_notify does not notify users , but tracks
* events so that uevent_notify can notify users later after resumed .
*/
static void uevent_notify ( struct charger_manager * cm , const char * event )
{
static char env_str [ UEVENT_BUF_SIZE + 1 ] = " " ;
static char env_str_save [ UEVENT_BUF_SIZE + 1 ] = " " ;
if ( cm_suspended ) {
/* Nothing in suspended-event buffer */
if ( env_str_save [ 0 ] = = 0 ) {
if ( ! strncmp ( env_str , event , UEVENT_BUF_SIZE ) )
return ; /* status not changed */
strncpy ( env_str_save , event , UEVENT_BUF_SIZE ) ;
return ;
}
if ( ! strncmp ( env_str_save , event , UEVENT_BUF_SIZE ) )
return ; /* Duplicated. */
2012-01-12 08:56:35 +04:00
strncpy ( env_str_save , event , UEVENT_BUF_SIZE ) ;
2011-12-27 13:47:48 +04:00
return ;
}
if ( event = = NULL ) {
/* No messages pending */
if ( ! env_str_save [ 0 ] )
return ;
strncpy ( env_str , env_str_save , UEVENT_BUF_SIZE ) ;
kobject_uevent ( & cm - > dev - > kobj , KOBJ_CHANGE ) ;
env_str_save [ 0 ] = 0 ;
return ;
}
/* status not changed */
if ( ! strncmp ( env_str , event , UEVENT_BUF_SIZE ) )
return ;
/* save the status and notify the update */
strncpy ( env_str , event , UEVENT_BUF_SIZE ) ;
kobject_uevent ( & cm - > dev - > kobj , KOBJ_CHANGE ) ;
2013-06-07 05:25:12 +04:00
dev_info ( cm - > dev , " %s \n " , event ) ;
2011-12-27 13:47:48 +04:00
}
2012-05-05 17:24:10 +04:00
/**
* fullbatt_vchk - Check voltage drop some times after " FULL " event .
* @ work : the work_struct appointing the function
*
* If a user has designated " fullbatt_vchkdrop_ms/uV " values with
* charger_desc , Charger Manager checks voltage drop after the battery
* " FULL " event . It checks whether the voltage has dropped more than
* fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms .
*/
static void fullbatt_vchk ( struct work_struct * work )
{
struct delayed_work * dwork = to_delayed_work ( work ) ;
struct charger_manager * cm = container_of ( dwork ,
struct charger_manager , fullbatt_vchk_work ) ;
struct charger_desc * desc = cm - > desc ;
int batt_uV , err , diff ;
/* remove the appointment for fullbatt_vchk */
cm - > fullbatt_vchk_jiffies_at = 0 ;
if ( ! desc - > fullbatt_vchkdrop_uV | | ! desc - > fullbatt_vchkdrop_ms )
return ;
err = get_batt_uV ( cm , & batt_uV ) ;
if ( err ) {
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " %s: get_batt_uV error(%d) \n " , __func__ , err ) ;
2012-05-05 17:24:10 +04:00
return ;
}
2012-11-22 11:53:51 +04:00
diff = desc - > fullbatt_uV - batt_uV ;
if ( diff < 0 )
return ;
2012-05-05 17:24:10 +04:00
2013-06-07 05:25:12 +04:00
dev_info ( cm - > dev , " VBATT dropped %duV after full-batt \n " , diff ) ;
2012-05-05 17:24:10 +04:00
if ( diff > desc - > fullbatt_vchkdrop_uV ) {
try_charger_restart ( cm ) ;
2012-09-21 08:20:05 +04:00
uevent_notify ( cm , " Recharging " ) ;
}
}
/**
* check_charging_duration - Monitor charging / discharging duration
* @ cm : the Charger Manager representing the battery .
*
* If whole charging duration exceed ' charging_max_duration_ms ' ,
* cm stop charging to prevent overcharge / overheat . If discharging
* duration exceed ' discharging _max_duration_ms ' , charger cable is
* attached , after full - batt , cm start charging to maintain fully
* charged state for battery .
*/
static int check_charging_duration ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
u64 curr = ktime_to_ms ( ktime_get ( ) ) ;
u64 duration ;
int ret = false ;
if ( ! desc - > charging_max_duration_ms & &
! desc - > discharging_max_duration_ms )
return ret ;
if ( cm - > charger_enabled ) {
duration = curr - cm - > charging_start_time ;
if ( duration > desc - > charging_max_duration_ms ) {
2013-12-18 10:42:35 +04:00
dev_info ( cm - > dev , " Charging duration exceed %ums \n " ,
2012-09-21 08:20:05 +04:00
desc - > charging_max_duration_ms ) ;
uevent_notify ( cm , " Discharging " ) ;
try_charger_enable ( cm , false ) ;
ret = true ;
}
} else if ( is_ext_pwr_online ( cm ) & & ! cm - > charger_enabled ) {
duration = curr - cm - > charging_end_time ;
if ( duration > desc - > charging_max_duration_ms & &
is_ext_pwr_online ( cm ) ) {
2013-12-18 10:42:35 +04:00
dev_info ( cm - > dev , " Discharging duration exceed %ums \n " ,
2012-09-21 08:20:05 +04:00
desc - > discharging_max_duration_ms ) ;
2013-06-07 05:25:12 +04:00
uevent_notify ( cm , " Recharging " ) ;
2012-09-21 08:20:05 +04:00
try_charger_enable ( cm , true ) ;
ret = true ;
}
2012-05-05 17:24:10 +04:00
}
2012-09-21 08:20:05 +04:00
return ret ;
2012-05-05 17:24:10 +04:00
}
2013-12-18 10:42:34 +04:00
static int cm_get_battery_temperature ( struct charger_manager * cm ,
int * temp )
{
int ret ;
if ( ! cm - > desc - > measure_battery_temp )
return - ENODEV ;
# ifdef CONFIG_THERMAL
ret = thermal_zone_get_temp ( cm - > tzd_batt , ( unsigned long * ) temp ) ;
if ( ! ret )
/* Calibrate temperature unit */
* temp / = 100 ;
# else
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_TEMP ,
( union power_supply_propval * ) temp ) ;
# endif
return ret ;
}
static int cm_check_thermal_status ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
int temp , upper_limit , lower_limit ;
int ret = 0 ;
ret = cm_get_battery_temperature ( cm , & temp ) ;
if ( ret ) {
/* FIXME:
* No information of battery temperature might
* occur hazadous result . We have to handle it
* depending on battery type .
*/
dev_err ( cm - > dev , " Failed to get battery temperature \n " ) ;
return 0 ;
}
upper_limit = desc - > temp_max ;
lower_limit = desc - > temp_min ;
if ( cm - > emergency_stop ) {
upper_limit - = desc - > temp_diff ;
lower_limit + = desc - > temp_diff ;
}
if ( temp > upper_limit )
ret = CM_EVENT_BATT_OVERHEAT ;
else if ( temp < lower_limit )
ret = CM_EVENT_BATT_COLD ;
return ret ;
}
2011-12-27 13:47:48 +04:00
/**
* _cm_monitor - Monitor the temperature and return true for exceptions .
* @ cm : the Charger Manager representing the battery .
*
* Returns true if there is an event to notify for the battery .
* ( True if the status of " emergency_stop " changes )
*/
static bool _cm_monitor ( struct charger_manager * cm )
{
2013-12-18 10:42:34 +04:00
int temp_alrt ;
2011-12-27 13:47:48 +04:00
2013-12-18 10:42:34 +04:00
temp_alrt = cm_check_thermal_status ( cm ) ;
2011-12-27 13:47:48 +04:00
2012-08-21 12:06:52 +04:00
/* It has been stopped already */
2013-12-18 10:42:34 +04:00
if ( temp_alrt & & cm - > emergency_stop )
2011-12-27 13:47:48 +04:00
return false ;
2012-08-21 12:06:52 +04:00
/*
* Check temperature whether overheat or cold .
* If temperature is out of range normal state , stop charging .
*/
2013-12-18 10:42:34 +04:00
if ( temp_alrt ) {
cm - > emergency_stop = temp_alrt ;
if ( ! try_charger_enable ( cm , false ) )
uevent_notify ( cm , default_event_names [ temp_alrt ] ) ;
2012-08-21 12:06:52 +04:00
2012-09-21 08:20:05 +04:00
/*
* Check whole charging duration and discharing duration
* after full - batt .
*/
} else if ( ! cm - > emergency_stop & & check_charging_duration ( cm ) ) {
dev_dbg ( cm - > dev ,
2013-06-07 05:25:12 +04:00
" Charging/Discharging duration is out of range \n " ) ;
2012-08-21 12:06:52 +04:00
/*
* Check dropped voltage of battery . If battery voltage is more
* dropped than fullbatt_vchkdrop_uV after fully charged state ,
* charger - manager have to recharge battery .
*/
} else if ( ! cm - > emergency_stop & & is_ext_pwr_online ( cm ) & &
! cm - > charger_enabled ) {
fullbatt_vchk ( & cm - > fullbatt_vchk_work . work ) ;
/*
* Check whether fully charged state to protect overcharge
* if charger - manager is charging for battery .
*/
} else if ( ! cm - > emergency_stop & & is_full_charged ( cm ) & &
cm - > charger_enabled ) {
2013-06-07 05:25:12 +04:00
dev_info ( cm - > dev , " EVENT_HANDLE: Battery Fully Charged \n " ) ;
2012-08-21 12:06:52 +04:00
uevent_notify ( cm , default_event_names [ CM_EVENT_BATT_FULL ] ) ;
try_charger_enable ( cm , false ) ;
fullbatt_vchk ( & cm - > fullbatt_vchk_work . work ) ;
2011-12-27 13:47:48 +04:00
} else {
cm - > emergency_stop = 0 ;
2012-08-21 12:06:52 +04:00
if ( is_ext_pwr_online ( cm ) ) {
if ( ! try_charger_enable ( cm , true ) )
uevent_notify ( cm , " CHARGING " ) ;
}
2011-12-27 13:47:48 +04:00
}
return true ;
}
/**
* cm_monitor - Monitor every battery .
*
* Returns true if there is an event to notify from any of the batteries .
* ( True if the status of " emergency_stop " changes )
*/
static bool cm_monitor ( void )
{
bool stop = false ;
struct charger_manager * cm ;
mutex_lock ( & cm_list_mtx ) ;
2012-01-12 08:56:35 +04:00
list_for_each_entry ( cm , & cm_list , entry ) {
if ( _cm_monitor ( cm ) )
stop = true ;
}
2011-12-27 13:47:48 +04:00
mutex_unlock ( & cm_list_mtx ) ;
return stop ;
}
2012-05-05 17:24:10 +04:00
/**
* _setup_polling - Setup the next instance of polling .
* @ work : work_struct of the function _setup_polling .
*/
static void _setup_polling ( struct work_struct * work )
{
unsigned long min = ULONG_MAX ;
struct charger_manager * cm ;
bool keep_polling = false ;
unsigned long _next_polling ;
mutex_lock ( & cm_list_mtx ) ;
list_for_each_entry ( cm , & cm_list , entry ) {
if ( is_polling_required ( cm ) & & cm - > desc - > polling_interval_ms ) {
keep_polling = true ;
if ( min > cm - > desc - > polling_interval_ms )
min = cm - > desc - > polling_interval_ms ;
}
}
polling_jiffy = msecs_to_jiffies ( min ) ;
if ( polling_jiffy < = CM_JIFFIES_SMALL )
polling_jiffy = CM_JIFFIES_SMALL + 1 ;
if ( ! keep_polling )
polling_jiffy = ULONG_MAX ;
if ( polling_jiffy = = ULONG_MAX )
goto out ;
WARN ( cm_wq = = NULL , " charger-manager: workqueue not initialized "
" . try it later. %s \n " , __func__ ) ;
2012-12-22 05:56:51 +04:00
/*
* Use mod_delayed_work ( ) iff the next polling interval should
* occur before the currently scheduled one . If @ cm_monitor_work
* isn ' t active , the end result is the same , so no need to worry
* about stale @ next_polling .
*/
2012-05-05 17:24:10 +04:00
_next_polling = jiffies + polling_jiffy ;
2012-12-22 05:56:51 +04:00
if ( time_before ( _next_polling , next_polling ) ) {
2012-08-03 21:30:47 +04:00
mod_delayed_work ( cm_wq , & cm_monitor_work , polling_jiffy ) ;
2012-12-22 05:56:51 +04:00
next_polling = _next_polling ;
} else {
if ( queue_delayed_work ( cm_wq , & cm_monitor_work , polling_jiffy ) )
next_polling = _next_polling ;
2012-05-05 17:24:10 +04:00
}
out :
mutex_unlock ( & cm_list_mtx ) ;
}
static DECLARE_WORK ( setup_polling , _setup_polling ) ;
/**
* cm_monitor_poller - The Monitor / Poller .
* @ work : work_struct of the function cm_monitor_poller
*
* During non - suspended state , cm_monitor_poller is used to poll and monitor
* the batteries .
*/
static void cm_monitor_poller ( struct work_struct * work )
{
cm_monitor ( ) ;
schedule_work ( & setup_polling ) ;
}
2012-05-05 17:26:47 +04:00
/**
* fullbatt_handler - Event handler for CM_EVENT_BATT_FULL
* @ cm : the Charger Manager representing the battery .
*/
static void fullbatt_handler ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
if ( ! desc - > fullbatt_vchkdrop_uV | | ! desc - > fullbatt_vchkdrop_ms )
goto out ;
if ( cm_suspended )
device_set_wakeup_capable ( cm - > dev , true ) ;
2012-08-03 21:30:47 +04:00
mod_delayed_work ( cm_wq , & cm - > fullbatt_vchk_work ,
msecs_to_jiffies ( desc - > fullbatt_vchkdrop_ms ) ) ;
2012-05-05 17:26:47 +04:00
cm - > fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies (
desc - > fullbatt_vchkdrop_ms ) ;
if ( cm - > fullbatt_vchk_jiffies_at = = 0 )
cm - > fullbatt_vchk_jiffies_at = 1 ;
out :
2013-06-07 05:25:12 +04:00
dev_info ( cm - > dev , " EVENT_HANDLE: Battery Fully Charged \n " ) ;
2012-05-05 17:26:47 +04:00
uevent_notify ( cm , default_event_names [ CM_EVENT_BATT_FULL ] ) ;
}
/**
* battout_handler - Event handler for CM_EVENT_BATT_OUT
* @ cm : the Charger Manager representing the battery .
*/
static void battout_handler ( struct charger_manager * cm )
{
if ( cm_suspended )
device_set_wakeup_capable ( cm - > dev , true ) ;
if ( ! is_batt_present ( cm ) ) {
dev_emerg ( cm - > dev , " Battery Pulled Out! \n " ) ;
uevent_notify ( cm , default_event_names [ CM_EVENT_BATT_OUT ] ) ;
} else {
uevent_notify ( cm , " Battery Reinserted? " ) ;
}
}
/**
* misc_event_handler - Handler for other evnets
* @ cm : the Charger Manager representing the battery .
* @ type : the Charger Manager representing the battery .
*/
static void misc_event_handler ( struct charger_manager * cm ,
enum cm_event_types type )
{
if ( cm_suspended )
device_set_wakeup_capable ( cm - > dev , true ) ;
2012-12-22 05:56:51 +04:00
if ( is_polling_required ( cm ) & & cm - > desc - > polling_interval_ms )
2012-05-05 17:26:47 +04:00
schedule_work ( & setup_polling ) ;
uevent_notify ( cm , default_event_names [ type ] ) ;
}
2011-12-27 13:47:49 +04:00
static int charger_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct charger_manager * cm = container_of ( psy ,
struct charger_manager , charger_psy ) ;
struct charger_desc * desc = cm - > desc ;
2012-03-15 21:01:28 +04:00
int ret = 0 ;
int uV ;
2011-12-27 13:47:49 +04:00
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
if ( is_charging ( cm ) )
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else if ( is_ext_pwr_online ( cm ) )
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
if ( cm - > emergency_stop > 0 )
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
else if ( cm - > emergency_stop < 0 )
val - > intval = POWER_SUPPLY_HEALTH_COLD ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
break ;
case POWER_SUPPLY_PROP_PRESENT :
if ( is_batt_present ( cm ) )
val - > intval = 1 ;
else
val - > intval = 0 ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
2012-03-15 21:01:28 +04:00
ret = get_batt_uV ( cm , & val - > intval ) ;
2011-12-27 13:47:49 +04:00
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CURRENT_NOW , val ) ;
break ;
case POWER_SUPPLY_PROP_TEMP :
case POWER_SUPPLY_PROP_TEMP_AMBIENT :
2013-12-18 10:42:34 +04:00
return cm_get_battery_temperature ( cm , & val - > intval ) ;
2011-12-27 13:47:49 +04:00
case POWER_SUPPLY_PROP_CAPACITY :
if ( ! cm - > fuel_gauge ) {
ret = - ENODEV ;
break ;
}
if ( ! is_batt_present ( cm ) ) {
/* There is no battery. Assume 100% */
val - > intval = 100 ;
break ;
}
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CAPACITY , val ) ;
if ( ret )
break ;
if ( val - > intval > 100 ) {
val - > intval = 100 ;
break ;
}
if ( val - > intval < 0 )
val - > intval = 0 ;
/* Do not adjust SOC when charging: voltage is overrated */
if ( is_charging ( cm ) )
break ;
/*
* If the capacity value is inconsistent , calibrate it base on
* the battery voltage values and the thresholds given as desc
*/
ret = get_batt_uV ( cm , & uV ) ;
if ( ret ) {
/* Voltage information not available. No calibration */
ret = 0 ;
break ;
}
if ( desc - > fullbatt_uV > 0 & & uV > = desc - > fullbatt_uV & &
! is_charging ( cm ) ) {
val - > intval = 100 ;
break ;
}
break ;
case POWER_SUPPLY_PROP_ONLINE :
if ( is_ext_pwr_online ( cm ) )
val - > intval = 1 ;
else
val - > intval = 0 ;
break ;
case POWER_SUPPLY_PROP_CHARGE_FULL :
2012-08-21 12:06:52 +04:00
if ( is_full_charged ( cm ) )
2011-12-27 13:47:49 +04:00
val - > intval = 1 ;
2012-08-21 12:06:52 +04:00
else
val - > intval = 0 ;
2011-12-27 13:47:49 +04:00
ret = 0 ;
break ;
case POWER_SUPPLY_PROP_CHARGE_NOW :
if ( is_charging ( cm ) ) {
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CHARGE_NOW ,
val ) ;
if ( ret ) {
val - > intval = 1 ;
ret = 0 ;
} else {
/* If CHARGE_NOW is supplied, use it */
val - > intval = ( val - > intval > 0 ) ?
val - > intval : 1 ;
}
} else {
val - > intval = 0 ;
}
break ;
default :
return - EINVAL ;
}
return ret ;
}
# define NUM_CHARGER_PSY_OPTIONAL (4)
static enum power_supply_property default_charger_props [ ] = {
/* Guaranteed to provide */
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CAPACITY ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_CHARGE_FULL ,
/*
* Optional properties are :
* POWER_SUPPLY_PROP_CHARGE_NOW ,
* POWER_SUPPLY_PROP_CURRENT_NOW ,
* POWER_SUPPLY_PROP_TEMP , and
* POWER_SUPPLY_PROP_TEMP_AMBIENT ,
*/
} ;
static struct power_supply psy_default = {
. name = " battery " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = default_charger_props ,
. num_properties = ARRAY_SIZE ( default_charger_props ) ,
. get_property = charger_get_property ,
} ;
2011-12-27 13:47:48 +04:00
/**
* cm_setup_timer - For in - suspend monitoring setup wakeup alarm
* for suspend_again .
*
* Returns true if the alarm is set for Charger Manager to use .
* Returns false if
* cm_setup_timer fails to set an alarm ,
* cm_setup_timer does not need to set an alarm for Charger Manager ,
* or an alarm previously configured is to be used .
*/
static bool cm_setup_timer ( void )
{
struct charger_manager * cm ;
unsigned int wakeup_ms = UINT_MAX ;
bool ret = false ;
mutex_lock ( & cm_list_mtx ) ;
list_for_each_entry ( cm , & cm_list , entry ) {
2012-05-05 17:24:10 +04:00
unsigned int fbchk_ms = 0 ;
/* fullbatt_vchk is required. setup timer for that */
if ( cm - > fullbatt_vchk_jiffies_at ) {
fbchk_ms = jiffies_to_msecs ( cm - > fullbatt_vchk_jiffies_at
- jiffies ) ;
if ( time_is_before_eq_jiffies (
cm - > fullbatt_vchk_jiffies_at ) | |
msecs_to_jiffies ( fbchk_ms ) < CM_JIFFIES_SMALL ) {
fullbatt_vchk ( & cm - > fullbatt_vchk_work . work ) ;
fbchk_ms = 0 ;
}
}
CM_MIN_VALID ( wakeup_ms , fbchk_ms ) ;
2011-12-27 13:47:48 +04:00
/* Skip if polling is not required for this CM */
if ( ! is_polling_required ( cm ) & & ! cm - > emergency_stop )
continue ;
if ( cm - > desc - > polling_interval_ms = = 0 )
continue ;
CM_MIN_VALID ( wakeup_ms , cm - > desc - > polling_interval_ms ) ;
}
mutex_unlock ( & cm_list_mtx ) ;
if ( wakeup_ms < UINT_MAX & & wakeup_ms > 0 ) {
2013-06-07 05:25:12 +04:00
pr_info ( " Charger Manager wakeup timer: %u ms \n " , wakeup_ms ) ;
2011-12-27 13:47:48 +04:00
if ( rtc_dev ) {
struct rtc_wkalrm tmp ;
unsigned long time , now ;
unsigned long add = DIV_ROUND_UP ( wakeup_ms , 1000 ) ;
/*
* Set alarm with the polling interval ( wakeup_ms )
* except when rtc_wkalarm_save comes first .
* However , the alarm time should be NOW +
* CM_RTC_SMALL or later .
*/
tmp . enabled = 1 ;
rtc_read_time ( rtc_dev , & tmp . time ) ;
rtc_tm_to_time ( & tmp . time , & now ) ;
if ( add < CM_RTC_SMALL )
add = CM_RTC_SMALL ;
time = now + add ;
ret = true ;
if ( rtc_wkalarm_save . enabled & &
rtc_wkalarm_save_time & &
rtc_wkalarm_save_time < time ) {
if ( rtc_wkalarm_save_time < now + CM_RTC_SMALL )
time = now + CM_RTC_SMALL ;
else
time = rtc_wkalarm_save_time ;
/* The timer is not appointed by CM */
ret = false ;
}
2013-06-07 05:25:12 +04:00
pr_info ( " Waking up after %lu secs \n " , time - now ) ;
2011-12-27 13:47:48 +04:00
rtc_time_to_tm ( time , & tmp . time ) ;
rtc_set_alarm ( rtc_dev , & tmp ) ;
cm_suspend_duration_ms + = wakeup_ms ;
return ret ;
}
}
if ( rtc_dev )
rtc_set_alarm ( rtc_dev , & rtc_wkalarm_save ) ;
return false ;
}
2012-05-05 17:24:10 +04:00
static void _cm_fbchk_in_suspend ( struct charger_manager * cm )
{
unsigned long jiffy_now = jiffies ;
if ( ! cm - > fullbatt_vchk_jiffies_at )
return ;
if ( g_desc & & g_desc - > assume_timer_stops_in_suspend )
jiffy_now + = msecs_to_jiffies ( cm_suspend_duration_ms ) ;
/* Execute now if it's going to be executed not too long after */
jiffy_now + = CM_JIFFIES_SMALL ;
if ( time_after_eq ( jiffy_now , cm - > fullbatt_vchk_jiffies_at ) )
fullbatt_vchk ( & cm - > fullbatt_vchk_work . work ) ;
}
2011-12-27 13:47:48 +04:00
/**
* cm_suspend_again - Determine whether suspend again or not
*
* Returns true if the system should be suspended again
* Returns false if the system should be woken up
*/
bool cm_suspend_again ( void )
{
struct charger_manager * cm ;
bool ret = false ;
if ( ! g_desc | | ! g_desc - > rtc_only_wakeup | | ! g_desc - > rtc_only_wakeup ( ) | |
! cm_rtc_set )
return false ;
if ( cm_monitor ( ) )
goto out ;
ret = true ;
mutex_lock ( & cm_list_mtx ) ;
list_for_each_entry ( cm , & cm_list , entry ) {
2012-05-05 17:24:10 +04:00
_cm_fbchk_in_suspend ( cm ) ;
2011-12-27 13:47:48 +04:00
if ( cm - > status_save_ext_pwr_inserted ! = is_ext_pwr_online ( cm ) | |
2012-01-12 08:56:35 +04:00
cm - > status_save_batt ! = is_batt_present ( cm ) ) {
2011-12-27 13:47:48 +04:00
ret = false ;
2012-01-12 08:56:35 +04:00
break ;
}
2011-12-27 13:47:48 +04:00
}
mutex_unlock ( & cm_list_mtx ) ;
cm_rtc_set = cm_setup_timer ( ) ;
out :
/* It's about the time when the non-CM appointed timer goes off */
if ( rtc_wkalarm_save . enabled ) {
unsigned long now ;
struct rtc_time tmp ;
rtc_read_time ( rtc_dev , & tmp ) ;
rtc_tm_to_time ( & tmp , & now ) ;
if ( rtc_wkalarm_save_time & &
now + CM_RTC_SMALL > = rtc_wkalarm_save_time )
return false ;
}
return ret ;
}
EXPORT_SYMBOL_GPL ( cm_suspend_again ) ;
/**
* setup_charger_manager - initialize charger_global_desc data
* @ gd : pointer to instance of charger_global_desc
*/
int setup_charger_manager ( struct charger_global_desc * gd )
{
if ( ! gd )
return - EINVAL ;
if ( rtc_dev )
rtc_class_close ( rtc_dev ) ;
rtc_dev = NULL ;
g_desc = NULL ;
if ( ! gd - > rtc_only_wakeup ) {
2013-06-07 05:25:12 +04:00
pr_err ( " The callback rtc_only_wakeup is not given \n " ) ;
2011-12-27 13:47:48 +04:00
return - EINVAL ;
}
if ( gd - > rtc_name ) {
rtc_dev = rtc_class_open ( gd - > rtc_name ) ;
if ( IS_ERR_OR_NULL ( rtc_dev ) ) {
rtc_dev = NULL ;
/* Retry at probe. RTC may be not registered yet */
}
} else {
2013-06-07 05:25:12 +04:00
pr_warn ( " No wakeup timer is given for charger manager. "
2011-12-27 13:47:48 +04:00
" In-suspend monitoring won't work. \n " ) ;
}
g_desc = gd ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( setup_charger_manager ) ;
2012-07-12 10:03:25 +04:00
/**
* charger_extcon_work - enable / diable charger according to the state
* of charger cable
*
* @ work : work_struct of the function charger_extcon_work .
*/
static void charger_extcon_work ( struct work_struct * work )
{
struct charger_cable * cable =
container_of ( work , struct charger_cable , wq ) ;
2012-07-12 10:03:29 +04:00
int ret ;
if ( cable - > attached & & cable - > min_uA ! = 0 & & cable - > max_uA ! = 0 ) {
ret = regulator_set_current_limit ( cable - > charger - > consumer ,
cable - > min_uA , cable - > max_uA ) ;
if ( ret < 0 ) {
pr_err ( " Cannot set current limit of %s (%s) \n " ,
2013-06-07 05:25:12 +04:00
cable - > charger - > regulator_name , cable - > name ) ;
2012-07-12 10:03:29 +04:00
return ;
}
pr_info ( " Set current limit of %s : %duA ~ %duA \n " ,
2013-06-07 05:25:12 +04:00
cable - > charger - > regulator_name ,
cable - > min_uA , cable - > max_uA ) ;
2012-07-12 10:03:29 +04:00
}
2012-07-12 10:03:25 +04:00
try_charger_enable ( cable - > cm , cable - > attached ) ;
}
/**
* charger_extcon_notifier - receive the state of charger cable
* when registered cable is attached or detached .
*
* @ self : the notifier block of the charger_extcon_notifier .
* @ event : the cable state .
* @ ptr : the data pointer of notifier block .
*/
static int charger_extcon_notifier ( struct notifier_block * self ,
unsigned long event , void * ptr )
{
struct charger_cable * cable =
container_of ( self , struct charger_cable , nb ) ;
2012-08-21 12:06:52 +04:00
/*
* The newly state of charger cable .
* If cable is attached , cable - > attached is true .
*/
2012-07-12 10:03:25 +04:00
cable - > attached = event ;
2012-08-21 12:06:52 +04:00
/*
* Setup monitoring to check battery state
* when charger cable is attached .
*/
if ( cable - > attached & & is_polling_required ( cable - > cm ) ) {
2012-12-22 05:56:51 +04:00
cancel_work_sync ( & setup_polling ) ;
2012-08-21 12:06:52 +04:00
schedule_work ( & setup_polling ) ;
}
/*
* Setup work for controlling charger ( regulator )
* according to charger cable .
*/
2012-07-12 10:03:25 +04:00
schedule_work ( & cable - > wq ) ;
return NOTIFY_DONE ;
}
/**
* charger_extcon_init - register external connector to use it
* as the charger cable
*
* @ cm : the Charger Manager representing the battery .
* @ cable : the Charger cable representing the external connector .
*/
static int charger_extcon_init ( struct charger_manager * cm ,
struct charger_cable * cable )
{
int ret = 0 ;
/*
* Charger manager use Extcon framework to identify
* the charger cable among various external connector
* cable ( e . g . , TA , USB , MHL , Dock ) .
*/
INIT_WORK ( & cable - > wq , charger_extcon_work ) ;
cable - > nb . notifier_call = charger_extcon_notifier ;
ret = extcon_register_interest ( & cable - > extcon_dev ,
cable - > extcon_name , cable - > name , & cable - > nb ) ;
if ( ret < 0 ) {
2013-06-07 05:25:12 +04:00
pr_info ( " Cannot register extcon_dev for %s(cable: %s) \n " ,
cable - > extcon_name , cable - > name ) ;
2012-07-12 10:03:25 +04:00
ret = - EINVAL ;
}
return ret ;
}
2012-11-22 11:54:26 +04:00
/**
* charger_manager_register_extcon - Register extcon device to recevie state
* of charger cable .
* @ cm : the Charger Manager representing the battery .
*
* This function support EXTCON ( External Connector ) subsystem to detect the
* state of charger cables for enabling or disabling charger ( regulator ) and
* select the charger cable for charging among a number of external cable
* according to policy of H / W board .
*/
static int charger_manager_register_extcon ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
struct charger_regulator * charger ;
int ret = 0 ;
int i ;
int j ;
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
charger = & desc - > charger_regulators [ i ] ;
charger - > consumer = regulator_get ( cm - > dev ,
charger - > regulator_name ) ;
2013-06-25 09:18:14 +04:00
if ( IS_ERR ( charger - > consumer ) ) {
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " Cannot find charger(%s) \n " ,
charger - > regulator_name ) ;
2013-06-25 09:18:14 +04:00
return PTR_ERR ( charger - > consumer ) ;
2012-11-22 11:54:26 +04:00
}
charger - > cm = cm ;
for ( j = 0 ; j < charger - > num_cables ; j + + ) {
struct charger_cable * cable = & charger - > cables [ j ] ;
ret = charger_extcon_init ( cm , cable ) ;
if ( ret < 0 ) {
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " Cannot initialize charger(%s) \n " ,
charger - > regulator_name ) ;
2012-11-22 11:54:26 +04:00
goto err ;
}
cable - > charger = charger ;
cable - > cm = cm ;
}
}
err :
return ret ;
}
2012-09-21 13:49:37 +04:00
/* help function of sysfs node to control charger(regulator) */
static ssize_t charger_name_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct charger_regulator * charger
= container_of ( attr , struct charger_regulator , attr_name ) ;
return sprintf ( buf , " %s \n " , charger - > regulator_name ) ;
}
static ssize_t charger_state_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct charger_regulator * charger
= container_of ( attr , struct charger_regulator , attr_state ) ;
int state = 0 ;
if ( ! charger - > externally_control )
state = regulator_is_enabled ( charger - > consumer ) ;
return sprintf ( buf , " %s \n " , state ? " enabled " : " disabled " ) ;
}
static ssize_t charger_externally_control_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct charger_regulator * charger = container_of ( attr ,
struct charger_regulator , attr_externally_control ) ;
return sprintf ( buf , " %d \n " , charger - > externally_control ) ;
}
static ssize_t charger_externally_control_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t count )
{
struct charger_regulator * charger
= container_of ( attr , struct charger_regulator ,
attr_externally_control ) ;
struct charger_manager * cm = charger - > cm ;
struct charger_desc * desc = cm - > desc ;
int i ;
int ret ;
int externally_control ;
int chargers_externally_control = 1 ;
ret = sscanf ( buf , " %d " , & externally_control ) ;
if ( ret = = 0 ) {
ret = - EINVAL ;
return ret ;
}
if ( ! externally_control ) {
charger - > externally_control = 0 ;
return count ;
}
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
if ( & desc - > charger_regulators [ i ] ! = charger & &
2012-11-22 11:54:26 +04:00
! desc - > charger_regulators [ i ] . externally_control ) {
2012-09-21 13:49:37 +04:00
/*
* At least , one charger is controlled by
* charger - manager
*/
chargers_externally_control = 0 ;
break ;
}
}
if ( ! chargers_externally_control ) {
if ( cm - > charger_enabled ) {
try_charger_enable ( charger - > cm , false ) ;
charger - > externally_control = externally_control ;
try_charger_enable ( charger - > cm , true ) ;
} else {
charger - > externally_control = externally_control ;
}
} else {
dev_warn ( cm - > dev ,
2013-06-07 05:25:12 +04:00
" '%s' regulator should be controlled in charger-manager because charger-manager must need at least one charger for charging \n " ,
charger - > regulator_name ) ;
2012-09-21 13:49:37 +04:00
}
return count ;
}
2012-11-22 11:54:26 +04:00
/**
* charger_manager_register_sysfs - Register sysfs entry for each charger
* @ cm : the Charger Manager representing the battery .
*
* This function add sysfs entry for charger ( regulator ) to control charger from
* user - space . If some development board use one more chargers for charging
* but only need one charger on specific case which is dependent on user
* scenario or hardware restrictions , the user enter 1 or 0 ( zero ) to ' / sys /
* class / power_supply / battery / charger . [ index ] / externally_control ' . For example ,
* if user enter 1 to ' sys / class / power_supply / battery / charger . [ index ] /
* externally_control , this charger isn ' t controlled from charger - manager and
* always stay off state of regulator .
*/
static int charger_manager_register_sysfs ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
struct charger_regulator * charger ;
int chargers_externally_control = 1 ;
char buf [ 11 ] ;
char * str ;
int ret = 0 ;
int i ;
/* Create sysfs entry to control charger(regulator) */
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
charger = & desc - > charger_regulators [ i ] ;
snprintf ( buf , 10 , " charger.%d " , i ) ;
2013-10-25 06:47:31 +04:00
str = devm_kzalloc ( cm - > dev ,
sizeof ( char ) * ( strlen ( buf ) + 1 ) , GFP_KERNEL ) ;
2012-11-22 11:54:26 +04:00
if ( ! str ) {
ret = - ENOMEM ;
goto err ;
}
strcpy ( str , buf ) ;
charger - > attrs [ 0 ] = & charger - > attr_name . attr ;
charger - > attrs [ 1 ] = & charger - > attr_state . attr ;
charger - > attrs [ 2 ] = & charger - > attr_externally_control . attr ;
charger - > attrs [ 3 ] = NULL ;
charger - > attr_g . name = str ;
charger - > attr_g . attrs = charger - > attrs ;
sysfs_attr_init ( & charger - > attr_name . attr ) ;
charger - > attr_name . attr . name = " name " ;
charger - > attr_name . attr . mode = 0444 ;
charger - > attr_name . show = charger_name_show ;
sysfs_attr_init ( & charger - > attr_state . attr ) ;
charger - > attr_state . attr . name = " state " ;
charger - > attr_state . attr . mode = 0444 ;
charger - > attr_state . show = charger_state_show ;
sysfs_attr_init ( & charger - > attr_externally_control . attr ) ;
charger - > attr_externally_control . attr . name
= " externally_control " ;
charger - > attr_externally_control . attr . mode = 0644 ;
charger - > attr_externally_control . show
= charger_externally_control_show ;
charger - > attr_externally_control . store
= charger_externally_control_store ;
if ( ! desc - > charger_regulators [ i ] . externally_control | |
! chargers_externally_control )
chargers_externally_control = 0 ;
2013-06-07 05:25:12 +04:00
dev_info ( cm - > dev , " '%s' regulator's externally_control is %d \n " ,
charger - > regulator_name , charger - > externally_control ) ;
2012-11-22 11:54:26 +04:00
ret = sysfs_create_group ( & cm - > charger_psy . dev - > kobj ,
& charger - > attr_g ) ;
if ( ret < 0 ) {
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " Cannot create sysfs entry of %s regulator \n " ,
charger - > regulator_name ) ;
2012-11-22 11:54:26 +04:00
ret = - EINVAL ;
goto err ;
}
}
if ( chargers_externally_control ) {
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " Cannot register regulator because charger-manager must need at least one charger for charging battery \n " ) ;
2012-11-22 11:54:26 +04:00
ret = - EINVAL ;
goto err ;
}
err :
return ret ;
}
2013-12-18 10:42:34 +04:00
static int cm_init_thermal_data ( struct charger_manager * cm )
{
struct charger_desc * desc = cm - > desc ;
union power_supply_propval val ;
int ret ;
/* Verify whether fuel gauge provides battery temperature */
ret = cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_TEMP , & val ) ;
if ( ! ret ) {
cm - > charger_psy . properties [ cm - > charger_psy . num_properties ] =
POWER_SUPPLY_PROP_TEMP ;
cm - > charger_psy . num_properties + + ;
cm - > desc - > measure_battery_temp = true ;
}
# ifdef CONFIG_THERMAL
cm - > tzd_batt = cm - > fuel_gauge - > tzd ;
if ( ret & & desc - > thermal_zone ) {
cm - > tzd_batt =
thermal_zone_get_zone_by_name ( desc - > thermal_zone ) ;
if ( IS_ERR ( cm - > tzd_batt ) )
return PTR_ERR ( cm - > tzd_batt ) ;
/* Use external thermometer */
cm - > charger_psy . properties [ cm - > charger_psy . num_properties ] =
POWER_SUPPLY_PROP_TEMP_AMBIENT ;
cm - > charger_psy . num_properties + + ;
cm - > desc - > measure_battery_temp = true ;
ret = 0 ;
}
# endif
if ( cm - > desc - > measure_battery_temp ) {
/* NOTICE : Default allowable minimum charge temperature is 0 */
if ( ! desc - > temp_max )
desc - > temp_max = CM_DEFAULT_CHARGE_TEMP_MAX ;
if ( ! desc - > temp_diff )
desc - > temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF ;
}
return ret ;
}
2013-12-18 10:42:35 +04:00
static struct of_device_id charger_manager_match [ ] = {
{
. compatible = " charger-manager " ,
} ,
{ } ,
} ;
2013-12-24 06:17:05 +04:00
static struct charger_desc * of_cm_parse_desc ( struct device * dev )
2013-12-18 10:42:35 +04:00
{
struct charger_desc * desc ;
struct device_node * np = dev - > of_node ;
u32 poll_mode = CM_POLL_DISABLE ;
u32 battery_stat = CM_NO_BATTERY ;
int num_chgs = 0 ;
desc = devm_kzalloc ( dev , sizeof ( * desc ) , GFP_KERNEL ) ;
if ( ! desc )
return ERR_PTR ( - ENOMEM ) ;
of_property_read_string ( np , " cm-name " , & desc - > psy_name ) ;
of_property_read_u32 ( np , " cm-poll-mode " , & poll_mode ) ;
desc - > polling_mode = poll_mode ;
of_property_read_u32 ( np , " cm-poll-interval " ,
& desc - > polling_interval_ms ) ;
of_property_read_u32 ( np , " cm-fullbatt-vchkdrop-ms " ,
& desc - > fullbatt_vchkdrop_ms ) ;
of_property_read_u32 ( np , " cm-fullbatt-vchkdrop-volt " ,
& desc - > fullbatt_vchkdrop_uV ) ;
of_property_read_u32 ( np , " cm-fullbatt-voltage " , & desc - > fullbatt_uV ) ;
of_property_read_u32 ( np , " cm-fullbatt-soc " , & desc - > fullbatt_soc ) ;
of_property_read_u32 ( np , " cm-fullbatt-capacity " ,
& desc - > fullbatt_full_capacity ) ;
of_property_read_u32 ( np , " cm-battery-stat " , & battery_stat ) ;
desc - > battery_present = battery_stat ;
/* chargers */
of_property_read_u32 ( np , " cm-num-chargers " , & num_chgs ) ;
if ( num_chgs ) {
/* Allocate empty bin at the tail of array */
desc - > psy_charger_stat = devm_kzalloc ( dev , sizeof ( char * )
* ( num_chgs + 1 ) , GFP_KERNEL ) ;
if ( desc - > psy_charger_stat ) {
int i ;
for ( i = 0 ; i < num_chgs ; i + + )
of_property_read_string_index ( np , " cm-chargers " ,
i , & desc - > psy_charger_stat [ i ] ) ;
} else {
return ERR_PTR ( - ENOMEM ) ;
}
}
of_property_read_string ( np , " cm-fuel-gauge " , & desc - > psy_fuel_gauge ) ;
of_property_read_string ( np , " cm-thermal-zone " , & desc - > thermal_zone ) ;
of_property_read_u32 ( np , " cm-battery-cold " , & desc - > temp_min ) ;
if ( of_get_property ( np , " cm-battery-cold-in-minus " , NULL ) )
desc - > temp_min * = - 1 ;
of_property_read_u32 ( np , " cm-battery-hot " , & desc - > temp_max ) ;
of_property_read_u32 ( np , " cm-battery-temp-diff " , & desc - > temp_diff ) ;
of_property_read_u32 ( np , " cm-charging-max " ,
& desc - > charging_max_duration_ms ) ;
of_property_read_u32 ( np , " cm-discharging-max " ,
& desc - > discharging_max_duration_ms ) ;
/* battery charger regualtors */
desc - > num_charger_regulators = of_get_child_count ( np ) ;
if ( desc - > num_charger_regulators ) {
struct charger_regulator * chg_regs ;
struct device_node * child ;
chg_regs = devm_kzalloc ( dev , sizeof ( * chg_regs )
* desc - > num_charger_regulators ,
GFP_KERNEL ) ;
if ( ! chg_regs )
return ERR_PTR ( - ENOMEM ) ;
desc - > charger_regulators = chg_regs ;
for_each_child_of_node ( np , child ) {
struct charger_cable * cables ;
struct device_node * _child ;
of_property_read_string ( child , " cm-regulator-name " ,
& chg_regs - > regulator_name ) ;
/* charger cables */
chg_regs - > num_cables = of_get_child_count ( child ) ;
if ( chg_regs - > num_cables ) {
cables = devm_kzalloc ( dev , sizeof ( * cables )
* chg_regs - > num_cables ,
GFP_KERNEL ) ;
if ( ! cables )
return ERR_PTR ( - ENOMEM ) ;
chg_regs - > cables = cables ;
for_each_child_of_node ( child , _child ) {
of_property_read_string ( _child ,
" cm-cable-name " , & cables - > name ) ;
of_property_read_string ( _child ,
" cm-cable-extcon " ,
& cables - > extcon_name ) ;
of_property_read_u32 ( _child ,
" cm-cable-min " ,
& cables - > min_uA ) ;
of_property_read_u32 ( _child ,
" cm-cable-max " ,
& cables - > max_uA ) ;
cables + + ;
}
}
chg_regs + + ;
}
}
return desc ;
}
static inline struct charger_desc * cm_get_drv_data ( struct platform_device * pdev )
{
if ( pdev - > dev . of_node )
return of_cm_parse_desc ( & pdev - > dev ) ;
return ( struct charger_desc * ) dev_get_platdata ( & pdev - > dev ) ;
}
2011-12-27 13:47:48 +04:00
static int charger_manager_probe ( struct platform_device * pdev )
{
2013-12-18 10:42:35 +04:00
struct charger_desc * desc = cm_get_drv_data ( pdev ) ;
2011-12-27 13:47:48 +04:00
struct charger_manager * cm ;
int ret = 0 , i = 0 ;
2012-07-12 10:03:25 +04:00
int j = 0 ;
2011-12-27 13:47:49 +04:00
union power_supply_propval val ;
2011-12-27 13:47:48 +04:00
if ( g_desc & & ! rtc_dev & & g_desc - > rtc_name ) {
rtc_dev = rtc_class_open ( g_desc - > rtc_name ) ;
if ( IS_ERR_OR_NULL ( rtc_dev ) ) {
rtc_dev = NULL ;
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " Cannot get RTC %s \n " ,
2011-12-27 13:47:48 +04:00
g_desc - > rtc_name ) ;
2013-10-25 06:47:31 +04:00
return - ENODEV ;
2011-12-27 13:47:48 +04:00
}
}
if ( ! desc ) {
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " No platform data (desc) found \n " ) ;
2013-10-25 06:47:31 +04:00
return - ENODEV ;
2011-12-27 13:47:48 +04:00
}
2013-10-25 06:47:31 +04:00
cm = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct charger_manager ) , GFP_KERNEL ) ;
if ( ! cm )
return - ENOMEM ;
2011-12-27 13:47:48 +04:00
/* Basic Values. Unspecified are Null or 0 */
cm - > dev = & pdev - > dev ;
2013-10-25 06:47:31 +04:00
cm - > desc = desc ;
2011-12-27 13:47:48 +04:00
2012-05-05 17:24:10 +04:00
/*
* The following two do not need to be errors .
* Users may intentionally ignore those two features .
*/
if ( desc - > fullbatt_uV = = 0 ) {
2013-06-07 05:25:12 +04:00
dev_info ( & pdev - > dev , " Ignoring full-battery voltage threshold as it is not supplied \n " ) ;
2012-05-05 17:24:10 +04:00
}
if ( ! desc - > fullbatt_vchkdrop_ms | | ! desc - > fullbatt_vchkdrop_uV ) {
2013-06-07 05:25:12 +04:00
dev_info ( & pdev - > dev , " Disabling full-battery voltage drop checking mechanism as it is not supplied \n " ) ;
2012-05-05 17:24:10 +04:00
desc - > fullbatt_vchkdrop_ms = 0 ;
desc - > fullbatt_vchkdrop_uV = 0 ;
}
2012-08-21 12:06:52 +04:00
if ( desc - > fullbatt_soc = = 0 ) {
2013-06-07 05:25:12 +04:00
dev_info ( & pdev - > dev , " Ignoring full-battery soc(state of charge) threshold as it is not supplied \n " ) ;
2012-08-21 12:06:52 +04:00
}
if ( desc - > fullbatt_full_capacity = = 0 ) {
2013-06-07 05:25:12 +04:00
dev_info ( & pdev - > dev , " Ignoring full-battery full capacity threshold as it is not supplied \n " ) ;
2012-08-21 12:06:52 +04:00
}
2012-05-05 17:24:10 +04:00
2011-12-27 13:47:48 +04:00
if ( ! desc - > charger_regulators | | desc - > num_charger_regulators < 1 ) {
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " charger_regulators undefined \n " ) ;
2013-10-25 06:47:31 +04:00
return - EINVAL ;
2011-12-27 13:47:48 +04:00
}
if ( ! desc - > psy_charger_stat | | ! desc - > psy_charger_stat [ 0 ] ) {
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " No power supply defined \n " ) ;
2013-10-25 06:47:31 +04:00
return - EINVAL ;
2011-12-27 13:47:48 +04:00
}
/* Counting index only */
while ( desc - > psy_charger_stat [ i ] )
i + + ;
2013-10-25 06:47:31 +04:00
cm - > charger_stat = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct power_supply * ) * i , GFP_KERNEL ) ;
if ( ! cm - > charger_stat )
return - ENOMEM ;
2011-12-27 13:47:48 +04:00
for ( i = 0 ; desc - > psy_charger_stat [ i ] ; i + + ) {
cm - > charger_stat [ i ] = power_supply_get_by_name (
desc - > psy_charger_stat [ i ] ) ;
if ( ! cm - > charger_stat [ i ] ) {
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " Cannot find power supply \" %s \" \n " ,
desc - > psy_charger_stat [ i ] ) ;
2013-10-25 06:47:31 +04:00
return - ENODEV ;
2011-12-27 13:47:48 +04:00
}
}
cm - > fuel_gauge = power_supply_get_by_name ( desc - > psy_fuel_gauge ) ;
if ( ! cm - > fuel_gauge ) {
dev_err ( & pdev - > dev , " Cannot find power supply \" %s \" \n " ,
2013-06-07 05:25:12 +04:00
desc - > psy_fuel_gauge ) ;
2013-10-25 06:47:31 +04:00
return - ENODEV ;
2011-12-27 13:47:48 +04:00
}
if ( desc - > polling_interval_ms = = 0 | |
msecs_to_jiffies ( desc - > polling_interval_ms ) < = CM_JIFFIES_SMALL ) {
dev_err ( & pdev - > dev , " polling_interval_ms is too small \n " ) ;
2013-10-25 06:47:31 +04:00
return - EINVAL ;
2011-12-27 13:47:48 +04:00
}
2012-09-21 08:20:05 +04:00
if ( ! desc - > charging_max_duration_ms | |
! desc - > discharging_max_duration_ms ) {
2013-06-07 05:25:12 +04:00
dev_info ( & pdev - > dev , " Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration \n " ) ;
2012-09-21 08:20:05 +04:00
desc - > charging_max_duration_ms = 0 ;
desc - > discharging_max_duration_ms = 0 ;
}
2011-12-27 13:47:48 +04:00
platform_set_drvdata ( pdev , cm ) ;
2012-01-12 08:56:35 +04:00
memcpy ( & cm - > charger_psy , & psy_default , sizeof ( psy_default ) ) ;
2012-11-22 11:54:26 +04:00
if ( ! desc - > psy_name )
2012-01-12 08:56:35 +04:00
strncpy ( cm - > psy_name_buf , psy_default . name , PSY_NAME_MAX ) ;
2012-11-22 11:54:26 +04:00
else
2011-12-27 13:47:49 +04:00
strncpy ( cm - > psy_name_buf , desc - > psy_name , PSY_NAME_MAX ) ;
cm - > charger_psy . name = cm - > psy_name_buf ;
/* Allocate for psy properties because they may vary */
2013-10-25 06:47:31 +04:00
cm - > charger_psy . properties = devm_kzalloc ( & pdev - > dev ,
sizeof ( enum power_supply_property )
2011-12-27 13:47:49 +04:00
* ( ARRAY_SIZE ( default_charger_props ) +
2013-10-25 06:47:31 +04:00
NUM_CHARGER_PSY_OPTIONAL ) , GFP_KERNEL ) ;
if ( ! cm - > charger_psy . properties )
return - ENOMEM ;
2011-12-27 13:47:49 +04:00
memcpy ( cm - > charger_psy . properties , default_charger_props ,
sizeof ( enum power_supply_property ) *
ARRAY_SIZE ( default_charger_props ) ) ;
cm - > charger_psy . num_properties = psy_default . num_properties ;
/* Find which optional psy-properties are available */
if ( ! cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CHARGE_NOW , & val ) ) {
cm - > charger_psy . properties [ cm - > charger_psy . num_properties ] =
POWER_SUPPLY_PROP_CHARGE_NOW ;
cm - > charger_psy . num_properties + + ;
}
if ( ! cm - > fuel_gauge - > get_property ( cm - > fuel_gauge ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
& val ) ) {
cm - > charger_psy . properties [ cm - > charger_psy . num_properties ] =
POWER_SUPPLY_PROP_CURRENT_NOW ;
cm - > charger_psy . num_properties + + ;
}
2012-01-12 08:56:35 +04:00
2013-12-18 10:42:34 +04:00
ret = cm_init_thermal_data ( cm ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to initialize thermal data \n " ) ;
cm - > desc - > measure_battery_temp = false ;
2011-12-27 13:47:49 +04:00
}
2012-05-05 17:24:10 +04:00
INIT_DELAYED_WORK ( & cm - > fullbatt_vchk_work , fullbatt_vchk ) ;
2011-12-27 13:47:49 +04:00
ret = power_supply_register ( NULL , & cm - > charger_psy ) ;
if ( ret ) {
2013-06-07 05:25:12 +04:00
dev_err ( & pdev - > dev , " Cannot register charger-manager with name \" %s \" \n " ,
cm - > charger_psy . name ) ;
2013-10-25 06:47:31 +04:00
return ret ;
2011-12-27 13:47:49 +04:00
}
2012-11-22 11:54:26 +04:00
/* Register extcon device for charger cable */
ret = charger_manager_register_extcon ( cm ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Cannot initialize extcon device \n " ) ;
goto err_reg_extcon ;
2011-12-27 13:47:48 +04:00
}
2012-11-22 11:54:26 +04:00
/* Register sysfs entry for charger(regulator) */
ret = charger_manager_register_sysfs ( cm ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev ,
" Cannot initialize sysfs entry of regulator \n " ) ;
goto err_reg_sysfs ;
2011-12-27 13:47:48 +04:00
}
/* Add to the list */
mutex_lock ( & cm_list_mtx ) ;
list_add ( & cm - > entry , & cm_list ) ;
mutex_unlock ( & cm_list_mtx ) ;
2012-05-05 17:26:47 +04:00
/*
* Charger - manager is capable of waking up the systme from sleep
* when event is happend through cm_notify_event ( )
*/
device_init_wakeup ( & pdev - > dev , true ) ;
device_set_wakeup_capable ( & pdev - > dev , false ) ;
2012-05-05 17:24:10 +04:00
schedule_work ( & setup_polling ) ;
2011-12-27 13:47:48 +04:00
return 0 ;
2012-11-22 11:54:26 +04:00
err_reg_sysfs :
2012-09-21 13:49:37 +04:00
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
struct charger_regulator * charger ;
charger = & desc - > charger_regulators [ i ] ;
sysfs_remove_group ( & cm - > charger_psy . dev - > kobj ,
& charger - > attr_g ) ;
}
2012-11-22 11:54:26 +04:00
err_reg_extcon :
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
struct charger_regulator * charger ;
charger = & desc - > charger_regulators [ i ] ;
for ( j = 0 ; j < charger - > num_cables ; j + + ) {
2012-07-12 10:03:25 +04:00
struct charger_cable * cable = & charger - > cables [ j ] ;
2013-06-25 09:02:49 +04:00
/* Remove notifier block if only edev exists */
if ( cable - > extcon_dev . edev )
extcon_unregister_interest ( & cable - > extcon_dev ) ;
2012-07-12 10:03:25 +04:00
}
2012-11-22 11:54:26 +04:00
2012-07-12 10:03:25 +04:00
regulator_put ( desc - > charger_regulators [ i ] . consumer ) ;
2012-11-22 11:54:26 +04:00
}
2012-07-12 10:03:25 +04:00
2011-12-27 13:47:49 +04:00
power_supply_unregister ( & cm - > charger_psy ) ;
2013-10-25 06:47:31 +04:00
2011-12-27 13:47:48 +04:00
return ret ;
}
2012-11-19 22:26:07 +04:00
static int charger_manager_remove ( struct platform_device * pdev )
2011-12-27 13:47:48 +04:00
{
struct charger_manager * cm = platform_get_drvdata ( pdev ) ;
struct charger_desc * desc = cm - > desc ;
2012-07-12 10:03:25 +04:00
int i = 0 ;
int j = 0 ;
2011-12-27 13:47:48 +04:00
/* Remove from the list */
mutex_lock ( & cm_list_mtx ) ;
list_del ( & cm - > entry ) ;
mutex_unlock ( & cm_list_mtx ) ;
2012-12-22 05:56:51 +04:00
cancel_work_sync ( & setup_polling ) ;
cancel_delayed_work_sync ( & cm_monitor_work ) ;
2012-05-05 17:24:10 +04:00
2012-07-12 10:03:25 +04:00
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + ) {
struct charger_regulator * charger
= & desc - > charger_regulators [ i ] ;
for ( j = 0 ; j < charger - > num_cables ; j + + ) {
struct charger_cable * cable = & charger - > cables [ j ] ;
extcon_unregister_interest ( & cable - > extcon_dev ) ;
}
}
for ( i = 0 ; i < desc - > num_charger_regulators ; i + + )
regulator_put ( desc - > charger_regulators [ i ] . consumer ) ;
2011-12-27 13:47:49 +04:00
power_supply_unregister ( & cm - > charger_psy ) ;
2012-05-05 17:24:10 +04:00
try_charger_enable ( cm , false ) ;
2011-12-27 13:47:48 +04:00
return 0 ;
}
2012-01-11 13:19:45 +04:00
static const struct platform_device_id charger_manager_id [ ] = {
2011-12-27 13:47:48 +04:00
{ " charger-manager " , 0 } ,
{ } ,
} ;
2012-01-11 13:19:45 +04:00
MODULE_DEVICE_TABLE ( platform , charger_manager_id ) ;
2011-12-27 13:47:48 +04:00
2012-05-05 17:26:47 +04:00
static int cm_suspend_noirq ( struct device * dev )
{
int ret = 0 ;
if ( device_may_wakeup ( dev ) ) {
device_set_wakeup_capable ( dev , false ) ;
ret = - EAGAIN ;
}
return ret ;
}
2011-12-27 13:47:48 +04:00
static int cm_suspend_prepare ( struct device * dev )
{
2012-01-12 08:56:35 +04:00
struct charger_manager * cm = dev_get_drvdata ( dev ) ;
2011-12-27 13:47:48 +04:00
if ( ! cm_suspended ) {
if ( rtc_dev ) {
struct rtc_time tmp ;
unsigned long now ;
rtc_read_alarm ( rtc_dev , & rtc_wkalarm_save ) ;
rtc_read_time ( rtc_dev , & tmp ) ;
if ( rtc_wkalarm_save . enabled ) {
rtc_tm_to_time ( & rtc_wkalarm_save . time ,
& rtc_wkalarm_save_time ) ;
rtc_tm_to_time ( & tmp , & now ) ;
if ( now > rtc_wkalarm_save_time )
rtc_wkalarm_save_time = 0 ;
} else {
rtc_wkalarm_save_time = 0 ;
}
}
cm_suspended = true ;
}
2012-12-22 05:56:51 +04:00
cancel_delayed_work ( & cm - > fullbatt_vchk_work ) ;
2011-12-27 13:47:48 +04:00
cm - > status_save_ext_pwr_inserted = is_ext_pwr_online ( cm ) ;
cm - > status_save_batt = is_batt_present ( cm ) ;
if ( ! cm_rtc_set ) {
cm_suspend_duration_ms = 0 ;
cm_rtc_set = cm_setup_timer ( ) ;
}
return 0 ;
}
static void cm_suspend_complete ( struct device * dev )
{
2012-01-12 08:56:35 +04:00
struct charger_manager * cm = dev_get_drvdata ( dev ) ;
2011-12-27 13:47:48 +04:00
if ( cm_suspended ) {
if ( rtc_dev ) {
struct rtc_wkalrm tmp ;
rtc_read_alarm ( rtc_dev , & tmp ) ;
rtc_wkalarm_save . pending = tmp . pending ;
rtc_set_alarm ( rtc_dev , & rtc_wkalarm_save ) ;
}
cm_suspended = false ;
cm_rtc_set = false ;
}
2012-05-05 17:24:10 +04:00
/* Re-enqueue delayed work (fullbatt_vchk_work) */
if ( cm - > fullbatt_vchk_jiffies_at ) {
unsigned long delay = 0 ;
unsigned long now = jiffies + CM_JIFFIES_SMALL ;
if ( time_after_eq ( now , cm - > fullbatt_vchk_jiffies_at ) ) {
delay = ( unsigned long ) ( ( long ) now
- ( long ) ( cm - > fullbatt_vchk_jiffies_at ) ) ;
delay = jiffies_to_msecs ( delay ) ;
} else {
delay = 0 ;
}
/*
* Account for cm_suspend_duration_ms if
* assume_timer_stops_in_suspend is active
*/
if ( g_desc & & g_desc - > assume_timer_stops_in_suspend ) {
if ( delay > cm_suspend_duration_ms )
delay - = cm_suspend_duration_ms ;
else
delay = 0 ;
}
queue_delayed_work ( cm_wq , & cm - > fullbatt_vchk_work ,
msecs_to_jiffies ( delay ) ) ;
}
2012-05-05 17:26:47 +04:00
device_set_wakeup_capable ( cm - > dev , false ) ;
2011-12-27 13:47:48 +04:00
uevent_notify ( cm , NULL ) ;
}
static const struct dev_pm_ops charger_manager_pm = {
. prepare = cm_suspend_prepare ,
2012-05-05 17:26:47 +04:00
. suspend_noirq = cm_suspend_noirq ,
2011-12-27 13:47:48 +04:00
. complete = cm_suspend_complete ,
} ;
static struct platform_driver charger_manager_driver = {
. driver = {
. name = " charger-manager " ,
. owner = THIS_MODULE ,
. pm = & charger_manager_pm ,
2013-12-18 10:42:35 +04:00
. of_match_table = charger_manager_match ,
2011-12-27 13:47:48 +04:00
} ,
. probe = charger_manager_probe ,
2012-11-19 22:20:40 +04:00
. remove = charger_manager_remove ,
2011-12-27 13:47:48 +04:00
. id_table = charger_manager_id ,
} ;
static int __init charger_manager_init ( void )
{
2012-05-05 17:24:10 +04:00
cm_wq = create_freezable_workqueue ( " charger_manager " ) ;
INIT_DELAYED_WORK ( & cm_monitor_work , cm_monitor_poller ) ;
2011-12-27 13:47:48 +04:00
return platform_driver_register ( & charger_manager_driver ) ;
}
late_initcall ( charger_manager_init ) ;
static void __exit charger_manager_cleanup ( void )
{
2012-05-05 17:24:10 +04:00
destroy_workqueue ( cm_wq ) ;
cm_wq = NULL ;
2011-12-27 13:47:48 +04:00
platform_driver_unregister ( & charger_manager_driver ) ;
}
module_exit ( charger_manager_cleanup ) ;
2012-05-05 17:26:47 +04:00
/**
* find_power_supply - find the associated power_supply of charger
* @ cm : the Charger Manager representing the battery
* @ psy : pointer to instance of charger ' s power_supply
*/
static bool find_power_supply ( struct charger_manager * cm ,
struct power_supply * psy )
{
int i ;
bool found = false ;
for ( i = 0 ; cm - > charger_stat [ i ] ; i + + ) {
if ( psy = = cm - > charger_stat [ i ] ) {
found = true ;
break ;
}
}
return found ;
}
/**
* cm_notify_event - charger driver notify Charger Manager of charger event
* @ psy : pointer to instance of charger ' s power_supply
* @ type : type of charger event
* @ msg : optional message passed to uevent_notify fuction
*/
void cm_notify_event ( struct power_supply * psy , enum cm_event_types type ,
char * msg )
{
struct charger_manager * cm ;
bool found_power_supply = false ;
if ( psy = = NULL )
return ;
mutex_lock ( & cm_list_mtx ) ;
list_for_each_entry ( cm , & cm_list , entry ) {
found_power_supply = find_power_supply ( cm , psy ) ;
if ( found_power_supply )
break ;
}
mutex_unlock ( & cm_list_mtx ) ;
if ( ! found_power_supply )
return ;
switch ( type ) {
case CM_EVENT_BATT_FULL :
fullbatt_handler ( cm ) ;
break ;
case CM_EVENT_BATT_OUT :
battout_handler ( cm ) ;
break ;
case CM_EVENT_BATT_IN :
case CM_EVENT_EXT_PWR_IN_OUT . . . CM_EVENT_CHG_START_STOP :
misc_event_handler ( cm , type ) ;
break ;
case CM_EVENT_UNKNOWN :
case CM_EVENT_OTHERS :
uevent_notify ( cm , msg ? msg : default_event_names [ type ] ) ;
break ;
default :
2013-06-07 05:25:12 +04:00
dev_err ( cm - > dev , " %s: type not specified \n " , __func__ ) ;
2012-05-05 17:26:47 +04:00
break ;
}
}
EXPORT_SYMBOL_GPL ( cm_notify_event ) ;
2011-12-27 13:47:48 +04:00
MODULE_AUTHOR ( " MyungJoo Ham <myungjoo.ham@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Charger Manager " ) ;
MODULE_LICENSE ( " GPL " ) ;