2017-05-31 17:19:21 -07:00
/*
* Battery driver for CPCAP PMIC
*
* Copyright ( C ) 2017 Tony Lindgren < tony @ atomide . com >
*
* Some parts of the code based on earlie Motorola mapphone Linux kernel
* drivers :
*
* Copyright ( C ) 2009 - 2010 Motorola , Inc .
*
* 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 .
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/reboot.h>
# include <linux/regmap.h>
# include <linux/iio/consumer.h>
# include <linux/iio/types.h>
# include <linux/mfd/motorola-cpcap.h>
# include <asm/div64.h>
/*
* Register bit defines for CPCAP_REG_BPEOL . Some of these seem to
* map to MC13783UG . pdf " Table 5-19. Register 13, Power Control 0 "
* to enable BATTDETEN , LOBAT and EOL features . We currently use
* LOBAT interrupts instead of EOL .
*/
# define CPCAP_REG_BPEOL_BIT_EOL9 BIT(9) /* Set for EOL irq */
# define CPCAP_REG_BPEOL_BIT_EOL8 BIT(8) /* Set for EOL irq */
# define CPCAP_REG_BPEOL_BIT_UNKNOWN7 BIT(7)
# define CPCAP_REG_BPEOL_BIT_UNKNOWN6 BIT(6)
# define CPCAP_REG_BPEOL_BIT_UNKNOWN5 BIT(5)
# define CPCAP_REG_BPEOL_BIT_EOL_MULTI BIT(4) /* Set for multiple EOL irqs */
# define CPCAP_REG_BPEOL_BIT_UNKNOWN3 BIT(3)
# define CPCAP_REG_BPEOL_BIT_UNKNOWN2 BIT(2)
# define CPCAP_REG_BPEOL_BIT_BATTDETEN BIT(1) /* Enable battery detect */
# define CPCAP_REG_BPEOL_BIT_EOLSEL BIT(0) /* BPDET = 0, EOL = 1 */
# define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250
enum {
CPCAP_BATTERY_IIO_BATTDET ,
CPCAP_BATTERY_IIO_VOLTAGE ,
CPCAP_BATTERY_IIO_CHRG_CURRENT ,
CPCAP_BATTERY_IIO_BATT_CURRENT ,
CPCAP_BATTERY_IIO_NR ,
} ;
enum cpcap_battery_irq_action {
CPCAP_BATTERY_IRQ_ACTION_NONE ,
CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW ,
CPCAP_BATTERY_IRQ_ACTION_POWEROFF ,
} ;
struct cpcap_interrupt_desc {
const char * name ;
struct list_head node ;
int irq ;
enum cpcap_battery_irq_action action ;
} ;
struct cpcap_battery_config {
int ccm ;
int cd_factor ;
struct power_supply_info info ;
} ;
struct cpcap_coulomb_counter_data {
s32 sample ; /* 24-bits */
s32 accumulator ;
s16 offset ; /* 10-bits */
} ;
enum cpcap_battery_state {
CPCAP_BATTERY_STATE_PREVIOUS ,
CPCAP_BATTERY_STATE_LATEST ,
CPCAP_BATTERY_STATE_NR ,
} ;
struct cpcap_battery_state_data {
int voltage ;
int current_ua ;
int counter_uah ;
int temperature ;
ktime_t time ;
struct cpcap_coulomb_counter_data cc ;
} ;
struct cpcap_battery_ddata {
struct device * dev ;
struct regmap * reg ;
struct list_head irq_list ;
struct iio_channel * channels [ CPCAP_BATTERY_IIO_NR ] ;
struct power_supply * psy ;
struct cpcap_battery_config config ;
struct cpcap_battery_state_data state [ CPCAP_BATTERY_STATE_NR ] ;
atomic_t active ;
int status ;
u16 vendor ;
} ;
# define CPCAP_NO_BATTERY -400
static struct cpcap_battery_state_data *
cpcap_battery_get_state ( struct cpcap_battery_ddata * ddata ,
enum cpcap_battery_state state )
{
if ( state > = CPCAP_BATTERY_STATE_NR )
return NULL ;
return & ddata - > state [ state ] ;
}
static struct cpcap_battery_state_data *
cpcap_battery_latest ( struct cpcap_battery_ddata * ddata )
{
return cpcap_battery_get_state ( ddata , CPCAP_BATTERY_STATE_LATEST ) ;
}
static struct cpcap_battery_state_data *
cpcap_battery_previous ( struct cpcap_battery_ddata * ddata )
{
return cpcap_battery_get_state ( ddata , CPCAP_BATTERY_STATE_PREVIOUS ) ;
}
static int cpcap_charger_battery_temperature ( struct cpcap_battery_ddata * ddata ,
int * value )
{
struct iio_channel * channel ;
int error ;
channel = ddata - > channels [ CPCAP_BATTERY_IIO_BATTDET ] ;
error = iio_read_channel_processed ( channel , value ) ;
if ( error < 0 ) {
dev_warn ( ddata - > dev , " %s failed: %i \n " , __func__ , error ) ;
* value = CPCAP_NO_BATTERY ;
return error ;
}
* value / = 100 ;
return 0 ;
}
static int cpcap_battery_get_voltage ( struct cpcap_battery_ddata * ddata )
{
struct iio_channel * channel ;
int error , value = 0 ;
channel = ddata - > channels [ CPCAP_BATTERY_IIO_VOLTAGE ] ;
error = iio_read_channel_processed ( channel , & value ) ;
if ( error < 0 ) {
dev_warn ( ddata - > dev , " %s failed: %i \n " , __func__ , error ) ;
return 0 ;
}
return value * 1000 ;
}
static int cpcap_battery_get_current ( struct cpcap_battery_ddata * ddata )
{
struct iio_channel * channel ;
int error , value = 0 ;
channel = ddata - > channels [ CPCAP_BATTERY_IIO_BATT_CURRENT ] ;
error = iio_read_channel_processed ( channel , & value ) ;
if ( error < 0 ) {
dev_warn ( ddata - > dev , " %s failed: %i \n " , __func__ , error ) ;
return 0 ;
}
return value * 1000 ;
}
/**
* cpcap_battery_cc_raw_div - calculate and divide coulomb counter μ Ams values
* @ ddata : device driver data
* @ sample : coulomb counter sample value
* @ accumulator : coulomb counter integrator value
* @ offset : coulomb counter offset value
* @ divider : conversion divider
*
* Note that cc_lsb and cc_dur values are from Motorola Linux kernel
* function data_get_avg_curr_ua ( ) and seem to be based on measured test
* results . It also has the following comment :
*
* Adjustment factors are applied here as a temp solution per the test
* results . Need to work out a formal solution for this adjustment .
*
* A coulomb counter for similar hardware seems to be documented in
* " TWL6030 Gas Gauging Basics (Rev. A) " swca095a . pdf in chapter
* " 10 Calculating Accumulated Current " . We however follow what the
* Motorola mapphone Linux kernel is doing as there may be either a
* TI or ST coulomb counter in the PMIC .
*/
static int cpcap_battery_cc_raw_div ( struct cpcap_battery_ddata * ddata ,
u32 sample , s32 accumulator ,
s16 offset , u32 divider )
{
s64 acc ;
u64 tmp ;
int avg_current ;
u32 cc_lsb ;
sample & = 0xffffff ; /* 24-bits, unsigned */
offset & = 0x7ff ; /* 10-bits, signed */
switch ( ddata - > vendor ) {
case CPCAP_VENDOR_ST :
cc_lsb = 95374 ; /* μAms per LSB */
break ;
case CPCAP_VENDOR_TI :
cc_lsb = 91501 ; /* μAms per LSB */
break ;
default :
return - EINVAL ;
}
acc = accumulator ;
acc = acc - ( ( s64 ) sample * offset ) ;
cc_lsb = ( cc_lsb * ddata - > config . cd_factor ) / 1000 ;
if ( acc > = 0 )
tmp = acc ;
else
tmp = acc * - 1 ;
tmp = tmp * cc_lsb ;
do_div ( tmp , divider ) ;
avg_current = tmp ;
if ( acc > = 0 )
return - avg_current ;
else
return avg_current ;
}
/* 3600000μAms = 1μAh */
static int cpcap_battery_cc_to_uah ( struct cpcap_battery_ddata * ddata ,
u32 sample , s32 accumulator ,
s16 offset )
{
return cpcap_battery_cc_raw_div ( ddata , sample ,
accumulator , offset ,
3600000 ) ;
}
static int cpcap_battery_cc_to_ua ( struct cpcap_battery_ddata * ddata ,
u32 sample , s32 accumulator ,
s16 offset )
{
return cpcap_battery_cc_raw_div ( ddata , sample ,
accumulator , offset ,
sample *
CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS ) ;
}
/**
* cpcap_battery_read_accumulated - reads cpcap coulomb counter
* @ ddata : device driver data
* @ regs : coulomb counter values
*
* Based on Motorola mapphone kernel function data_read_regs ( ) .
* Looking at the registers , the coulomb counter seems similar to
* the coulomb counter in TWL6030 . See " TWL6030 Gas Gauging Basics
* ( Rev . A ) swca095a . pdf for " 10 Calculating Accumulated Current " .
*
* Note that swca095a . pdf instructs to stop the coulomb counter
* before reading to avoid values changing . Motorola mapphone
* Linux kernel does not do it , so let ' s assume they ' ve verified
* the data produced is correct .
*/
static int
cpcap_battery_read_accumulated ( struct cpcap_battery_ddata * ddata ,
struct cpcap_coulomb_counter_data * ccd )
{
u16 buf [ 7 ] ; /* CPCAP_REG_CC1 to CCI */
int error ;
ccd - > sample = 0 ;
ccd - > accumulator = 0 ;
ccd - > offset = 0 ;
/* Read coulomb counter register range */
error = regmap_bulk_read ( ddata - > reg , CPCAP_REG_CCS1 ,
buf , ARRAY_SIZE ( buf ) ) ;
if ( error )
return 0 ;
/* Sample value CPCAP_REG_CCS1 & 2 */
ccd - > sample = ( buf [ 1 ] & 0x0fff ) < < 16 ;
ccd - > sample | = buf [ 0 ] ;
/* Accumulator value CPCAP_REG_CCA1 & 2 */
ccd - > accumulator = ( ( s16 ) buf [ 3 ] ) < < 16 ;
ccd - > accumulator | = buf [ 2 ] ;
/* Offset value CPCAP_REG_CCO */
ccd - > offset = buf [ 5 ] ;
/* Adjust offset based on mode value CPCAP_REG_CCM? */
if ( buf [ 4 ] > = 0x200 )
ccd - > offset | = 0xfc00 ;
return cpcap_battery_cc_to_uah ( ddata ,
ccd - > sample ,
ccd - > accumulator ,
ccd - > offset ) ;
}
/**
* cpcap_battery_cc_get_avg_current - read cpcap coulumb counter
* @ ddata : cpcap battery driver device data
*/
static int cpcap_battery_cc_get_avg_current ( struct cpcap_battery_ddata * ddata )
{
int value , acc , error ;
s32 sample = 1 ;
s16 offset ;
if ( ddata - > vendor = = CPCAP_VENDOR_ST )
sample = 4 ;
/* Coulomb counter integrator */
error = regmap_read ( ddata - > reg , CPCAP_REG_CCI , & value ) ;
if ( error )
return error ;
if ( ( ddata - > vendor = = CPCAP_VENDOR_TI ) & & ( value > 0x2000 ) )
value = value | 0xc000 ;
acc = ( s16 ) value ;
/* Coulomb counter sample time */
error = regmap_read ( ddata - > reg , CPCAP_REG_CCM , & value ) ;
if ( error )
return error ;
if ( value < 0x200 )
offset = value ;
else
offset = value | 0xfc00 ;
return cpcap_battery_cc_to_ua ( ddata , sample , acc , offset ) ;
}
static bool cpcap_battery_full ( struct cpcap_battery_ddata * ddata )
{
struct cpcap_battery_state_data * state = cpcap_battery_latest ( ddata ) ;
/* Basically anything that measures above 4347000 is full */
if ( state - > voltage > = ( ddata - > config . info . voltage_max_design - 4000 ) )
return true ;
return false ;
}
static int cpcap_battery_update_status ( struct cpcap_battery_ddata * ddata )
{
struct cpcap_battery_state_data state , * latest , * previous ;
ktime_t now ;
int error ;
memset ( & state , 0 , sizeof ( state ) ) ;
now = ktime_get ( ) ;
latest = cpcap_battery_latest ( ddata ) ;
if ( latest ) {
s64 delta_ms = ktime_to_ms ( ktime_sub ( now , latest - > time ) ) ;
if ( delta_ms < CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS )
return delta_ms ;
}
state . time = now ;
state . voltage = cpcap_battery_get_voltage ( ddata ) ;
state . current_ua = cpcap_battery_get_current ( ddata ) ;
state . counter_uah = cpcap_battery_read_accumulated ( ddata , & state . cc ) ;
error = cpcap_charger_battery_temperature ( ddata ,
& state . temperature ) ;
if ( error )
return error ;
previous = cpcap_battery_previous ( ddata ) ;
memcpy ( previous , latest , sizeof ( * previous ) ) ;
memcpy ( latest , & state , sizeof ( * latest ) ) ;
return 0 ;
}
static enum power_supply_property cpcap_battery_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_CURRENT_AVG ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN ,
POWER_SUPPLY_PROP_CHARGE_COUNTER ,
POWER_SUPPLY_PROP_POWER_NOW ,
POWER_SUPPLY_PROP_POWER_AVG ,
POWER_SUPPLY_PROP_CAPACITY_LEVEL ,
POWER_SUPPLY_PROP_SCOPE ,
POWER_SUPPLY_PROP_TEMP ,
} ;
static int cpcap_battery_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct cpcap_battery_ddata * ddata = power_supply_get_drvdata ( psy ) ;
struct cpcap_battery_state_data * latest , * previous ;
u32 sample ;
s32 accumulator ;
int cached ;
s64 tmp ;
cached = cpcap_battery_update_status ( ddata ) ;
if ( cached < 0 )
return cached ;
latest = cpcap_battery_latest ( ddata ) ;
previous = cpcap_battery_previous ( ddata ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_PRESENT :
if ( latest - > temperature > CPCAP_NO_BATTERY )
val - > intval = 1 ;
else
val - > intval = 0 ;
break ;
case POWER_SUPPLY_PROP_STATUS :
if ( cpcap_battery_full ( ddata ) ) {
val - > intval = POWER_SUPPLY_STATUS_FULL ;
break ;
}
if ( cpcap_battery_cc_get_avg_current ( ddata ) < 0 )
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = ddata - > config . info . technology ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
val - > intval = cpcap_battery_get_voltage ( ddata ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN :
val - > intval = ddata - > config . info . voltage_max_design ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = ddata - > config . info . voltage_min_design ;
break ;
case POWER_SUPPLY_PROP_CURRENT_AVG :
if ( cached ) {
val - > intval = cpcap_battery_cc_get_avg_current ( ddata ) ;
break ;
}
sample = latest - > cc . sample - previous - > cc . sample ;
accumulator = latest - > cc . accumulator - previous - > cc . accumulator ;
val - > intval = cpcap_battery_cc_to_ua ( ddata , sample ,
accumulator ,
latest - > cc . offset ) ;
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
val - > intval = latest - > current_ua ;
break ;
case POWER_SUPPLY_PROP_CHARGE_COUNTER :
val - > intval = latest - > counter_uah ;
break ;
case POWER_SUPPLY_PROP_POWER_NOW :
tmp = ( latest - > voltage / 10000 ) * latest - > current_ua ;
val - > intval = div64_s64 ( tmp , 100 ) ;
break ;
case POWER_SUPPLY_PROP_POWER_AVG :
if ( cached ) {
tmp = cpcap_battery_cc_get_avg_current ( ddata ) ;
tmp * = ( latest - > voltage / 10000 ) ;
val - > intval = div64_s64 ( tmp , 100 ) ;
break ;
}
sample = latest - > cc . sample - previous - > cc . sample ;
accumulator = latest - > cc . accumulator - previous - > cc . accumulator ;
tmp = cpcap_battery_cc_to_ua ( ddata , sample , accumulator ,
latest - > cc . offset ) ;
tmp * = ( ( latest - > voltage + previous - > voltage ) / 20000 ) ;
val - > intval = div64_s64 ( tmp , 100 ) ;
break ;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL :
if ( cpcap_battery_full ( ddata ) )
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL ;
else if ( latest - > voltage > = 3750000 )
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH ;
else if ( latest - > voltage > = 3300000 )
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL ;
else if ( latest - > voltage > 3100000 )
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW ;
else if ( latest - > voltage < = 3100000 )
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL ;
else
val - > intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN ;
break ;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN :
val - > intval = ddata - > config . info . charge_full_design ;
break ;
case POWER_SUPPLY_PROP_SCOPE :
val - > intval = POWER_SUPPLY_SCOPE_SYSTEM ;
break ;
case POWER_SUPPLY_PROP_TEMP :
val - > intval = latest - > temperature ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static irqreturn_t cpcap_battery_irq_thread ( int irq , void * data )
{
struct cpcap_battery_ddata * ddata = data ;
struct cpcap_battery_state_data * latest ;
struct cpcap_interrupt_desc * d ;
if ( ! atomic_read ( & ddata - > active ) )
return IRQ_NONE ;
list_for_each_entry ( d , & ddata - > irq_list , node ) {
if ( irq = = d - > irq )
break ;
}
if ( ! d )
return IRQ_NONE ;
latest = cpcap_battery_latest ( ddata ) ;
switch ( d - > action ) {
case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW :
if ( latest - > counter_uah > = 0 )
dev_warn ( ddata - > dev , " Battery low at 3.3V! \n " ) ;
break ;
case CPCAP_BATTERY_IRQ_ACTION_POWEROFF :
if ( latest - > counter_uah > = 0 ) {
dev_emerg ( ddata - > dev ,
" Battery empty at 3.1V, powering off \n " ) ;
orderly_poweroff ( true ) ;
}
break ;
default :
break ;
}
power_supply_changed ( ddata - > psy ) ;
return IRQ_HANDLED ;
}
static int cpcap_battery_init_irq ( struct platform_device * pdev ,
struct cpcap_battery_ddata * ddata ,
const char * name )
{
struct cpcap_interrupt_desc * d ;
int irq , error ;
irq = platform_get_irq_byname ( pdev , name ) ;
2017-11-24 22:31:40 +05:30
if ( irq < 0 )
return irq ;
2017-05-31 17:19:21 -07:00
error = devm_request_threaded_irq ( ddata - > dev , irq , NULL ,
cpcap_battery_irq_thread ,
IRQF_SHARED ,
name , ddata ) ;
if ( error ) {
dev_err ( ddata - > dev , " could not get irq %s: %i \n " ,
name , error ) ;
return error ;
}
d = devm_kzalloc ( ddata - > dev , sizeof ( * d ) , GFP_KERNEL ) ;
if ( ! d )
return - ENOMEM ;
d - > name = name ;
d - > irq = irq ;
if ( ! strncmp ( name , " lowbph " , 6 ) )
d - > action = CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW ;
else if ( ! strncmp ( name , " lowbpl " , 6 ) )
d - > action = CPCAP_BATTERY_IRQ_ACTION_POWEROFF ;
list_add ( & d - > node , & ddata - > irq_list ) ;
return 0 ;
}
static int cpcap_battery_init_interrupts ( struct platform_device * pdev ,
struct cpcap_battery_ddata * ddata )
{
2018-11-29 23:03:42 +00:00
static const char * const cpcap_battery_irqs [ ] = {
2017-05-31 17:19:21 -07:00
" eol " , " lowbph " , " lowbpl " ,
" chrgcurr1 " , " battdetb "
} ;
int i , error ;
for ( i = 0 ; i < ARRAY_SIZE ( cpcap_battery_irqs ) ; i + + ) {
error = cpcap_battery_init_irq ( pdev , ddata ,
cpcap_battery_irqs [ i ] ) ;
if ( error )
return error ;
}
/* Enable low battery interrupts for 3.3V high and 3.1V low */
error = regmap_update_bits ( ddata - > reg , CPCAP_REG_BPEOL ,
0xffff ,
CPCAP_REG_BPEOL_BIT_BATTDETEN ) ;
if ( error )
return error ;
return 0 ;
}
static int cpcap_battery_init_iio ( struct cpcap_battery_ddata * ddata )
{
const char * const names [ CPCAP_BATTERY_IIO_NR ] = {
" battdetb " , " battp " , " chg_isense " , " batti " ,
} ;
int error , i ;
for ( i = 0 ; i < CPCAP_BATTERY_IIO_NR ; i + + ) {
ddata - > channels [ i ] = devm_iio_channel_get ( ddata - > dev ,
names [ i ] ) ;
if ( IS_ERR ( ddata - > channels [ i ] ) ) {
error = PTR_ERR ( ddata - > channels [ i ] ) ;
goto out_err ;
}
if ( ! ddata - > channels [ i ] - > indio_dev ) {
error = - ENXIO ;
goto out_err ;
}
}
return 0 ;
out_err :
dev_err ( ddata - > dev , " could not initialize VBUS or ID IIO: %i \n " ,
error ) ;
return error ;
}
/*
* Based on the values from Motorola mapphone Linux kernel . In the
* the Motorola mapphone Linux kernel tree the value for pm_cd_factor
* is passed to the kernel via device tree . If it turns out to be
* something device specific we can consider that too later .
*
* And looking at the battery full and shutdown values for the stock
* kernel on droid 4 , full is 4351000 and software initiates shutdown
* at 3078000. The device will die around 2743000.
*/
static const struct cpcap_battery_config cpcap_battery_default_data = {
. ccm = 0x3ff ,
. cd_factor = 0x3cc ,
. info . technology = POWER_SUPPLY_TECHNOLOGY_LION ,
. info . voltage_max_design = 4351000 ,
. info . voltage_min_design = 3100000 ,
. info . charge_full_design = 1740000 ,
} ;
# ifdef CONFIG_OF
static const struct of_device_id cpcap_battery_id_table [ ] = {
{
. compatible = " motorola,cpcap-battery " ,
. data = & cpcap_battery_default_data ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cpcap_battery_id_table ) ;
# endif
static int cpcap_battery_probe ( struct platform_device * pdev )
{
struct power_supply_desc * psy_desc ;
struct cpcap_battery_ddata * ddata ;
const struct of_device_id * match ;
struct power_supply_config psy_cfg = { } ;
int error ;
match = of_match_device ( of_match_ptr ( cpcap_battery_id_table ) ,
& pdev - > dev ) ;
if ( ! match )
return - EINVAL ;
if ( ! match - > data ) {
dev_err ( & pdev - > dev , " no configuration data found \n " ) ;
return - ENODEV ;
}
ddata = devm_kzalloc ( & pdev - > dev , sizeof ( * ddata ) , GFP_KERNEL ) ;
if ( ! ddata )
return - ENOMEM ;
INIT_LIST_HEAD ( & ddata - > irq_list ) ;
ddata - > dev = & pdev - > dev ;
memcpy ( & ddata - > config , match - > data , sizeof ( ddata - > config ) ) ;
ddata - > reg = dev_get_regmap ( ddata - > dev - > parent , NULL ) ;
if ( ! ddata - > reg )
return - ENODEV ;
error = cpcap_get_vendor ( ddata - > dev , ddata - > reg , & ddata - > vendor ) ;
if ( error )
return error ;
platform_set_drvdata ( pdev , ddata ) ;
error = regmap_update_bits ( ddata - > reg , CPCAP_REG_CCM ,
0xffff , ddata - > config . ccm ) ;
if ( error )
return error ;
error = cpcap_battery_init_interrupts ( pdev , ddata ) ;
if ( error )
return error ;
error = cpcap_battery_init_iio ( ddata ) ;
if ( error )
return error ;
psy_desc = devm_kzalloc ( ddata - > dev , sizeof ( * psy_desc ) , GFP_KERNEL ) ;
if ( ! psy_desc )
return - ENOMEM ;
psy_desc - > name = " battery " ,
psy_desc - > type = POWER_SUPPLY_TYPE_BATTERY ,
psy_desc - > properties = cpcap_battery_props ,
psy_desc - > num_properties = ARRAY_SIZE ( cpcap_battery_props ) ,
psy_desc - > get_property = cpcap_battery_get_property ,
psy_cfg . of_node = pdev - > dev . of_node ;
psy_cfg . drv_data = ddata ;
ddata - > psy = devm_power_supply_register ( ddata - > dev , psy_desc ,
& psy_cfg ) ;
error = PTR_ERR_OR_ZERO ( ddata - > psy ) ;
if ( error ) {
dev_err ( ddata - > dev , " failed to register power supply \n " ) ;
return error ;
}
atomic_set ( & ddata - > active , 1 ) ;
return 0 ;
}
static int cpcap_battery_remove ( struct platform_device * pdev )
{
struct cpcap_battery_ddata * ddata = platform_get_drvdata ( pdev ) ;
int error ;
atomic_set ( & ddata - > active , 0 ) ;
error = regmap_update_bits ( ddata - > reg , CPCAP_REG_BPEOL ,
0xffff , 0 ) ;
if ( error )
dev_err ( & pdev - > dev , " could not disable: %i \n " , error ) ;
return 0 ;
}
static struct platform_driver cpcap_battery_driver = {
. driver = {
. name = " cpcap_battery " ,
. of_match_table = of_match_ptr ( cpcap_battery_id_table ) ,
} ,
. probe = cpcap_battery_probe ,
. remove = cpcap_battery_remove ,
} ;
module_platform_driver ( cpcap_battery_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Tony Lindgren <tony@atomide.com> " ) ;
MODULE_DESCRIPTION ( " CPCAP PMIC Battery Driver " ) ;