2013-10-12 01:15:43 +04:00
/*
* Battery charger driver for TI BQ24735
*
* Copyright ( c ) 2013 , NVIDIA CORPORATION . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ;
*
* This program is distributed in the hope that 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 , write to the Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/i2c.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_gpio.h>
# include <linux/power_supply.h>
# include <linux/slab.h>
# include <linux/power/bq24735-charger.h>
# define BQ24735_CHG_OPT 0x12
# define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0)
# define BQ24735_CHG_OPT_AC_PRESENT (1 << 4)
# define BQ24735_CHARGE_CURRENT 0x14
# define BQ24735_CHARGE_CURRENT_MASK 0x1fc0
# define BQ24735_CHARGE_VOLTAGE 0x15
# define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0
# define BQ24735_INPUT_CURRENT 0x3f
# define BQ24735_INPUT_CURRENT_MASK 0x1f80
# define BQ24735_MANUFACTURER_ID 0xfe
# define BQ24735_DEVICE_ID 0xff
struct bq24735 {
2015-03-12 10:44:11 +03:00
struct power_supply * charger ;
struct power_supply_desc charger_desc ;
struct i2c_client * client ;
struct bq24735_platform * pdata ;
2016-01-12 23:30:59 +03:00
struct mutex lock ;
bool charging ;
2013-10-12 01:15:43 +04:00
} ;
static inline struct bq24735 * to_bq24735 ( struct power_supply * psy )
{
2015-03-12 10:44:11 +03:00
return power_supply_get_drvdata ( psy ) ;
2013-10-12 01:15:43 +04:00
}
static enum power_supply_property bq24735_charger_properties [ ] = {
2016-01-12 23:30:59 +03:00
POWER_SUPPLY_PROP_STATUS ,
2013-10-12 01:15:43 +04:00
POWER_SUPPLY_PROP_ONLINE ,
} ;
2016-01-12 23:30:59 +03:00
static int bq24735_charger_property_is_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
return 1 ;
default :
break ;
}
return 0 ;
}
2013-10-12 01:15:43 +04:00
static inline int bq24735_write_word ( struct i2c_client * client , u8 reg ,
u16 value )
{
return i2c_smbus_write_word_data ( client , reg , le16_to_cpu ( value ) ) ;
}
static inline int bq24735_read_word ( struct i2c_client * client , u8 reg )
{
s32 ret = i2c_smbus_read_word_data ( client , reg ) ;
return ret < 0 ? ret : le16_to_cpu ( ret ) ;
}
static int bq24735_update_word ( struct i2c_client * client , u8 reg ,
u16 mask , u16 value )
{
unsigned int tmp ;
int ret ;
ret = bq24735_read_word ( client , reg ) ;
if ( ret < 0 )
return ret ;
tmp = ret & ~ mask ;
tmp | = value & mask ;
return bq24735_write_word ( client , reg , tmp ) ;
}
static inline int bq24735_enable_charging ( struct bq24735 * charger )
{
2016-02-03 09:03:45 +03:00
if ( charger - > pdata - > ext_control )
return 0 ;
2013-10-12 01:15:43 +04:00
return bq24735_update_word ( charger - > client , BQ24735_CHG_OPT ,
BQ24735_CHG_OPT_CHARGE_DISABLE ,
~ BQ24735_CHG_OPT_CHARGE_DISABLE ) ;
}
static inline int bq24735_disable_charging ( struct bq24735 * charger )
{
2016-02-03 09:03:45 +03:00
if ( charger - > pdata - > ext_control )
return 0 ;
2013-10-12 01:15:43 +04:00
return bq24735_update_word ( charger - > client , BQ24735_CHG_OPT ,
BQ24735_CHG_OPT_CHARGE_DISABLE ,
BQ24735_CHG_OPT_CHARGE_DISABLE ) ;
}
static int bq24735_config_charger ( struct bq24735 * charger )
{
struct bq24735_platform * pdata = charger - > pdata ;
int ret ;
u16 value ;
2016-02-03 09:03:45 +03:00
if ( pdata - > ext_control )
return 0 ;
2013-10-12 01:15:43 +04:00
if ( pdata - > charge_current ) {
value = pdata - > charge_current & BQ24735_CHARGE_CURRENT_MASK ;
ret = bq24735_write_word ( charger - > client ,
BQ24735_CHARGE_CURRENT , value ) ;
if ( ret < 0 ) {
dev_err ( & charger - > client - > dev ,
" Failed to write charger current : %d \n " ,
ret ) ;
return ret ;
}
}
if ( pdata - > charge_voltage ) {
value = pdata - > charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK ;
ret = bq24735_write_word ( charger - > client ,
BQ24735_CHARGE_VOLTAGE , value ) ;
if ( ret < 0 ) {
dev_err ( & charger - > client - > dev ,
" Failed to write charger voltage : %d \n " ,
ret ) ;
return ret ;
}
}
if ( pdata - > input_current ) {
value = pdata - > input_current & BQ24735_INPUT_CURRENT_MASK ;
ret = bq24735_write_word ( charger - > client ,
BQ24735_INPUT_CURRENT , value ) ;
if ( ret < 0 ) {
dev_err ( & charger - > client - > dev ,
" Failed to write input current : %d \n " ,
ret ) ;
return ret ;
}
}
return 0 ;
}
static bool bq24735_charger_is_present ( struct bq24735 * charger )
{
struct bq24735_platform * pdata = charger - > pdata ;
int ret ;
if ( pdata - > status_gpio_valid ) {
ret = gpio_get_value_cansleep ( pdata - > status_gpio ) ;
return ret ^ = pdata - > status_gpio_active_low = = 0 ;
} else {
int ac = 0 ;
ac = bq24735_read_word ( charger - > client , BQ24735_CHG_OPT ) ;
if ( ac < 0 ) {
dev_err ( & charger - > client - > dev ,
" Failed to read charger options : %d \n " ,
ac ) ;
return false ;
}
return ( ac & BQ24735_CHG_OPT_AC_PRESENT ) ? true : false ;
}
return false ;
}
2016-01-12 23:30:59 +03:00
static int bq24735_charger_is_charging ( struct bq24735 * charger )
{
int ret = bq24735_read_word ( charger - > client , BQ24735_CHG_OPT ) ;
if ( ret < 0 )
return ret ;
return ! ( ret & BQ24735_CHG_OPT_CHARGE_DISABLE ) ;
}
2013-10-12 01:15:43 +04:00
static irqreturn_t bq24735_charger_isr ( int irq , void * devid )
{
struct power_supply * psy = devid ;
struct bq24735 * charger = to_bq24735 ( psy ) ;
2016-01-12 23:30:59 +03:00
mutex_lock ( & charger - > lock ) ;
if ( charger - > charging & & bq24735_charger_is_present ( charger ) )
2013-10-12 01:15:43 +04:00
bq24735_enable_charging ( charger ) ;
else
bq24735_disable_charging ( charger ) ;
2016-01-12 23:30:59 +03:00
mutex_unlock ( & charger - > lock ) ;
2013-10-12 01:15:43 +04:00
power_supply_changed ( psy ) ;
return IRQ_HANDLED ;
}
static int bq24735_charger_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 bq24735 * charger = to_bq24735 ( psy ) ;
2013-10-12 01:15:43 +04:00
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = bq24735_charger_is_present ( charger ) ? 1 : 0 ;
break ;
2016-01-12 23:30:59 +03:00
case POWER_SUPPLY_PROP_STATUS :
switch ( bq24735_charger_is_charging ( charger ) ) {
case 1 :
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
break ;
case 0 :
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
break ;
default :
val - > intval = POWER_SUPPLY_STATUS_UNKNOWN ;
break ;
}
break ;
2013-10-12 01:15:43 +04:00
default :
return - EINVAL ;
}
return 0 ;
}
2016-01-12 23:30:59 +03:00
static int bq24735_charger_set_property ( struct power_supply * psy ,
enum power_supply_property psp ,
const union power_supply_propval * val )
{
struct bq24735 * charger = to_bq24735 ( psy ) ;
int ret ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
switch ( val - > intval ) {
case POWER_SUPPLY_STATUS_CHARGING :
mutex_lock ( & charger - > lock ) ;
charger - > charging = true ;
ret = bq24735_enable_charging ( charger ) ;
mutex_unlock ( & charger - > lock ) ;
if ( ret )
return ret ;
bq24735_config_charger ( charger ) ;
break ;
case POWER_SUPPLY_STATUS_DISCHARGING :
case POWER_SUPPLY_STATUS_NOT_CHARGING :
mutex_lock ( & charger - > lock ) ;
charger - > charging = false ;
ret = bq24735_disable_charging ( charger ) ;
mutex_unlock ( & charger - > lock ) ;
if ( ret )
return ret ;
break ;
default :
return - EINVAL ;
}
power_supply_changed ( psy ) ;
break ;
default :
return - EPERM ;
}
return 0 ;
}
2013-10-12 01:15:43 +04:00
static struct bq24735_platform * bq24735_parse_dt_data ( struct i2c_client * client )
{
struct bq24735_platform * pdata ;
struct device_node * np = client - > dev . of_node ;
u32 val ;
int ret ;
enum of_gpio_flags flags ;
pdata = devm_kzalloc ( & client - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata ) {
dev_err ( & client - > dev ,
" Memory alloc for bq24735 pdata failed \n " ) ;
return NULL ;
}
pdata - > status_gpio = of_get_named_gpio_flags ( np , " ti,ac-detect-gpios " ,
0 , & flags ) ;
if ( flags & OF_GPIO_ACTIVE_LOW )
pdata - > status_gpio_active_low = 1 ;
ret = of_property_read_u32 ( np , " ti,charge-current " , & val ) ;
if ( ! ret )
pdata - > charge_current = val ;
ret = of_property_read_u32 ( np , " ti,charge-voltage " , & val ) ;
if ( ! ret )
pdata - > charge_voltage = val ;
ret = of_property_read_u32 ( np , " ti,input-current " , & val ) ;
if ( ! ret )
pdata - > input_current = val ;
2016-02-03 09:03:45 +03:00
pdata - > ext_control = of_property_read_bool ( np , " ti,external-control " ) ;
2013-10-12 01:15:43 +04:00
return pdata ;
}
static int bq24735_charger_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
int ret ;
struct bq24735 * charger ;
2015-03-12 10:44:11 +03:00
struct power_supply_desc * supply_desc ;
2015-03-12 10:44:02 +03:00
struct power_supply_config psy_cfg = { } ;
2013-10-12 01:15:43 +04:00
char * name ;
charger = devm_kzalloc ( & client - > dev , sizeof ( * charger ) , GFP_KERNEL ) ;
if ( ! charger )
return - ENOMEM ;
2016-01-12 23:30:59 +03:00
mutex_init ( & charger - > lock ) ;
charger - > charging = true ;
2013-10-12 01:15:43 +04:00
charger - > pdata = client - > dev . platform_data ;
if ( IS_ENABLED ( CONFIG_OF ) & & ! charger - > pdata & & client - > dev . of_node )
charger - > pdata = bq24735_parse_dt_data ( client ) ;
if ( ! charger - > pdata ) {
dev_err ( & client - > dev , " no platform data provided \n " ) ;
return - EINVAL ;
}
name = ( char * ) charger - > pdata - > name ;
if ( ! name ) {
2015-07-25 06:53:16 +03:00
name = devm_kasprintf ( & client - > dev , GFP_KERNEL ,
" bq24735@%s " ,
dev_name ( & client - > dev ) ) ;
2013-10-12 01:15:43 +04:00
if ( ! name ) {
dev_err ( & client - > dev , " Failed to alloc device name \n " ) ;
return - ENOMEM ;
}
}
charger - > client = client ;
2015-03-12 10:44:11 +03:00
supply_desc = & charger - > charger_desc ;
2013-10-12 01:15:43 +04:00
2015-03-12 10:44:11 +03:00
supply_desc - > name = name ;
supply_desc - > type = POWER_SUPPLY_TYPE_MAINS ;
supply_desc - > properties = bq24735_charger_properties ;
supply_desc - > num_properties = ARRAY_SIZE ( bq24735_charger_properties ) ;
supply_desc - > get_property = bq24735_charger_get_property ;
2016-01-12 23:30:59 +03:00
supply_desc - > set_property = bq24735_charger_set_property ;
supply_desc - > property_is_writeable =
bq24735_charger_property_is_writeable ;
2015-03-12 10:44:02 +03:00
psy_cfg . supplied_to = charger - > pdata - > supplied_to ;
psy_cfg . num_supplicants = charger - > pdata - > num_supplicants ;
psy_cfg . of_node = client - > dev . of_node ;
2015-03-12 10:44:11 +03:00
psy_cfg . drv_data = charger ;
2013-10-12 01:15:43 +04:00
i2c_set_clientdata ( client , charger ) ;
if ( gpio_is_valid ( charger - > pdata - > status_gpio ) ) {
ret = devm_gpio_request ( & client - > dev ,
charger - > pdata - > status_gpio ,
name ) ;
if ( ret ) {
dev_err ( & client - > dev ,
" Failed GPIO request for GPIO %d: %d \n " ,
charger - > pdata - > status_gpio , ret ) ;
}
charger - > pdata - > status_gpio_valid = ! ret ;
}
2016-02-03 09:03:44 +03:00
if ( ! charger - > pdata - > status_gpio_valid
| | bq24735_charger_is_present ( charger ) ) {
ret = bq24735_read_word ( client , BQ24735_MANUFACTURER_ID ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " Failed to read manufacturer id : %d \n " ,
ret ) ;
return ret ;
} else if ( ret ! = 0x0040 ) {
dev_err ( & client - > dev ,
" manufacturer id mismatch. 0x0040 != 0x%04x \n " , ret ) ;
return - ENODEV ;
}
ret = bq24735_read_word ( client , BQ24735_DEVICE_ID ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " Failed to read device id : %d \n " , ret ) ;
return ret ;
} else if ( ret ! = 0x000B ) {
dev_err ( & client - > dev ,
" device id mismatch. 0x000b != 0x%04x \n " , ret ) ;
return - ENODEV ;
}
}
2013-10-12 01:15:43 +04:00
ret = bq24735_config_charger ( charger ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " failed in configuring charger " ) ;
2015-07-25 06:53:16 +03:00
return ret ;
2013-10-12 01:15:43 +04:00
}
/* check for AC adapter presence */
if ( bq24735_charger_is_present ( charger ) ) {
ret = bq24735_enable_charging ( charger ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " Failed to enable charging \n " ) ;
2015-07-25 06:53:16 +03:00
return ret ;
2013-10-12 01:15:43 +04:00
}
}
2015-07-25 06:53:16 +03:00
charger - > charger = devm_power_supply_register ( & client - > dev , supply_desc ,
& psy_cfg ) ;
2015-03-12 10:44:11 +03:00
if ( IS_ERR ( charger - > charger ) ) {
ret = PTR_ERR ( charger - > charger ) ;
2013-10-12 01:15:43 +04:00
dev_err ( & client - > dev , " Failed to register power supply: %d \n " ,
ret ) ;
2015-07-25 06:53:16 +03:00
return ret ;
2013-10-12 01:15:43 +04:00
}
if ( client - > irq ) {
ret = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL , bq24735_charger_isr ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT ,
2015-03-12 10:44:11 +03:00
supply_desc - > name ,
charger - > charger ) ;
2013-10-12 01:15:43 +04:00
if ( ret ) {
dev_err ( & client - > dev ,
" Unable to register IRQ %d err %d \n " ,
client - > irq , ret ) ;
2015-07-25 06:53:16 +03:00
return ret ;
2013-10-12 01:15:43 +04:00
}
}
return 0 ;
}
static const struct i2c_device_id bq24735_charger_id [ ] = {
{ " bq24735-charger " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , bq24735_charger_id ) ;
static const struct of_device_id bq24735_match_ids [ ] = {
{ . compatible = " ti,bq24735 " , } ,
{ /* end */ }
} ;
MODULE_DEVICE_TABLE ( of , bq24735_match_ids ) ;
static struct i2c_driver bq24735_charger_driver = {
. driver = {
. name = " bq24735-charger " ,
. of_match_table = bq24735_match_ids ,
} ,
. probe = bq24735_charger_probe ,
. id_table = bq24735_charger_id ,
} ;
module_i2c_driver ( bq24735_charger_driver ) ;
MODULE_DESCRIPTION ( " bq24735 battery charging driver " ) ;
MODULE_AUTHOR ( " Darbha Sriharsha <dsriharsha@nvidia.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;