2013-08-29 13:41:52 +04:00
/*
* Dumb driver for LiIon batteries using TWL4030 madc .
*
* Copyright 2013 Golden Delicious Computers
* Lukas Märdian < lukas @ goldelico . com >
*
* Based on dumb driver for gta01 battery
* Copyright 2009 Openmoko , Inc
* Balaji Rao < balajirrao @ openmoko . org >
*/
# include <linux/module.h>
# include <linux/param.h>
# include <linux/delay.h>
# include <linux/workqueue.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/slab.h>
# include <linux/sort.h>
# include <linux/i2c/twl4030-madc.h>
# include <linux/power/twl4030_madc_battery.h>
2015-03-11 00:27:22 +03:00
# include <linux/iio/consumer.h>
2013-08-29 13:41:52 +04:00
struct twl4030_madc_battery {
2015-03-12 10:44:11 +03:00
struct power_supply * psy ;
2013-08-29 13:41:52 +04:00
struct twl4030_madc_bat_platform_data * pdata ;
2015-03-11 00:27:22 +03:00
struct iio_channel * channel_temp ;
struct iio_channel * channel_ichg ;
struct iio_channel * channel_vbat ;
2013-08-29 13:41:52 +04:00
} ;
static enum power_supply_property twl4030_madc_bat_props [ ] = {
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_CAPACITY ,
POWER_SUPPLY_PROP_CHARGE_FULL ,
POWER_SUPPLY_PROP_CHARGE_NOW ,
POWER_SUPPLY_PROP_TEMP ,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW ,
} ;
2015-03-11 00:27:22 +03:00
static int madc_read ( struct iio_channel * channel )
2013-08-29 13:41:52 +04:00
{
2015-03-11 00:27:22 +03:00
int val , err ;
err = iio_read_channel_processed ( channel , & val ) ;
if ( err < 0 )
return err ;
2013-08-29 13:41:52 +04:00
2015-03-11 00:27:22 +03:00
return val ;
2013-08-29 13:41:52 +04:00
}
2015-03-11 00:27:22 +03:00
static int twl4030_madc_bat_get_charging_status ( struct twl4030_madc_battery * bt )
2013-08-29 13:41:52 +04:00
{
2015-03-11 00:27:22 +03:00
return ( madc_read ( bt - > channel_ichg ) > 0 ) ? 1 : 0 ;
2013-08-29 13:41:52 +04:00
}
2015-03-11 00:27:22 +03:00
static int twl4030_madc_bat_get_voltage ( struct twl4030_madc_battery * bt )
2013-08-29 13:41:52 +04:00
{
2015-03-11 00:27:22 +03:00
return madc_read ( bt - > channel_vbat ) ;
2013-08-29 13:41:52 +04:00
}
2015-03-11 00:27:22 +03:00
static int twl4030_madc_bat_get_current ( struct twl4030_madc_battery * bt )
2013-08-29 13:41:52 +04:00
{
2015-03-11 00:27:22 +03:00
return madc_read ( bt - > channel_ichg ) * 1000 ;
2013-08-29 13:41:52 +04:00
}
2015-03-11 00:27:22 +03:00
static int twl4030_madc_bat_get_temp ( struct twl4030_madc_battery * bt )
2013-08-29 13:41:52 +04:00
{
2015-03-11 00:27:22 +03:00
return madc_read ( bt - > channel_temp ) * 10 ;
2013-08-29 13:41:52 +04:00
}
static int twl4030_madc_bat_voltscale ( struct twl4030_madc_battery * bat ,
int volt )
{
struct twl4030_madc_bat_calibration * calibration ;
int i , res = 0 ;
/* choose charging curve */
2015-03-11 00:27:22 +03:00
if ( twl4030_madc_bat_get_charging_status ( bat ) )
2013-08-29 13:41:52 +04:00
calibration = bat - > pdata - > charging ;
else
calibration = bat - > pdata - > discharging ;
if ( volt > calibration [ 0 ] . voltage ) {
res = calibration [ 0 ] . level ;
} else {
for ( i = 0 ; calibration [ i + 1 ] . voltage > = 0 ; i + + ) {
if ( volt < = calibration [ i ] . voltage & &
volt > = calibration [ i + 1 ] . voltage ) {
/* interval found - interpolate within range */
res = calibration [ i ] . level -
( ( calibration [ i ] . voltage - volt ) *
( calibration [ i ] . level -
calibration [ i + 1 ] . level ) ) /
( calibration [ i ] . voltage -
calibration [ i + 1 ] . voltage ) ;
break ;
}
}
}
return res ;
}
static int twl4030_madc_bat_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 twl4030_madc_battery * bat = power_supply_get_drvdata ( psy ) ;
2013-08-29 13:41:52 +04:00
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
if ( twl4030_madc_bat_voltscale ( bat ,
2015-03-11 00:27:22 +03:00
twl4030_madc_bat_get_voltage ( bat ) ) > 95 )
2013-08-29 13:41:52 +04:00
val - > intval = POWER_SUPPLY_STATUS_FULL ;
else {
2015-03-11 00:27:22 +03:00
if ( twl4030_madc_bat_get_charging_status ( bat ) )
2013-08-29 13:41:52 +04:00
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
}
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
2015-03-11 00:27:22 +03:00
val - > intval = twl4030_madc_bat_get_voltage ( bat ) * 1000 ;
2013-08-29 13:41:52 +04:00
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = POWER_SUPPLY_TECHNOLOGY_LION ;
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
2015-03-11 00:27:22 +03:00
val - > intval = twl4030_madc_bat_get_current ( bat ) ;
2013-08-29 13:41:52 +04:00
break ;
case POWER_SUPPLY_PROP_PRESENT :
/* assume battery is always present */
val - > intval = 1 ;
break ;
case POWER_SUPPLY_PROP_CHARGE_NOW : {
int percent = twl4030_madc_bat_voltscale ( bat ,
2015-03-11 00:27:22 +03:00
twl4030_madc_bat_get_voltage ( bat ) ) ;
2013-08-29 13:41:52 +04:00
val - > intval = ( percent * bat - > pdata - > capacity ) / 100 ;
break ;
}
case POWER_SUPPLY_PROP_CAPACITY :
val - > intval = twl4030_madc_bat_voltscale ( bat ,
2015-03-11 00:27:22 +03:00
twl4030_madc_bat_get_voltage ( bat ) ) ;
2013-08-29 13:41:52 +04:00
break ;
case POWER_SUPPLY_PROP_CHARGE_FULL :
val - > intval = bat - > pdata - > capacity ;
break ;
case POWER_SUPPLY_PROP_TEMP :
2015-03-11 00:27:22 +03:00
val - > intval = twl4030_madc_bat_get_temp ( bat ) ;
2013-08-29 13:41:52 +04:00
break ;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW : {
int percent = twl4030_madc_bat_voltscale ( bat ,
2015-03-11 00:27:22 +03:00
twl4030_madc_bat_get_voltage ( bat ) ) ;
2013-08-29 13:41:52 +04:00
/* in mAh */
int chg = ( percent * ( bat - > pdata - > capacity / 1000 ) ) / 100 ;
/* assume discharge with 400 mA (ca. 1.5W) */
val - > intval = ( 3600l * chg ) / 400 ;
break ;
}
default :
return - EINVAL ;
}
return 0 ;
}
static void twl4030_madc_bat_ext_changed ( struct power_supply * psy )
{
2015-03-12 10:44:11 +03:00
power_supply_changed ( psy ) ;
2013-08-29 13:41:52 +04:00
}
2015-03-12 10:44:11 +03:00
static const struct power_supply_desc twl4030_madc_bat_desc = {
. name = " twl4030_battery " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = twl4030_madc_bat_props ,
. num_properties = ARRAY_SIZE ( twl4030_madc_bat_props ) ,
. get_property = twl4030_madc_bat_get_property ,
. external_power_changed = twl4030_madc_bat_ext_changed ,
} ;
2013-08-29 13:41:52 +04:00
static int twl4030_cmp ( const void * a , const void * b )
{
return ( ( struct twl4030_madc_bat_calibration * ) b ) - > voltage -
( ( struct twl4030_madc_bat_calibration * ) a ) - > voltage ;
}
static int twl4030_madc_battery_probe ( struct platform_device * pdev )
{
struct twl4030_madc_battery * twl4030_madc_bat ;
struct twl4030_madc_bat_platform_data * pdata = pdev - > dev . platform_data ;
2015-03-12 10:44:11 +03:00
struct power_supply_config psy_cfg = { } ;
2015-02-20 16:32:22 +03:00
int ret = 0 ;
2013-08-29 13:41:52 +04:00
2015-03-11 00:27:22 +03:00
twl4030_madc_bat = devm_kzalloc ( & pdev - > dev , sizeof ( * twl4030_madc_bat ) ,
GFP_KERNEL ) ;
2013-08-29 13:41:52 +04:00
if ( ! twl4030_madc_bat )
return - ENOMEM ;
2015-03-11 00:27:22 +03:00
twl4030_madc_bat - > channel_temp = iio_channel_get ( & pdev - > dev , " temp " ) ;
if ( IS_ERR ( twl4030_madc_bat - > channel_temp ) ) {
ret = PTR_ERR ( twl4030_madc_bat - > channel_temp ) ;
goto err ;
}
twl4030_madc_bat - > channel_ichg = iio_channel_get ( & pdev - > dev , " ichg " ) ;
if ( IS_ERR ( twl4030_madc_bat - > channel_ichg ) ) {
ret = PTR_ERR ( twl4030_madc_bat - > channel_ichg ) ;
goto err_temp ;
}
twl4030_madc_bat - > channel_vbat = iio_channel_get ( & pdev - > dev , " vbat " ) ;
if ( IS_ERR ( twl4030_madc_bat - > channel_vbat ) ) {
ret = PTR_ERR ( twl4030_madc_bat - > channel_vbat ) ;
goto err_ichg ;
}
2013-08-29 13:41:52 +04:00
/* sort charging and discharging calibration data */
sort ( pdata - > charging , pdata - > charging_size ,
sizeof ( struct twl4030_madc_bat_calibration ) ,
twl4030_cmp , NULL ) ;
sort ( pdata - > discharging , pdata - > discharging_size ,
sizeof ( struct twl4030_madc_bat_calibration ) ,
twl4030_cmp , NULL ) ;
twl4030_madc_bat - > pdata = pdata ;
platform_set_drvdata ( pdev , twl4030_madc_bat ) ;
2015-03-12 10:44:11 +03:00
psy_cfg . drv_data = twl4030_madc_bat ;
twl4030_madc_bat - > psy = power_supply_register ( & pdev - > dev ,
& twl4030_madc_bat_desc ,
& psy_cfg ) ;
if ( IS_ERR ( twl4030_madc_bat - > psy ) ) {
ret = PTR_ERR ( twl4030_madc_bat - > psy ) ;
2015-03-11 00:27:22 +03:00
goto err_vbat ;
2015-03-12 10:44:11 +03:00
}
2013-08-29 13:41:52 +04:00
2015-03-11 00:27:22 +03:00
return 0 ;
err_vbat :
iio_channel_release ( twl4030_madc_bat - > channel_vbat ) ;
err_ichg :
iio_channel_release ( twl4030_madc_bat - > channel_ichg ) ;
err_temp :
iio_channel_release ( twl4030_madc_bat - > channel_temp ) ;
err :
2015-02-20 16:32:22 +03:00
return ret ;
2013-08-29 13:41:52 +04:00
}
static int twl4030_madc_battery_remove ( struct platform_device * pdev )
{
struct twl4030_madc_battery * bat = platform_get_drvdata ( pdev ) ;
2015-03-12 10:44:11 +03:00
power_supply_unregister ( bat - > psy ) ;
2015-03-11 00:27:22 +03:00
iio_channel_release ( bat - > channel_vbat ) ;
iio_channel_release ( bat - > channel_ichg ) ;
iio_channel_release ( bat - > channel_temp ) ;
2013-08-29 13:41:52 +04:00
return 0 ;
}
static struct platform_driver twl4030_madc_battery_driver = {
. driver = {
. name = " twl4030_madc_battery " ,
} ,
. probe = twl4030_madc_battery_probe ,
. remove = twl4030_madc_battery_remove ,
} ;
module_platform_driver ( twl4030_madc_battery_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Lukas Märdian <lukas@goldelico.com> " ) ;
MODULE_DESCRIPTION ( " twl4030_madc battery driver " ) ;
2015-03-11 00:27:27 +03:00
MODULE_ALIAS ( " platform:twl4030_madc_battery " ) ;