2013-03-13 02:08:09 +04:00
/*
* Battery charger driver for TI ' s tps65090
*
* Copyright ( c ) 2013 , NVIDIA CORPORATION . All rights reserved .
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2013-09-28 08:32:24 +04:00
# include <linux/delay.h>
2013-03-13 02:08:09 +04:00
# include <linux/err.h>
2014-06-21 01:42:03 +04:00
# include <linux/freezer.h>
2013-03-13 02:08:09 +04:00
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
2014-06-21 01:42:03 +04:00
# include <linux/kthread.h>
2013-03-13 02:08:09 +04:00
# include <linux/module.h>
2013-09-28 08:32:24 +04:00
# include <linux/of_device.h>
2013-03-13 02:08:09 +04:00
# include <linux/platform_device.h>
# include <linux/power_supply.h>
2013-09-28 08:32:24 +04:00
# include <linux/slab.h>
2013-03-13 02:08:09 +04:00
# include <linux/mfd/tps65090.h>
# define TPS65090_CHARGER_ENABLE BIT(0)
# define TPS65090_VACG BIT(1)
# define TPS65090_NOITERM BIT(5)
2014-06-21 01:42:03 +04:00
# define POLL_INTERVAL (HZ * 2) /* Used when no irq */
2013-03-13 02:08:09 +04:00
struct tps65090_charger {
struct device * dev ;
int ac_online ;
int prev_ac_online ;
int irq ;
2014-06-21 01:42:03 +04:00
struct task_struct * poll_task ;
bool passive_mode ;
2015-03-12 10:44:11 +03:00
struct power_supply * ac ;
2013-03-13 02:08:09 +04:00
struct tps65090_platform_data * pdata ;
} ;
static enum power_supply_property tps65090_ac_props [ ] = {
POWER_SUPPLY_PROP_ONLINE ,
} ;
static int tps65090_low_chrg_current ( struct tps65090_charger * charger )
{
int ret ;
2014-06-21 01:42:03 +04:00
if ( charger - > passive_mode )
return 0 ;
2013-03-13 02:08:09 +04:00
ret = tps65090_write ( charger - > dev - > parent , TPS65090_REG_CG_CTRL5 ,
TPS65090_NOITERM ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): error reading in register 0x%x \n " ,
__func__ , TPS65090_REG_CG_CTRL5 ) ;
return ret ;
}
return 0 ;
}
2013-06-07 01:12:43 +04:00
static int tps65090_enable_charging ( struct tps65090_charger * charger )
2013-03-13 02:08:09 +04:00
{
int ret ;
uint8_t ctrl0 = 0 ;
2014-06-21 01:42:03 +04:00
if ( charger - > passive_mode )
return 0 ;
2013-03-13 02:08:09 +04:00
ret = tps65090_read ( charger - > dev - > parent , TPS65090_REG_CG_CTRL0 ,
& ctrl0 ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): error reading in register 0x%x \n " ,
__func__ , TPS65090_REG_CG_CTRL0 ) ;
return ret ;
}
ret = tps65090_write ( charger - > dev - > parent , TPS65090_REG_CG_CTRL0 ,
( ctrl0 | TPS65090_CHARGER_ENABLE ) ) ;
if ( ret < 0 ) {
2013-06-07 01:12:43 +04:00
dev_err ( charger - > dev , " %s(): error writing in register 0x%x \n " ,
2013-03-13 02:08:09 +04:00
__func__ , TPS65090_REG_CG_CTRL0 ) ;
return ret ;
}
return 0 ;
}
static int tps65090_config_charger ( struct tps65090_charger * charger )
{
2013-06-07 01:12:43 +04:00
uint8_t intrmask = 0 ;
2013-03-13 02:08:09 +04:00
int ret ;
2014-06-21 01:42:03 +04:00
if ( charger - > passive_mode )
return 0 ;
2013-03-13 02:08:09 +04:00
if ( charger - > pdata - > enable_low_current_chrg ) {
ret = tps65090_low_chrg_current ( charger ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev ,
" error configuring low charge current \n " ) ;
return ret ;
}
}
2013-06-07 01:12:43 +04:00
/* Enable the VACG interrupt for AC power detect */
ret = tps65090_read ( charger - > dev - > parent , TPS65090_REG_INTR_MASK ,
& intrmask ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): error reading in register 0x%x \n " ,
__func__ , TPS65090_REG_INTR_MASK ) ;
return ret ;
}
ret = tps65090_write ( charger - > dev - > parent , TPS65090_REG_INTR_MASK ,
( intrmask | TPS65090_VACG ) ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): error writing in register 0x%x \n " ,
__func__ , TPS65090_REG_CG_CTRL0 ) ;
return ret ;
}
2013-03-13 02:08:09 +04:00
return 0 ;
}
static int tps65090_ac_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 tps65090_charger * charger = power_supply_get_drvdata ( psy ) ;
2013-03-13 02:08:09 +04:00
if ( psp = = POWER_SUPPLY_PROP_ONLINE ) {
val - > intval = charger - > ac_online ;
charger - > prev_ac_online = charger - > ac_online ;
return 0 ;
}
return - EINVAL ;
}
static irqreturn_t tps65090_charger_isr ( int irq , void * dev_id )
{
struct tps65090_charger * charger = dev_id ;
int ret ;
uint8_t status1 = 0 ;
uint8_t intrsts = 0 ;
ret = tps65090_read ( charger - > dev - > parent , TPS65090_REG_CG_STATUS1 ,
& status1 ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): Error in reading reg 0x%x \n " ,
__func__ , TPS65090_REG_CG_STATUS1 ) ;
return IRQ_HANDLED ;
}
msleep ( 75 ) ;
ret = tps65090_read ( charger - > dev - > parent , TPS65090_REG_INTR_STS ,
& intrsts ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev , " %s(): Error in reading reg 0x%x \n " ,
__func__ , TPS65090_REG_INTR_STS ) ;
return IRQ_HANDLED ;
}
if ( intrsts & TPS65090_VACG ) {
2013-06-07 01:12:43 +04:00
ret = tps65090_enable_charging ( charger ) ;
2013-03-13 02:08:09 +04:00
if ( ret < 0 )
return IRQ_HANDLED ;
charger - > ac_online = 1 ;
} else {
charger - > ac_online = 0 ;
}
2013-06-07 01:12:43 +04:00
/* Clear interrupts. */
2014-06-21 01:42:03 +04:00
if ( ! charger - > passive_mode ) {
ret = tps65090_write ( charger - > dev - > parent ,
TPS65090_REG_INTR_STS , 0x00 ) ;
if ( ret < 0 ) {
dev_err ( charger - > dev ,
" %s(): Error in writing reg 0x%x \n " ,
2013-06-07 01:12:43 +04:00
__func__ , TPS65090_REG_INTR_STS ) ;
2014-06-21 01:42:03 +04:00
}
2013-06-07 01:12:43 +04:00
}
2013-03-13 02:08:09 +04:00
if ( charger - > prev_ac_online ! = charger - > ac_online )
2015-03-12 10:44:11 +03:00
power_supply_changed ( charger - > ac ) ;
2013-03-13 02:08:09 +04:00
return IRQ_HANDLED ;
}
static struct tps65090_platform_data *
tps65090_parse_dt_charger_data ( struct platform_device * pdev )
{
struct tps65090_platform_data * pdata ;
2013-03-22 00:33:05 +04:00
struct device_node * np = pdev - > dev . of_node ;
2013-03-13 02:08:09 +04:00
unsigned int prop ;
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " Memory alloc for tps65090_pdata failed \n " ) ;
return NULL ;
}
prop = of_property_read_bool ( np , " ti,enable-low-current-chrg " ) ;
pdata - > enable_low_current_chrg = prop ;
pdata - > irq_base = - 1 ;
return pdata ;
}
2014-06-21 01:42:03 +04:00
static int tps65090_charger_poll_task ( void * data )
{
set_freezable ( ) ;
while ( ! kthread_should_stop ( ) ) {
schedule_timeout_interruptible ( POLL_INTERVAL ) ;
try_to_freeze ( ) ;
tps65090_charger_isr ( - 1 , data ) ;
}
return 0 ;
}
2015-03-12 10:44:11 +03:00
static const struct power_supply_desc tps65090_charger_desc = {
. name = " tps65090-ac " ,
. type = POWER_SUPPLY_TYPE_MAINS ,
. get_property = tps65090_ac_get_property ,
. properties = tps65090_ac_props ,
. num_properties = ARRAY_SIZE ( tps65090_ac_props ) ,
} ;
2013-03-13 02:08:09 +04:00
static int tps65090_charger_probe ( struct platform_device * pdev )
{
struct tps65090_charger * cdata ;
struct tps65090_platform_data * pdata ;
2015-03-12 10:44:02 +03:00
struct power_supply_config psy_cfg = { } ;
2013-03-13 02:08:09 +04:00
uint8_t status1 = 0 ;
int ret ;
int irq ;
pdata = dev_get_platdata ( pdev - > dev . parent ) ;
2013-09-28 08:32:24 +04:00
if ( IS_ENABLED ( CONFIG_OF ) & & ! pdata & & pdev - > dev . of_node )
2013-03-13 02:08:09 +04:00
pdata = tps65090_parse_dt_charger_data ( pdev ) ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " %s():no platform data available \n " ,
__func__ ) ;
return - ENODEV ;
}
cdata = devm_kzalloc ( & pdev - > dev , sizeof ( * cdata ) , GFP_KERNEL ) ;
if ( ! cdata ) {
dev_err ( & pdev - > dev , " failed to allocate memory status \n " ) ;
return - ENOMEM ;
}
2013-05-23 14:34:39 +04:00
platform_set_drvdata ( pdev , cdata ) ;
2013-03-13 02:08:09 +04:00
cdata - > dev = & pdev - > dev ;
cdata - > pdata = pdata ;
2015-03-12 10:44:02 +03:00
psy_cfg . supplied_to = pdata - > supplied_to ;
psy_cfg . num_supplicants = pdata - > num_supplicants ;
psy_cfg . of_node = pdev - > dev . of_node ;
2015-03-12 10:44:11 +03:00
psy_cfg . drv_data = cdata ;
2015-03-12 10:44:02 +03:00
2015-03-12 10:44:11 +03:00
cdata - > ac = power_supply_register ( & pdev - > dev , & tps65090_charger_desc ,
& psy_cfg ) ;
if ( IS_ERR ( cdata - > ac ) ) {
2013-03-13 02:08:09 +04:00
dev_err ( & pdev - > dev , " failed: power supply register \n " ) ;
2015-03-12 10:44:11 +03:00
return PTR_ERR ( cdata - > ac ) ;
2013-03-13 02:08:09 +04:00
}
irq = platform_get_irq ( pdev , 0 ) ;
2014-06-21 01:42:03 +04:00
if ( irq < 0 )
irq = - ENXIO ;
2013-03-13 02:08:09 +04:00
cdata - > irq = irq ;
ret = tps65090_config_charger ( cdata ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " charger config failed, err %d \n " , ret ) ;
2013-09-23 14:04:19 +04:00
goto fail_unregister_supply ;
2013-03-13 02:08:09 +04:00
}
/* Check for charger presence */
ret = tps65090_read ( cdata - > dev - > parent , TPS65090_REG_CG_STATUS1 ,
& status1 ) ;
if ( ret < 0 ) {
dev_err ( cdata - > dev , " %s(): Error in reading reg 0x%x " , __func__ ,
TPS65090_REG_CG_STATUS1 ) ;
2013-09-23 14:04:19 +04:00
goto fail_unregister_supply ;
2013-03-13 02:08:09 +04:00
}
if ( status1 ! = 0 ) {
2013-06-07 01:12:43 +04:00
ret = tps65090_enable_charging ( cdata ) ;
2013-03-13 02:08:09 +04:00
if ( ret < 0 ) {
dev_err ( cdata - > dev , " error enabling charger \n " ) ;
2013-09-23 14:04:19 +04:00
goto fail_unregister_supply ;
2013-03-13 02:08:09 +04:00
}
cdata - > ac_online = 1 ;
2015-03-12 10:44:11 +03:00
power_supply_changed ( cdata - > ac ) ;
2013-03-13 02:08:09 +04:00
}
2014-06-21 01:42:03 +04:00
if ( irq ! = - ENXIO ) {
ret = devm_request_threaded_irq ( & pdev - > dev , irq , NULL ,
tps65090_charger_isr , 0 , " tps65090-charger " , cdata ) ;
if ( ret ) {
dev_err ( cdata - > dev ,
" Unable to register irq %d err %d \n " , irq ,
ret ) ;
goto fail_unregister_supply ;
}
} else {
cdata - > poll_task = kthread_run ( tps65090_charger_poll_task ,
cdata , " ktps65090charger " ) ;
cdata - > passive_mode = true ;
if ( IS_ERR ( cdata - > poll_task ) ) {
ret = PTR_ERR ( cdata - > poll_task ) ;
dev_err ( cdata - > dev ,
" Unable to run kthread err %d \n " , ret ) ;
goto fail_unregister_supply ;
}
}
2013-03-13 02:08:09 +04:00
return 0 ;
fail_unregister_supply :
2015-03-12 10:44:11 +03:00
power_supply_unregister ( cdata - > ac ) ;
2013-03-13 02:08:09 +04:00
return ret ;
}
static int tps65090_charger_remove ( struct platform_device * pdev )
{
2013-05-23 14:34:39 +04:00
struct tps65090_charger * cdata = platform_get_drvdata ( pdev ) ;
2013-03-13 02:08:09 +04:00
2014-06-21 01:42:03 +04:00
if ( cdata - > irq = = - ENXIO )
kthread_stop ( cdata - > poll_task ) ;
2015-03-12 10:44:11 +03:00
power_supply_unregister ( cdata - > ac ) ;
2013-03-13 02:08:09 +04:00
return 0 ;
}
2015-03-16 22:17:12 +03:00
static const struct of_device_id of_tps65090_charger_match [ ] = {
2013-03-22 00:33:05 +04:00
{ . compatible = " ti,tps65090-charger " , } ,
{ /* end */ }
} ;
2015-09-18 19:54:07 +03:00
MODULE_DEVICE_TABLE ( of , of_tps65090_charger_match ) ;
2013-03-22 00:33:05 +04:00
2013-03-13 02:08:09 +04:00
static struct platform_driver tps65090_charger_driver = {
. driver = {
. name = " tps65090-charger " ,
2013-03-22 00:33:05 +04:00
. of_match_table = of_tps65090_charger_match ,
2013-03-13 02:08:09 +04:00
} ,
. probe = tps65090_charger_probe ,
. remove = tps65090_charger_remove ,
} ;
module_platform_driver ( tps65090_charger_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Syed Rafiuddin <srafiuddin@nvidia.com> " ) ;
MODULE_DESCRIPTION ( " tps65090 battery charger driver " ) ;