2008-12-30 22:44:53 +01:00
/*
* Battery charger driver for Dialog Semiconductor DA9030
*
* Copyright ( C ) 2008 Compulab , Ltd .
* Mike Rapoport < mike @ compulab . co . il >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/device.h>
# include <linux/workqueue.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/mfd/da903x.h>
# include <linux/debugfs.h>
# include <linux/seq_file.h>
# define DA9030_FAULT_LOG 0x0a
# define DA9030_FAULT_LOG_OVER_TEMP (1 << 7)
# define DA9030_FAULT_LOG_VBAT_OVER (1 << 4)
# define DA9030_CHARGE_CONTROL 0x28
# define DA9030_CHRG_CHARGER_ENABLE (1 << 7)
# define DA9030_ADC_MAN_CONTROL 0x30
# define DA9030_ADC_TBATREF_ENABLE (1 << 5)
# define DA9030_ADC_LDO_INT_ENABLE (1 << 4)
# define DA9030_ADC_AUTO_CONTROL 0x31
# define DA9030_ADC_TBAT_ENABLE (1 << 5)
# define DA9030_ADC_VBAT_IN_TXON (1 << 4)
# define DA9030_ADC_VCH_ENABLE (1 << 3)
# define DA9030_ADC_ICH_ENABLE (1 << 2)
# define DA9030_ADC_VBAT_ENABLE (1 << 1)
# define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0)
# define DA9030_VBATMON 0x32
# define DA9030_VBATMONTXON 0x33
# define DA9030_TBATHIGHP 0x34
# define DA9030_TBATHIGHN 0x35
# define DA9030_TBATLOW 0x36
# define DA9030_VBAT_RES 0x41
# define DA9030_VBATMIN_RES 0x42
# define DA9030_VBATMINTXON_RES 0x43
# define DA9030_ICHMAX_RES 0x44
# define DA9030_ICHMIN_RES 0x45
# define DA9030_ICHAVERAGE_RES 0x46
# define DA9030_VCHMAX_RES 0x47
# define DA9030_VCHMIN_RES 0x48
# define DA9030_TBAT_RES 0x49
struct da9030_adc_res {
uint8_t vbat_res ;
uint8_t vbatmin_res ;
uint8_t vbatmintxon ;
uint8_t ichmax_res ;
uint8_t ichmin_res ;
uint8_t ichaverage_res ;
uint8_t vchmax_res ;
uint8_t vchmin_res ;
uint8_t tbat_res ;
uint8_t adc_in4_res ;
uint8_t adc_in5_res ;
} ;
struct da9030_battery_thresholds {
int tbat_low ;
int tbat_high ;
int tbat_restart ;
int vbat_low ;
int vbat_crit ;
int vbat_charge_start ;
int vbat_charge_stop ;
int vbat_charge_restart ;
int vcharge_min ;
int vcharge_max ;
} ;
struct da9030_charger {
struct power_supply psy ;
struct device * master ;
struct da9030_adc_res adc ;
struct delayed_work work ;
unsigned int interval ;
struct power_supply_info * battery_info ;
struct da9030_battery_thresholds thresholds ;
unsigned int charge_milliamp ;
unsigned int charge_millivolt ;
/* charger status */
bool chdet ;
uint8_t fault ;
int mA ;
int mV ;
bool is_on ;
struct notifier_block nb ;
/* platform callbacks for battery low and critical events */
void ( * battery_low ) ( void ) ;
void ( * battery_critical ) ( void ) ;
struct dentry * debug_file ;
} ;
static inline int da9030_reg_to_mV ( int reg )
{
return ( ( reg * 2650 ) > > 8 ) + 2650 ;
}
static inline int da9030_millivolt_to_reg ( int mV )
{
return ( ( mV - 2650 ) < < 8 ) / 2650 ;
}
static inline int da9030_reg_to_mA ( int reg )
{
return ( ( reg * 24000 ) > > 8 ) / 15 ;
}
# ifdef CONFIG_DEBUG_FS
static int bat_debug_show ( struct seq_file * s , void * data )
{
struct da9030_charger * charger = s - > private ;
seq_printf ( s , " charger is %s \n " , charger - > is_on ? " on " : " off " ) ;
if ( charger - > chdet ) {
seq_printf ( s , " iset = %dmA, vset = %dmV \n " ,
charger - > mA , charger - > mV ) ;
}
seq_printf ( s , " vbat_res = %d (%dmV) \n " ,
charger - > adc . vbat_res ,
da9030_reg_to_mV ( charger - > adc . vbat_res ) ) ;
seq_printf ( s , " vbatmin_res = %d (%dmV) \n " ,
charger - > adc . vbatmin_res ,
da9030_reg_to_mV ( charger - > adc . vbatmin_res ) ) ;
seq_printf ( s , " vbatmintxon = %d (%dmV) \n " ,
charger - > adc . vbatmintxon ,
da9030_reg_to_mV ( charger - > adc . vbatmintxon ) ) ;
seq_printf ( s , " ichmax_res = %d (%dmA) \n " ,
charger - > adc . ichmax_res ,
da9030_reg_to_mV ( charger - > adc . ichmax_res ) ) ;
seq_printf ( s , " ichmin_res = %d (%dmA) \n " ,
charger - > adc . ichmin_res ,
da9030_reg_to_mA ( charger - > adc . ichmin_res ) ) ;
seq_printf ( s , " ichaverage_res = %d (%dmA) \n " ,
charger - > adc . ichaverage_res ,
da9030_reg_to_mA ( charger - > adc . ichaverage_res ) ) ;
seq_printf ( s , " vchmax_res = %d (%dmV) \n " ,
charger - > adc . vchmax_res ,
da9030_reg_to_mA ( charger - > adc . vchmax_res ) ) ;
seq_printf ( s , " vchmin_res = %d (%dmV) \n " ,
charger - > adc . vchmin_res ,
da9030_reg_to_mV ( charger - > adc . vchmin_res ) ) ;
return 0 ;
}
static int debug_open ( struct inode * inode , struct file * file )
{
return single_open ( file , bat_debug_show , inode - > i_private ) ;
}
static const struct file_operations bat_debug_fops = {
. open = debug_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static struct dentry * da9030_bat_create_debugfs ( struct da9030_charger * charger )
{
charger - > debug_file = debugfs_create_file ( " charger " , 0666 , 0 , charger ,
& bat_debug_fops ) ;
return charger - > debug_file ;
}
static void da9030_bat_remove_debugfs ( struct da9030_charger * charger )
{
debugfs_remove ( charger - > debug_file ) ;
}
# else
static inline struct dentry * da9030_bat_create_debugfs ( struct da9030_charger * charger )
{
return NULL ;
}
static inline void da9030_bat_remove_debugfs ( struct da9030_charger * charger )
{
}
# endif
static inline void da9030_read_adc ( struct da9030_charger * charger ,
struct da9030_adc_res * adc )
{
da903x_reads ( charger - > master , DA9030_VBAT_RES ,
sizeof ( * adc ) , ( uint8_t * ) adc ) ;
}
static void da9030_charger_update_state ( struct da9030_charger * charger )
{
uint8_t val ;
da903x_read ( charger - > master , DA9030_CHARGE_CONTROL , & val ) ;
charger - > is_on = ( val & DA9030_CHRG_CHARGER_ENABLE ) ? 1 : 0 ;
charger - > mA = ( ( val > > 3 ) & 0xf ) * 100 ;
charger - > mV = ( val & 0x7 ) * 50 + 4000 ;
da9030_read_adc ( charger , & charger - > adc ) ;
da903x_read ( charger - > master , DA9030_FAULT_LOG , & charger - > fault ) ;
charger - > chdet = da903x_query_status ( charger - > master ,
DA9030_STATUS_CHDET ) ;
}
static void da9030_set_charge ( struct da9030_charger * charger , int on )
{
uint8_t val ;
if ( on ) {
val = DA9030_CHRG_CHARGER_ENABLE ;
val | = ( charger - > charge_milliamp / 100 ) < < 3 ;
val | = ( charger - > charge_millivolt - 4000 ) / 50 ;
charger - > is_on = 1 ;
} else {
val = 0 ;
charger - > is_on = 0 ;
}
da903x_write ( charger - > master , DA9030_CHARGE_CONTROL , val ) ;
2009-06-09 01:09:45 +04:00
power_supply_changed ( & charger - > psy ) ;
2008-12-30 22:44:53 +01:00
}
static void da9030_charger_check_state ( struct da9030_charger * charger )
{
da9030_charger_update_state ( charger ) ;
/* we wake or boot with external power on */
if ( ! charger - > is_on ) {
if ( ( charger - > chdet ) & &
( charger - > adc . vbat_res <
charger - > thresholds . vbat_charge_start ) ) {
da9030_set_charge ( charger , 1 ) ;
}
} else {
2009-06-09 01:09:45 +04:00
/* Charger has been pulled out */
if ( ! charger - > chdet ) {
da9030_set_charge ( charger , 0 ) ;
return ;
}
2008-12-30 22:44:53 +01:00
if ( charger - > adc . vbat_res > =
charger - > thresholds . vbat_charge_stop ) {
da9030_set_charge ( charger , 0 ) ;
da903x_write ( charger - > master , DA9030_VBATMON ,
charger - > thresholds . vbat_charge_restart ) ;
} else if ( charger - > adc . vbat_res >
charger - > thresholds . vbat_low ) {
/* we are charging and passed LOW_THRESH,
so upate DA9030 VBAT threshold
*/
da903x_write ( charger - > master , DA9030_VBATMON ,
charger - > thresholds . vbat_low ) ;
}
if ( charger - > adc . vchmax_res > charger - > thresholds . vcharge_max | |
charger - > adc . vchmin_res < charger - > thresholds . vcharge_min | |
/* Tempreture readings are negative */
charger - > adc . tbat_res < charger - > thresholds . tbat_high | |
charger - > adc . tbat_res > charger - > thresholds . tbat_low ) {
/* disable charger */
da9030_set_charge ( charger , 0 ) ;
}
}
}
static void da9030_charging_monitor ( struct work_struct * work )
{
struct da9030_charger * charger ;
charger = container_of ( work , struct da9030_charger , work . work ) ;
da9030_charger_check_state ( charger ) ;
/* reschedule for the next time */
schedule_delayed_work ( & charger - > work , charger - > interval ) ;
}
static enum power_supply_property da9030_battery_props [ ] = {
POWER_SUPPLY_PROP_MODEL_NAME ,
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_AVG ,
} ;
static void da9030_battery_check_status ( struct da9030_charger * charger ,
union power_supply_propval * val )
{
if ( charger - > chdet ) {
if ( charger - > is_on )
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
} else {
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
}
}
static void da9030_battery_check_health ( struct da9030_charger * charger ,
union power_supply_propval * val )
{
if ( charger - > fault & DA9030_FAULT_LOG_OVER_TEMP )
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
else if ( charger - > fault & DA9030_FAULT_LOG_VBAT_OVER )
val - > intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
}
static int da9030_battery_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct da9030_charger * charger ;
charger = container_of ( psy , struct da9030_charger , psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
da9030_battery_check_status ( charger , val ) ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
da9030_battery_check_health ( charger , val ) ;
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = charger - > battery_info - > technology ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN :
val - > intval = charger - > battery_info - > voltage_max_design ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = charger - > battery_info - > voltage_min_design ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
val - > intval = da9030_reg_to_mV ( charger - > adc . vbat_res ) * 1000 ;
break ;
case POWER_SUPPLY_PROP_CURRENT_AVG :
val - > intval =
da9030_reg_to_mA ( charger - > adc . ichaverage_res ) * 1000 ;
break ;
case POWER_SUPPLY_PROP_MODEL_NAME :
val - > strval = charger - > battery_info - > name ;
break ;
default :
break ;
}
return 0 ;
}
static void da9030_battery_vbat_event ( struct da9030_charger * charger )
{
da9030_read_adc ( charger , & charger - > adc ) ;
if ( charger - > is_on )
return ;
if ( charger - > adc . vbat_res < charger - > thresholds . vbat_low ) {
/* set VBAT threshold for critical */
da903x_write ( charger - > master , DA9030_VBATMON ,
charger - > thresholds . vbat_crit ) ;
if ( charger - > battery_low )
charger - > battery_low ( ) ;
} else if ( charger - > adc . vbat_res <
charger - > thresholds . vbat_crit ) {
/* notify the system of battery critical */
if ( charger - > battery_critical )
charger - > battery_critical ( ) ;
}
}
static int da9030_battery_event ( struct notifier_block * nb , unsigned long event ,
void * data )
{
struct da9030_charger * charger =
container_of ( nb , struct da9030_charger , nb ) ;
switch ( event ) {
case DA9030_EVENT_CHDET :
2009-06-09 01:09:45 +04:00
cancel_delayed_work_sync ( & charger - > work ) ;
schedule_work ( & charger - > work . work ) ;
2008-12-30 22:44:53 +01:00
break ;
case DA9030_EVENT_VBATMON :
da9030_battery_vbat_event ( charger ) ;
break ;
case DA9030_EVENT_CHIOVER :
case DA9030_EVENT_TBAT :
da9030_set_charge ( charger , 0 ) ;
break ;
}
return 0 ;
}
static void da9030_battery_convert_thresholds ( struct da9030_charger * charger ,
struct da9030_battery_info * pdata )
{
charger - > thresholds . tbat_low = pdata - > tbat_low ;
charger - > thresholds . tbat_high = pdata - > tbat_high ;
charger - > thresholds . tbat_restart = pdata - > tbat_restart ;
charger - > thresholds . vbat_low =
da9030_millivolt_to_reg ( pdata - > vbat_low ) ;
charger - > thresholds . vbat_crit =
da9030_millivolt_to_reg ( pdata - > vbat_crit ) ;
charger - > thresholds . vbat_charge_start =
da9030_millivolt_to_reg ( pdata - > vbat_charge_start ) ;
charger - > thresholds . vbat_charge_stop =
da9030_millivolt_to_reg ( pdata - > vbat_charge_stop ) ;
charger - > thresholds . vbat_charge_restart =
da9030_millivolt_to_reg ( pdata - > vbat_charge_restart ) ;
charger - > thresholds . vcharge_min =
da9030_millivolt_to_reg ( pdata - > vcharge_min ) ;
charger - > thresholds . vcharge_max =
da9030_millivolt_to_reg ( pdata - > vcharge_max ) ;
}
static void da9030_battery_setup_psy ( struct da9030_charger * charger )
{
struct power_supply * psy = & charger - > psy ;
struct power_supply_info * info = charger - > battery_info ;
psy - > name = info - > name ;
psy - > use_for_apm = info - > use_for_apm ;
psy - > type = POWER_SUPPLY_TYPE_BATTERY ;
psy - > get_property = da9030_battery_get_property ;
psy - > properties = da9030_battery_props ;
psy - > num_properties = ARRAY_SIZE ( da9030_battery_props ) ;
} ;
static int da9030_battery_charger_init ( struct da9030_charger * charger )
{
char v [ 5 ] ;
int ret ;
v [ 0 ] = v [ 1 ] = charger - > thresholds . vbat_low ;
v [ 2 ] = charger - > thresholds . tbat_high ;
v [ 3 ] = charger - > thresholds . tbat_restart ;
v [ 4 ] = charger - > thresholds . tbat_low ;
ret = da903x_writes ( charger - > master , DA9030_VBATMON , 5 , v ) ;
if ( ret )
return ret ;
/*
* Enable reference voltage supply for ADC from the LDO_INTERNAL
* regulator . Must be set before ADC measurements can be made .
*/
ret = da903x_write ( charger - > master , DA9030_ADC_MAN_CONTROL ,
DA9030_ADC_LDO_INT_ENABLE |
DA9030_ADC_TBATREF_ENABLE ) ;
if ( ret )
return ret ;
/* enable auto ADC measuremnts */
return da903x_write ( charger - > master , DA9030_ADC_AUTO_CONTROL ,
DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON |
DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE |
DA9030_ADC_VBAT_ENABLE |
DA9030_ADC_AUTO_SLEEP_ENABLE ) ;
}
static int da9030_battery_probe ( struct platform_device * pdev )
{
struct da9030_charger * charger ;
struct da9030_battery_info * pdata = pdev - > dev . platform_data ;
int ret ;
if ( pdata = = NULL )
return - EINVAL ;
if ( pdata - > charge_milliamp > = 1500 | |
pdata - > charge_millivolt < 4000 | |
pdata - > charge_millivolt > 4350 )
return - EINVAL ;
charger = kzalloc ( sizeof ( * charger ) , GFP_KERNEL ) ;
if ( charger = = NULL )
return - ENOMEM ;
charger - > master = pdev - > dev . parent ;
2010-02-02 14:45:04 -08:00
/* 10 seconds between monitor runs unless platform defines other
2008-12-30 22:44:53 +01:00
interval */
charger - > interval = msecs_to_jiffies (
( pdata - > batmon_interval ? : 10 ) * 1000 ) ;
charger - > charge_milliamp = pdata - > charge_milliamp ;
charger - > charge_millivolt = pdata - > charge_millivolt ;
charger - > battery_info = pdata - > battery_info ;
charger - > battery_low = pdata - > battery_low ;
charger - > battery_critical = pdata - > battery_critical ;
da9030_battery_convert_thresholds ( charger , pdata ) ;
ret = da9030_battery_charger_init ( charger ) ;
if ( ret )
goto err_charger_init ;
INIT_DELAYED_WORK ( & charger - > work , da9030_charging_monitor ) ;
schedule_delayed_work ( & charger - > work , charger - > interval ) ;
charger - > nb . notifier_call = da9030_battery_event ;
ret = da903x_register_notifier ( charger - > master , & charger - > nb ,
DA9030_EVENT_CHDET |
DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER |
DA9030_EVENT_TBAT ) ;
if ( ret )
goto err_notifier ;
da9030_battery_setup_psy ( charger ) ;
ret = power_supply_register ( & pdev - > dev , & charger - > psy ) ;
if ( ret )
goto err_ps_register ;
charger - > debug_file = da9030_bat_create_debugfs ( charger ) ;
platform_set_drvdata ( pdev , charger ) ;
return 0 ;
err_ps_register :
da903x_unregister_notifier ( charger - > master , & charger - > nb ,
DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT ) ;
err_notifier :
cancel_delayed_work ( & charger - > work ) ;
err_charger_init :
kfree ( charger ) ;
return ret ;
}
static int da9030_battery_remove ( struct platform_device * dev )
{
struct da9030_charger * charger = platform_get_drvdata ( dev ) ;
da9030_bat_remove_debugfs ( charger ) ;
da903x_unregister_notifier ( charger - > master , & charger - > nb ,
DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT ) ;
2009-06-09 01:09:45 +04:00
cancel_delayed_work_sync ( & charger - > work ) ;
da9030_set_charge ( charger , 0 ) ;
2008-12-30 22:44:53 +01:00
power_supply_unregister ( & charger - > psy ) ;
kfree ( charger ) ;
return 0 ;
}
static struct platform_driver da903x_battery_driver = {
. driver = {
. name = " da903x-battery " ,
. owner = THIS_MODULE ,
} ,
. probe = da9030_battery_probe ,
. remove = da9030_battery_remove ,
} ;
static int da903x_battery_init ( void )
{
return platform_driver_register ( & da903x_battery_driver ) ;
}
static void da903x_battery_exit ( void )
{
platform_driver_unregister ( & da903x_battery_driver ) ;
}
module_init ( da903x_battery_init ) ;
module_exit ( da903x_battery_exit ) ;
MODULE_DESCRIPTION ( " DA9030 battery charger driver " ) ;
MODULE_AUTHOR ( " Mike Rapoport, CompuLab " ) ;
MODULE_LICENSE ( " GPL " ) ;