2015-10-07 16:54:16 +03:00
/*
* DA9150 Fuel - Gauge Driver
*
* Copyright ( c ) 2015 Dialog Semiconductor
*
* Author : Adam Thomson < Adam . Thomson . Opensource @ diasemi . 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 .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/power_supply.h>
# include <linux/list.h>
# include <asm/div64.h>
# include <linux/mfd/da9150/core.h>
# include <linux/mfd/da9150/registers.h>
/* Core2Wire */
# define DA9150_QIF_READ (0x0 << 7)
# define DA9150_QIF_WRITE (0x1 << 7)
# define DA9150_QIF_CODE_MASK 0x7F
# define DA9150_QIF_BYTE_SIZE 8
# define DA9150_QIF_BYTE_MASK 0xFF
# define DA9150_QIF_SHORT_SIZE 2
# define DA9150_QIF_LONG_SIZE 4
/* QIF Codes */
# define DA9150_QIF_UAVG 6
# define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE
# define DA9150_QIF_IAVG 8
# define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE
# define DA9150_QIF_NTCAVG 12
# define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE
# define DA9150_QIF_SHUNT_VAL 36
# define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_SD_GAIN 38
# define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE
# define DA9150_QIF_FCC_MAH 40
# define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_SOC_PCT 43
# define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_CHARGE_LIMIT 44
# define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_DISCHARGE_LIMIT 45
# define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_FW_MAIN_VER 118
# define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_E_FG_STATUS 126
# define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_SYNC 127
# define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE
# define DA9150_QIF_MAX_CODES 128
/* QIF Sync Timeout */
# define DA9150_QIF_SYNC_TIMEOUT 1000
# define DA9150_QIF_SYNC_RETRIES 10
/* QIF E_FG_STATUS */
# define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0)
# define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1)
# define DA9150_FG_IRQ_SOC_MASK \
( DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK )
/* Private data */
struct da9150_fg {
struct da9150 * da9150 ;
struct device * dev ;
struct mutex io_lock ;
struct power_supply * battery ;
struct delayed_work work ;
u32 interval ;
int warn_soc ;
int crit_soc ;
int soc ;
} ;
/* Battery Properties */
static u32 da9150_fg_read_attr ( struct da9150_fg * fg , u8 code , u8 size )
{
2018-03-09 21:27:08 +03:00
u8 buf [ DA9150_QIF_LONG_SIZE ] ;
2015-10-07 16:54:16 +03:00
u8 read_addr ;
u32 res = 0 ;
int i ;
/* Set QIF code (READ mode) */
read_addr = ( code & DA9150_QIF_CODE_MASK ) | DA9150_QIF_READ ;
da9150_read_qif ( fg - > da9150 , read_addr , size , buf ) ;
for ( i = 0 ; i < size ; + + i )
res | = ( buf [ i ] < < ( i * DA9150_QIF_BYTE_SIZE ) ) ;
return res ;
}
static void da9150_fg_write_attr ( struct da9150_fg * fg , u8 code , u8 size ,
u32 val )
{
2018-03-09 21:27:08 +03:00
u8 buf [ DA9150_QIF_LONG_SIZE ] ;
2015-10-07 16:54:16 +03:00
u8 write_addr ;
int i ;
/* Set QIF code (WRITE mode) */
write_addr = ( code & DA9150_QIF_CODE_MASK ) | DA9150_QIF_WRITE ;
for ( i = 0 ; i < size ; + + i ) {
buf [ i ] = ( val > > ( i * DA9150_QIF_BYTE_SIZE ) ) &
DA9150_QIF_BYTE_MASK ;
}
da9150_write_qif ( fg - > da9150 , write_addr , size , buf ) ;
}
/* Trigger QIF Sync to update QIF readable data */
static void da9150_fg_read_sync_start ( struct da9150_fg * fg )
{
int i = 0 ;
u32 res = 0 ;
mutex_lock ( & fg - > io_lock ) ;
/* Check if QIF sync already requested, and write to sync if not */
res = da9150_fg_read_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE ) ;
if ( res > 0 )
da9150_fg_write_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE , 0 ) ;
/* Wait for sync to complete */
res = 0 ;
while ( ( res = = 0 ) & & ( i + + < DA9150_QIF_SYNC_RETRIES ) ) {
usleep_range ( DA9150_QIF_SYNC_TIMEOUT ,
DA9150_QIF_SYNC_TIMEOUT * 2 ) ;
res = da9150_fg_read_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE ) ;
}
/* Check if sync completed */
if ( res = = 0 )
dev_err ( fg - > dev , " Failed to perform QIF read sync! \n " ) ;
}
/*
* Should always be called after QIF sync read has been performed , and all
* attributes required have been accessed .
*/
static inline void da9150_fg_read_sync_end ( struct da9150_fg * fg )
{
mutex_unlock ( & fg - > io_lock ) ;
}
/* Sync read of single QIF attribute */
static u32 da9150_fg_read_attr_sync ( struct da9150_fg * fg , u8 code , u8 size )
{
u32 val ;
da9150_fg_read_sync_start ( fg ) ;
val = da9150_fg_read_attr ( fg , code , size ) ;
da9150_fg_read_sync_end ( fg ) ;
return val ;
}
/* Wait for QIF Sync, write QIF data and wait for ack */
static void da9150_fg_write_attr_sync ( struct da9150_fg * fg , u8 code , u8 size ,
u32 val )
{
int i = 0 ;
u32 res = 0 , sync_val ;
mutex_lock ( & fg - > io_lock ) ;
/* Check if QIF sync already requested */
res = da9150_fg_read_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE ) ;
/* Wait for an existing sync to complete */
while ( ( res = = 0 ) & & ( i + + < DA9150_QIF_SYNC_RETRIES ) ) {
usleep_range ( DA9150_QIF_SYNC_TIMEOUT ,
DA9150_QIF_SYNC_TIMEOUT * 2 ) ;
res = da9150_fg_read_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE ) ;
}
if ( res = = 0 ) {
dev_err ( fg - > dev , " Timeout waiting for existing QIF sync! \n " ) ;
mutex_unlock ( & fg - > io_lock ) ;
return ;
}
/* Write value for QIF code */
da9150_fg_write_attr ( fg , code , size , val ) ;
/* Wait for write acknowledgment */
i = 0 ;
sync_val = res ;
while ( ( res = = sync_val ) & & ( i + + < DA9150_QIF_SYNC_RETRIES ) ) {
usleep_range ( DA9150_QIF_SYNC_TIMEOUT ,
DA9150_QIF_SYNC_TIMEOUT * 2 ) ;
res = da9150_fg_read_attr ( fg , DA9150_QIF_SYNC ,
DA9150_QIF_SYNC_SIZE ) ;
}
mutex_unlock ( & fg - > io_lock ) ;
/* Check write was actually successful */
if ( res ! = ( sync_val + 1 ) )
dev_err ( fg - > dev , " Error performing QIF sync write for code %d \n " ,
code ) ;
}
/* Power Supply attributes */
static int da9150_fg_capacity ( struct da9150_fg * fg ,
union power_supply_propval * val )
{
val - > intval = da9150_fg_read_attr_sync ( fg , DA9150_QIF_SOC_PCT ,
DA9150_QIF_SOC_PCT_SIZE ) ;
if ( val - > intval > 100 )
val - > intval = 100 ;
return 0 ;
}
static int da9150_fg_current_avg ( struct da9150_fg * fg ,
union power_supply_propval * val )
{
u32 iavg , sd_gain , shunt_val ;
u64 div , res ;
da9150_fg_read_sync_start ( fg ) ;
iavg = da9150_fg_read_attr ( fg , DA9150_QIF_IAVG ,
DA9150_QIF_IAVG_SIZE ) ;
shunt_val = da9150_fg_read_attr ( fg , DA9150_QIF_SHUNT_VAL ,
DA9150_QIF_SHUNT_VAL_SIZE ) ;
sd_gain = da9150_fg_read_attr ( fg , DA9150_QIF_SD_GAIN ,
DA9150_QIF_SD_GAIN_SIZE ) ;
da9150_fg_read_sync_end ( fg ) ;
div = ( u64 ) ( sd_gain * shunt_val * 65536ULL ) ;
do_div ( div , 1000000 ) ;
res = ( u64 ) ( iavg * 1000000ULL ) ;
do_div ( res , div ) ;
val - > intval = ( int ) res ;
return 0 ;
}
static int da9150_fg_voltage_avg ( struct da9150_fg * fg ,
union power_supply_propval * val )
{
u64 res ;
val - > intval = da9150_fg_read_attr_sync ( fg , DA9150_QIF_UAVG ,
DA9150_QIF_UAVG_SIZE ) ;
res = ( u64 ) ( val - > intval * 186ULL ) ;
do_div ( res , 10000 ) ;
val - > intval = ( int ) res ;
return 0 ;
}
static int da9150_fg_charge_full ( struct da9150_fg * fg ,
union power_supply_propval * val )
{
val - > intval = da9150_fg_read_attr_sync ( fg , DA9150_QIF_FCC_MAH ,
DA9150_QIF_FCC_MAH_SIZE ) ;
val - > intval = val - > intval * 1000 ;
return 0 ;
}
/*
* Temperature reading from device is only valid if battery / system provides
* valid NTC to associated pin of DA9150 chip .
*/
static int da9150_fg_temp ( struct da9150_fg * fg ,
union power_supply_propval * val )
{
val - > intval = da9150_fg_read_attr_sync ( fg , DA9150_QIF_NTCAVG ,
DA9150_QIF_NTCAVG_SIZE ) ;
val - > intval = ( val - > intval * 10 ) / 1048576 ;
return 0 ;
}
static enum power_supply_property da9150_fg_props [ ] = {
POWER_SUPPLY_PROP_CAPACITY ,
POWER_SUPPLY_PROP_CURRENT_AVG ,
POWER_SUPPLY_PROP_VOLTAGE_AVG ,
POWER_SUPPLY_PROP_CHARGE_FULL ,
POWER_SUPPLY_PROP_TEMP ,
} ;
static int da9150_fg_get_prop ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct da9150_fg * fg = dev_get_drvdata ( psy - > dev . parent ) ;
int ret ;
switch ( psp ) {
case POWER_SUPPLY_PROP_CAPACITY :
ret = da9150_fg_capacity ( fg , val ) ;
break ;
case POWER_SUPPLY_PROP_CURRENT_AVG :
ret = da9150_fg_current_avg ( fg , val ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_AVG :
ret = da9150_fg_voltage_avg ( fg , val ) ;
break ;
case POWER_SUPPLY_PROP_CHARGE_FULL :
ret = da9150_fg_charge_full ( fg , val ) ;
break ;
case POWER_SUPPLY_PROP_TEMP :
ret = da9150_fg_temp ( fg , val ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
/* Repeated SOC check */
static bool da9150_fg_soc_changed ( struct da9150_fg * fg )
{
union power_supply_propval val ;
da9150_fg_capacity ( fg , & val ) ;
if ( val . intval ! = fg - > soc ) {
fg - > soc = val . intval ;
return true ;
}
return false ;
}
static void da9150_fg_work ( struct work_struct * work )
{
struct da9150_fg * fg = container_of ( work , struct da9150_fg , work . work ) ;
/* Report if SOC has changed */
if ( da9150_fg_soc_changed ( fg ) )
power_supply_changed ( fg - > battery ) ;
schedule_delayed_work ( & fg - > work , msecs_to_jiffies ( fg - > interval ) ) ;
}
/* SOC level event configuration */
static void da9150_fg_soc_event_config ( struct da9150_fg * fg )
{
int soc ;
soc = da9150_fg_read_attr_sync ( fg , DA9150_QIF_SOC_PCT ,
DA9150_QIF_SOC_PCT_SIZE ) ;
if ( soc > fg - > warn_soc ) {
/* If SOC > warn level, set discharge warn level event */
da9150_fg_write_attr_sync ( fg , DA9150_QIF_DISCHARGE_LIMIT ,
DA9150_QIF_DISCHARGE_LIMIT_SIZE ,
fg - > warn_soc + 1 ) ;
} else if ( ( soc < = fg - > warn_soc ) & & ( soc > fg - > crit_soc ) ) {
/*
* If SOC < = warn level , set discharge crit level event ,
* and set charge warn level event .
*/
da9150_fg_write_attr_sync ( fg , DA9150_QIF_DISCHARGE_LIMIT ,
DA9150_QIF_DISCHARGE_LIMIT_SIZE ,
fg - > crit_soc + 1 ) ;
da9150_fg_write_attr_sync ( fg , DA9150_QIF_CHARGE_LIMIT ,
DA9150_QIF_CHARGE_LIMIT_SIZE ,
fg - > warn_soc ) ;
} else if ( soc < = fg - > crit_soc ) {
/* If SOC <= crit level, set charge crit level event */
da9150_fg_write_attr_sync ( fg , DA9150_QIF_CHARGE_LIMIT ,
DA9150_QIF_CHARGE_LIMIT_SIZE ,
fg - > crit_soc ) ;
}
}
static irqreturn_t da9150_fg_irq ( int irq , void * data )
{
struct da9150_fg * fg = data ;
u32 e_fg_status ;
/* Read FG IRQ status info */
e_fg_status = da9150_fg_read_attr ( fg , DA9150_QIF_E_FG_STATUS ,
DA9150_QIF_E_FG_STATUS_SIZE ) ;
/* Handle warning/critical threhold events */
if ( e_fg_status & DA9150_FG_IRQ_SOC_MASK )
da9150_fg_soc_event_config ( fg ) ;
/* Clear any FG IRQs */
da9150_fg_write_attr ( fg , DA9150_QIF_E_FG_STATUS ,
DA9150_QIF_E_FG_STATUS_SIZE , e_fg_status ) ;
return IRQ_HANDLED ;
}
static struct da9150_fg_pdata * da9150_fg_dt_pdata ( struct device * dev )
{
struct device_node * fg_node = dev - > of_node ;
struct da9150_fg_pdata * pdata ;
pdata = devm_kzalloc ( dev , sizeof ( struct da9150_fg_pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return NULL ;
of_property_read_u32 ( fg_node , " dlg,update-interval " ,
& pdata - > update_interval ) ;
of_property_read_u8 ( fg_node , " dlg,warn-soc-level " ,
& pdata - > warn_soc_lvl ) ;
of_property_read_u8 ( fg_node , " dlg,crit-soc-level " ,
& pdata - > crit_soc_lvl ) ;
return pdata ;
}
static const struct power_supply_desc fg_desc = {
. name = " da9150-fg " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = da9150_fg_props ,
. num_properties = ARRAY_SIZE ( da9150_fg_props ) ,
. get_property = da9150_fg_get_prop ,
} ;
static int da9150_fg_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct da9150 * da9150 = dev_get_drvdata ( dev - > parent ) ;
struct da9150_fg_pdata * fg_pdata = dev_get_platdata ( dev ) ;
struct da9150_fg * fg ;
int ver , irq , ret = 0 ;
fg = devm_kzalloc ( dev , sizeof ( * fg ) , GFP_KERNEL ) ;
if ( fg = = NULL )
return - ENOMEM ;
platform_set_drvdata ( pdev , fg ) ;
fg - > da9150 = da9150 ;
fg - > dev = dev ;
mutex_init ( & fg - > io_lock ) ;
/* Enable QIF */
da9150_set_bits ( da9150 , DA9150_CORE2WIRE_CTRL_A , DA9150_FG_QIF_EN_MASK ,
DA9150_FG_QIF_EN_MASK ) ;
fg - > battery = devm_power_supply_register ( dev , & fg_desc , NULL ) ;
if ( IS_ERR ( fg - > battery ) ) {
ret = PTR_ERR ( fg - > battery ) ;
return ret ;
}
ver = da9150_fg_read_attr ( fg , DA9150_QIF_FW_MAIN_VER ,
DA9150_QIF_FW_MAIN_VER_SIZE ) ;
dev_info ( dev , " Version: 0x%x \n " , ver ) ;
/* Handle DT data if provided */
if ( dev - > of_node ) {
fg_pdata = da9150_fg_dt_pdata ( dev ) ;
dev - > platform_data = fg_pdata ;
}
/* Handle any pdata provided */
if ( fg_pdata ) {
fg - > interval = fg_pdata - > update_interval ;
if ( fg_pdata - > warn_soc_lvl > 100 )
dev_warn ( dev , " Invalid SOC warning level provided, Ignoring " ) ;
else
fg - > warn_soc = fg_pdata - > warn_soc_lvl ;
if ( ( fg_pdata - > crit_soc_lvl > 100 ) | |
( fg_pdata - > crit_soc_lvl > = fg_pdata - > warn_soc_lvl ) )
dev_warn ( dev , " Invalid SOC critical level provided, Ignoring " ) ;
else
fg - > crit_soc = fg_pdata - > crit_soc_lvl ;
}
/* Configure initial SOC level events */
da9150_fg_soc_event_config ( fg ) ;
/*
* If an interval period has been provided then setup repeating
* work for reporting data updates .
*/
if ( fg - > interval ) {
INIT_DELAYED_WORK ( & fg - > work , da9150_fg_work ) ;
schedule_delayed_work ( & fg - > work ,
msecs_to_jiffies ( fg - > interval ) ) ;
}
/* Register IRQ */
irq = platform_get_irq_byname ( pdev , " FG " ) ;
if ( irq < 0 ) {
dev_err ( dev , " Failed to get IRQ FG: %d \n " , irq ) ;
ret = irq ;
goto irq_fail ;
}
ret = devm_request_threaded_irq ( dev , irq , NULL , da9150_fg_irq ,
IRQF_ONESHOT , " FG " , fg ) ;
if ( ret ) {
dev_err ( dev , " Failed to request IRQ %d: %d \n " , irq , ret ) ;
goto irq_fail ;
}
return 0 ;
irq_fail :
if ( fg - > interval )
cancel_delayed_work ( & fg - > work ) ;
return ret ;
}
static int da9150_fg_remove ( struct platform_device * pdev )
{
struct da9150_fg * fg = platform_get_drvdata ( pdev ) ;
if ( fg - > interval )
cancel_delayed_work ( & fg - > work ) ;
return 0 ;
}
static int da9150_fg_resume ( struct platform_device * pdev )
{
struct da9150_fg * fg = platform_get_drvdata ( pdev ) ;
/*
* Trigger SOC check to happen now so as to indicate any value change
* since last check before suspend .
*/
if ( fg - > interval )
flush_delayed_work ( & fg - > work ) ;
return 0 ;
}
static struct platform_driver da9150_fg_driver = {
. driver = {
. name = " da9150-fuel-gauge " ,
} ,
. probe = da9150_fg_probe ,
. remove = da9150_fg_remove ,
. resume = da9150_fg_resume ,
} ;
module_platform_driver ( da9150_fg_driver ) ;
MODULE_DESCRIPTION ( " Fuel-Gauge Driver for DA9150 " ) ;
MODULE_AUTHOR ( " Adam Thomson <Adam.Thomson.Opensource@diasemi.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;