2012-04-26 12:00:04 +04:00
/*
* Copyright 2012 ST Ericsson .
*
2013-01-23 18:33:47 +04:00
* Power supply driver for ST Ericsson pm2xxx_charger charger
*
2012-04-26 12:00:04 +04:00
* 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 .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/device.h>
# 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/regulator/consumer.h>
# include <linux/err.h>
# include <linux/i2c.h>
# include <linux/workqueue.h>
# include <linux/kobject.h>
# include <linux/mfd/abx500.h>
# include <linux/mfd/abx500/ab8500.h>
# include <linux/mfd/abx500/ab8500-bm.h>
# include <linux/mfd/abx500/ab8500-gpadc.h>
# include <linux/mfd/abx500/ux500_chargalg.h>
# include <linux/pm2301_charger.h>
2013-01-23 18:33:47 +04:00
# include "pm2301_charger.h"
2012-04-26 12:00:04 +04:00
# define to_pm2xxx_charger_ac_device_info(x) container_of((x), \
struct pm2xxx_charger , ac_chg )
static int pm2xxx_interrupt_registers [ ] = {
PM2XXX_REG_INT1 ,
PM2XXX_REG_INT2 ,
PM2XXX_REG_INT3 ,
PM2XXX_REG_INT4 ,
PM2XXX_REG_INT5 ,
PM2XXX_REG_INT6 ,
} ;
static enum power_supply_property pm2xxx_charger_ac_props [ ] = {
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_VOLTAGE_AVG ,
} ;
static int pm2xxx_charger_voltage_map [ ] = {
3500 ,
3525 ,
3550 ,
3575 ,
3600 ,
3625 ,
3650 ,
3675 ,
3700 ,
3725 ,
3750 ,
3775 ,
3800 ,
3825 ,
3850 ,
3875 ,
3900 ,
3925 ,
3950 ,
3975 ,
4000 ,
4025 ,
4050 ,
4075 ,
4100 ,
4125 ,
4150 ,
4175 ,
4200 ,
4225 ,
4250 ,
4275 ,
4300 ,
} ;
static int pm2xxx_charger_current_map [ ] = {
200 ,
200 ,
400 ,
600 ,
800 ,
1000 ,
1200 ,
1400 ,
1600 ,
1800 ,
2000 ,
2200 ,
2400 ,
2600 ,
2800 ,
3000 ,
} ;
static const struct i2c_device_id pm2xxx_ident [ ] = {
{ " pm2301 " , 0 } ,
{ }
} ;
static int pm2xxx_reg_read ( struct pm2xxx_charger * pm2 , int reg , u8 * val )
{
int ret ;
ret = i2c_smbus_read_i2c_block_data ( pm2 - > config . pm2xxx_i2c , reg ,
1 , val ) ;
if ( ret < 0 )
dev_err ( pm2 - > dev , " Error reading register at 0x%x \n " , reg ) ;
return ret ;
}
static int pm2xxx_reg_write ( struct pm2xxx_charger * pm2 , int reg , u8 val )
{
int ret ;
ret = i2c_smbus_write_i2c_block_data ( pm2 - > config . pm2xxx_i2c , reg ,
1 , & val ) ;
if ( ret < 0 )
dev_err ( pm2 - > dev , " Error writing register at 0x%x \n " , reg ) ;
return ret ;
}
static int pm2xxx_charging_enable_mngt ( struct pm2xxx_charger * pm2 )
{
int ret ;
/* Enable charging */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG2 ,
( PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA ) ) ;
return ret ;
}
static int pm2xxx_charging_disable_mngt ( struct pm2xxx_charger * pm2 )
{
int ret ;
/* Disable charging */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG2 ,
( PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS ) ) ;
return ret ;
}
static int pm2xxx_charger_batt_therm_mngt ( struct pm2xxx_charger * pm2 , int val )
{
queue_work ( pm2 - > charger_wq , & pm2 - > check_main_thermal_prot_work ) ;
return 0 ;
}
int pm2xxx_charger_die_therm_mngt ( struct pm2xxx_charger * pm2 , int val )
{
queue_work ( pm2 - > charger_wq , & pm2 - > check_main_thermal_prot_work ) ;
return 0 ;
}
static int pm2xxx_charger_ovv_mngt ( struct pm2xxx_charger * pm2 , int val )
{
int ret = 0 ;
pm2 - > failure_input_ovv + + ;
if ( pm2 - > failure_input_ovv < 4 ) {
ret = pm2xxx_charging_enable_mngt ( pm2 ) ;
goto out ;
} else {
pm2 - > failure_input_ovv = 0 ;
dev_err ( pm2 - > dev , " Overvoltage detected \n " ) ;
pm2 - > flags . ovv = true ;
power_supply_changed ( & pm2 - > ac_chg . psy ) ;
}
out :
return ret ;
}
static int pm2xxx_charger_wd_exp_mngt ( struct pm2xxx_charger * pm2 , int val )
{
dev_dbg ( pm2 - > dev , " 20 minutes watchdog occured \n " ) ;
pm2 - > ac . wd_expired = true ;
power_supply_changed ( & pm2 - > ac_chg . psy ) ;
return 0 ;
}
static int pm2xxx_charger_vbat_lsig_mngt ( struct pm2xxx_charger * pm2 , int val )
{
switch ( val ) {
case PM2XXX_INT1_ITVBATLOWR :
dev_dbg ( pm2 - > dev , " VBAT grows above VBAT_LOW level \n " ) ;
break ;
case PM2XXX_INT1_ITVBATLOWF :
dev_dbg ( pm2 - > dev , " VBAT drops below VBAT_LOW level \n " ) ;
break ;
default :
dev_err ( pm2 - > dev , " Unknown VBAT level \n " ) ;
}
return 0 ;
}
static int pm2xxx_charger_bat_disc_mngt ( struct pm2xxx_charger * pm2 , int val )
{
dev_dbg ( pm2 - > dev , " battery disconnected \n " ) ;
2012-05-14 18:50:29 +04:00
return 0 ;
2012-04-26 12:00:04 +04:00
}
static int pm2xxx_charger_detection ( struct pm2xxx_charger * pm2 , u8 * val )
{
int ret = 0 ;
ret = pm2xxx_reg_read ( pm2 , PM2XXX_SRCE_REG_INT2 , val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev , " Charger detection failed \n " ) ;
goto out ;
}
* val & = ( PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG ) ;
out :
return ret ;
}
static int pm2xxx_charger_itv_pwr_plug_mngt ( struct pm2xxx_charger * pm2 , int val )
{
int ret ;
u8 read_val ;
/*
* Since we can ' t be sure that the events are received
* synchronously , we have the check if the main charger is
* connected by reading the interrupt source register .
*/
ret = pm2xxx_charger_detection ( pm2 , & read_val ) ;
if ( ( ret = = 0 ) & & read_val ) {
pm2 - > ac . charger_connected = 1 ;
pm2 - > ac_conn = true ;
queue_work ( pm2 - > charger_wq , & pm2 - > ac_work ) ;
}
return ret ;
}
static int pm2xxx_charger_itv_pwr_unplug_mngt ( struct pm2xxx_charger * pm2 ,
int val )
{
pm2 - > ac . charger_connected = 0 ;
queue_work ( pm2 - > charger_wq , & pm2 - > ac_work ) ;
return 0 ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg0 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
2012-04-26 12:00:04 +04:00
int ret = 0 ;
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF ) ) {
ret = pm2xxx_charger_vbat_lsig_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF ) ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & PM2XXX_INT1_ITVBATDISCONNECT ) {
2012-04-26 12:00:04 +04:00
ret = pm2xxx_charger_bat_disc_mngt ( pm2 ,
PM2XXX_INT1_ITVBATDISCONNECT ) ;
}
return ret ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg1 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
2012-04-26 12:00:04 +04:00
int ret = 0 ;
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " Main charger plugged \n " ) ;
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_itv_pwr_plug_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG ) ) ;
}
2012-05-14 18:50:29 +04:00
if ( val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG ) ) {
dev_dbg ( pm2 - > dev , " Main charger unplugged \n " ) ;
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_itv_pwr_unplug_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT2_ITVPWR1UNPLUG |
PM2XXX_INT2_ITVPWR2UNPLUG ) ) ;
}
return ret ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg2 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
2012-04-26 12:00:04 +04:00
int ret = 0 ;
2012-05-14 18:50:29 +04:00
if ( val & PM2XXX_INT3_ITAUTOTIMEOUTWD )
ret = pm2xxx_charger_wd_exp_mngt ( pm2 , val ) ;
2012-04-26 12:00:04 +04:00
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT3_ITCHPRECHARGEWD |
2012-04-26 12:00:04 +04:00
PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD ) ) {
dev_dbg ( pm2 - > dev ,
" Watchdog occured for precharge, CC and CV charge \n " ) ;
}
return ret ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg3 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
2012-04-26 12:00:04 +04:00
int ret = 0 ;
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_ITCHARGINGON ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev ,
" chargind operation has started \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_ITVRESUME ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev ,
" battery discharged down to VResume threshold \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_ITBATTFULL ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " battery fully detected \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_ITCVPHASE ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " CV phase enter with 0.5C charging \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV ) ) {
2012-04-26 12:00:04 +04:00
pm2 - > failure_case = VPWR_OVV ;
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_ovv_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV ) ) ;
dev_dbg ( pm2 - > dev , " VPWR/VSYSTEM overvoltage detected \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT4_S_ITBATTEMPCOLD |
2012-04-26 12:00:04 +04:00
PM2XXX_INT4_S_ITBATTEMPHOT ) ) {
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_batt_therm_mngt ( pm2 , val &
( PM2XXX_INT4_S_ITBATTEMPCOLD |
PM2XXX_INT4_S_ITBATTEMPHOT ) ) ;
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " BTEMP is too Low/High \n " ) ;
}
return ret ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg4 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
2012-04-26 12:00:04 +04:00
int ret = 0 ;
2012-05-14 18:50:29 +04:00
if ( val & PM2XXX_INT5_ITVSYSTEMOVV ) {
2012-04-26 12:00:04 +04:00
pm2 - > failure_case = VSYSTEM_OVV ;
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_ovv_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
PM2XXX_INT5_ITVSYSTEMOVV ) ;
dev_dbg ( pm2 - > dev , " VSYSTEM overvoltage detected \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT5_ITTHERMALWARNINGFALL |
2012-04-26 12:00:04 +04:00
PM2XXX_INT5_ITTHERMALWARNINGRISE |
PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
PM2XXX_INT5_ITTHERMALSHUTDOWNRISE ) ) {
dev_dbg ( pm2 - > dev , " BTEMP die temperature is too Low/High \n " ) ;
2012-05-14 18:50:29 +04:00
ret = pm2xxx_charger_die_therm_mngt ( pm2 , val &
2012-04-26 12:00:04 +04:00
( PM2XXX_INT5_ITTHERMALWARNINGFALL |
PM2XXX_INT5_ITTHERMALWARNINGRISE |
PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
PM2XXX_INT5_ITTHERMALSHUTDOWNRISE ) ) ;
}
return ret ;
}
2012-05-14 18:50:29 +04:00
static int pm2_int_reg5 ( void * pm2_data , int val )
2012-04-26 12:00:04 +04:00
{
2012-05-14 18:50:29 +04:00
struct pm2xxx_charger * pm2 = pm2_data ;
int ret = 0 ;
2012-04-26 12:00:04 +04:00
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " VMPWR drop to VBAT level \n " ) ;
}
2012-05-14 18:50:29 +04:00
if ( val & ( PM2XXX_INT6_ITVPWR2VALIDRISE |
PM2XXX_INT6_ITVPWR1VALIDRISE |
PM2XXX_INT6_ITVPWR2VALIDFALL |
PM2XXX_INT6_ITVPWR1VALIDFALL ) ) {
2012-04-26 12:00:04 +04:00
dev_dbg ( pm2 - > dev , " Falling/Rising edge on WPWR1/2 \n " ) ;
}
2012-05-14 18:50:29 +04:00
return ret ;
2012-04-26 12:00:04 +04:00
}
static irqreturn_t pm2xxx_irq_int ( int irq , void * data )
{
struct pm2xxx_charger * pm2 = data ;
2012-05-14 18:50:29 +04:00
struct pm2xxx_interrupts * interrupt = pm2 - > pm2_int ;
int i ;
2012-04-26 12:00:04 +04:00
2012-05-14 18:50:29 +04:00
for ( i = 0 ; i < PM2XXX_NUM_INT_REG ; i + + ) {
pm2xxx_reg_read ( pm2 ,
pm2xxx_interrupt_registers [ i ] ,
& ( interrupt - > reg [ i ] ) ) ;
2012-04-26 12:00:04 +04:00
2012-05-14 18:50:29 +04:00
if ( interrupt - > reg [ i ] > 0 )
interrupt - > handler [ i ] ( pm2 , interrupt - > reg [ i ] ) ;
}
2012-04-26 12:00:04 +04:00
return IRQ_HANDLED ;
}
static int pm2xxx_charger_get_ac_cv ( struct pm2xxx_charger * pm2 )
{
int ret = 0 ;
u8 val ;
if ( pm2 - > ac . charger_connected & & pm2 - > ac . charger_online ) {
ret = pm2xxx_reg_read ( pm2 , PM2XXX_SRCE_REG_INT4 , & val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev , " %s pm2xxx read failed \n " , __func__ ) ;
goto out ;
}
if ( val & PM2XXX_INT4_S_ITCVPHASE )
ret = PM2XXX_CONST_VOLT ;
else
ret = PM2XXX_CONST_CURR ;
}
out :
return ret ;
}
static int pm2xxx_current_to_regval ( int curr )
{
int i ;
if ( curr < pm2xxx_charger_current_map [ 0 ] )
return 0 ;
for ( i = 1 ; i < ARRAY_SIZE ( pm2xxx_charger_current_map ) ; i + + ) {
if ( curr < pm2xxx_charger_current_map [ i ] )
return ( i - 1 ) ;
}
i = ARRAY_SIZE ( pm2xxx_charger_current_map ) - 1 ;
if ( curr = = pm2xxx_charger_current_map [ i ] )
return i ;
else
return - EINVAL ;
}
static int pm2xxx_voltage_to_regval ( int curr )
{
int i ;
if ( curr < pm2xxx_charger_voltage_map [ 0 ] )
return 0 ;
for ( i = 1 ; i < ARRAY_SIZE ( pm2xxx_charger_voltage_map ) ; i + + ) {
if ( curr < pm2xxx_charger_voltage_map [ i ] )
return i - 1 ;
}
i = ARRAY_SIZE ( pm2xxx_charger_voltage_map ) - 1 ;
if ( curr = = pm2xxx_charger_voltage_map [ i ] )
return i ;
else
return - EINVAL ;
}
static int pm2xxx_charger_update_charger_current ( struct ux500_charger * charger ,
int ich_out )
{
int ret ;
int curr_index ;
struct pm2xxx_charger * pm2 ;
u8 val ;
if ( charger - > psy . type = = POWER_SUPPLY_TYPE_MAINS )
pm2 = to_pm2xxx_charger_ac_device_info ( charger ) ;
else
return - ENXIO ;
curr_index = pm2xxx_current_to_regval ( ich_out ) ;
if ( curr_index < 0 ) {
dev_err ( pm2 - > dev ,
2012-05-14 18:50:29 +04:00
" Charger current too high, charging not started \n " ) ;
2012-04-26 12:00:04 +04:00
return - ENXIO ;
}
ret = pm2xxx_reg_read ( pm2 , PM2XXX_BATT_CTRL_REG6 , & val ) ;
if ( ret > = 0 ) {
val & = ~ PM2XXX_DIR_CH_CC_CURRENT_MASK ;
val | = curr_index ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG6 , val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev ,
" %s write failed \n " , __func__ ) ;
}
}
else
dev_err ( pm2 - > dev , " %s read failed \n " , __func__ ) ;
return ret ;
}
static int pm2xxx_charger_ac_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct pm2xxx_charger * pm2 ;
pm2 = to_pm2xxx_charger_ac_device_info ( psy_to_ux500_charger ( psy ) ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_HEALTH :
if ( pm2 - > flags . mainextchnotok )
val - > intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
else if ( pm2 - > ac . wd_expired )
val - > intval = POWER_SUPPLY_HEALTH_DEAD ;
else if ( pm2 - > flags . main_thermal_prot )
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
break ;
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = pm2 - > ac . charger_online ;
break ;
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = pm2 - > ac . charger_connected ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_AVG :
pm2 - > ac . cv_active = pm2xxx_charger_get_ac_cv ( pm2 ) ;
val - > intval = pm2 - > ac . cv_active ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int pm2xxx_charging_init ( struct pm2xxx_charger * pm2 )
{
int ret = 0 ;
/* enable CC and CV watchdog */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG3 ,
( PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN ) ) ;
if ( ret < 0 )
return ret ;
/* enable precharge watchdog */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG4 ,
PM2XXX_CH_WD_PRECH_PHASE_60MIN ) ;
2012-05-14 18:50:29 +04:00
/* Disable auto timeout */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG5 ,
PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN ) ;
/*
* EOC current level = 100 mA
* Precharge current level = 100 mA
* CC current level = 1000 mA
*/
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG6 ,
( PM2XXX_DIR_CH_CC_CURRENT_1000MA |
PM2XXX_CH_PRECH_CURRENT_100MA |
PM2XXX_CH_EOC_CURRENT_100MA ) ) ;
/*
* recharge threshold = 3.8 V
* Precharge to CC threshold = 2.9 V
*/
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG7 ,
( PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8 ) ) ;
/* float voltage charger level = 4.2V */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG8 ,
PM2XXX_CH_VOLT_4_2 ) ;
/* Voltage drop between VBAT and VSYS in HW charging = 300mV */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG9 ,
( PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS |
PM2XXX_CH_CC_REDUCED_CURRENT_IDENT |
PM2XXX_CH_CC_MODEDROP_DIS ) ) ;
/* Input charger level of over voltage = 10V */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_INP_VOLT_VPWR2 ,
PM2XXX_VPWR2_OVV_10 ) ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_INP_VOLT_VPWR1 ,
PM2XXX_VPWR1_OVV_10 ) ;
/* Input charger drop */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_INP_DROP_VPWR2 ,
( PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS |
PM2XXX_VPWR2_DROP_DIS ) ) ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_INP_DROP_VPWR1 ,
( PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS |
PM2XXX_VPWR1_DROP_DIS ) ) ;
/* Disable battery low monitoring */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_LOW_LEV_COMP_REG ,
PM2XXX_VBAT_LOW_MONITORING_DIS ) ;
/* Disable LED */
ret = pm2xxx_reg_write ( pm2 , PM2XXX_LED_CTRL_REG ,
PM2XXX_LED_SELECT_DIS ) ;
2012-04-26 12:00:04 +04:00
return ret ;
}
static int pm2xxx_charger_ac_en ( struct ux500_charger * charger ,
int enable , int vset , int iset )
{
int ret ;
int volt_index ;
int curr_index ;
u8 val ;
struct pm2xxx_charger * pm2 = to_pm2xxx_charger_ac_device_info ( charger ) ;
if ( enable ) {
if ( ! pm2 - > ac . charger_connected ) {
dev_dbg ( pm2 - > dev , " AC charger not connected \n " ) ;
return - ENXIO ;
}
dev_dbg ( pm2 - > dev , " Enable AC: %dmV %dmA \n " , vset , iset ) ;
if ( ! pm2 - > vddadc_en_ac ) {
regulator_enable ( pm2 - > regu ) ;
pm2 - > vddadc_en_ac = true ;
}
ret = pm2xxx_charging_init ( pm2 ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev , " %s charging init failed \n " ,
__func__ ) ;
goto error_occured ;
}
volt_index = pm2xxx_voltage_to_regval ( vset ) ;
curr_index = pm2xxx_current_to_regval ( iset ) ;
if ( volt_index < 0 | | curr_index < 0 ) {
dev_err ( pm2 - > dev ,
" Charger voltage or current too high, "
" charging not started \n " ) ;
return - ENXIO ;
}
ret = pm2xxx_reg_read ( pm2 , PM2XXX_BATT_CTRL_REG8 , & val ) ;
if ( ret > = 0 ) {
val & = ~ PM2XXX_CH_VOLT_MASK ;
val | = volt_index ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG8 , val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev ,
" %s write failed \n " , __func__ ) ;
goto error_occured ;
}
else
dev_err ( pm2 - > dev , " %s read failed \n " , __func__ ) ;
}
ret = pm2xxx_reg_read ( pm2 , PM2XXX_BATT_CTRL_REG6 , & val ) ;
if ( ret > = 0 ) {
val & = ~ PM2XXX_DIR_CH_CC_CURRENT_MASK ;
val | = curr_index ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_CTRL_REG6 , val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev ,
" %s write failed \n " , __func__ ) ;
goto error_occured ;
}
else
dev_err ( pm2 - > dev , " %s read failed \n " , __func__ ) ;
}
if ( ! pm2 - > bat - > enable_overshoot ) {
ret = pm2xxx_reg_read ( pm2 , PM2XXX_LED_CTRL_REG , & val ) ;
if ( ret > = 0 ) {
val | = PM2XXX_ANTI_OVERSHOOT_EN ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_LED_CTRL_REG ,
val ) ;
if ( ret < 0 ) {
dev_err ( pm2 - > dev , " %s write failed \n " ,
__func__ ) ;
goto error_occured ;
}
}
else
dev_err ( pm2 - > dev , " %s read failed \n " , __func__ ) ;
}
ret = pm2xxx_charging_enable_mngt ( pm2 ) ;
if ( ret ) {
dev_err ( pm2 - > dev , " %s write failed \n " , __func__ ) ;
goto error_occured ;
}
pm2 - > ac . charger_online = 1 ;
} else {
pm2 - > ac . charger_online = 0 ;
pm2 - > ac . wd_expired = false ;
/* Disable regulator if enabled */
if ( pm2 - > vddadc_en_ac ) {
regulator_disable ( pm2 - > regu ) ;
pm2 - > vddadc_en_ac = false ;
}
ret = pm2xxx_charging_disable_mngt ( pm2 ) ;
if ( ret ) {
dev_err ( pm2 - > dev , " %s write failed \n " , __func__ ) ;
return ret ;
}
dev_dbg ( pm2 - > dev , " PM2301: " " Disabled AC charging \n " ) ;
}
power_supply_changed ( & pm2 - > ac_chg . psy ) ;
error_occured :
return ret ;
}
static int pm2xxx_charger_watchdog_kick ( struct ux500_charger * charger )
{
int ret ;
struct pm2xxx_charger * pm2 ;
if ( charger - > psy . type = = POWER_SUPPLY_TYPE_MAINS )
pm2 = to_pm2xxx_charger_ac_device_info ( charger ) ;
else
return - ENXIO ;
ret = pm2xxx_reg_write ( pm2 , PM2XXX_BATT_WD_KICK , WD_TIMER ) ;
if ( ret )
dev_err ( pm2 - > dev , " Failed to kick WD! \n " ) ;
return ret ;
}
static void pm2xxx_charger_ac_work ( struct work_struct * work )
{
struct pm2xxx_charger * pm2 = container_of ( work ,
struct pm2xxx_charger , ac_work ) ;
power_supply_changed ( & pm2 - > ac_chg . psy ) ;
sysfs_notify ( & pm2 - > ac_chg . psy . dev - > kobj , NULL , " present " ) ;
} ;
static void pm2xxx_charger_check_main_thermal_prot_work (
struct work_struct * work )
{
} ;
2012-05-14 18:50:29 +04:00
static struct pm2xxx_interrupts pm2xxx_int = {
. handler [ 0 ] = pm2_int_reg0 ,
. handler [ 1 ] = pm2_int_reg1 ,
. handler [ 2 ] = pm2_int_reg2 ,
. handler [ 3 ] = pm2_int_reg3 ,
. handler [ 4 ] = pm2_int_reg4 ,
. handler [ 5 ] = pm2_int_reg5 ,
} ;
2012-04-26 12:00:04 +04:00
static struct pm2xxx_irq pm2xxx_charger_irq [ ] = {
{ " PM2XXX_IRQ_INT " , pm2xxx_irq_int } ,
} ;
static int pm2xxx_wall_charger_resume ( struct i2c_client * i2c_client )
{
return 0 ;
}
static int pm2xxx_wall_charger_suspend ( struct i2c_client * i2c_client ,
pm_message_t state )
{
return 0 ;
}
static int __devinit pm2xxx_wall_charger_probe ( struct i2c_client * i2c_client ,
const struct i2c_device_id * id )
{
struct pm2xxx_platform_data * pl_data = i2c_client - > dev . platform_data ;
struct pm2xxx_charger * pm2 ;
int ret = 0 ;
u8 val ;
pm2 = kzalloc ( sizeof ( struct pm2xxx_charger ) , GFP_KERNEL ) ;
if ( ! pm2 ) {
dev_err ( pm2 - > dev , " pm2xxx_charger allocation failed \n " ) ;
return - ENOMEM ;
}
/* get parent data */
pm2 - > dev = & i2c_client - > dev ;
pm2 - > gpadc = ab8500_gpadc_get ( " ab8500-gpadc.0 " ) ;
2012-05-14 18:50:29 +04:00
pm2 - > pm2_int = & pm2xxx_int ;
2012-04-26 12:00:04 +04:00
/* get charger spcific platform data */
if ( ! pl_data - > wall_charger ) {
dev_err ( pm2 - > dev , " no charger platform data supplied \n " ) ;
ret = - EINVAL ;
goto free_device_info ;
}
pm2 - > pdata = pl_data - > wall_charger ;
/* get battery specific platform data */
if ( ! pl_data - > battery ) {
dev_err ( pm2 - > dev , " no battery platform data supplied \n " ) ;
ret = - EINVAL ;
goto free_device_info ;
}
pm2 - > bat = pl_data - > battery ;
if ( ! i2c_check_functionality ( i2c_client - > adapter ,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA ) ) {
ret = - ENODEV ;
dev_info ( pm2 - > dev , " pm2301 i2c_check_functionality failed \n " ) ;
goto free_device_info ;
}
pm2 - > config . pm2xxx_i2c = i2c_client ;
pm2 - > config . pm2xxx_id = ( struct i2c_device_id * ) id ;
i2c_set_clientdata ( i2c_client , pm2 ) ;
/* AC supply */
/* power_supply base class */
pm2 - > ac_chg . psy . name = pm2 - > pdata - > label ;
pm2 - > ac_chg . psy . type = POWER_SUPPLY_TYPE_MAINS ;
pm2 - > ac_chg . psy . properties = pm2xxx_charger_ac_props ;
pm2 - > ac_chg . psy . num_properties = ARRAY_SIZE ( pm2xxx_charger_ac_props ) ;
pm2 - > ac_chg . psy . get_property = pm2xxx_charger_ac_get_property ;
pm2 - > ac_chg . psy . supplied_to = pm2 - > pdata - > supplied_to ;
pm2 - > ac_chg . psy . num_supplicants = pm2 - > pdata - > num_supplicants ;
/* pm2xxx_charger sub-class */
pm2 - > ac_chg . ops . enable = & pm2xxx_charger_ac_en ;
pm2 - > ac_chg . ops . kick_wd = & pm2xxx_charger_watchdog_kick ;
pm2 - > ac_chg . ops . update_curr = & pm2xxx_charger_update_charger_current ;
pm2 - > ac_chg . max_out_volt = pm2xxx_charger_voltage_map [
ARRAY_SIZE ( pm2xxx_charger_voltage_map ) - 1 ] ;
pm2 - > ac_chg . max_out_curr = pm2xxx_charger_current_map [
ARRAY_SIZE ( pm2xxx_charger_current_map ) - 1 ] ;
2012-05-10 17:33:56 +04:00
pm2 - > ac_chg . wdt_refresh = WD_KICK_INTERVAL ;
2012-05-14 18:50:29 +04:00
pm2 - > ac_chg . enabled = true ;
2012-05-10 17:33:56 +04:00
pm2 - > ac_chg . external = true ;
2012-04-26 12:00:04 +04:00
/* Create a work queue for the charger */
pm2 - > charger_wq =
create_singlethread_workqueue ( " pm2xxx_charger_wq " ) ;
if ( pm2 - > charger_wq = = NULL ) {
dev_err ( pm2 - > dev , " failed to create work queue \n " ) ;
goto free_device_info ;
}
/* Init work for charger detection */
INIT_WORK ( & pm2 - > ac_work , pm2xxx_charger_ac_work ) ;
/* Init work for checking HW status */
INIT_WORK ( & pm2 - > check_main_thermal_prot_work ,
pm2xxx_charger_check_main_thermal_prot_work ) ;
/*
* VDD ADC supply needs to be enabled from this driver when there
* is a charger connected to avoid erroneous BTEMP_HIGH / LOW
* interrupts during charging
*/
pm2 - > regu = regulator_get ( pm2 - > dev , " vddadc " ) ;
if ( IS_ERR ( pm2 - > regu ) ) {
ret = PTR_ERR ( pm2 - > regu ) ;
dev_err ( pm2 - > dev , " failed to get vddadc regulator \n " ) ;
goto free_charger_wq ;
}
/* Register AC charger class */
ret = power_supply_register ( pm2 - > dev , & pm2 - > ac_chg . psy ) ;
if ( ret ) {
dev_err ( pm2 - > dev , " failed to register AC charger \n " ) ;
goto free_regulator ;
}
/* Register interrupts */
ret = request_threaded_irq ( pm2 - > pdata - > irq_number , NULL ,
pm2xxx_charger_irq [ 0 ] . isr ,
pm2 - > pdata - > irq_type ,
pm2xxx_charger_irq [ 0 ] . name , pm2 ) ;
if ( ret ! = 0 ) {
dev_err ( pm2 - > dev , " failed to request %s IRQ %d: %d \n " ,
pm2xxx_charger_irq [ 0 ] . name , pm2 - > pdata - > irq_number , ret ) ;
goto unregister_pm2xxx_charger ;
}
/*
* I2C Read / Write will fail , if AC adaptor is not connected .
* fix the charger detection mechanism .
*/
ret = pm2xxx_charger_detection ( pm2 , & val ) ;
if ( ( ret = = 0 ) & & val ) {
pm2 - > ac . charger_connected = 1 ;
pm2 - > ac_conn = true ;
power_supply_changed ( & pm2 - > ac_chg . psy ) ;
sysfs_notify ( & pm2 - > ac_chg . psy . dev - > kobj , NULL , " present " ) ;
}
return 0 ;
unregister_pm2xxx_charger :
/* unregister power supply */
power_supply_unregister ( & pm2 - > ac_chg . psy ) ;
free_regulator :
/* disable the regulator */
regulator_put ( pm2 - > regu ) ;
free_charger_wq :
destroy_workqueue ( pm2 - > charger_wq ) ;
free_device_info :
kfree ( pm2 ) ;
return ret ;
}
static int __devexit pm2xxx_wall_charger_remove ( struct i2c_client * i2c_client )
{
struct pm2xxx_charger * pm2 = i2c_get_clientdata ( i2c_client ) ;
/* Disable AC charging */
pm2xxx_charger_ac_en ( & pm2 - > ac_chg , false , 0 , 0 ) ;
/* Disable interrupts */
free_irq ( pm2 - > pdata - > irq_number , pm2 ) ;
/* Delete the work queue */
destroy_workqueue ( pm2 - > charger_wq ) ;
flush_scheduled_work ( ) ;
/* disable the regulator */
regulator_put ( pm2 - > regu ) ;
power_supply_unregister ( & pm2 - > ac_chg . psy ) ;
kfree ( pm2 ) ;
return 0 ;
}
static const struct i2c_device_id pm2xxx_id [ ] = {
{ " pm2301 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , pm2xxx_id ) ;
static struct i2c_driver pm2xxx_charger_driver = {
. probe = pm2xxx_wall_charger_probe ,
. remove = __devexit_p ( pm2xxx_wall_charger_remove ) ,
. suspend = pm2xxx_wall_charger_suspend ,
. resume = pm2xxx_wall_charger_resume ,
. driver = {
. name = " pm2xxx-wall_charger " ,
. owner = THIS_MODULE ,
} ,
. id_table = pm2xxx_id ,
} ;
static int __init pm2xxx_charger_init ( void )
{
return i2c_add_driver ( & pm2xxx_charger_driver ) ;
}
static void __exit pm2xxx_charger_exit ( void )
{
i2c_del_driver ( & pm2xxx_charger_driver ) ;
}
subsys_initcall_sync ( pm2xxx_charger_init ) ;
module_exit ( pm2xxx_charger_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Rajkumar kasirajan, Olivier Launay " ) ;
MODULE_ALIAS ( " platform:pm2xxx-charger " ) ;
MODULE_DESCRIPTION ( " PM2xxx charger management driver " ) ;