2011-01-14 08:46:11 +03:00
/*
* Fuel gauge driver for Maxim 17042 / 8966 / 8997
* Note that Maxim 8966 and 8997 are mfd and this is its subdevice .
*
* Copyright ( C ) 2011 Samsung Electronics
* MyungJoo Ham < myungjoo . ham @ samsung . com >
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
* This driver is based on max17040_battery . c
*/
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/mod_devicetable.h>
# include <linux/power_supply.h>
# include <linux/power/max17042_battery.h>
struct max17042_chip {
struct i2c_client * client ;
struct power_supply battery ;
struct max17042_platform_data * pdata ;
} ;
static int max17042_write_reg ( struct i2c_client * client , u8 reg , u16 value )
{
int ret = i2c_smbus_write_word_data ( client , reg , value ) ;
if ( ret < 0 )
dev_err ( & client - > dev , " %s: err %d \n " , __func__ , ret ) ;
return ret ;
}
static int max17042_read_reg ( struct i2c_client * client , u8 reg )
{
int ret = i2c_smbus_read_word_data ( client , reg ) ;
if ( ret < 0 )
dev_err ( & client - > dev , " %s: err %d \n " , __func__ , ret ) ;
return ret ;
}
2011-06-30 13:07:41 +04:00
static void max17042_set_reg ( struct i2c_client * client ,
struct max17042_reg_data * data , int size )
{
int i ;
for ( i = 0 ; i < size ; i + + )
max17042_write_reg ( client , data [ i ] . addr , data [ i ] . data ) ;
}
2011-01-14 08:46:11 +03:00
static enum power_supply_property max17042_battery_props [ ] = {
2011-06-30 13:07:41 +04:00
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_CYCLE_COUNT ,
POWER_SUPPLY_PROP_VOLTAGE_MAX ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
2011-01-14 08:46:11 +03:00
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_AVG ,
POWER_SUPPLY_PROP_CAPACITY ,
2011-06-30 13:07:41 +04:00
POWER_SUPPLY_PROP_CHARGE_FULL ,
POWER_SUPPLY_PROP_TEMP ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_CURRENT_AVG ,
2011-01-14 08:46:11 +03:00
} ;
static int max17042_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct max17042_chip * chip = container_of ( psy ,
struct max17042_chip , battery ) ;
switch ( psp ) {
2011-06-30 13:07:41 +04:00
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_STATUS ) ;
if ( val - > intval & MAX17042_STATUS_BattAbsent )
val - > intval = 0 ;
else
val - > intval = 1 ;
break ;
case POWER_SUPPLY_PROP_CYCLE_COUNT :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_Cycles ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_MinMaxVolt ) ;
val - > intval > > = 8 ;
val - > intval * = 20000 ; /* Units of LSB = 20mV */
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_V_empty ) ;
val - > intval > > = 7 ;
val - > intval * = 10000 ; /* Units of LSB = 10mV */
break ;
2011-01-14 08:46:11 +03:00
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_VCELL ) * 83 ; /* 1000 / 12 = 83 */
break ;
case POWER_SUPPLY_PROP_VOLTAGE_AVG :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_AvgVCELL ) * 83 ;
break ;
case POWER_SUPPLY_PROP_CAPACITY :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_SOC ) / 256 ;
break ;
2011-06-30 13:07:41 +04:00
case POWER_SUPPLY_PROP_CHARGE_FULL :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_RepSOC ) ;
if ( ( val - > intval / 256 ) > = MAX17042_BATTERY_FULL )
val - > intval = 1 ;
else if ( val - > intval > = 0 )
val - > intval = 0 ;
break ;
case POWER_SUPPLY_PROP_TEMP :
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_TEMP ) ;
/* The value is signed. */
if ( val - > intval & 0x8000 ) {
val - > intval = ( 0x7fff & ~ val - > intval ) + 1 ;
val - > intval * = - 1 ;
}
/* The value is converted into deci-centigrade scale */
/* Units of LSB = 1 / 256 degree Celsius */
val - > intval = val - > intval * 10 / 256 ;
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
if ( chip - > pdata - > enable_current_sense ) {
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_Current ) ;
if ( val - > intval & 0x8000 ) {
/* Negative */
val - > intval = ~ val - > intval & 0x7fff ;
val - > intval + + ;
val - > intval * = - 1 ;
}
val - > intval > > = 4 ;
val - > intval * = 1000000 * 25 / chip - > pdata - > r_sns ;
} else {
return - EINVAL ;
}
break ;
case POWER_SUPPLY_PROP_CURRENT_AVG :
if ( chip - > pdata - > enable_current_sense ) {
val - > intval = max17042_read_reg ( chip - > client ,
MAX17042_AvgCurrent ) ;
if ( val - > intval & 0x8000 ) {
/* Negative */
val - > intval = ~ val - > intval & 0x7fff ;
val - > intval + + ;
val - > intval * = - 1 ;
}
val - > intval * = 1562500 / chip - > pdata - > r_sns ;
} else {
return - EINVAL ;
}
break ;
2011-01-14 08:46:11 +03:00
default :
return - EINVAL ;
}
return 0 ;
}
static int __devinit max17042_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct i2c_adapter * adapter = to_i2c_adapter ( client - > dev . parent ) ;
struct max17042_chip * chip ;
int ret ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_WORD_DATA ) )
return - EIO ;
chip = kzalloc ( sizeof ( * chip ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > client = client ;
chip - > pdata = client - > dev . platform_data ;
i2c_set_clientdata ( client , chip ) ;
chip - > battery . name = " max17042_battery " ;
chip - > battery . type = POWER_SUPPLY_TYPE_BATTERY ;
chip - > battery . get_property = max17042_get_property ;
chip - > battery . properties = max17042_battery_props ;
chip - > battery . num_properties = ARRAY_SIZE ( max17042_battery_props ) ;
2011-06-30 13:07:41 +04:00
/* When current is not measured,
* CURRENT_NOW and CURRENT_AVG properties should be invisible . */
if ( ! chip - > pdata - > enable_current_sense )
chip - > battery . num_properties - = 2 ;
2011-01-14 08:46:11 +03:00
ret = power_supply_register ( & client - > dev , & chip - > battery ) ;
if ( ret ) {
dev_err ( & client - > dev , " failed: power supply register \n " ) ;
kfree ( chip ) ;
return ret ;
}
2011-06-30 13:07:41 +04:00
/* Initialize registers according to values from the platform data */
if ( chip - > pdata - > init_data )
max17042_set_reg ( client , chip - > pdata - > init_data ,
chip - > pdata - > num_init_data ) ;
2011-01-14 08:46:11 +03:00
if ( ! chip - > pdata - > enable_current_sense ) {
max17042_write_reg ( client , MAX17042_CGAIN , 0x0000 ) ;
max17042_write_reg ( client , MAX17042_MiscCFG , 0x0003 ) ;
max17042_write_reg ( client , MAX17042_LearnCFG , 0x0007 ) ;
2011-06-30 13:07:41 +04:00
} else {
if ( chip - > pdata - > r_sns = = 0 )
chip - > pdata - > r_sns = MAX17042_DEFAULT_SNS_RESISTOR ;
2011-01-14 08:46:11 +03:00
}
return 0 ;
}
static int __devexit max17042_remove ( struct i2c_client * client )
{
struct max17042_chip * chip = i2c_get_clientdata ( client ) ;
power_supply_unregister ( & chip - > battery ) ;
kfree ( chip ) ;
return 0 ;
}
static const struct i2c_device_id max17042_id [ ] = {
{ " max17042 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , max17042_id ) ;
static struct i2c_driver max17042_i2c_driver = {
. driver = {
. name = " max17042 " ,
} ,
. probe = max17042_probe ,
. remove = __devexit_p ( max17042_remove ) ,
. id_table = max17042_id ,
} ;
static int __init max17042_init ( void )
{
return i2c_add_driver ( & max17042_i2c_driver ) ;
}
module_init ( max17042_init ) ;
static void __exit max17042_exit ( void )
{
i2c_del_driver ( & max17042_i2c_driver ) ;
}
module_exit ( max17042_exit ) ;
MODULE_AUTHOR ( " MyungJoo Ham <myungjoo.ham@samsung.com> " ) ;
MODULE_DESCRIPTION ( " MAX17042 Fuel Gauge " ) ;
MODULE_LICENSE ( " GPL " ) ;