2012-02-29 20:24:25 +04:00
/*
* Copyright ( C ) ST - Ericsson SA 2012
2013-02-13 19:15:03 +04:00
* Copyright ( c ) 2012 Sony Mobile Communications AB
2012-02-29 20:24:25 +04:00
*
* Charging algorithm driver for abx500 variants
*
* License Terms : GNU General Public License v2
* Authors :
* Johan Palsson < johan . palsson @ stericsson . com >
* Karl Komierowski < karl . komierowski @ stericsson . com >
* Arun R Murthy < arun . murthy @ stericsson . com >
2013-02-13 19:15:03 +04:00
* Author : Imre Sunyi < imre . sunyi @ sonymobile . com >
2012-02-29 20:24:25 +04:00
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/device.h>
2013-02-13 19:15:03 +04:00
# include <linux/hrtimer.h>
2012-02-29 20:24:25 +04:00
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/completion.h>
# include <linux/workqueue.h>
# include <linux/kobject.h>
2012-10-31 19:40:33 +04:00
# include <linux/of.h>
# include <linux/mfd/core.h>
2012-02-29 20:24:25 +04:00
# include <linux/mfd/abx500.h>
2013-02-13 19:15:03 +04:00
# include <linux/mfd/abx500/ab8500.h>
2012-02-29 20:24:25 +04:00
# include <linux/mfd/abx500/ux500_chargalg.h>
# include <linux/mfd/abx500/ab8500-bm.h>
2013-02-13 15:39:19 +04:00
# include <linux/notifier.h>
2012-02-29 20:24:25 +04:00
/* Watchdog kick interval */
# define CHG_WD_INTERVAL (6 * HZ)
/* End-of-charge criteria counter */
# define EOC_COND_CNT 10
2013-02-13 19:15:03 +04:00
/* One hour expressed in seconds */
# define ONE_HOUR_IN_SECONDS 3600
/* Five minutes expressed in seconds */
# define FIVE_MINUTES_IN_SECONDS 300
2013-02-14 16:39:15 +04:00
/* Plus margin for the low battery threshold */
# define BAT_PLUS_MARGIN (100)
2013-02-15 14:58:58 +04:00
# define CHARGALG_CURR_STEP_LOW 0
# define CHARGALG_CURR_STEP_HIGH 100
2012-02-29 20:24:25 +04:00
enum abx500_chargers {
NO_CHG ,
AC_CHG ,
USB_CHG ,
} ;
struct abx500_chargalg_charger_info {
enum abx500_chargers conn_chg ;
enum abx500_chargers prev_conn_chg ;
enum abx500_chargers online_chg ;
enum abx500_chargers prev_online_chg ;
enum abx500_chargers charger_type ;
bool usb_chg_ok ;
bool ac_chg_ok ;
int usb_volt ;
int usb_curr ;
int ac_volt ;
int ac_curr ;
int usb_vset ;
int usb_iset ;
int ac_vset ;
int ac_iset ;
} ;
struct abx500_chargalg_suspension_status {
bool suspended_change ;
bool ac_suspended ;
bool usb_suspended ;
} ;
2013-02-15 14:58:58 +04:00
struct abx500_chargalg_current_step_status {
bool curr_step_change ;
int curr_step ;
} ;
2012-02-29 20:24:25 +04:00
struct abx500_chargalg_battery_data {
int temp ;
int volt ;
int avg_curr ;
int inst_curr ;
int percent ;
} ;
enum abx500_chargalg_states {
STATE_HANDHELD_INIT ,
STATE_HANDHELD ,
STATE_CHG_NOT_OK_INIT ,
STATE_CHG_NOT_OK ,
STATE_HW_TEMP_PROTECT_INIT ,
STATE_HW_TEMP_PROTECT ,
STATE_NORMAL_INIT ,
2013-02-14 16:39:15 +04:00
STATE_USB_PP_PRE_CHARGE ,
2012-02-29 20:24:25 +04:00
STATE_NORMAL ,
STATE_WAIT_FOR_RECHARGE_INIT ,
STATE_WAIT_FOR_RECHARGE ,
STATE_MAINTENANCE_A_INIT ,
STATE_MAINTENANCE_A ,
STATE_MAINTENANCE_B_INIT ,
STATE_MAINTENANCE_B ,
STATE_TEMP_UNDEROVER_INIT ,
STATE_TEMP_UNDEROVER ,
STATE_TEMP_LOWHIGH_INIT ,
STATE_TEMP_LOWHIGH ,
STATE_SUSPENDED_INIT ,
STATE_SUSPENDED ,
STATE_OVV_PROTECT_INIT ,
STATE_OVV_PROTECT ,
STATE_SAFETY_TIMER_EXPIRED_INIT ,
STATE_SAFETY_TIMER_EXPIRED ,
STATE_BATT_REMOVED_INIT ,
STATE_BATT_REMOVED ,
STATE_WD_EXPIRED_INIT ,
STATE_WD_EXPIRED ,
} ;
static const char * states [ ] = {
" HANDHELD_INIT " ,
" HANDHELD " ,
" CHG_NOT_OK_INIT " ,
" CHG_NOT_OK " ,
" HW_TEMP_PROTECT_INIT " ,
" HW_TEMP_PROTECT " ,
" NORMAL_INIT " ,
2013-02-14 16:39:15 +04:00
" USB_PP_PRE_CHARGE " ,
2012-02-29 20:24:25 +04:00
" NORMAL " ,
" WAIT_FOR_RECHARGE_INIT " ,
" WAIT_FOR_RECHARGE " ,
" MAINTENANCE_A_INIT " ,
" MAINTENANCE_A " ,
" MAINTENANCE_B_INIT " ,
" MAINTENANCE_B " ,
" TEMP_UNDEROVER_INIT " ,
" TEMP_UNDEROVER " ,
" TEMP_LOWHIGH_INIT " ,
" TEMP_LOWHIGH " ,
" SUSPENDED_INIT " ,
" SUSPENDED " ,
" OVV_PROTECT_INIT " ,
" OVV_PROTECT " ,
" SAFETY_TIMER_EXPIRED_INIT " ,
" SAFETY_TIMER_EXPIRED " ,
" BATT_REMOVED_INIT " ,
" BATT_REMOVED " ,
" WD_EXPIRED_INIT " ,
" WD_EXPIRED " ,
} ;
struct abx500_chargalg_events {
bool batt_unknown ;
bool mainextchnotok ;
bool batt_ovv ;
bool batt_rem ;
bool btemp_underover ;
bool btemp_lowhigh ;
bool main_thermal_prot ;
bool usb_thermal_prot ;
bool main_ovv ;
bool vbus_ovv ;
bool usbchargernotok ;
bool safety_timer_expired ;
bool maintenance_timer_expired ;
bool ac_wd_expired ;
bool usb_wd_expired ;
bool ac_cv_active ;
bool usb_cv_active ;
bool vbus_collapsed ;
} ;
/**
* struct abx500_charge_curr_maximization - Charger maximization parameters
* @ original_iset : the non optimized / maximised charger current
* @ current_iset : the charging current used at this moment
* @ test_delta_i : the delta between the current we want to charge and the
current that is really going into the battery
* @ condition_cnt : number of iterations needed before a new charger current
is set
* @ max_current : maximum charger current
* @ wait_cnt : to avoid too fast current step down in case of charger
* voltage collapse , we insert this delay between step
* down
* @ level : tells in how many steps the charging current has been
increased
*/
struct abx500_charge_curr_maximization {
int original_iset ;
int current_iset ;
int test_delta_i ;
int condition_cnt ;
int max_current ;
int wait_cnt ;
u8 level ;
} ;
enum maxim_ret {
MAXIM_RET_NOACTION ,
MAXIM_RET_CHANGE ,
MAXIM_RET_IBAT_TOO_HIGH ,
} ;
/**
* struct abx500_chargalg - abx500 Charging algorithm device information
* @ dev : pointer to the structure device
* @ charge_status : battery operating status
* @ eoc_cnt : counter used to determine end - of_charge
* @ maintenance_chg : indicate if maintenance charge is active
* @ t_hyst_norm temperature hysteresis when the temperature has been
* over or under normal limits
* @ t_hyst_lowhigh temperature hysteresis when the temperature has been
* over or under the high or low limits
* @ charge_state : current state of the charging algorithm
* @ ccm charging current maximization parameters
* @ chg_info : information about connected charger types
* @ batt_data : data of the battery
* @ susp_status : current charger suspension status
2012-11-30 14:09:42 +04:00
* @ bm : Platform specific battery management information
2013-02-15 14:58:58 +04:00
* @ curr_status : Current step status for over - current protection
2013-02-15 14:53:57 +04:00
* @ parent : pointer to the struct abx500
2012-02-29 20:24:25 +04:00
* @ chargalg_psy : structure that holds the battery properties exposed by
* the charging algorithm
* @ events : structure for information about events triggered
* @ chargalg_wq : work queue for running the charging algorithm
* @ chargalg_periodic_work : work to run the charging algorithm periodically
* @ chargalg_wd_work : work to kick the charger watchdog periodically
* @ chargalg_work : work to run the charging algorithm instantly
* @ safety_timer : charging safety timer
* @ maintenance_timer : maintenance charging timer
* @ chargalg_kobject : structure of type kobject
*/
struct abx500_chargalg {
struct device * dev ;
int charge_status ;
int eoc_cnt ;
bool maintenance_chg ;
int t_hyst_norm ;
int t_hyst_lowhigh ;
enum abx500_chargalg_states charge_state ;
struct abx500_charge_curr_maximization ccm ;
struct abx500_chargalg_charger_info chg_info ;
struct abx500_chargalg_battery_data batt_data ;
struct abx500_chargalg_suspension_status susp_status ;
2013-02-15 14:53:57 +04:00
struct ab8500 * parent ;
2013-02-15 14:58:58 +04:00
struct abx500_chargalg_current_step_status curr_status ;
2012-11-30 14:09:42 +04:00
struct abx500_bm_data * bm ;
2015-03-12 10:44:11 +03:00
struct power_supply * chargalg_psy ;
2012-02-29 20:24:25 +04:00
struct ux500_charger * ac_chg ;
struct ux500_charger * usb_chg ;
struct abx500_chargalg_events events ;
struct workqueue_struct * chargalg_wq ;
struct delayed_work chargalg_periodic_work ;
struct delayed_work chargalg_wd_work ;
struct work_struct chargalg_work ;
2013-02-13 19:15:03 +04:00
struct hrtimer safety_timer ;
struct hrtimer maintenance_timer ;
2012-02-29 20:24:25 +04:00
struct kobject chargalg_kobject ;
} ;
2013-02-13 15:39:19 +04:00
/*External charger prepare notifier*/
BLOCKING_NOTIFIER_HEAD ( charger_notifier_list ) ;
2012-02-29 20:24:25 +04:00
/* Main battery properties */
static enum power_supply_property abx500_chargalg_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_HEALTH ,
} ;
2013-02-15 14:58:58 +04:00
struct abx500_chargalg_sysfs_entry {
struct attribute attr ;
ssize_t ( * show ) ( struct abx500_chargalg * , char * ) ;
ssize_t ( * store ) ( struct abx500_chargalg * , const char * , size_t ) ;
} ;
2012-02-29 20:24:25 +04:00
/**
* abx500_chargalg_safety_timer_expired ( ) - Expiration of the safety timer
2013-02-13 19:15:03 +04:00
* @ timer : pointer to the hrtimer structure
2012-02-29 20:24:25 +04:00
*
* This function gets called when the safety timer for the charger
* expires
*/
2013-02-13 19:15:03 +04:00
static enum hrtimer_restart
abx500_chargalg_safety_timer_expired ( struct hrtimer * timer )
2012-02-29 20:24:25 +04:00
{
2013-02-13 19:15:03 +04:00
struct abx500_chargalg * di = container_of ( timer , struct abx500_chargalg ,
safety_timer ) ;
2012-02-29 20:24:25 +04:00
dev_err ( di - > dev , " Safety timer expired \n " ) ;
di - > events . safety_timer_expired = true ;
/* Trigger execution of the algorithm instantly */
queue_work ( di - > chargalg_wq , & di - > chargalg_work ) ;
2013-02-13 19:15:03 +04:00
return HRTIMER_NORESTART ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_maintenance_timer_expired ( ) - Expiration of
* the maintenance timer
2013-02-13 19:15:03 +04:00
* @ timer : pointer to the timer structure
2012-02-29 20:24:25 +04:00
*
* This function gets called when the maintenence timer
* expires
*/
2013-02-13 19:15:03 +04:00
static enum hrtimer_restart
abx500_chargalg_maintenance_timer_expired ( struct hrtimer * timer )
2012-02-29 20:24:25 +04:00
{
2013-02-13 19:15:03 +04:00
struct abx500_chargalg * di = container_of ( timer , struct abx500_chargalg ,
maintenance_timer ) ;
2012-02-29 20:24:25 +04:00
dev_dbg ( di - > dev , " Maintenance timer expired \n " ) ;
di - > events . maintenance_timer_expired = true ;
/* Trigger execution of the algorithm instantly */
queue_work ( di - > chargalg_wq , & di - > chargalg_work ) ;
2013-02-13 19:15:03 +04:00
return HRTIMER_NORESTART ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_state_to ( ) - Change charge state
* @ di : pointer to the abx500_chargalg structure
*
* This function gets called when a charge state change should occur
*/
static void abx500_chargalg_state_to ( struct abx500_chargalg * di ,
enum abx500_chargalg_states state )
{
dev_dbg ( di - > dev ,
" State changed: %s (From state: [%d] %s =to=> [%d] %s ) \n " ,
di - > charge_state = = state ? " NO " : " YES " ,
di - > charge_state ,
states [ di - > charge_state ] ,
state ,
states [ state ] ) ;
di - > charge_state = state ;
}
2013-02-14 13:24:10 +04:00
static int abx500_chargalg_check_charger_enable ( struct abx500_chargalg * di )
{
switch ( di - > charge_state ) {
case STATE_NORMAL :
case STATE_MAINTENANCE_A :
case STATE_MAINTENANCE_B :
break ;
default :
return 0 ;
}
if ( di - > chg_info . charger_type & USB_CHG ) {
return di - > usb_chg - > ops . check_enable ( di - > usb_chg ,
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_vol_lvl ,
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_cur_lvl ) ;
} else if ( ( di - > chg_info . charger_type & AC_CHG ) & &
! ( di - > ac_chg - > external ) ) {
return di - > ac_chg - > ops . check_enable ( di - > ac_chg ,
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_vol_lvl ,
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_cur_lvl ) ;
}
return 0 ;
}
2012-02-29 20:24:25 +04:00
/**
* abx500_chargalg_check_charger_connection ( ) - Check charger connection change
* @ di : pointer to the abx500_chargalg structure
*
* This function will check if there is a change in the charger connection
* and change charge state accordingly . AC has precedence over USB .
*/
static int abx500_chargalg_check_charger_connection ( struct abx500_chargalg * di )
{
if ( di - > chg_info . conn_chg ! = di - > chg_info . prev_conn_chg | |
di - > susp_status . suspended_change ) {
/*
* Charger state changed or suspension
* has changed since last update
*/
if ( ( di - > chg_info . conn_chg & AC_CHG ) & &
! di - > susp_status . ac_suspended ) {
dev_dbg ( di - > dev , " Charging source is AC \n " ) ;
if ( di - > chg_info . charger_type ! = AC_CHG ) {
di - > chg_info . charger_type = AC_CHG ;
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
}
} else if ( ( di - > chg_info . conn_chg & USB_CHG ) & &
! di - > susp_status . usb_suspended ) {
dev_dbg ( di - > dev , " Charging source is USB \n " ) ;
di - > chg_info . charger_type = USB_CHG ;
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
} else if ( di - > chg_info . conn_chg & &
( di - > susp_status . ac_suspended | |
di - > susp_status . usb_suspended ) ) {
dev_dbg ( di - > dev , " Charging is suspended \n " ) ;
di - > chg_info . charger_type = NO_CHG ;
abx500_chargalg_state_to ( di , STATE_SUSPENDED_INIT ) ;
} else {
dev_dbg ( di - > dev , " Charging source is OFF \n " ) ;
di - > chg_info . charger_type = NO_CHG ;
abx500_chargalg_state_to ( di , STATE_HANDHELD_INIT ) ;
}
di - > chg_info . prev_conn_chg = di - > chg_info . conn_chg ;
di - > susp_status . suspended_change = false ;
}
return di - > chg_info . conn_chg ;
}
2013-02-15 14:58:58 +04:00
/**
* abx500_chargalg_check_current_step_status ( ) - Check charging current
* step status .
* @ di : pointer to the abx500_chargalg structure
*
* This function will check if there is a change in the charging current step
* and change charge state accordingly .
*/
static void abx500_chargalg_check_current_step_status
( struct abx500_chargalg * di )
{
if ( di - > curr_status . curr_step_change )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
di - > curr_status . curr_step_change = false ;
}
2012-02-29 20:24:25 +04:00
/**
* abx500_chargalg_start_safety_timer ( ) - Start charging safety timer
* @ di : pointer to the abx500_chargalg structure
*
* The safety timer is used to avoid overcharging of old or bad batteries .
* There are different timers for AC and USB
*/
static void abx500_chargalg_start_safety_timer ( struct abx500_chargalg * di )
{
2013-02-13 19:15:03 +04:00
/* Charger-dependent expiration time in hours*/
int timer_expiration = 0 ;
2012-02-29 20:24:25 +04:00
switch ( di - > chg_info . charger_type ) {
case AC_CHG :
2013-02-13 19:15:03 +04:00
timer_expiration = di - > bm - > main_safety_tmr_h ;
2012-02-29 20:24:25 +04:00
break ;
case USB_CHG :
2013-02-13 19:15:03 +04:00
timer_expiration = di - > bm - > usb_safety_tmr_h ;
2012-02-29 20:24:25 +04:00
break ;
default :
dev_err ( di - > dev , " Unknown charger to charge from \n " ) ;
break ;
}
di - > events . safety_timer_expired = false ;
2013-02-13 19:15:03 +04:00
hrtimer_set_expires_range ( & di - > safety_timer ,
ktime_set ( timer_expiration * ONE_HOUR_IN_SECONDS , 0 ) ,
ktime_set ( FIVE_MINUTES_IN_SECONDS , 0 ) ) ;
hrtimer_start_expires ( & di - > safety_timer , HRTIMER_MODE_REL ) ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_stop_safety_timer ( ) - Stop charging safety timer
* @ di : pointer to the abx500_chargalg structure
*
* The safety timer is stopped whenever the NORMAL state is exited
*/
static void abx500_chargalg_stop_safety_timer ( struct abx500_chargalg * di )
{
2013-02-13 19:15:03 +04:00
if ( hrtimer_try_to_cancel ( & di - > safety_timer ) > = 0 )
di - > events . safety_timer_expired = false ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_start_maintenance_timer ( ) - Start charging maintenance timer
* @ di : pointer to the abx500_chargalg structure
* @ duration : duration of ther maintenance timer in hours
*
* The maintenance timer is used to maintain the charge in the battery once
* the battery is considered full . These timers are chosen to match the
* discharge curve of the battery
*/
static void abx500_chargalg_start_maintenance_timer ( struct abx500_chargalg * di ,
int duration )
{
2013-02-13 19:15:03 +04:00
hrtimer_set_expires_range ( & di - > maintenance_timer ,
ktime_set ( duration * ONE_HOUR_IN_SECONDS , 0 ) ,
ktime_set ( FIVE_MINUTES_IN_SECONDS , 0 ) ) ;
2012-02-29 20:24:25 +04:00
di - > events . maintenance_timer_expired = false ;
2013-02-13 19:15:03 +04:00
hrtimer_start_expires ( & di - > maintenance_timer , HRTIMER_MODE_REL ) ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_stop_maintenance_timer ( ) - Stop maintenance timer
* @ di : pointer to the abx500_chargalg structure
*
* The maintenance timer is stopped whenever maintenance ends or when another
* state is entered
*/
static void abx500_chargalg_stop_maintenance_timer ( struct abx500_chargalg * di )
{
2013-02-13 19:15:03 +04:00
if ( hrtimer_try_to_cancel ( & di - > maintenance_timer ) > = 0 )
di - > events . maintenance_timer_expired = false ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_kick_watchdog ( ) - Kick charger watchdog
* @ di : pointer to the abx500_chargalg structure
*
* The charger watchdog have to be kicked periodically whenever the charger is
* on , else the ABB will reset the system
*/
static int abx500_chargalg_kick_watchdog ( struct abx500_chargalg * di )
{
/* Check if charger exists and kick watchdog if charging */
if ( di - > ac_chg & & di - > ac_chg - > ops . kick_wd & &
2012-05-10 17:33:56 +04:00
di - > chg_info . online_chg & AC_CHG ) {
/*
* If AB charger watchdog expired , pm2xxx charging
* gets disabled . To be safe , kick both AB charger watchdog
* and pm2xxx watchdog .
*/
if ( di - > ac_chg - > external & &
di - > usb_chg & & di - > usb_chg - > ops . kick_wd )
di - > usb_chg - > ops . kick_wd ( di - > usb_chg ) ;
2012-02-29 20:24:25 +04:00
return di - > ac_chg - > ops . kick_wd ( di - > ac_chg ) ;
2012-05-10 17:33:56 +04:00
}
2012-02-29 20:24:25 +04:00
else if ( di - > usb_chg & & di - > usb_chg - > ops . kick_wd & &
di - > chg_info . online_chg & USB_CHG )
return di - > usb_chg - > ops . kick_wd ( di - > usb_chg ) ;
return - ENXIO ;
}
/**
* abx500_chargalg_ac_en ( ) - Turn on / off the AC charger
* @ di : pointer to the abx500_chargalg structure
* @ enable : charger on / off
* @ vset : requested charger output voltage
* @ iset : requested charger output current
*
* The AC charger will be turned on / off with the requested charge voltage and
* current
*/
static int abx500_chargalg_ac_en ( struct abx500_chargalg * di , int enable ,
int vset , int iset )
{
2013-02-13 15:39:19 +04:00
static int abx500_chargalg_ex_ac_enable_toggle ;
2012-02-29 20:24:25 +04:00
if ( ! di - > ac_chg | | ! di - > ac_chg - > ops . enable )
return - ENXIO ;
/* Select maximum of what both the charger and the battery supports */
if ( di - > ac_chg - > max_out_volt )
vset = min ( vset , di - > ac_chg - > max_out_volt ) ;
if ( di - > ac_chg - > max_out_curr )
iset = min ( iset , di - > ac_chg - > max_out_curr ) ;
di - > chg_info . ac_iset = iset ;
di - > chg_info . ac_vset = vset ;
2013-02-13 15:39:19 +04:00
/* Enable external charger */
if ( enable & & di - > ac_chg - > external & &
! abx500_chargalg_ex_ac_enable_toggle ) {
blocking_notifier_call_chain ( & charger_notifier_list ,
0 , di - > dev ) ;
abx500_chargalg_ex_ac_enable_toggle + + ;
}
2012-02-29 20:24:25 +04:00
return di - > ac_chg - > ops . enable ( di - > ac_chg , enable , vset , iset ) ;
}
/**
* abx500_chargalg_usb_en ( ) - Turn on / off the USB charger
* @ di : pointer to the abx500_chargalg structure
* @ enable : charger on / off
* @ vset : requested charger output voltage
* @ iset : requested charger output current
*
* The USB charger will be turned on / off with the requested charge voltage and
* current
*/
static int abx500_chargalg_usb_en ( struct abx500_chargalg * di , int enable ,
int vset , int iset )
{
if ( ! di - > usb_chg | | ! di - > usb_chg - > ops . enable )
return - ENXIO ;
/* Select maximum of what both the charger and the battery supports */
if ( di - > usb_chg - > max_out_volt )
vset = min ( vset , di - > usb_chg - > max_out_volt ) ;
if ( di - > usb_chg - > max_out_curr )
iset = min ( iset , di - > usb_chg - > max_out_curr ) ;
di - > chg_info . usb_iset = iset ;
di - > chg_info . usb_vset = vset ;
return di - > usb_chg - > ops . enable ( di - > usb_chg , enable , vset , iset ) ;
}
2013-02-14 16:39:15 +04:00
/**
* ab8540_chargalg_usb_pp_en ( ) - Enable / disable USB power path
* @ di : pointer to the abx500_chargalg structure
* @ enable : power path enable / disable
*
* The USB power path will be enable / disable
*/
static int ab8540_chargalg_usb_pp_en ( struct abx500_chargalg * di , bool enable )
{
if ( ! di - > usb_chg | | ! di - > usb_chg - > ops . pp_enable )
return - ENXIO ;
return di - > usb_chg - > ops . pp_enable ( di - > usb_chg , enable ) ;
}
/**
* ab8540_chargalg_usb_pre_chg_en ( ) - Enable / disable USB pre - charge
* @ di : pointer to the abx500_chargalg structure
* @ enable : USB pre - charge enable / disable
*
* The USB USB pre - charge will be enable / disable
*/
static int ab8540_chargalg_usb_pre_chg_en ( struct abx500_chargalg * di ,
bool enable )
{
if ( ! di - > usb_chg | | ! di - > usb_chg - > ops . pre_chg_enable )
return - ENXIO ;
return di - > usb_chg - > ops . pre_chg_enable ( di - > usb_chg , enable ) ;
}
2012-02-29 20:24:25 +04:00
/**
* abx500_chargalg_update_chg_curr ( ) - Update charger current
* @ di : pointer to the abx500_chargalg structure
* @ iset : requested charger output current
*
* The charger output current will be updated for the charger
* that is currently in use
*/
static int abx500_chargalg_update_chg_curr ( struct abx500_chargalg * di ,
int iset )
{
/* Check if charger exists and update current if charging */
if ( di - > ac_chg & & di - > ac_chg - > ops . update_curr & &
di - > chg_info . charger_type & AC_CHG ) {
/*
* Select maximum of what both the charger
* and the battery supports
*/
if ( di - > ac_chg - > max_out_curr )
iset = min ( iset , di - > ac_chg - > max_out_curr ) ;
di - > chg_info . ac_iset = iset ;
return di - > ac_chg - > ops . update_curr ( di - > ac_chg , iset ) ;
} else if ( di - > usb_chg & & di - > usb_chg - > ops . update_curr & &
di - > chg_info . charger_type & USB_CHG ) {
/*
* Select maximum of what both the charger
* and the battery supports
*/
if ( di - > usb_chg - > max_out_curr )
iset = min ( iset , di - > usb_chg - > max_out_curr ) ;
di - > chg_info . usb_iset = iset ;
return di - > usb_chg - > ops . update_curr ( di - > usb_chg , iset ) ;
}
return - ENXIO ;
}
/**
* abx500_chargalg_stop_charging ( ) - Stop charging
* @ di : pointer to the abx500_chargalg structure
*
* This function is called from any state where charging should be stopped .
* All charging is disabled and all status parameters and timers are changed
* accordingly
*/
static void abx500_chargalg_stop_charging ( struct abx500_chargalg * di )
{
abx500_chargalg_ac_en ( di , false , 0 , 0 ) ;
abx500_chargalg_usb_en ( di , false , 0 , 0 ) ;
abx500_chargalg_stop_safety_timer ( di ) ;
abx500_chargalg_stop_maintenance_timer ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING ;
di - > maintenance_chg = false ;
cancel_delayed_work ( & di - > chargalg_wd_work ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_hold_charging ( ) - Pauses charging
* @ di : pointer to the abx500_chargalg structure
*
* This function is called in the case where maintenance charging has been
* disabled and instead a battery voltage mode is entered to check when the
* battery voltage has reached a certain recharge voltage
*/
static void abx500_chargalg_hold_charging ( struct abx500_chargalg * di )
{
abx500_chargalg_ac_en ( di , false , 0 , 0 ) ;
abx500_chargalg_usb_en ( di , false , 0 , 0 ) ;
abx500_chargalg_stop_safety_timer ( di ) ;
abx500_chargalg_stop_maintenance_timer ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_CHARGING ;
di - > maintenance_chg = false ;
cancel_delayed_work ( & di - > chargalg_wd_work ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_start_charging ( ) - Start the charger
* @ di : pointer to the abx500_chargalg structure
* @ vset : requested charger output voltage
* @ iset : requested charger output current
*
* A charger will be enabled depending on the requested charger type that was
* detected previously .
*/
static void abx500_chargalg_start_charging ( struct abx500_chargalg * di ,
int vset , int iset )
{
switch ( di - > chg_info . charger_type ) {
case AC_CHG :
dev_dbg ( di - > dev ,
" AC parameters: Vset %d, Ich %d \n " , vset , iset ) ;
abx500_chargalg_usb_en ( di , false , 0 , 0 ) ;
abx500_chargalg_ac_en ( di , true , vset , iset ) ;
break ;
case USB_CHG :
dev_dbg ( di - > dev ,
" USB parameters: Vset %d, Ich %d \n " , vset , iset ) ;
abx500_chargalg_ac_en ( di , false , 0 , 0 ) ;
abx500_chargalg_usb_en ( di , true , vset , iset ) ;
break ;
default :
dev_err ( di - > dev , " Unknown charger to charge from \n " ) ;
break ;
}
}
/**
* abx500_chargalg_check_temp ( ) - Check battery temperature ranges
* @ di : pointer to the abx500_chargalg structure
*
* The battery temperature is checked against the predefined limits and the
* charge state is changed accordingly
*/
static void abx500_chargalg_check_temp ( struct abx500_chargalg * di )
{
2012-11-30 14:09:42 +04:00
if ( di - > batt_data . temp > ( di - > bm - > temp_low + di - > t_hyst_norm ) & &
di - > batt_data . temp < ( di - > bm - > temp_high - di - > t_hyst_norm ) ) {
2012-02-29 20:24:25 +04:00
/* Temp OK! */
di - > events . btemp_underover = false ;
di - > events . btemp_lowhigh = false ;
di - > t_hyst_norm = 0 ;
di - > t_hyst_lowhigh = 0 ;
} else {
2012-11-30 14:09:42 +04:00
if ( ( ( di - > batt_data . temp > = di - > bm - > temp_high ) & &
2012-02-29 20:24:25 +04:00
( di - > batt_data . temp <
2012-11-30 14:09:42 +04:00
( di - > bm - > temp_over - di - > t_hyst_lowhigh ) ) ) | |
2012-02-29 20:24:25 +04:00
( ( di - > batt_data . temp >
2012-11-30 14:09:42 +04:00
( di - > bm - > temp_under + di - > t_hyst_lowhigh ) ) & &
( di - > batt_data . temp < = di - > bm - > temp_low ) ) ) {
2012-02-29 20:24:25 +04:00
/* TEMP minor!!!!! */
di - > events . btemp_underover = false ;
di - > events . btemp_lowhigh = true ;
2012-11-30 14:09:42 +04:00
di - > t_hyst_norm = di - > bm - > temp_hysteresis ;
2012-02-29 20:24:25 +04:00
di - > t_hyst_lowhigh = 0 ;
2012-11-30 14:09:42 +04:00
} else if ( di - > batt_data . temp < = di - > bm - > temp_under | |
di - > batt_data . temp > = di - > bm - > temp_over ) {
2012-02-29 20:24:25 +04:00
/* TEMP major!!!!! */
di - > events . btemp_underover = true ;
di - > events . btemp_lowhigh = false ;
di - > t_hyst_norm = 0 ;
2012-11-30 14:09:42 +04:00
di - > t_hyst_lowhigh = di - > bm - > temp_hysteresis ;
2012-02-29 20:24:25 +04:00
} else {
/* Within hysteresis */
dev_dbg ( di - > dev , " Within hysteresis limit temp: %d "
" hyst_lowhigh %d, hyst normal %d \n " ,
di - > batt_data . temp , di - > t_hyst_lowhigh ,
di - > t_hyst_norm ) ;
}
}
}
/**
* abx500_chargalg_check_charger_voltage ( ) - Check charger voltage
* @ di : pointer to the abx500_chargalg structure
*
* Charger voltage is checked against maximum limit
*/
static void abx500_chargalg_check_charger_voltage ( struct abx500_chargalg * di )
{
2012-11-30 14:09:42 +04:00
if ( di - > chg_info . usb_volt > di - > bm - > chg_params - > usb_volt_max )
2012-02-29 20:24:25 +04:00
di - > chg_info . usb_chg_ok = false ;
else
di - > chg_info . usb_chg_ok = true ;
2012-11-30 14:09:42 +04:00
if ( di - > chg_info . ac_volt > di - > bm - > chg_params - > ac_volt_max )
2012-02-29 20:24:25 +04:00
di - > chg_info . ac_chg_ok = false ;
else
di - > chg_info . ac_chg_ok = true ;
}
/**
* abx500_chargalg_end_of_charge ( ) - Check if end - of - charge criteria is fulfilled
* @ di : pointer to the abx500_chargalg structure
*
* End - of - charge criteria is fulfilled when the battery voltage is above a
* certain limit and the battery current is below a certain limit for a
* predefined number of consecutive seconds . If true , the battery is full
*/
static void abx500_chargalg_end_of_charge ( struct abx500_chargalg * di )
{
if ( di - > charge_status = = POWER_SUPPLY_STATUS_CHARGING & &
di - > charge_state = = STATE_NORMAL & &
! di - > maintenance_chg & & ( di - > batt_data . volt > =
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [ di - > bm - > batt_id ] . termination_vol | |
2012-02-29 20:24:25 +04:00
di - > events . usb_cv_active | | di - > events . ac_cv_active ) & &
di - > batt_data . avg_curr <
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [ di - > bm - > batt_id ] . termination_curr & &
2012-02-29 20:24:25 +04:00
di - > batt_data . avg_curr > 0 ) {
if ( + + di - > eoc_cnt > = EOC_COND_CNT ) {
di - > eoc_cnt = 0 ;
2013-02-14 16:39:15 +04:00
if ( ( di - > chg_info . charger_type & USB_CHG ) & &
( di - > usb_chg - > power_path ) )
ab8540_chargalg_usb_pp_en ( di , true ) ;
2012-02-29 20:24:25 +04:00
di - > charge_status = POWER_SUPPLY_STATUS_FULL ;
di - > maintenance_chg = true ;
dev_dbg ( di - > dev , " EOC reached! \n " ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
} else {
dev_dbg ( di - > dev ,
" EOC limit reached for the %d "
" time, out of %d before EOC \n " ,
di - > eoc_cnt ,
EOC_COND_CNT ) ;
}
} else {
di - > eoc_cnt = 0 ;
}
}
static void init_maxim_chg_curr ( struct abx500_chargalg * di )
{
di - > ccm . original_iset =
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_cur_lvl ;
2012-02-29 20:24:25 +04:00
di - > ccm . current_iset =
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_cur_lvl ;
di - > ccm . test_delta_i = di - > bm - > maxi - > charger_curr_step ;
di - > ccm . max_current = di - > bm - > maxi - > chg_curr ;
di - > ccm . condition_cnt = di - > bm - > maxi - > wait_cycles ;
2012-02-29 20:24:25 +04:00
di - > ccm . level = 0 ;
}
/**
* abx500_chargalg_chg_curr_maxim - increases the charger current to
* compensate for the system load
* @ di pointer to the abx500_chargalg structure
*
* This maximization function is used to raise the charger current to get the
* battery current as close to the optimal value as possible . The battery
* current during charging is affected by the system load
*/
static enum maxim_ret abx500_chargalg_chg_curr_maxim ( struct abx500_chargalg * di )
{
int delta_i ;
2012-11-30 14:09:42 +04:00
if ( ! di - > bm - > maxi - > ena_maxi )
2012-02-29 20:24:25 +04:00
return MAXIM_RET_NOACTION ;
delta_i = di - > ccm . original_iset - di - > batt_data . inst_curr ;
if ( di - > events . vbus_collapsed ) {
dev_dbg ( di - > dev , " Charger voltage has collapsed %d \n " ,
di - > ccm . wait_cnt ) ;
if ( di - > ccm . wait_cnt = = 0 ) {
dev_dbg ( di - > dev , " lowering current \n " ) ;
di - > ccm . wait_cnt + + ;
2012-11-30 14:09:42 +04:00
di - > ccm . condition_cnt = di - > bm - > maxi - > wait_cycles ;
2012-02-29 20:24:25 +04:00
di - > ccm . max_current =
di - > ccm . current_iset - di - > ccm . test_delta_i ;
di - > ccm . current_iset = di - > ccm . max_current ;
di - > ccm . level - - ;
return MAXIM_RET_CHANGE ;
} else {
dev_dbg ( di - > dev , " waiting \n " ) ;
/* Let's go in here twice before lowering curr again */
di - > ccm . wait_cnt = ( di - > ccm . wait_cnt + 1 ) % 3 ;
return MAXIM_RET_NOACTION ;
}
}
di - > ccm . wait_cnt = 0 ;
if ( ( di - > batt_data . inst_curr > di - > ccm . original_iset ) ) {
dev_dbg ( di - > dev , " Maximization Ibat (%dmA) too high "
" (limit %dmA) (current iset: %dmA)! \n " ,
di - > batt_data . inst_curr , di - > ccm . original_iset ,
di - > ccm . current_iset ) ;
if ( di - > ccm . current_iset = = di - > ccm . original_iset )
return MAXIM_RET_NOACTION ;
2012-11-30 14:09:42 +04:00
di - > ccm . condition_cnt = di - > bm - > maxi - > wait_cycles ;
2012-02-29 20:24:25 +04:00
di - > ccm . current_iset = di - > ccm . original_iset ;
di - > ccm . level = 0 ;
return MAXIM_RET_IBAT_TOO_HIGH ;
}
if ( delta_i > di - > ccm . test_delta_i & &
( di - > ccm . current_iset + di - > ccm . test_delta_i ) <
di - > ccm . max_current ) {
if ( di - > ccm . condition_cnt - - = = 0 ) {
/* Increse the iset with cco.test_delta_i */
2012-11-30 14:09:42 +04:00
di - > ccm . condition_cnt = di - > bm - > maxi - > wait_cycles ;
2012-02-29 20:24:25 +04:00
di - > ccm . current_iset + = di - > ccm . test_delta_i ;
di - > ccm . level + + ;
dev_dbg ( di - > dev , " Maximization needed, increase "
" with %d mA to %dmA (Optimal ibat: %d) "
" Level %d \n " ,
di - > ccm . test_delta_i ,
di - > ccm . current_iset ,
di - > ccm . original_iset ,
di - > ccm . level ) ;
return MAXIM_RET_CHANGE ;
} else {
return MAXIM_RET_NOACTION ;
}
} else {
2012-11-30 14:09:42 +04:00
di - > ccm . condition_cnt = di - > bm - > maxi - > wait_cycles ;
2012-02-29 20:24:25 +04:00
return MAXIM_RET_NOACTION ;
}
}
static void handle_maxim_chg_curr ( struct abx500_chargalg * di )
{
enum maxim_ret ret ;
int result ;
ret = abx500_chargalg_chg_curr_maxim ( di ) ;
switch ( ret ) {
case MAXIM_RET_CHANGE :
result = abx500_chargalg_update_chg_curr ( di ,
di - > ccm . current_iset ) ;
if ( result )
dev_err ( di - > dev , " failed to set chg curr \n " ) ;
break ;
case MAXIM_RET_IBAT_TOO_HIGH :
result = abx500_chargalg_update_chg_curr ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [ di - > bm - > batt_id ] . normal_cur_lvl ) ;
2012-02-29 20:24:25 +04:00
if ( result )
dev_err ( di - > dev , " failed to set chg curr \n " ) ;
break ;
case MAXIM_RET_NOACTION :
default :
/* Do nothing..*/
break ;
}
}
static int abx500_chargalg_get_ext_psy_data ( struct device * dev , void * data )
{
struct power_supply * psy ;
2016-03-18 00:22:29 +03:00
struct power_supply * ext = dev_get_drvdata ( dev ) ;
const char * * supplicants = ( const char * * ) ext - > supplied_to ;
2012-02-29 20:24:25 +04:00
struct abx500_chargalg * di ;
union power_supply_propval ret ;
2016-03-18 00:22:29 +03:00
int j ;
2013-01-11 17:12:54 +04:00
bool capacity_updated = false ;
2012-02-29 20:24:25 +04:00
psy = ( struct power_supply * ) data ;
2015-03-12 10:44:11 +03:00
di = power_supply_get_drvdata ( psy ) ;
2012-02-29 20:24:25 +04:00
/* For all psy where the driver name appears in any supplied_to */
2016-03-18 00:22:29 +03:00
j = match_string ( supplicants , ext - > num_supplicants , psy - > desc - > name ) ;
if ( j < 0 )
2012-02-29 20:24:25 +04:00
return 0 ;
2013-01-11 17:12:54 +04:00
/*
* If external is not registering ' POWER_SUPPLY_PROP_CAPACITY ' to its
* property because of handling that sysfs entry on its own , this is
* the place to get the battery capacity .
*/
2015-03-12 10:44:06 +03:00
if ( ! power_supply_get_property ( ext , POWER_SUPPLY_PROP_CAPACITY , & ret ) ) {
2013-01-11 17:12:54 +04:00
di - > batt_data . percent = ret . intval ;
capacity_updated = true ;
}
2012-02-29 20:24:25 +04:00
/* Go through all properties for the psy */
2015-03-12 10:44:11 +03:00
for ( j = 0 ; j < ext - > desc - > num_properties ; j + + ) {
2012-02-29 20:24:25 +04:00
enum power_supply_property prop ;
2015-03-12 10:44:11 +03:00
prop = ext - > desc - > properties [ j ] ;
2012-02-29 20:24:25 +04:00
2015-03-12 10:44:11 +03:00
/*
* Initialize chargers if not already done .
* The ab8500_charger */
2012-02-29 20:24:25 +04:00
if ( ! di - > ac_chg & &
2015-03-12 10:44:11 +03:00
ext - > desc - > type = = POWER_SUPPLY_TYPE_MAINS )
2012-02-29 20:24:25 +04:00
di - > ac_chg = psy_to_ux500_charger ( ext ) ;
else if ( ! di - > usb_chg & &
2015-03-12 10:44:11 +03:00
ext - > desc - > type = = POWER_SUPPLY_TYPE_USB )
2012-02-29 20:24:25 +04:00
di - > usb_chg = psy_to_ux500_charger ( ext ) ;
2015-03-12 10:44:06 +03:00
if ( power_supply_get_property ( ext , prop , & ret ) )
2012-02-29 20:24:25 +04:00
continue ;
switch ( prop ) {
case POWER_SUPPLY_PROP_PRESENT :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
/* Battery present */
if ( ret . intval )
di - > events . batt_rem = false ;
/* Battery removed */
else
di - > events . batt_rem = true ;
break ;
case POWER_SUPPLY_TYPE_MAINS :
/* AC disconnected */
if ( ! ret . intval & &
( di - > chg_info . conn_chg & AC_CHG ) ) {
di - > chg_info . prev_conn_chg =
di - > chg_info . conn_chg ;
di - > chg_info . conn_chg & = ~ AC_CHG ;
}
/* AC connected */
else if ( ret . intval & &
! ( di - > chg_info . conn_chg & AC_CHG ) ) {
di - > chg_info . prev_conn_chg =
di - > chg_info . conn_chg ;
di - > chg_info . conn_chg | = AC_CHG ;
}
break ;
case POWER_SUPPLY_TYPE_USB :
/* USB disconnected */
if ( ! ret . intval & &
( di - > chg_info . conn_chg & USB_CHG ) ) {
di - > chg_info . prev_conn_chg =
di - > chg_info . conn_chg ;
di - > chg_info . conn_chg & = ~ USB_CHG ;
}
/* USB connected */
else if ( ret . intval & &
! ( di - > chg_info . conn_chg & USB_CHG ) ) {
di - > chg_info . prev_conn_chg =
di - > chg_info . conn_chg ;
di - > chg_info . conn_chg | = USB_CHG ;
}
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_ONLINE :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
break ;
case POWER_SUPPLY_TYPE_MAINS :
/* AC offline */
if ( ! ret . intval & &
( di - > chg_info . online_chg & AC_CHG ) ) {
di - > chg_info . prev_online_chg =
di - > chg_info . online_chg ;
di - > chg_info . online_chg & = ~ AC_CHG ;
}
/* AC online */
else if ( ret . intval & &
! ( di - > chg_info . online_chg & AC_CHG ) ) {
di - > chg_info . prev_online_chg =
di - > chg_info . online_chg ;
di - > chg_info . online_chg | = AC_CHG ;
queue_delayed_work ( di - > chargalg_wq ,
& di - > chargalg_wd_work , 0 ) ;
}
break ;
case POWER_SUPPLY_TYPE_USB :
/* USB offline */
if ( ! ret . intval & &
( di - > chg_info . online_chg & USB_CHG ) ) {
di - > chg_info . prev_online_chg =
di - > chg_info . online_chg ;
di - > chg_info . online_chg & = ~ USB_CHG ;
}
/* USB online */
else if ( ret . intval & &
! ( di - > chg_info . online_chg & USB_CHG ) ) {
di - > chg_info . prev_online_chg =
di - > chg_info . online_chg ;
di - > chg_info . online_chg | = USB_CHG ;
queue_delayed_work ( di - > chargalg_wq ,
& di - > chargalg_wd_work , 0 ) ;
}
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_HEALTH :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
break ;
case POWER_SUPPLY_TYPE_MAINS :
switch ( ret . intval ) {
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE :
di - > events . mainextchnotok = true ;
di - > events . main_thermal_prot = false ;
di - > events . main_ovv = false ;
di - > events . ac_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_DEAD :
di - > events . ac_wd_expired = true ;
di - > events . mainextchnotok = false ;
di - > events . main_ovv = false ;
di - > events . main_thermal_prot = false ;
break ;
case POWER_SUPPLY_HEALTH_COLD :
case POWER_SUPPLY_HEALTH_OVERHEAT :
di - > events . main_thermal_prot = true ;
di - > events . mainextchnotok = false ;
di - > events . main_ovv = false ;
di - > events . ac_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_OVERVOLTAGE :
di - > events . main_ovv = true ;
di - > events . mainextchnotok = false ;
di - > events . main_thermal_prot = false ;
di - > events . ac_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_GOOD :
di - > events . main_thermal_prot = false ;
di - > events . mainextchnotok = false ;
di - > events . main_ovv = false ;
di - > events . ac_wd_expired = false ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_TYPE_USB :
switch ( ret . intval ) {
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE :
di - > events . usbchargernotok = true ;
di - > events . usb_thermal_prot = false ;
di - > events . vbus_ovv = false ;
di - > events . usb_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_DEAD :
di - > events . usb_wd_expired = true ;
di - > events . usbchargernotok = false ;
di - > events . usb_thermal_prot = false ;
di - > events . vbus_ovv = false ;
break ;
case POWER_SUPPLY_HEALTH_COLD :
case POWER_SUPPLY_HEALTH_OVERHEAT :
di - > events . usb_thermal_prot = true ;
di - > events . usbchargernotok = false ;
di - > events . vbus_ovv = false ;
di - > events . usb_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_OVERVOLTAGE :
di - > events . vbus_ovv = true ;
di - > events . usbchargernotok = false ;
di - > events . usb_thermal_prot = false ;
di - > events . usb_wd_expired = false ;
break ;
case POWER_SUPPLY_HEALTH_GOOD :
di - > events . usbchargernotok = false ;
di - > events . usb_thermal_prot = false ;
di - > events . vbus_ovv = false ;
di - > events . usb_wd_expired = false ;
break ;
default :
break ;
}
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
di - > batt_data . volt = ret . intval / 1000 ;
break ;
case POWER_SUPPLY_TYPE_MAINS :
di - > chg_info . ac_volt = ret . intval / 1000 ;
break ;
case POWER_SUPPLY_TYPE_USB :
di - > chg_info . usb_volt = ret . intval / 1000 ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_VOLTAGE_AVG :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_MAINS :
/* AVG is used to indicate when we are
* in CV mode */
if ( ret . intval )
di - > events . ac_cv_active = true ;
else
di - > events . ac_cv_active = false ;
break ;
case POWER_SUPPLY_TYPE_USB :
/* AVG is used to indicate when we are
* in CV mode */
if ( ret . intval )
di - > events . usb_cv_active = true ;
else
di - > events . usb_cv_active = false ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
if ( ret . intval )
di - > events . batt_unknown = false ;
else
di - > events . batt_unknown = true ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_TEMP :
di - > batt_data . temp = ret . intval / 10 ;
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_MAINS :
di - > chg_info . ac_curr =
ret . intval / 1000 ;
break ;
case POWER_SUPPLY_TYPE_USB :
di - > chg_info . usb_curr =
ret . intval / 1000 ;
break ;
case POWER_SUPPLY_TYPE_BATTERY :
di - > batt_data . inst_curr = ret . intval / 1000 ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_CURRENT_AVG :
2015-03-12 10:44:11 +03:00
switch ( ext - > desc - > type ) {
2012-02-29 20:24:25 +04:00
case POWER_SUPPLY_TYPE_BATTERY :
di - > batt_data . avg_curr = ret . intval / 1000 ;
break ;
case POWER_SUPPLY_TYPE_USB :
if ( ret . intval )
di - > events . vbus_collapsed = true ;
else
di - > events . vbus_collapsed = false ;
break ;
default :
break ;
}
break ;
case POWER_SUPPLY_PROP_CAPACITY :
2013-01-11 17:12:54 +04:00
if ( ! capacity_updated )
di - > batt_data . percent = ret . intval ;
2012-02-29 20:24:25 +04:00
break ;
default :
break ;
}
}
return 0 ;
}
/**
* abx500_chargalg_external_power_changed ( ) - callback for power supply changes
* @ psy : pointer to the structure power_supply
*
* This function is the entry point of the pointer external_power_changed
* of the structure power_supply .
* This function gets executed when there is a change in any external power
* supply that this driver needs to be notified of .
*/
static void abx500_chargalg_external_power_changed ( struct power_supply * psy )
{
2015-03-12 10:44:11 +03:00
struct abx500_chargalg * di = power_supply_get_drvdata ( psy ) ;
2012-02-29 20:24:25 +04:00
/*
* Trigger execution of the algorithm instantly and read
* all power_supply properties there instead
*/
queue_work ( di - > chargalg_wq , & di - > chargalg_work ) ;
}
/**
* abx500_chargalg_algorithm ( ) - Main function for the algorithm
* @ di : pointer to the abx500_chargalg structure
*
* This is the main control function for the charging algorithm .
* It is called periodically or when something happens that will
* trigger a state change
*/
static void abx500_chargalg_algorithm ( struct abx500_chargalg * di )
{
int charger_status ;
2013-02-14 13:24:10 +04:00
int ret ;
2013-02-15 14:58:58 +04:00
int curr_step_lvl ;
2012-02-29 20:24:25 +04:00
/* Collect data from all power_supply class devices */
class_for_each_device ( power_supply_class , NULL ,
2015-03-12 10:44:11 +03:00
di - > chargalg_psy , abx500_chargalg_get_ext_psy_data ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_end_of_charge ( di ) ;
abx500_chargalg_check_temp ( di ) ;
abx500_chargalg_check_charger_voltage ( di ) ;
charger_status = abx500_chargalg_check_charger_connection ( di ) ;
2013-02-15 14:58:58 +04:00
abx500_chargalg_check_current_step_status ( di ) ;
2013-02-14 13:24:10 +04:00
if ( is_ab8500 ( di - > parent ) ) {
ret = abx500_chargalg_check_charger_enable ( di ) ;
if ( ret < 0 )
dev_err ( di - > dev , " Checking charger is enabled error "
" : Returned Value %d \n " , ret ) ;
}
2012-02-29 20:24:25 +04:00
/*
* First check if we have a charger connected .
* Also we don ' t allow charging of unknown batteries if configured
* this way
*/
if ( ! charger_status | |
2012-11-30 14:09:42 +04:00
( di - > events . batt_unknown & & ! di - > bm - > chg_unknown_bat ) ) {
2012-02-29 20:24:25 +04:00
if ( di - > charge_state ! = STATE_HANDHELD ) {
di - > events . safety_timer_expired = false ;
abx500_chargalg_state_to ( di , STATE_HANDHELD_INIT ) ;
}
}
/* If suspended, we should not continue checking the flags */
else if ( di - > charge_state = = STATE_SUSPENDED_INIT | |
di - > charge_state = = STATE_SUSPENDED ) {
/* We don't do anything here, just don,t continue */
}
/* Safety timer expiration */
else if ( di - > events . safety_timer_expired ) {
if ( di - > charge_state ! = STATE_SAFETY_TIMER_EXPIRED )
abx500_chargalg_state_to ( di ,
STATE_SAFETY_TIMER_EXPIRED_INIT ) ;
}
/*
* Check if any interrupts has occured
* that will prevent us from charging
*/
/* Battery removed */
else if ( di - > events . batt_rem ) {
if ( di - > charge_state ! = STATE_BATT_REMOVED )
abx500_chargalg_state_to ( di , STATE_BATT_REMOVED_INIT ) ;
}
/* Main or USB charger not ok. */
else if ( di - > events . mainextchnotok | | di - > events . usbchargernotok ) {
/*
* If vbus_collapsed is set , we have to lower the charger
* current , which is done in the normal state below
*/
if ( di - > charge_state ! = STATE_CHG_NOT_OK & &
! di - > events . vbus_collapsed )
abx500_chargalg_state_to ( di , STATE_CHG_NOT_OK_INIT ) ;
}
/* VBUS, Main or VBAT OVV. */
else if ( di - > events . vbus_ovv | |
di - > events . main_ovv | |
di - > events . batt_ovv | |
! di - > chg_info . usb_chg_ok | |
! di - > chg_info . ac_chg_ok ) {
if ( di - > charge_state ! = STATE_OVV_PROTECT )
abx500_chargalg_state_to ( di , STATE_OVV_PROTECT_INIT ) ;
}
/* USB Thermal, stop charging */
else if ( di - > events . main_thermal_prot | |
di - > events . usb_thermal_prot ) {
if ( di - > charge_state ! = STATE_HW_TEMP_PROTECT )
abx500_chargalg_state_to ( di ,
STATE_HW_TEMP_PROTECT_INIT ) ;
}
/* Battery temp over/under */
else if ( di - > events . btemp_underover ) {
if ( di - > charge_state ! = STATE_TEMP_UNDEROVER )
abx500_chargalg_state_to ( di ,
STATE_TEMP_UNDEROVER_INIT ) ;
}
/* Watchdog expired */
else if ( di - > events . ac_wd_expired | |
di - > events . usb_wd_expired ) {
if ( di - > charge_state ! = STATE_WD_EXPIRED )
abx500_chargalg_state_to ( di , STATE_WD_EXPIRED_INIT ) ;
}
/* Battery temp high/low */
else if ( di - > events . btemp_lowhigh ) {
if ( di - > charge_state ! = STATE_TEMP_LOWHIGH )
abx500_chargalg_state_to ( di , STATE_TEMP_LOWHIGH_INIT ) ;
}
dev_dbg ( di - > dev ,
" [CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d "
" State %s Active_chg %d Chg_status %d AC %d USB %d "
" AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d "
" USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d \n " ,
di - > batt_data . volt ,
di - > batt_data . avg_curr ,
di - > batt_data . inst_curr ,
di - > batt_data . temp ,
di - > batt_data . percent ,
di - > maintenance_chg ,
states [ di - > charge_state ] ,
di - > chg_info . charger_type ,
di - > charge_status ,
di - > chg_info . conn_chg & AC_CHG ,
di - > chg_info . conn_chg & USB_CHG ,
di - > chg_info . online_chg & AC_CHG ,
di - > chg_info . online_chg & USB_CHG ,
di - > events . ac_cv_active ,
di - > events . usb_cv_active ,
di - > chg_info . ac_curr ,
di - > chg_info . usb_curr ,
di - > chg_info . ac_vset ,
di - > chg_info . ac_iset ,
di - > chg_info . usb_vset ,
di - > chg_info . usb_iset ) ;
switch ( di - > charge_state ) {
case STATE_HANDHELD_INIT :
abx500_chargalg_stop_charging ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_DISCHARGING ;
abx500_chargalg_state_to ( di , STATE_HANDHELD ) ;
/* Intentional fallthrough */
case STATE_HANDHELD :
break ;
case STATE_SUSPENDED_INIT :
if ( di - > susp_status . ac_suspended )
abx500_chargalg_ac_en ( di , false , 0 , 0 ) ;
if ( di - > susp_status . usb_suspended )
abx500_chargalg_usb_en ( di , false , 0 , 0 ) ;
abx500_chargalg_stop_safety_timer ( di ) ;
abx500_chargalg_stop_maintenance_timer ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING ;
di - > maintenance_chg = false ;
abx500_chargalg_state_to ( di , STATE_SUSPENDED ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
/* Intentional fallthrough */
case STATE_SUSPENDED :
/* CHARGING is suspended */
break ;
case STATE_BATT_REMOVED_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_BATT_REMOVED ) ;
/* Intentional fallthrough */
case STATE_BATT_REMOVED :
if ( ! di - > events . batt_rem )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_HW_TEMP_PROTECT_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_HW_TEMP_PROTECT ) ;
/* Intentional fallthrough */
case STATE_HW_TEMP_PROTECT :
if ( ! di - > events . main_thermal_prot & &
! di - > events . usb_thermal_prot )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_OVV_PROTECT_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_OVV_PROTECT ) ;
/* Intentional fallthrough */
case STATE_OVV_PROTECT :
if ( ! di - > events . vbus_ovv & &
! di - > events . main_ovv & &
! di - > events . batt_ovv & &
di - > chg_info . usb_chg_ok & &
di - > chg_info . ac_chg_ok )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_CHG_NOT_OK_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_CHG_NOT_OK ) ;
/* Intentional fallthrough */
case STATE_CHG_NOT_OK :
if ( ! di - > events . mainextchnotok & &
! di - > events . usbchargernotok )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_SAFETY_TIMER_EXPIRED_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_SAFETY_TIMER_EXPIRED ) ;
/* Intentional fallthrough */
case STATE_SAFETY_TIMER_EXPIRED :
/* We exit this state when charger is removed */
break ;
case STATE_NORMAL_INIT :
2013-02-14 16:39:15 +04:00
if ( ( di - > chg_info . charger_type & USB_CHG ) & &
di - > usb_chg - > power_path ) {
if ( di - > batt_data . volt >
( di - > bm - > fg_params - > lowbat_threshold +
BAT_PLUS_MARGIN ) ) {
ab8540_chargalg_usb_pre_chg_en ( di , false ) ;
ab8540_chargalg_usb_pp_en ( di , false ) ;
} else {
ab8540_chargalg_usb_pp_en ( di , true ) ;
ab8540_chargalg_usb_pre_chg_en ( di , true ) ;
abx500_chargalg_state_to ( di ,
STATE_USB_PP_PRE_CHARGE ) ;
break ;
}
}
2013-02-15 14:58:58 +04:00
if ( di - > curr_status . curr_step = = CHARGALG_CURR_STEP_LOW )
abx500_chargalg_stop_charging ( di ) ;
else {
curr_step_lvl = di - > bm - > bat_type [
di - > bm - > batt_id ] . normal_cur_lvl
* di - > curr_status . curr_step
/ CHARGALG_CURR_STEP_HIGH ;
abx500_chargalg_start_charging ( di ,
di - > bm - > bat_type [ di - > bm - > batt_id ]
. normal_vol_lvl , curr_step_lvl ) ;
}
2012-02-29 20:24:25 +04:00
abx500_chargalg_state_to ( di , STATE_NORMAL ) ;
abx500_chargalg_start_safety_timer ( di ) ;
abx500_chargalg_stop_maintenance_timer ( di ) ;
init_maxim_chg_curr ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_CHARGING ;
di - > eoc_cnt = 0 ;
di - > maintenance_chg = false ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
break ;
2013-02-14 16:39:15 +04:00
case STATE_USB_PP_PRE_CHARGE :
if ( di - > batt_data . volt >
( di - > bm - > fg_params - > lowbat_threshold +
BAT_PLUS_MARGIN ) )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
2012-02-29 20:24:25 +04:00
case STATE_NORMAL :
handle_maxim_chg_curr ( di ) ;
if ( di - > charge_status = = POWER_SUPPLY_STATUS_FULL & &
di - > maintenance_chg ) {
2012-11-30 14:09:42 +04:00
if ( di - > bm - > no_maintenance )
2012-02-29 20:24:25 +04:00
abx500_chargalg_state_to ( di ,
STATE_WAIT_FOR_RECHARGE_INIT ) ;
else
abx500_chargalg_state_to ( di ,
STATE_MAINTENANCE_A_INIT ) ;
}
break ;
/* This state will be used when the maintenance state is disabled */
case STATE_WAIT_FOR_RECHARGE_INIT :
abx500_chargalg_hold_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_WAIT_FOR_RECHARGE ) ;
/* Intentional fallthrough */
case STATE_WAIT_FOR_RECHARGE :
2013-01-11 17:12:54 +04:00
if ( di - > batt_data . percent < =
di - > bm - > bat_type [ di - > bm - > batt_id ] .
recharge_cap )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
2012-02-29 20:24:25 +04:00
break ;
case STATE_MAINTENANCE_A_INIT :
abx500_chargalg_stop_safety_timer ( di ) ;
abx500_chargalg_start_maintenance_timer ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_a_chg_timer_h ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_start_charging ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_a_vol_lvl ,
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_a_cur_lvl ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_state_to ( di , STATE_MAINTENANCE_A ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
/* Intentional fallthrough*/
case STATE_MAINTENANCE_A :
if ( di - > events . maintenance_timer_expired ) {
abx500_chargalg_stop_maintenance_timer ( di ) ;
abx500_chargalg_state_to ( di , STATE_MAINTENANCE_B_INIT ) ;
}
break ;
case STATE_MAINTENANCE_B_INIT :
abx500_chargalg_start_maintenance_timer ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_b_chg_timer_h ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_start_charging ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_b_vol_lvl ,
di - > bm - > bat_type [
di - > bm - > batt_id ] . maint_b_cur_lvl ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_state_to ( di , STATE_MAINTENANCE_B ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
/* Intentional fallthrough*/
case STATE_MAINTENANCE_B :
if ( di - > events . maintenance_timer_expired ) {
abx500_chargalg_stop_maintenance_timer ( di ) ;
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
}
break ;
case STATE_TEMP_LOWHIGH_INIT :
abx500_chargalg_start_charging ( di ,
2012-11-30 14:09:42 +04:00
di - > bm - > bat_type [
di - > bm - > batt_id ] . low_high_vol_lvl ,
di - > bm - > bat_type [
di - > bm - > batt_id ] . low_high_cur_lvl ) ;
2012-02-29 20:24:25 +04:00
abx500_chargalg_stop_maintenance_timer ( di ) ;
di - > charge_status = POWER_SUPPLY_STATUS_CHARGING ;
abx500_chargalg_state_to ( di , STATE_TEMP_LOWHIGH ) ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
/* Intentional fallthrough */
case STATE_TEMP_LOWHIGH :
if ( ! di - > events . btemp_lowhigh )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_WD_EXPIRED_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_WD_EXPIRED ) ;
/* Intentional fallthrough */
case STATE_WD_EXPIRED :
if ( ! di - > events . ac_wd_expired & &
! di - > events . usb_wd_expired )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
case STATE_TEMP_UNDEROVER_INIT :
abx500_chargalg_stop_charging ( di ) ;
abx500_chargalg_state_to ( di , STATE_TEMP_UNDEROVER ) ;
/* Intentional fallthrough */
case STATE_TEMP_UNDEROVER :
if ( ! di - > events . btemp_underover )
abx500_chargalg_state_to ( di , STATE_NORMAL_INIT ) ;
break ;
}
/* Start charging directly if the new state is a charge state */
if ( di - > charge_state = = STATE_NORMAL_INIT | |
di - > charge_state = = STATE_MAINTENANCE_A_INIT | |
di - > charge_state = = STATE_MAINTENANCE_B_INIT )
queue_work ( di - > chargalg_wq , & di - > chargalg_work ) ;
}
/**
* abx500_chargalg_periodic_work ( ) - Periodic work for the algorithm
* @ work : pointer to the work_struct structure
*
* Work queue function for the charging algorithm
*/
static void abx500_chargalg_periodic_work ( struct work_struct * work )
{
struct abx500_chargalg * di = container_of ( work ,
struct abx500_chargalg , chargalg_periodic_work . work ) ;
abx500_chargalg_algorithm ( di ) ;
/*
* If a charger is connected then the battery has to be monitored
* frequently , else the work can be delayed .
*/
if ( di - > chg_info . conn_chg )
queue_delayed_work ( di - > chargalg_wq ,
& di - > chargalg_periodic_work ,
2012-11-30 14:09:42 +04:00
di - > bm - > interval_charging * HZ ) ;
2012-02-29 20:24:25 +04:00
else
queue_delayed_work ( di - > chargalg_wq ,
& di - > chargalg_periodic_work ,
2012-11-30 14:09:42 +04:00
di - > bm - > interval_not_charging * HZ ) ;
2012-02-29 20:24:25 +04:00
}
/**
* abx500_chargalg_wd_work ( ) - periodic work to kick the charger watchdog
* @ work : pointer to the work_struct structure
*
* Work queue function for kicking the charger watchdog
*/
static void abx500_chargalg_wd_work ( struct work_struct * work )
{
int ret ;
struct abx500_chargalg * di = container_of ( work ,
struct abx500_chargalg , chargalg_wd_work . work ) ;
dev_dbg ( di - > dev , " abx500_chargalg_wd_work \n " ) ;
ret = abx500_chargalg_kick_watchdog ( di ) ;
if ( ret < 0 )
dev_err ( di - > dev , " failed to kick watchdog \n " ) ;
queue_delayed_work ( di - > chargalg_wq ,
& di - > chargalg_wd_work , CHG_WD_INTERVAL ) ;
}
/**
* abx500_chargalg_work ( ) - Work to run the charging algorithm instantly
* @ work : pointer to the work_struct structure
*
* Work queue function for calling the charging algorithm
*/
static void abx500_chargalg_work ( struct work_struct * work )
{
struct abx500_chargalg * di = container_of ( work ,
struct abx500_chargalg , chargalg_work ) ;
abx500_chargalg_algorithm ( di ) ;
}
/**
* abx500_chargalg_get_property ( ) - get the chargalg properties
* @ psy : pointer to the power_supply structure
* @ psp : pointer to the power_supply_property structure
* @ val : pointer to the power_supply_propval union
*
* This function gets called when an application tries to get the
* chargalg properties by reading the sysfs files .
* status : charging / discharging / full / unknown
* health : health of the battery
* Returns error code in case of failure else 0 on success
*/
static int abx500_chargalg_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
2015-03-12 10:44:11 +03:00
struct abx500_chargalg * di = power_supply_get_drvdata ( psy ) ;
2012-02-29 20:24:25 +04:00
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
val - > intval = di - > charge_status ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
if ( di - > events . batt_ovv ) {
val - > intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE ;
} else if ( di - > events . btemp_underover ) {
2012-11-30 14:09:42 +04:00
if ( di - > batt_data . temp < = di - > bm - > temp_under )
2012-02-29 20:24:25 +04:00
val - > intval = POWER_SUPPLY_HEALTH_COLD ;
else
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
2013-01-17 17:49:45 +04:00
} else if ( di - > charge_state = = STATE_SAFETY_TIMER_EXPIRED | |
di - > charge_state = = STATE_SAFETY_TIMER_EXPIRED_INIT ) {
val - > intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
2012-02-29 20:24:25 +04:00
} else {
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
}
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/* Exposure to the sysfs interface */
2013-02-15 14:58:58 +04:00
static ssize_t abx500_chargalg_curr_step_show ( struct abx500_chargalg * di ,
char * buf )
2013-01-21 15:22:56 +04:00
{
2013-02-15 14:58:58 +04:00
return sprintf ( buf , " %d \n " , di - > curr_status . curr_step ) ;
}
static ssize_t abx500_chargalg_curr_step_store ( struct abx500_chargalg * di ,
const char * buf , size_t length )
{
long int param ;
int ret ;
ret = kstrtol ( buf , 10 , & param ) ;
if ( ret < 0 )
return ret ;
2013-01-21 15:22:56 +04:00
2013-02-15 14:58:58 +04:00
di - > curr_status . curr_step = param ;
if ( di - > curr_status . curr_step > = CHARGALG_CURR_STEP_LOW & &
di - > curr_status . curr_step < = CHARGALG_CURR_STEP_HIGH ) {
di - > curr_status . curr_step_change = true ;
queue_work ( di - > chargalg_wq , & di - > chargalg_work ) ;
} else
dev_info ( di - > dev , " Wrong current step \n "
" Enter 0. Disable AC/USB Charging \n "
" 1--100. Set AC/USB charging current step \n "
" 100. Enable AC/USB Charging \n " ) ;
return strlen ( buf ) ;
}
static ssize_t abx500_chargalg_en_show ( struct abx500_chargalg * di ,
char * buf )
{
2013-01-21 15:22:56 +04:00
return sprintf ( buf , " %d \n " ,
di - > susp_status . ac_suspended & &
di - > susp_status . usb_suspended ) ;
}
2013-02-15 14:58:58 +04:00
static ssize_t abx500_chargalg_en_store ( struct abx500_chargalg * di ,
const char * buf , size_t length )
2012-02-29 20:24:25 +04:00
{
long int param ;
int ac_usb ;
int ret ;
2013-02-15 14:58:58 +04:00
ret = kstrtol ( buf , 10 , & param ) ;
if ( ret < 0 )
return ret ;
ac_usb = param ;
switch ( ac_usb ) {
case 0 :
/* Disable charging */
di - > susp_status . ac_suspended = true ;
di - > susp_status . usb_suspended = true ;
di - > susp_status . suspended_change = true ;
/* Trigger a state change */
queue_work ( di - > chargalg_wq ,
& di - > chargalg_work ) ;
break ;
case 1 :
/* Enable AC Charging */
di - > susp_status . ac_suspended = false ;
di - > susp_status . suspended_change = true ;
/* Trigger a state change */
queue_work ( di - > chargalg_wq ,
& di - > chargalg_work ) ;
2012-02-29 20:24:25 +04:00
break ;
2013-02-15 14:58:58 +04:00
case 2 :
/* Enable USB charging */
di - > susp_status . usb_suspended = false ;
di - > susp_status . suspended_change = true ;
/* Trigger a state change */
queue_work ( di - > chargalg_wq ,
& di - > chargalg_work ) ;
break ;
default :
dev_info ( di - > dev , " Wrong input \n "
" Enter 0. Disable AC/USB Charging \n "
" 1. Enable AC charging \n "
" 2. Enable USB Charging \n " ) ;
2012-02-29 20:24:25 +04:00
} ;
return strlen ( buf ) ;
}
2013-02-15 14:58:58 +04:00
static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger =
__ATTR ( chargalg , 0644 , abx500_chargalg_en_show ,
abx500_chargalg_en_store ) ;
static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step =
__ATTR ( chargalg_curr_step , 0644 , abx500_chargalg_curr_step_show ,
abx500_chargalg_curr_step_store ) ;
static ssize_t abx500_chargalg_sysfs_show ( struct kobject * kobj ,
struct attribute * attr , char * buf )
{
struct abx500_chargalg_sysfs_entry * entry = container_of ( attr ,
struct abx500_chargalg_sysfs_entry , attr ) ;
struct abx500_chargalg * di = container_of ( kobj ,
struct abx500_chargalg , chargalg_kobject ) ;
if ( ! entry - > show )
return - EIO ;
return entry - > show ( di , buf ) ;
}
static ssize_t abx500_chargalg_sysfs_charger ( struct kobject * kobj ,
struct attribute * attr , const char * buf , size_t length )
2012-02-29 20:24:25 +04:00
{
2013-02-15 14:58:58 +04:00
struct abx500_chargalg_sysfs_entry * entry = container_of ( attr ,
struct abx500_chargalg_sysfs_entry , attr ) ;
struct abx500_chargalg * di = container_of ( kobj ,
struct abx500_chargalg , chargalg_kobject ) ;
if ( ! entry - > store )
return - EIO ;
return entry - > store ( di , buf , length ) ;
}
2012-02-29 20:24:25 +04:00
static struct attribute * abx500_chargalg_chg [ ] = {
2013-02-15 14:58:58 +04:00
& abx500_chargalg_en_charger . attr ,
& abx500_chargalg_curr_step . attr ,
NULL ,
2012-02-29 20:24:25 +04:00
} ;
2012-03-14 04:43:11 +04:00
static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
2013-01-21 15:22:56 +04:00
. show = abx500_chargalg_sysfs_show ,
2012-02-29 20:24:25 +04:00
. store = abx500_chargalg_sysfs_charger ,
} ;
static struct kobj_type abx500_chargalg_ktype = {
. sysfs_ops = & abx500_chargalg_sysfs_ops ,
. default_attrs = abx500_chargalg_chg ,
} ;
/**
* abx500_chargalg_sysfs_exit ( ) - de - init of sysfs entry
* @ di : pointer to the struct abx500_chargalg
*
* This function removes the entry in sysfs .
*/
static void abx500_chargalg_sysfs_exit ( struct abx500_chargalg * di )
{
kobject_del ( & di - > chargalg_kobject ) ;
}
/**
* abx500_chargalg_sysfs_init ( ) - init of sysfs entry
* @ di : pointer to the struct abx500_chargalg
*
* This function adds an entry in sysfs .
* Returns error code in case of failure else 0 ( on success )
*/
static int abx500_chargalg_sysfs_init ( struct abx500_chargalg * di )
{
int ret = 0 ;
ret = kobject_init_and_add ( & di - > chargalg_kobject ,
& abx500_chargalg_ktype ,
NULL , " abx500_chargalg " ) ;
if ( ret < 0 )
dev_err ( di - > dev , " failed to create sysfs entry \n " ) ;
return ret ;
}
/* Exposure to the sysfs interface <<END>> */
# if defined(CONFIG_PM)
static int abx500_chargalg_resume ( struct platform_device * pdev )
{
struct abx500_chargalg * di = platform_get_drvdata ( pdev ) ;
/* Kick charger watchdog if charging (any charger online) */
if ( di - > chg_info . online_chg )
queue_delayed_work ( di - > chargalg_wq , & di - > chargalg_wd_work , 0 ) ;
/*
* Run the charging algorithm directly to be sure we don ' t
* do it too seldom
*/
queue_delayed_work ( di - > chargalg_wq , & di - > chargalg_periodic_work , 0 ) ;
return 0 ;
}
static int abx500_chargalg_suspend ( struct platform_device * pdev ,
pm_message_t state )
{
struct abx500_chargalg * di = platform_get_drvdata ( pdev ) ;
if ( di - > chg_info . online_chg )
cancel_delayed_work_sync ( & di - > chargalg_wd_work ) ;
cancel_delayed_work_sync ( & di - > chargalg_periodic_work ) ;
return 0 ;
}
# else
# define abx500_chargalg_suspend NULL
# define abx500_chargalg_resume NULL
# endif
2012-11-19 22:26:07 +04:00
static int abx500_chargalg_remove ( struct platform_device * pdev )
2012-02-29 20:24:25 +04:00
{
struct abx500_chargalg * di = platform_get_drvdata ( pdev ) ;
/* sysfs interface to enable/disbale charging from user space */
abx500_chargalg_sysfs_exit ( di ) ;
2013-02-13 19:15:03 +04:00
hrtimer_cancel ( & di - > safety_timer ) ;
hrtimer_cancel ( & di - > maintenance_timer ) ;
cancel_delayed_work_sync ( & di - > chargalg_periodic_work ) ;
cancel_delayed_work_sync ( & di - > chargalg_wd_work ) ;
cancel_work_sync ( & di - > chargalg_work ) ;
2012-02-29 20:24:25 +04:00
/* Delete the work queue */
destroy_workqueue ( di - > chargalg_wq ) ;
2015-03-12 10:44:11 +03:00
power_supply_unregister ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
return 0 ;
}
2012-10-31 19:40:33 +04:00
static char * supply_interface [ ] = {
" ab8500_fg " ,
} ;
2015-03-12 10:44:11 +03:00
static const struct power_supply_desc abx500_chargalg_desc = {
. name = " abx500_chargalg " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = abx500_chargalg_props ,
. num_properties = ARRAY_SIZE ( abx500_chargalg_props ) ,
. get_property = abx500_chargalg_get_property ,
. external_power_changed = abx500_chargalg_external_power_changed ,
} ;
2012-11-19 22:22:23 +04:00
static int abx500_chargalg_probe ( struct platform_device * pdev )
2012-02-29 20:24:25 +04:00
{
2012-10-31 19:40:33 +04:00
struct device_node * np = pdev - > dev . of_node ;
2012-11-30 14:57:14 +04:00
struct abx500_bm_data * plat = pdev - > dev . platform_data ;
2015-03-12 10:44:02 +03:00
struct power_supply_config psy_cfg = { } ;
2012-10-31 19:40:33 +04:00
struct abx500_chargalg * di ;
2012-02-29 20:24:25 +04:00
int ret = 0 ;
2012-10-31 19:40:33 +04:00
di = devm_kzalloc ( & pdev - > dev , sizeof ( * di ) , GFP_KERNEL ) ;
if ( ! di ) {
dev_err ( & pdev - > dev , " %s no mem for ab8500_chargalg \n " , __func__ ) ;
2012-02-29 20:24:25 +04:00
return - ENOMEM ;
2012-10-31 19:40:33 +04:00
}
2012-11-30 14:57:14 +04:00
if ( ! plat ) {
dev_err ( & pdev - > dev , " no battery management data supplied \n " ) ;
return - EINVAL ;
}
di - > bm = plat ;
if ( np ) {
ret = ab8500_bm_of_probe ( & pdev - > dev , np , di - > bm ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get battery information \n " ) ;
return ret ;
2012-10-31 19:40:33 +04:00
}
}
2012-02-29 20:24:25 +04:00
2013-02-15 14:53:57 +04:00
/* get device struct and parent */
2012-02-29 20:24:25 +04:00
di - > dev = & pdev - > dev ;
2013-02-15 14:53:57 +04:00
di - > parent = dev_get_drvdata ( pdev - > dev . parent ) ;
2012-02-29 20:24:25 +04:00
2015-03-12 10:44:02 +03:00
psy_cfg . supplied_to = supply_interface ;
psy_cfg . num_supplicants = ARRAY_SIZE ( supply_interface ) ;
2015-03-12 10:44:11 +03:00
psy_cfg . drv_data = di ;
2015-03-12 10:44:02 +03:00
2012-02-29 20:24:25 +04:00
/* Initilialize safety timer */
2013-02-13 19:15:03 +04:00
hrtimer_init ( & di - > safety_timer , CLOCK_REALTIME , HRTIMER_MODE_ABS ) ;
2012-02-29 20:24:25 +04:00
di - > safety_timer . function = abx500_chargalg_safety_timer_expired ;
/* Initilialize maintenance timer */
2013-02-13 19:15:03 +04:00
hrtimer_init ( & di - > maintenance_timer , CLOCK_REALTIME , HRTIMER_MODE_ABS ) ;
2012-02-29 20:24:25 +04:00
di - > maintenance_timer . function =
abx500_chargalg_maintenance_timer_expired ;
/* Create a work queue for the chargalg */
2016-08-13 19:16:10 +03:00
di - > chargalg_wq = alloc_ordered_workqueue ( " abx500_chargalg_wq " ,
WQ_MEM_RECLAIM ) ;
2012-02-29 20:24:25 +04:00
if ( di - > chargalg_wq = = NULL ) {
dev_err ( di - > dev , " failed to create work queue \n " ) ;
2012-10-31 19:40:33 +04:00
return - ENOMEM ;
2012-02-29 20:24:25 +04:00
}
/* Init work for chargalg */
2012-08-22 00:18:23 +04:00
INIT_DEFERRABLE_WORK ( & di - > chargalg_periodic_work ,
2012-02-29 20:24:25 +04:00
abx500_chargalg_periodic_work ) ;
2012-08-22 00:18:23 +04:00
INIT_DEFERRABLE_WORK ( & di - > chargalg_wd_work ,
2012-02-29 20:24:25 +04:00
abx500_chargalg_wd_work ) ;
/* Init work for chargalg */
INIT_WORK ( & di - > chargalg_work , abx500_chargalg_work ) ;
/* To detect charger at startup */
di - > chg_info . prev_conn_chg = - 1 ;
/* Register chargalg power supply class */
2015-03-12 10:44:11 +03:00
di - > chargalg_psy = power_supply_register ( di - > dev , & abx500_chargalg_desc ,
& psy_cfg ) ;
if ( IS_ERR ( di - > chargalg_psy ) ) {
2012-02-29 20:24:25 +04:00
dev_err ( di - > dev , " failed to register chargalg psy \n " ) ;
2015-03-12 10:44:11 +03:00
ret = PTR_ERR ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
goto free_chargalg_wq ;
}
platform_set_drvdata ( pdev , di ) ;
/* sysfs interface to enable/disable charging from user space */
ret = abx500_chargalg_sysfs_init ( di ) ;
if ( ret ) {
dev_err ( di - > dev , " failed to create sysfs entry \n " ) ;
goto free_psy ;
}
2013-02-15 14:58:58 +04:00
di - > curr_status . curr_step = CHARGALG_CURR_STEP_HIGH ;
2012-02-29 20:24:25 +04:00
/* Run the charging algorithm */
queue_delayed_work ( di - > chargalg_wq , & di - > chargalg_periodic_work , 0 ) ;
dev_info ( di - > dev , " probe success \n " ) ;
return ret ;
free_psy :
2015-03-12 10:44:11 +03:00
power_supply_unregister ( di - > chargalg_psy ) ;
2012-02-29 20:24:25 +04:00
free_chargalg_wq :
destroy_workqueue ( di - > chargalg_wq ) ;
return ret ;
}
2012-10-31 19:40:33 +04:00
static const struct of_device_id ab8500_chargalg_match [ ] = {
{ . compatible = " stericsson,ab8500-chargalg " , } ,
{ } ,
} ;
2012-02-29 20:24:25 +04:00
static struct platform_driver abx500_chargalg_driver = {
. probe = abx500_chargalg_probe ,
2012-11-19 22:20:40 +04:00
. remove = abx500_chargalg_remove ,
2012-02-29 20:24:25 +04:00
. suspend = abx500_chargalg_suspend ,
. resume = abx500_chargalg_resume ,
. driver = {
2012-10-31 19:40:33 +04:00
. name = " ab8500-chargalg " ,
. of_match_table = ab8500_chargalg_match ,
2012-02-29 20:24:25 +04:00
} ,
} ;
2012-10-11 13:08:28 +04:00
module_platform_driver ( abx500_chargalg_driver ) ;
2012-02-29 20:24:25 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Johan Palsson, Karl Komierowski " ) ;
MODULE_ALIAS ( " platform:abx500-chargalg " ) ;
MODULE_DESCRIPTION ( " abx500 battery charging algorithm " ) ;