2012-01-04 08:27:43 +04:00
/*
2012-01-27 10:59:08 +04:00
* Driver for LP8727 Micro / Mini USB IC with integrated charger
2012-01-04 08:27:43 +04:00
*
2012-01-30 05:28:18 +04:00
* Copyright ( C ) 2011 Texas Instruments
2012-01-04 08:27:43 +04:00
* Copyright ( C ) 2011 National Semiconductor
*
* 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/module.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/i2c.h>
# include <linux/power_supply.h>
2012-07-03 05:19:03 +04:00
# include <linux/platform_data/lp8727.h>
2012-01-04 08:27:43 +04:00
# define DEBOUNCE_MSEC 270
/* Registers */
# define CTRL1 0x1
# define CTRL2 0x2
# define SWCTRL 0x3
# define INT1 0x4
# define INT2 0x5
# define STATUS1 0x6
2012-01-27 10:58:51 +04:00
# define STATUS2 0x7
2012-01-04 08:27:43 +04:00
# define CHGCTRL2 0x9
/* CTRL1 register */
# define CP_EN (1 << 0)
# define ADC_EN (1 << 1)
# define ID200_EN (1 << 4)
/* CTRL2 register */
# define CHGDET_EN (1 << 1)
# define INT_EN (1 << 6)
/* SWCTRL register */
# define SW_DM1_DM (0x0 << 0)
# define SW_DM1_U1 (0x1 << 0)
# define SW_DM1_HiZ (0x7 << 0)
# define SW_DP2_DP (0x0 << 3)
# define SW_DP2_U2 (0x1 << 3)
# define SW_DP2_HiZ (0x7 << 3)
/* INT1 register */
# define IDNO (0xF << 0)
# define VBUS (1 << 4)
/* STATUS1 register */
# define CHGSTAT (3 << 4)
# define CHPORT (1 << 6)
# define DCPORT (1 << 7)
/* STATUS2 register */
# define TEMP_STAT (3 << 5)
enum lp8727_dev_id {
ID_NONE ,
ID_TA ,
ID_DEDICATED_CHG ,
ID_USB_CHG ,
ID_USB_DS ,
ID_MAX ,
} ;
enum lp8727_chg_stat {
PRECHG ,
CC ,
CV ,
EOC ,
} ;
struct lp8727_psy {
struct power_supply ac ;
struct power_supply usb ;
struct power_supply batt ;
} ;
struct lp8727_chg {
struct device * dev ;
struct i2c_client * client ;
struct mutex xfer_lock ;
struct delayed_work work ;
struct workqueue_struct * irqthread ;
struct lp8727_platform_data * pdata ;
struct lp8727_psy * psy ;
struct lp8727_chg_param * chg_parm ;
enum lp8727_dev_id devid ;
} ;
2012-01-27 10:58:39 +04:00
static int lp8727_read_bytes ( struct lp8727_chg * pchg , u8 reg , u8 * data , u8 len )
2012-01-04 08:27:43 +04:00
{
s32 ret ;
mutex_lock ( & pchg - > xfer_lock ) ;
ret = i2c_smbus_read_i2c_block_data ( pchg - > client , reg , len , data ) ;
mutex_unlock ( & pchg - > xfer_lock ) ;
return ( ret ! = len ) ? - EIO : 0 ;
}
2012-01-27 10:58:39 +04:00
static inline int lp8727_read_byte ( struct lp8727_chg * pchg , u8 reg , u8 * data )
2012-01-04 08:27:43 +04:00
{
2012-01-27 10:58:39 +04:00
return lp8727_read_bytes ( pchg , reg , data , 1 ) ;
}
static int lp8727_write_byte ( struct lp8727_chg * pchg , u8 reg , u8 data )
{
int ret ;
2012-01-04 08:27:43 +04:00
mutex_lock ( & pchg - > xfer_lock ) ;
2012-01-27 10:58:39 +04:00
ret = i2c_smbus_write_byte_data ( pchg - > client , reg , data ) ;
2012-01-04 08:27:43 +04:00
mutex_unlock ( & pchg - > xfer_lock ) ;
return ret ;
}
static int lp8727_is_charger_attached ( const char * name , int id )
{
if ( name ) {
if ( ! strcmp ( name , " ac " ) )
return ( id = = ID_TA | | id = = ID_DEDICATED_CHG ) ? 1 : 0 ;
else if ( ! strcmp ( name , " usb " ) )
return ( id = = ID_USB_CHG ) ? 1 : 0 ;
}
return ( id > = ID_TA & & id < = ID_USB_CHG ) ? 1 : 0 ;
}
2012-01-27 10:58:30 +04:00
static int lp8727_init_device ( struct lp8727_chg * pchg )
2012-01-04 08:27:43 +04:00
{
u8 val ;
2012-01-27 10:58:30 +04:00
int ret ;
2012-01-04 08:27:43 +04:00
val = ID200_EN | ADC_EN | CP_EN ;
2012-01-27 10:58:39 +04:00
ret = lp8727_write_byte ( pchg , CTRL1 , val ) ;
2012-01-27 10:58:30 +04:00
if ( ret )
return ret ;
2012-01-04 08:27:43 +04:00
val = INT_EN | CHGDET_EN ;
2012-01-27 10:58:39 +04:00
ret = lp8727_write_byte ( pchg , CTRL2 , val ) ;
2012-01-27 10:58:30 +04:00
if ( ret )
return ret ;
return 0 ;
2012-01-04 08:27:43 +04:00
}
static int lp8727_is_dedicated_charger ( struct lp8727_chg * pchg )
{
u8 val ;
2012-01-27 10:58:39 +04:00
lp8727_read_byte ( pchg , STATUS1 , & val ) ;
2012-01-27 10:58:51 +04:00
return val & DCPORT ;
2012-01-04 08:27:43 +04:00
}
static int lp8727_is_usb_charger ( struct lp8727_chg * pchg )
{
u8 val ;
2012-01-27 10:58:39 +04:00
lp8727_read_byte ( pchg , STATUS1 , & val ) ;
2012-01-27 10:58:51 +04:00
return val & CHPORT ;
2012-01-04 08:27:43 +04:00
}
static void lp8727_ctrl_switch ( struct lp8727_chg * pchg , u8 sw )
{
2012-01-27 10:58:39 +04:00
lp8727_write_byte ( pchg , SWCTRL , sw ) ;
2012-01-04 08:27:43 +04:00
}
static void lp8727_id_detection ( struct lp8727_chg * pchg , u8 id , int vbusin )
{
u8 devid = ID_NONE ;
u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ ;
switch ( id ) {
case 0x5 :
devid = ID_TA ;
pchg - > chg_parm = & pchg - > pdata - > ac ;
break ;
case 0xB :
if ( lp8727_is_dedicated_charger ( pchg ) ) {
pchg - > chg_parm = & pchg - > pdata - > ac ;
devid = ID_DEDICATED_CHG ;
} else if ( lp8727_is_usb_charger ( pchg ) ) {
pchg - > chg_parm = & pchg - > pdata - > usb ;
devid = ID_USB_CHG ;
swctrl = SW_DM1_DM | SW_DP2_DP ;
} else if ( vbusin ) {
devid = ID_USB_DS ;
swctrl = SW_DM1_DM | SW_DP2_DP ;
}
break ;
default :
devid = ID_NONE ;
pchg - > chg_parm = NULL ;
break ;
}
pchg - > devid = devid ;
lp8727_ctrl_switch ( pchg , swctrl ) ;
}
static void lp8727_enable_chgdet ( struct lp8727_chg * pchg )
{
u8 val ;
2012-01-27 10:58:39 +04:00
lp8727_read_byte ( pchg , CTRL2 , & val ) ;
2012-01-04 08:27:43 +04:00
val | = CHGDET_EN ;
2012-01-27 10:58:39 +04:00
lp8727_write_byte ( pchg , CTRL2 , val ) ;
2012-01-04 08:27:43 +04:00
}
static void lp8727_delayed_func ( struct work_struct * _work )
{
u8 intstat [ 2 ] , idno , vbus ;
struct lp8727_chg * pchg =
container_of ( _work , struct lp8727_chg , work . work ) ;
2012-01-27 10:58:39 +04:00
if ( lp8727_read_bytes ( pchg , INT1 , intstat , 2 ) ) {
2012-01-04 08:27:43 +04:00
dev_err ( pchg - > dev , " can not read INT registers \n " ) ;
return ;
}
idno = intstat [ 0 ] & IDNO ;
vbus = intstat [ 0 ] & VBUS ;
lp8727_id_detection ( pchg , idno , vbus ) ;
lp8727_enable_chgdet ( pchg ) ;
power_supply_changed ( & pchg - > psy - > ac ) ;
power_supply_changed ( & pchg - > psy - > usb ) ;
power_supply_changed ( & pchg - > psy - > batt ) ;
}
static irqreturn_t lp8727_isr_func ( int irq , void * ptr )
{
struct lp8727_chg * pchg = ptr ;
unsigned long delay = msecs_to_jiffies ( DEBOUNCE_MSEC ) ;
queue_delayed_work ( pchg - > irqthread , & pchg - > work , delay ) ;
return IRQ_HANDLED ;
}
2012-01-27 10:58:30 +04:00
static int lp8727_intr_config ( struct lp8727_chg * pchg )
2012-01-04 08:27:43 +04:00
{
INIT_DELAYED_WORK ( & pchg - > work , lp8727_delayed_func ) ;
pchg - > irqthread = create_singlethread_workqueue ( " lp8727-irqthd " ) ;
2012-01-27 10:58:30 +04:00
if ( ! pchg - > irqthread ) {
2012-01-04 08:27:43 +04:00
dev_err ( pchg - > dev , " can not create thread for lp8727 \n " ) ;
2012-01-27 10:58:30 +04:00
return - ENOMEM ;
2012-01-04 08:27:43 +04:00
}
2012-01-27 10:58:30 +04:00
return request_threaded_irq ( pchg - > client - > irq ,
NULL ,
lp8727_isr_func ,
2012-08-23 15:42:29 +04:00
IRQF_TRIGGER_FALLING | IRQF_ONESHOT ,
2012-01-27 10:58:30 +04:00
" lp8727_irq " ,
pchg ) ;
2012-01-04 08:27:43 +04:00
}
static enum power_supply_property lp8727_charger_prop [ ] = {
POWER_SUPPLY_PROP_ONLINE ,
} ;
static enum power_supply_property lp8727_battery_prop [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CAPACITY ,
POWER_SUPPLY_PROP_TEMP ,
} ;
static char * battery_supplied_to [ ] = {
" main_batt " ,
} ;
static int lp8727_charger_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct lp8727_chg * pchg = dev_get_drvdata ( psy - > dev - > parent ) ;
2012-01-04 09:03:18 +04:00
if ( psp = = POWER_SUPPLY_PROP_ONLINE )
2012-01-04 08:27:43 +04:00
val - > intval = lp8727_is_charger_attached ( psy - > name ,
pchg - > devid ) ;
return 0 ;
}
static int lp8727_battery_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct lp8727_chg * pchg = dev_get_drvdata ( psy - > dev - > parent ) ;
u8 read ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
if ( lp8727_is_charger_attached ( psy - > name , pchg - > devid ) ) {
2012-01-27 10:58:39 +04:00
lp8727_read_byte ( pchg , STATUS1 , & read ) ;
2012-01-04 08:27:43 +04:00
if ( ( ( read & CHGSTAT ) > > 4 ) = = EOC )
val - > intval = POWER_SUPPLY_STATUS_FULL ;
else
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
} else {
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
}
break ;
case POWER_SUPPLY_PROP_HEALTH :
2012-01-27 10:58:39 +04:00
lp8727_read_byte ( pchg , STATUS2 , & read ) ;
2012-01-04 08:27:43 +04:00
read = ( read & TEMP_STAT ) > > 5 ;
if ( read > = 0x1 & & read < = 0x3 )
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
break ;
case POWER_SUPPLY_PROP_PRESENT :
if ( pchg - > pdata - > get_batt_present )
val - > intval = pchg - > pdata - > get_batt_present ( ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
if ( pchg - > pdata - > get_batt_level )
val - > intval = pchg - > pdata - > get_batt_level ( ) ;
break ;
case POWER_SUPPLY_PROP_CAPACITY :
if ( pchg - > pdata - > get_batt_capacity )
val - > intval = pchg - > pdata - > get_batt_capacity ( ) ;
break ;
case POWER_SUPPLY_PROP_TEMP :
if ( pchg - > pdata - > get_batt_temp )
val - > intval = pchg - > pdata - > get_batt_temp ( ) ;
break ;
default :
break ;
}
return 0 ;
}
static void lp8727_charger_changed ( struct power_supply * psy )
{
struct lp8727_chg * pchg = dev_get_drvdata ( psy - > dev - > parent ) ;
u8 val ;
u8 eoc_level , ichg ;
if ( lp8727_is_charger_attached ( psy - > name , pchg - > devid ) ) {
if ( pchg - > chg_parm ) {
eoc_level = pchg - > chg_parm - > eoc_level ;
ichg = pchg - > chg_parm - > ichg ;
val = ( ichg < < 4 ) | eoc_level ;
2012-01-27 10:58:39 +04:00
lp8727_write_byte ( pchg , CHGCTRL2 , val ) ;
2012-01-04 08:27:43 +04:00
}
}
}
static int lp8727_register_psy ( struct lp8727_chg * pchg )
{
struct lp8727_psy * psy ;
psy = kzalloc ( sizeof ( * psy ) , GFP_KERNEL ) ;
if ( ! psy )
2012-07-29 21:46:55 +04:00
return - ENOMEM ;
2012-01-04 08:27:43 +04:00
pchg - > psy = psy ;
psy - > ac . name = " ac " ;
psy - > ac . type = POWER_SUPPLY_TYPE_MAINS ;
psy - > ac . properties = lp8727_charger_prop ;
psy - > ac . num_properties = ARRAY_SIZE ( lp8727_charger_prop ) ;
psy - > ac . get_property = lp8727_charger_get_property ;
psy - > ac . supplied_to = battery_supplied_to ;
psy - > ac . num_supplicants = ARRAY_SIZE ( battery_supplied_to ) ;
if ( power_supply_register ( pchg - > dev , & psy - > ac ) )
2012-07-29 21:46:55 +04:00
goto err_psy_ac ;
2012-01-04 08:27:43 +04:00
psy - > usb . name = " usb " ;
psy - > usb . type = POWER_SUPPLY_TYPE_USB ;
psy - > usb . properties = lp8727_charger_prop ;
psy - > usb . num_properties = ARRAY_SIZE ( lp8727_charger_prop ) ;
psy - > usb . get_property = lp8727_charger_get_property ;
psy - > usb . supplied_to = battery_supplied_to ;
psy - > usb . num_supplicants = ARRAY_SIZE ( battery_supplied_to ) ;
if ( power_supply_register ( pchg - > dev , & psy - > usb ) )
2012-07-29 21:46:55 +04:00
goto err_psy_usb ;
2012-01-04 08:27:43 +04:00
psy - > batt . name = " main_batt " ;
psy - > batt . type = POWER_SUPPLY_TYPE_BATTERY ;
psy - > batt . properties = lp8727_battery_prop ;
psy - > batt . num_properties = ARRAY_SIZE ( lp8727_battery_prop ) ;
psy - > batt . get_property = lp8727_battery_get_property ;
psy - > batt . external_power_changed = lp8727_charger_changed ;
if ( power_supply_register ( pchg - > dev , & psy - > batt ) )
2012-07-29 21:46:55 +04:00
goto err_psy_batt ;
2012-01-04 08:27:43 +04:00
return 0 ;
2012-07-29 21:46:55 +04:00
err_psy_batt :
power_supply_unregister ( & psy - > usb ) ;
err_psy_usb :
power_supply_unregister ( & psy - > ac ) ;
err_psy_ac :
2012-01-04 08:27:43 +04:00
kfree ( psy ) ;
return - EPERM ;
}
static void lp8727_unregister_psy ( struct lp8727_chg * pchg )
{
struct lp8727_psy * psy = pchg - > psy ;
2012-01-04 09:03:18 +04:00
if ( ! psy )
return ;
power_supply_unregister ( & psy - > ac ) ;
power_supply_unregister ( & psy - > usb ) ;
power_supply_unregister ( & psy - > batt ) ;
kfree ( psy ) ;
2012-01-04 08:27:43 +04:00
}
static int lp8727_probe ( struct i2c_client * cl , const struct i2c_device_id * id )
{
struct lp8727_chg * pchg ;
int ret ;
2011-11-18 09:43:06 +04:00
if ( ! i2c_check_functionality ( cl - > adapter , I2C_FUNC_SMBUS_I2C_BLOCK ) )
return - EIO ;
2012-01-04 08:27:43 +04:00
pchg = kzalloc ( sizeof ( * pchg ) , GFP_KERNEL ) ;
if ( ! pchg )
return - ENOMEM ;
pchg - > client = cl ;
pchg - > dev = & cl - > dev ;
pchg - > pdata = cl - > dev . platform_data ;
i2c_set_clientdata ( cl , pchg ) ;
mutex_init ( & pchg - > xfer_lock ) ;
2012-01-27 10:58:30 +04:00
ret = lp8727_init_device ( pchg ) ;
if ( ret ) {
dev_err ( pchg - > dev , " i2c communication err: %d " , ret ) ;
goto error ;
}
ret = lp8727_intr_config ( pchg ) ;
if ( ret ) {
dev_err ( pchg - > dev , " irq handler err: %d " , ret ) ;
goto error ;
}
2012-01-04 08:27:43 +04:00
ret = lp8727_register_psy ( pchg ) ;
2012-01-27 10:58:30 +04:00
if ( ret ) {
dev_err ( pchg - > dev , " power supplies register err: %d " , ret ) ;
goto error ;
}
2012-01-04 08:27:43 +04:00
return 0 ;
2012-01-27 10:58:30 +04:00
error :
kfree ( pchg ) ;
return ret ;
2012-01-04 08:27:43 +04:00
}
static int __devexit lp8727_remove ( struct i2c_client * cl )
{
struct lp8727_chg * pchg = i2c_get_clientdata ( cl ) ;
lp8727_unregister_psy ( pchg ) ;
free_irq ( pchg - > client - > irq , pchg ) ;
flush_workqueue ( pchg - > irqthread ) ;
destroy_workqueue ( pchg - > irqthread ) ;
kfree ( pchg ) ;
return 0 ;
}
static const struct i2c_device_id lp8727_ids [ ] = {
{ " lp8727 " , 0 } ,
2012-01-16 09:48:20 +04:00
{ }
2012-01-04 08:27:43 +04:00
} ;
2012-01-12 16:45:02 +04:00
MODULE_DEVICE_TABLE ( i2c , lp8727_ids ) ;
2012-01-04 08:27:43 +04:00
static struct i2c_driver lp8727_driver = {
. driver = {
. name = " lp8727 " ,
} ,
. probe = lp8727_probe ,
. remove = __devexit_p ( lp8727_remove ) ,
. id_table = lp8727_ids ,
} ;
2012-01-21 10:42:54 +04:00
module_i2c_driver ( lp8727_driver ) ;
2012-01-04 08:27:43 +04:00
2012-01-30 05:28:18 +04:00
MODULE_DESCRIPTION ( " TI/National Semiconductor LP8727 charger driver " ) ;
2012-01-27 10:58:51 +04:00
MODULE_AUTHOR ( " Woogyom Kim <milo.kim@ti.com>, "
" Daniel Jeong <daniel.jeong@ti.com> " ) ;
2012-01-04 08:27:43 +04:00
MODULE_LICENSE ( " GPL " ) ;