2012-09-21 20:10:00 +04:00
/*
* Generic battery driver code using IIO
* Copyright ( C ) 2012 , Anish Kumar < anish198519851985 @ gmail . com >
* based on jz4740 - battery . c
* based on s3c_adc_battery . c
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive for
* more details .
*
*/
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/gpio.h>
# include <linux/err.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/iio/consumer.h>
# include <linux/iio/types.h>
# include <linux/power/generic-adc-battery.h>
# define JITTER_DEFAULT 10 /* hope 10ms is enough */
enum gab_chan_type {
GAB_VOLTAGE = 0 ,
GAB_CURRENT ,
GAB_POWER ,
GAB_MAX_CHAN_TYPE
} ;
/*
* gab_chan_name suggests the standard channel names for commonly used
* channel types .
*/
static const char * const gab_chan_name [ ] = {
[ GAB_VOLTAGE ] = " voltage " ,
[ GAB_CURRENT ] = " current " ,
[ GAB_POWER ] = " power " ,
} ;
struct gab {
struct power_supply psy ;
struct iio_channel * channel [ GAB_MAX_CHAN_TYPE ] ;
struct gab_platform_data * pdata ;
struct delayed_work bat_work ;
int level ;
int status ;
bool cable_plugged ;
} ;
static struct gab * to_generic_bat ( struct power_supply * psy )
{
return container_of ( psy , struct gab , psy ) ;
}
static void gab_ext_power_changed ( struct power_supply * psy )
{
struct gab * adc_bat = to_generic_bat ( psy ) ;
schedule_delayed_work ( & adc_bat - > bat_work , msecs_to_jiffies ( 0 ) ) ;
}
static const enum power_supply_property gab_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN ,
POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN ,
POWER_SUPPLY_PROP_CHARGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
POWER_SUPPLY_PROP_MODEL_NAME ,
} ;
/*
* This properties are set based on the received platform data and this
* should correspond one - to - one with enum chan_type .
*/
static const enum power_supply_property gab_dyn_props [ ] = {
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_POWER_NOW ,
} ;
static bool gab_charge_finished ( struct gab * adc_bat )
{
struct gab_platform_data * pdata = adc_bat - > pdata ;
bool ret = gpio_get_value ( pdata - > gpio_charge_finished ) ;
bool inv = pdata - > gpio_inverted ;
if ( ! gpio_is_valid ( pdata - > gpio_charge_finished ) )
return false ;
return ret ^ inv ;
}
static int gab_get_status ( struct gab * adc_bat )
{
struct gab_platform_data * pdata = adc_bat - > pdata ;
struct power_supply_info * bat_info ;
bat_info = & pdata - > battery_info ;
if ( adc_bat - > level = = bat_info - > charge_full_design )
return POWER_SUPPLY_STATUS_FULL ;
return adc_bat - > status ;
}
static enum gab_chan_type gab_prop_to_chan ( enum power_supply_property psp )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_POWER_NOW :
return GAB_POWER ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
return GAB_VOLTAGE ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
return GAB_CURRENT ;
default :
WARN_ON ( 1 ) ;
break ;
}
return GAB_POWER ;
}
static int read_channel ( struct gab * adc_bat , enum power_supply_property psp ,
int * result )
{
int ret ;
int chan_index ;
chan_index = gab_prop_to_chan ( psp ) ;
ret = iio_read_channel_processed ( adc_bat - > channel [ chan_index ] ,
result ) ;
if ( ret < 0 )
pr_err ( " read channel error \n " ) ;
return ret ;
}
static int gab_get_property ( struct power_supply * psy ,
enum power_supply_property psp , union power_supply_propval * val )
{
struct gab * adc_bat ;
struct gab_platform_data * pdata ;
struct power_supply_info * bat_info ;
int result = 0 ;
int ret = 0 ;
adc_bat = to_generic_bat ( psy ) ;
if ( ! adc_bat ) {
dev_err ( psy - > dev , " no battery infos ?! \n " ) ;
return - EINVAL ;
}
pdata = adc_bat - > pdata ;
bat_info = & pdata - > battery_info ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
gab_get_status ( adc_bat ) ;
break ;
case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN :
val - > intval = 0 ;
break ;
case POWER_SUPPLY_PROP_CHARGE_NOW :
val - > intval = pdata - > cal_charge ( result ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
case POWER_SUPPLY_PROP_CURRENT_NOW :
case POWER_SUPPLY_PROP_POWER_NOW :
ret = read_channel ( adc_bat , psp , & result ) ;
if ( ret < 0 )
goto err ;
val - > intval = result ;
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = bat_info - > technology ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = bat_info - > voltage_min_design ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN :
val - > intval = bat_info - > voltage_max_design ;
break ;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN :
val - > intval = bat_info - > charge_full_design ;
break ;
case POWER_SUPPLY_PROP_MODEL_NAME :
val - > strval = bat_info - > name ;
break ;
default :
return - EINVAL ;
}
err :
return ret ;
}
static void gab_work ( struct work_struct * work )
{
struct gab * adc_bat ;
struct gab_platform_data * pdata ;
struct delayed_work * delayed_work ;
bool is_plugged ;
int status ;
delayed_work = container_of ( work , struct delayed_work , work ) ;
adc_bat = container_of ( delayed_work , struct gab , bat_work ) ;
pdata = adc_bat - > pdata ;
status = adc_bat - > status ;
is_plugged = power_supply_am_i_supplied ( & adc_bat - > psy ) ;
adc_bat - > cable_plugged = is_plugged ;
if ( ! is_plugged )
adc_bat - > status = POWER_SUPPLY_STATUS_DISCHARGING ;
else if ( gab_charge_finished ( adc_bat ) )
adc_bat - > status = POWER_SUPPLY_STATUS_NOT_CHARGING ;
else
adc_bat - > status = POWER_SUPPLY_STATUS_CHARGING ;
if ( status ! = adc_bat - > status )
power_supply_changed ( & adc_bat - > psy ) ;
}
static irqreturn_t gab_charged ( int irq , void * dev_id )
{
struct gab * adc_bat = dev_id ;
struct gab_platform_data * pdata = adc_bat - > pdata ;
int delay ;
delay = pdata - > jitter_delay ? pdata - > jitter_delay : JITTER_DEFAULT ;
schedule_delayed_work ( & adc_bat - > bat_work ,
msecs_to_jiffies ( delay ) ) ;
return IRQ_HANDLED ;
}
2012-11-19 22:22:23 +04:00
static int gab_probe ( struct platform_device * pdev )
2012-09-21 20:10:00 +04:00
{
struct gab * adc_bat ;
struct power_supply * psy ;
struct gab_platform_data * pdata = pdev - > dev . platform_data ;
enum power_supply_property * properties ;
int ret = 0 ;
int chan ;
int index = 0 ;
adc_bat = devm_kzalloc ( & pdev - > dev , sizeof ( * adc_bat ) , GFP_KERNEL ) ;
if ( ! adc_bat ) {
dev_err ( & pdev - > dev , " failed to allocate memory \n " ) ;
return - ENOMEM ;
}
psy = & adc_bat - > psy ;
psy - > name = pdata - > battery_info . name ;
/* bootup default values for the battery */
adc_bat - > cable_plugged = false ;
adc_bat - > status = POWER_SUPPLY_STATUS_DISCHARGING ;
psy - > type = POWER_SUPPLY_TYPE_BATTERY ;
psy - > get_property = gab_get_property ;
psy - > external_power_changed = gab_ext_power_changed ;
adc_bat - > pdata = pdata ;
/*
* copying the static properties and allocating extra memory for holding
* the extra configurable properties received from platform data .
*/
psy - > properties = kcalloc ( ARRAY_SIZE ( gab_props ) +
ARRAY_SIZE ( gab_chan_name ) ,
sizeof ( * psy - > properties ) , GFP_KERNEL ) ;
if ( ! psy - > properties ) {
ret = - ENOMEM ;
goto first_mem_fail ;
}
memcpy ( psy - > properties , gab_props , sizeof ( gab_props ) ) ;
2012-09-29 11:13:46 +04:00
properties = ( enum power_supply_property * )
( ( char * ) psy - > properties + sizeof ( gab_props ) ) ;
2012-09-21 20:10:00 +04:00
/*
* getting channel from iio and copying the battery properties
* based on the channel supported by consumer device .
*/
for ( chan = 0 ; chan < ARRAY_SIZE ( gab_chan_name ) ; chan + + ) {
2013-02-05 00:26:00 +04:00
adc_bat - > channel [ chan ] = iio_channel_get ( & pdev - > dev ,
gab_chan_name [ chan ] ) ;
2012-09-21 20:10:00 +04:00
if ( IS_ERR ( adc_bat - > channel [ chan ] ) ) {
ret = PTR_ERR ( adc_bat - > channel [ chan ] ) ;
2013-02-14 11:26:43 +04:00
adc_bat - > channel [ chan ] = NULL ;
2012-09-21 20:10:00 +04:00
} else {
/* copying properties for supported channels only */
memcpy ( properties + sizeof ( * ( psy - > properties ) ) * index ,
& gab_dyn_props [ chan ] ,
sizeof ( gab_dyn_props [ chan ] ) ) ;
index + + ;
}
}
/* none of the channels are supported so let's bail out */
2013-05-24 21:41:44 +04:00
if ( index = = 0 ) {
ret = - ENODEV ;
2012-09-21 20:10:00 +04:00
goto second_mem_fail ;
2013-05-24 21:41:44 +04:00
}
2012-09-21 20:10:00 +04:00
/*
* Total number of properties is equal to static properties
* plus the dynamic properties . Some properties may not be set
* as come channels may be not be supported by the device . So
* we need to take care of that .
*/
psy - > num_properties = ARRAY_SIZE ( gab_props ) + index ;
ret = power_supply_register ( & pdev - > dev , psy ) ;
if ( ret )
goto err_reg_fail ;
INIT_DELAYED_WORK ( & adc_bat - > bat_work , gab_work ) ;
if ( gpio_is_valid ( pdata - > gpio_charge_finished ) ) {
int irq ;
ret = gpio_request ( pdata - > gpio_charge_finished , " charged " ) ;
if ( ret )
goto gpio_req_fail ;
irq = gpio_to_irq ( pdata - > gpio_charge_finished ) ;
ret = request_any_context_irq ( irq , gab_charged ,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,
" battery charged " , adc_bat ) ;
2012-11-16 13:09:22 +04:00
if ( ret < 0 )
2012-09-21 20:10:00 +04:00
goto err_gpio ;
}
platform_set_drvdata ( pdev , adc_bat ) ;
/* Schedule timer to check current status */
schedule_delayed_work ( & adc_bat - > bat_work ,
msecs_to_jiffies ( 0 ) ) ;
return 0 ;
err_gpio :
gpio_free ( pdata - > gpio_charge_finished ) ;
gpio_req_fail :
power_supply_unregister ( psy ) ;
err_reg_fail :
2013-02-14 11:26:43 +04:00
for ( chan = 0 ; chan < ARRAY_SIZE ( gab_chan_name ) ; chan + + ) {
if ( adc_bat - > channel [ chan ] )
iio_channel_release ( adc_bat - > channel [ chan ] ) ;
}
2012-09-21 20:10:00 +04:00
second_mem_fail :
kfree ( psy - > properties ) ;
first_mem_fail :
return ret ;
}
2012-11-19 22:26:07 +04:00
static int gab_remove ( struct platform_device * pdev )
2012-09-21 20:10:00 +04:00
{
int chan ;
struct gab * adc_bat = platform_get_drvdata ( pdev ) ;
struct gab_platform_data * pdata = adc_bat - > pdata ;
power_supply_unregister ( & adc_bat - > psy ) ;
if ( gpio_is_valid ( pdata - > gpio_charge_finished ) ) {
free_irq ( gpio_to_irq ( pdata - > gpio_charge_finished ) , adc_bat ) ;
gpio_free ( pdata - > gpio_charge_finished ) ;
}
2013-02-14 11:26:43 +04:00
for ( chan = 0 ; chan < ARRAY_SIZE ( gab_chan_name ) ; chan + + ) {
if ( adc_bat - > channel [ chan ] )
iio_channel_release ( adc_bat - > channel [ chan ] ) ;
}
2012-09-21 20:10:00 +04:00
kfree ( adc_bat - > psy . properties ) ;
cancel_delayed_work ( & adc_bat - > bat_work ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int gab_suspend ( struct device * dev )
{
struct gab * adc_bat = dev_get_drvdata ( dev ) ;
cancel_delayed_work_sync ( & adc_bat - > bat_work ) ;
adc_bat - > status = POWER_SUPPLY_STATUS_UNKNOWN ;
return 0 ;
}
static int gab_resume ( struct device * dev )
{
struct gab * adc_bat = dev_get_drvdata ( dev ) ;
struct gab_platform_data * pdata = adc_bat - > pdata ;
int delay ;
delay = pdata - > jitter_delay ? pdata - > jitter_delay : JITTER_DEFAULT ;
/* Schedule timer to check current status */
schedule_delayed_work ( & adc_bat - > bat_work ,
msecs_to_jiffies ( delay ) ) ;
return 0 ;
}
static const struct dev_pm_ops gab_pm_ops = {
. suspend = gab_suspend ,
. resume = gab_resume ,
} ;
# define GAB_PM_OPS (&gab_pm_ops)
# else
# define GAB_PM_OPS (NULL)
# endif
static struct platform_driver gab_driver = {
. driver = {
. name = " generic-adc-battery " ,
. owner = THIS_MODULE ,
. pm = GAB_PM_OPS
} ,
. probe = gab_probe ,
2012-11-19 22:20:40 +04:00
. remove = gab_remove ,
2012-09-21 20:10:00 +04:00
} ;
module_platform_driver ( gab_driver ) ;
MODULE_AUTHOR ( " anish kumar <anish198519851985@gmail.com> " ) ;
MODULE_DESCRIPTION ( " generic battery driver using IIO " ) ;
MODULE_LICENSE ( " GPL " ) ;