2018-11-05 10:39:11 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Spreadtrum Communications Inc.
# include <linux/gpio/consumer.h>
# include <linux/iio/consumer.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
2020-07-24 15:21:48 +03:00
# include <linux/math64.h>
2018-11-05 10:39:11 +03:00
# include <linux/module.h>
2018-11-14 12:07:05 +03:00
# include <linux/nvmem-consumer.h>
2018-11-05 10:39:11 +03:00
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/regmap.h>
2018-11-14 12:07:05 +03:00
# include <linux/slab.h>
2018-11-05 10:39:11 +03:00
/* PMIC global control registers definition */
# define SC27XX_MODULE_EN0 0xc08
# define SC27XX_CLK_EN0 0xc18
# define SC27XX_FGU_EN BIT(7)
# define SC27XX_FGU_RTC_EN BIT(6)
/* FGU registers definition */
# define SC27XX_FGU_START 0x0
# define SC27XX_FGU_CONFIG 0x4
# define SC27XX_FGU_ADC_CONFIG 0x8
# define SC27XX_FGU_STATUS 0xc
# define SC27XX_FGU_INT_EN 0x10
# define SC27XX_FGU_INT_CLR 0x14
# define SC27XX_FGU_INT_STS 0x1c
# define SC27XX_FGU_VOLTAGE 0x20
# define SC27XX_FGU_OCV 0x24
# define SC27XX_FGU_POCV 0x28
# define SC27XX_FGU_CURRENT 0x2c
2018-11-14 12:07:06 +03:00
# define SC27XX_FGU_LOW_OVERLOAD 0x34
2018-11-05 10:39:11 +03:00
# define SC27XX_FGU_CLBCNT_SETH 0x50
# define SC27XX_FGU_CLBCNT_SETL 0x54
2018-11-14 12:07:06 +03:00
# define SC27XX_FGU_CLBCNT_DELTH 0x58
# define SC27XX_FGU_CLBCNT_DELTL 0x5c
2018-11-05 10:39:11 +03:00
# define SC27XX_FGU_CLBCNT_VALH 0x68
# define SC27XX_FGU_CLBCNT_VALL 0x6c
# define SC27XX_FGU_CLBCNT_QMAXL 0x74
2018-12-06 06:20:39 +03:00
# define SC27XX_FGU_USER_AREA_SET 0xa0
# define SC27XX_FGU_USER_AREA_CLEAR 0xa4
# define SC27XX_FGU_USER_AREA_STATUS 0xa8
2020-04-20 06:42:06 +03:00
# define SC27XX_FGU_VOLTAGE_BUF 0xd0
# define SC27XX_FGU_CURRENT_BUF 0xf0
2018-11-05 10:39:11 +03:00
# define SC27XX_WRITE_SELCLB_EN BIT(0)
# define SC27XX_FGU_CLBCNT_MASK GENMASK(15, 0)
# define SC27XX_FGU_CLBCNT_SHIFT 16
2018-11-14 12:07:06 +03:00
# define SC27XX_FGU_LOW_OVERLOAD_MASK GENMASK(12, 0)
# define SC27XX_FGU_INT_MASK GENMASK(9, 0)
# define SC27XX_FGU_LOW_OVERLOAD_INT BIT(0)
# define SC27XX_FGU_CLBCNT_DELTA_INT BIT(2)
2018-11-05 10:39:11 +03:00
2018-12-06 06:20:39 +03:00
# define SC27XX_FGU_MODE_AREA_MASK GENMASK(15, 12)
# define SC27XX_FGU_CAP_AREA_MASK GENMASK(11, 0)
# define SC27XX_FGU_MODE_AREA_SHIFT 12
# define SC27XX_FGU_FIRST_POWERTON GENMASK(3, 0)
# define SC27XX_FGU_DEFAULT_CAP GENMASK(11, 0)
# define SC27XX_FGU_NORMAIL_POWERTON 0x5
2018-11-05 10:39:11 +03:00
# define SC27XX_FGU_CUR_BASIC_ADC 8192
# define SC27XX_FGU_SAMPLE_HZ 2
2019-12-09 06:56:25 +03:00
/* micro Ohms */
# define SC27XX_FGU_IDEAL_RESISTANCE 20000
2018-11-05 10:39:11 +03:00
/*
* struct sc27xx_fgu_data : describe the FGU device
* @ regmap : regmap for register access
* @ dev : platform device
* @ battery : battery power supply
* @ base : the base offset for the controller
* @ lock : protect the structure
* @ gpiod : GPIO for battery detection
* @ channel : IIO channel to get battery temperature
2019-01-15 13:32:35 +03:00
* @ charge_chan : IIO channel to get charge voltage
2018-11-05 10:39:11 +03:00
* @ internal_resist : the battery internal resistance in mOhm
* @ total_cap : the total capacity of the battery in mAh
* @ init_cap : the initial capacity of the battery in mAh
2018-11-14 12:07:06 +03:00
* @ alarm_cap : the alarm capacity
2018-11-05 10:39:11 +03:00
* @ init_clbcnt : the initial coulomb counter
* @ max_volt : the maximum constant input voltage in millivolt
2018-11-14 12:07:06 +03:00
* @ min_volt : the minimum drained battery voltage in microvolt
2020-04-20 06:42:07 +03:00
* @ boot_volt : the voltage measured during boot in microvolt
2018-11-05 10:39:11 +03:00
* @ table_len : the capacity table length
2019-12-09 06:56:23 +03:00
* @ resist_table_len : the resistance table length
2018-11-14 12:07:05 +03:00
* @ cur_1000ma_adc : ADC value corresponding to 1000 mA
* @ vol_1000mv_adc : ADC value corresponding to 1000 mV
2019-12-09 06:56:25 +03:00
* @ calib_resist : the real resistance of coulomb counter chip in uOhm
2018-11-05 10:39:11 +03:00
* @ cap_table : capacity table with corresponding ocv
2019-12-09 06:56:23 +03:00
* @ resist_table : resistance percent table with corresponding temperature
2018-11-05 10:39:11 +03:00
*/
struct sc27xx_fgu_data {
struct regmap * regmap ;
struct device * dev ;
struct power_supply * battery ;
u32 base ;
struct mutex lock ;
struct gpio_desc * gpiod ;
struct iio_channel * channel ;
2019-01-15 13:32:35 +03:00
struct iio_channel * charge_chan ;
2018-11-05 10:39:11 +03:00
bool bat_present ;
int internal_resist ;
int total_cap ;
int init_cap ;
2018-11-14 12:07:06 +03:00
int alarm_cap ;
2018-11-05 10:39:11 +03:00
int init_clbcnt ;
int max_volt ;
2018-11-14 12:07:06 +03:00
int min_volt ;
2020-04-20 06:42:07 +03:00
int boot_volt ;
2018-11-05 10:39:11 +03:00
int table_len ;
2019-12-09 06:56:23 +03:00
int resist_table_len ;
2018-11-14 12:07:05 +03:00
int cur_1000ma_adc ;
int vol_1000mv_adc ;
2019-12-09 06:56:25 +03:00
int calib_resist ;
2018-11-05 10:39:11 +03:00
struct power_supply_battery_ocv_table * cap_table ;
2019-12-09 06:56:23 +03:00
struct power_supply_resistance_temp_table * resist_table ;
2018-11-05 10:39:11 +03:00
} ;
2018-11-14 12:07:06 +03:00
static int sc27xx_fgu_cap_to_clbcnt ( struct sc27xx_fgu_data * data , int capacity ) ;
2019-07-31 13:00:26 +03:00
static void sc27xx_fgu_capacity_calibration ( struct sc27xx_fgu_data * data ,
int cap , bool int_mode ) ;
2019-07-31 13:00:28 +03:00
static void sc27xx_fgu_adjust_cap ( struct sc27xx_fgu_data * data , int cap ) ;
2019-12-09 06:56:23 +03:00
static int sc27xx_fgu_get_temp ( struct sc27xx_fgu_data * data , int * temp ) ;
2018-11-14 12:07:06 +03:00
2018-11-05 10:39:11 +03:00
static const char * const sc27xx_charger_supply_name [ ] = {
" sc2731_charger " ,
" sc2720_charger " ,
" sc2721_charger " ,
" sc2723_charger " ,
} ;
2020-07-24 15:21:48 +03:00
static int sc27xx_fgu_adc_to_current ( struct sc27xx_fgu_data * data , s64 adc )
2018-11-05 10:39:11 +03:00
{
2020-07-24 15:21:48 +03:00
return DIV_S64_ROUND_CLOSEST ( adc * 1000 , data - > cur_1000ma_adc ) ;
2018-11-05 10:39:11 +03:00
}
2020-07-24 15:21:48 +03:00
static int sc27xx_fgu_adc_to_voltage ( struct sc27xx_fgu_data * data , s64 adc )
2018-11-05 10:39:11 +03:00
{
2020-07-24 15:21:48 +03:00
return DIV_S64_ROUND_CLOSEST ( adc * 1000 , data - > vol_1000mv_adc ) ;
2018-11-05 10:39:11 +03:00
}
2018-11-14 12:07:06 +03:00
static int sc27xx_fgu_voltage_to_adc ( struct sc27xx_fgu_data * data , int vol )
{
return DIV_ROUND_CLOSEST ( vol * data - > vol_1000mv_adc , 1000 ) ;
}
2018-12-06 06:20:39 +03:00
static bool sc27xx_fgu_is_first_poweron ( struct sc27xx_fgu_data * data )
{
int ret , status , cap , mode ;
ret = regmap_read ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_STATUS , & status ) ;
if ( ret )
return false ;
/*
* We use low 4 bits to save the last battery capacity and high 12 bits
* to save the system boot mode .
*/
mode = ( status & SC27XX_FGU_MODE_AREA_MASK ) > > SC27XX_FGU_MODE_AREA_SHIFT ;
cap = status & SC27XX_FGU_CAP_AREA_MASK ;
/*
* When FGU has been powered down , the user area registers became
* default value ( 0xffff ) , which can be used to valid if the system is
* first power on or not .
*/
if ( mode = = SC27XX_FGU_FIRST_POWERTON | | cap = = SC27XX_FGU_DEFAULT_CAP )
return true ;
return false ;
}
static int sc27xx_fgu_save_boot_mode ( struct sc27xx_fgu_data * data ,
int boot_mode )
{
int ret ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_CLEAR ,
SC27XX_FGU_MODE_AREA_MASK ,
SC27XX_FGU_MODE_AREA_MASK ) ;
if ( ret )
return ret ;
2019-01-15 13:32:37 +03:00
/*
* Since the user area registers are put on power always - on region ,
* then these registers changing time will be a little long . Thus
* here we should delay 200u s to wait until values are updated
* successfully according to the datasheet .
*/
udelay ( 200 ) ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_SET ,
SC27XX_FGU_MODE_AREA_MASK ,
boot_mode < < SC27XX_FGU_MODE_AREA_SHIFT ) ;
if ( ret )
return ret ;
/*
* Since the user area registers are put on power always - on region ,
* then these registers changing time will be a little long . Thus
* here we should delay 200u s to wait until values are updated
* successfully according to the datasheet .
*/
udelay ( 200 ) ;
/*
* According to the datasheet , we should set the USER_AREA_CLEAR to 0 to
* make the user area data available , otherwise we can not save the user
* area data .
*/
2018-12-06 06:20:39 +03:00
return regmap_update_bits ( data - > regmap ,
2019-01-15 13:32:37 +03:00
data - > base + SC27XX_FGU_USER_AREA_CLEAR ,
SC27XX_FGU_MODE_AREA_MASK , 0 ) ;
2018-12-06 06:20:39 +03:00
}
static int sc27xx_fgu_save_last_cap ( struct sc27xx_fgu_data * data , int cap )
{
int ret ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_CLEAR ,
SC27XX_FGU_CAP_AREA_MASK ,
SC27XX_FGU_CAP_AREA_MASK ) ;
if ( ret )
return ret ;
2019-01-15 13:32:37 +03:00
/*
* Since the user area registers are put on power always - on region ,
* then these registers changing time will be a little long . Thus
* here we should delay 200u s to wait until values are updated
* successfully according to the datasheet .
*/
udelay ( 200 ) ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_SET ,
SC27XX_FGU_CAP_AREA_MASK , cap ) ;
if ( ret )
return ret ;
/*
* Since the user area registers are put on power always - on region ,
* then these registers changing time will be a little long . Thus
* here we should delay 200u s to wait until values are updated
* successfully according to the datasheet .
*/
udelay ( 200 ) ;
/*
* According to the datasheet , we should set the USER_AREA_CLEAR to 0 to
* make the user area data available , otherwise we can not save the user
* area data .
*/
2018-12-06 06:20:39 +03:00
return regmap_update_bits ( data - > regmap ,
2019-01-15 13:32:37 +03:00
data - > base + SC27XX_FGU_USER_AREA_CLEAR ,
SC27XX_FGU_CAP_AREA_MASK , 0 ) ;
2018-12-06 06:20:39 +03:00
}
static int sc27xx_fgu_read_last_cap ( struct sc27xx_fgu_data * data , int * cap )
{
int ret , value ;
ret = regmap_read ( data - > regmap ,
data - > base + SC27XX_FGU_USER_AREA_STATUS , & value ) ;
if ( ret )
return ret ;
* cap = value & SC27XX_FGU_CAP_AREA_MASK ;
return 0 ;
}
2018-11-05 10:39:11 +03:00
/*
* When system boots on , we can not read battery capacity from coulomb
* registers , since now the coulomb registers are invalid . So we should
* calculate the battery open circuit voltage , and get current battery
* capacity according to the capacity table .
*/
static int sc27xx_fgu_get_boot_capacity ( struct sc27xx_fgu_data * data , int * cap )
{
int volt , cur , oci , ocv , ret ;
2018-12-06 06:20:39 +03:00
bool is_first_poweron = sc27xx_fgu_is_first_poweron ( data ) ;
/*
* If system is not the first power on , we should use the last saved
* battery capacity as the initial battery capacity . Otherwise we should
* re - calculate the initial battery capacity .
*/
if ( ! is_first_poweron ) {
ret = sc27xx_fgu_read_last_cap ( data , cap ) ;
if ( ret )
return ret ;
return sc27xx_fgu_save_boot_mode ( data , SC27XX_FGU_NORMAIL_POWERTON ) ;
}
2018-11-05 10:39:11 +03:00
/*
* After system booting on , the SC27XX_FGU_CLBCNT_QMAXL register saved
* the first sampled open circuit current .
*/
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_CLBCNT_QMAXL ,
& cur ) ;
if ( ret )
return ret ;
cur < < = 1 ;
2018-11-14 12:07:05 +03:00
oci = sc27xx_fgu_adc_to_current ( data , cur - SC27XX_FGU_CUR_BASIC_ADC ) ;
2018-11-05 10:39:11 +03:00
/*
* Should get the OCV from SC27XX_FGU_POCV register at the system
* beginning . It is ADC values reading from registers which need to
* convert the corresponding voltage .
*/
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_POCV , & volt ) ;
if ( ret )
return ret ;
2018-11-14 12:07:05 +03:00
volt = sc27xx_fgu_adc_to_voltage ( data , volt ) ;
2018-11-05 10:39:11 +03:00
ocv = volt * 1000 - oci * data - > internal_resist ;
2020-04-20 06:42:07 +03:00
data - > boot_volt = ocv ;
2018-11-05 10:39:11 +03:00
/*
* Parse the capacity table to look up the correct capacity percent
* according to current battery ' s corresponding OCV values .
*/
* cap = power_supply_ocv2cap_simple ( data - > cap_table , data - > table_len ,
ocv ) ;
2018-12-06 06:20:39 +03:00
ret = sc27xx_fgu_save_last_cap ( data , * cap ) ;
if ( ret )
return ret ;
return sc27xx_fgu_save_boot_mode ( data , SC27XX_FGU_NORMAIL_POWERTON ) ;
2018-11-05 10:39:11 +03:00
}
static int sc27xx_fgu_set_clbcnt ( struct sc27xx_fgu_data * data , int clbcnt )
{
int ret ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_CLBCNT_SETL ,
SC27XX_FGU_CLBCNT_MASK , clbcnt ) ;
if ( ret )
return ret ;
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_CLBCNT_SETH ,
SC27XX_FGU_CLBCNT_MASK ,
clbcnt > > SC27XX_FGU_CLBCNT_SHIFT ) ;
if ( ret )
return ret ;
return regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_START ,
SC27XX_WRITE_SELCLB_EN ,
SC27XX_WRITE_SELCLB_EN ) ;
}
static int sc27xx_fgu_get_clbcnt ( struct sc27xx_fgu_data * data , int * clb_cnt )
{
int ccl , cch , ret ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_CLBCNT_VALL ,
& ccl ) ;
if ( ret )
return ret ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_CLBCNT_VALH ,
& cch ) ;
if ( ret )
return ret ;
* clb_cnt = ccl & SC27XX_FGU_CLBCNT_MASK ;
* clb_cnt | = ( cch & SC27XX_FGU_CLBCNT_MASK ) < < SC27XX_FGU_CLBCNT_SHIFT ;
return 0 ;
}
2020-04-20 06:42:06 +03:00
static int sc27xx_fgu_get_vol_now ( struct sc27xx_fgu_data * data , int * val )
{
int ret ;
u32 vol ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_VOLTAGE_BUF ,
& vol ) ;
if ( ret )
return ret ;
/*
* It is ADC values reading from registers which need to convert to
* corresponding voltage values .
*/
* val = sc27xx_fgu_adc_to_voltage ( data , vol ) ;
return 0 ;
}
static int sc27xx_fgu_get_cur_now ( struct sc27xx_fgu_data * data , int * val )
{
int ret ;
u32 cur ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_CURRENT_BUF ,
& cur ) ;
if ( ret )
return ret ;
/*
* It is ADC values reading from registers which need to convert to
* corresponding current values .
*/
* val = sc27xx_fgu_adc_to_current ( data , cur - SC27XX_FGU_CUR_BASIC_ADC ) ;
return 0 ;
}
2018-11-05 10:39:11 +03:00
static int sc27xx_fgu_get_capacity ( struct sc27xx_fgu_data * data , int * cap )
{
int ret , cur_clbcnt , delta_clbcnt , delta_cap , temp ;
/* Get current coulomb counters firstly */
ret = sc27xx_fgu_get_clbcnt ( data , & cur_clbcnt ) ;
if ( ret )
return ret ;
delta_clbcnt = cur_clbcnt - data - > init_clbcnt ;
/*
* Convert coulomb counter to delta capacity ( mAh ) , and set multiplier
2019-07-31 13:00:25 +03:00
* as 10 to improve the precision .
2018-11-05 10:39:11 +03:00
*/
2019-07-31 13:00:25 +03:00
temp = DIV_ROUND_CLOSEST ( delta_clbcnt * 10 , 36 * SC27XX_FGU_SAMPLE_HZ ) ;
temp = sc27xx_fgu_adc_to_current ( data , temp / 1000 ) ;
2018-11-05 10:39:11 +03:00
/*
* Convert to capacity percent of the battery total capacity ,
* and multiplier is 100 too .
*/
delta_cap = DIV_ROUND_CLOSEST ( temp * 100 , data - > total_cap ) ;
* cap = delta_cap + data - > init_cap ;
2019-07-31 13:00:26 +03:00
/* Calibrate the battery capacity in a normal range. */
sc27xx_fgu_capacity_calibration ( data , * cap , false ) ;
2018-11-05 10:39:11 +03:00
return 0 ;
}
static int sc27xx_fgu_get_vbat_vol ( struct sc27xx_fgu_data * data , int * val )
{
int ret , vol ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_VOLTAGE , & vol ) ;
if ( ret )
return ret ;
/*
* It is ADC values reading from registers which need to convert to
* corresponding voltage values .
*/
2018-11-14 12:07:05 +03:00
* val = sc27xx_fgu_adc_to_voltage ( data , vol ) ;
2018-11-05 10:39:11 +03:00
return 0 ;
}
static int sc27xx_fgu_get_current ( struct sc27xx_fgu_data * data , int * val )
{
int ret , cur ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_CURRENT , & cur ) ;
if ( ret )
return ret ;
/*
* It is ADC values reading from registers which need to convert to
* corresponding current values .
*/
2018-11-14 12:07:05 +03:00
* val = sc27xx_fgu_adc_to_current ( data , cur - SC27XX_FGU_CUR_BASIC_ADC ) ;
2018-11-05 10:39:11 +03:00
return 0 ;
}
static int sc27xx_fgu_get_vbat_ocv ( struct sc27xx_fgu_data * data , int * val )
{
2019-12-09 06:56:23 +03:00
int vol , cur , ret , temp , resistance ;
2018-11-05 10:39:11 +03:00
ret = sc27xx_fgu_get_vbat_vol ( data , & vol ) ;
if ( ret )
return ret ;
ret = sc27xx_fgu_get_current ( data , & cur ) ;
if ( ret )
return ret ;
2019-12-09 06:56:23 +03:00
resistance = data - > internal_resist ;
if ( data - > resist_table_len > 0 ) {
ret = sc27xx_fgu_get_temp ( data , & temp ) ;
if ( ret )
return ret ;
resistance = power_supply_temp2resist_simple ( data - > resist_table ,
data - > resist_table_len , temp ) ;
resistance = data - > internal_resist * resistance / 100 ;
}
2018-11-05 10:39:11 +03:00
/* Return the battery OCV in micro volts. */
2019-12-09 06:56:23 +03:00
* val = vol * 1000 - cur * resistance ;
2018-11-05 10:39:11 +03:00
return 0 ;
}
2019-01-15 13:32:35 +03:00
static int sc27xx_fgu_get_charge_vol ( struct sc27xx_fgu_data * data , int * val )
{
int ret , vol ;
ret = iio_read_channel_processed ( data - > charge_chan , & vol ) ;
if ( ret < 0 )
return ret ;
* val = vol * 1000 ;
return 0 ;
}
2018-11-05 10:39:11 +03:00
static int sc27xx_fgu_get_temp ( struct sc27xx_fgu_data * data , int * temp )
{
return iio_read_channel_processed ( data - > channel , temp ) ;
}
static int sc27xx_fgu_get_health ( struct sc27xx_fgu_data * data , int * health )
{
int ret , vol ;
ret = sc27xx_fgu_get_vbat_vol ( data , & vol ) ;
if ( ret )
return ret ;
if ( vol > data - > max_volt )
* health = POWER_SUPPLY_HEALTH_OVERVOLTAGE ;
else
* health = POWER_SUPPLY_HEALTH_GOOD ;
return 0 ;
}
static int sc27xx_fgu_get_status ( struct sc27xx_fgu_data * data , int * status )
{
union power_supply_propval val ;
struct power_supply * psy ;
int i , ret = - EINVAL ;
for ( i = 0 ; i < ARRAY_SIZE ( sc27xx_charger_supply_name ) ; i + + ) {
psy = power_supply_get_by_name ( sc27xx_charger_supply_name [ i ] ) ;
if ( ! psy )
continue ;
ret = power_supply_get_property ( psy , POWER_SUPPLY_PROP_STATUS ,
& val ) ;
power_supply_put ( psy ) ;
if ( ret )
return ret ;
* status = val . intval ;
}
return ret ;
}
static int sc27xx_fgu_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct sc27xx_fgu_data * data = power_supply_get_drvdata ( psy ) ;
int ret = 0 ;
int value ;
mutex_lock ( & data - > lock ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
ret = sc27xx_fgu_get_status ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
ret = sc27xx_fgu_get_health ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = data - > bat_present ;
break ;
case POWER_SUPPLY_PROP_TEMP :
ret = sc27xx_fgu_get_temp ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = POWER_SUPPLY_TECHNOLOGY_LION ;
break ;
case POWER_SUPPLY_PROP_CAPACITY :
ret = sc27xx_fgu_get_capacity ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
2020-04-20 06:42:06 +03:00
case POWER_SUPPLY_PROP_VOLTAGE_AVG :
2018-11-05 10:39:11 +03:00
ret = sc27xx_fgu_get_vbat_vol ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value * 1000 ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_OCV :
ret = sc27xx_fgu_get_vbat_ocv ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
2019-01-15 13:32:35 +03:00
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE :
ret = sc27xx_fgu_get_charge_vol ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value ;
break ;
2018-11-05 10:39:11 +03:00
case POWER_SUPPLY_PROP_CURRENT_AVG :
ret = sc27xx_fgu_get_current ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value * 1000 ;
break ;
2019-07-31 13:00:23 +03:00
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN :
val - > intval = data - > total_cap * 1000 ;
break ;
2020-02-14 08:38:58 +03:00
case POWER_SUPPLY_PROP_CHARGE_NOW :
ret = sc27xx_fgu_get_clbcnt ( data , & value ) ;
if ( ret )
goto error ;
value = DIV_ROUND_CLOSEST ( value * 10 ,
36 * SC27XX_FGU_SAMPLE_HZ ) ;
val - > intval = sc27xx_fgu_adc_to_current ( data , value ) ;
break ;
2020-04-20 06:42:06 +03:00
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
ret = sc27xx_fgu_get_vol_now ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value * 1000 ;
break ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
ret = sc27xx_fgu_get_cur_now ( data , & value ) ;
if ( ret )
goto error ;
val - > intval = value * 1000 ;
break ;
2020-04-20 06:42:07 +03:00
case POWER_SUPPLY_PROP_VOLTAGE_BOOT :
val - > intval = data - > boot_volt ;
break ;
2018-11-05 10:39:11 +03:00
default :
ret = - EINVAL ;
break ;
}
error :
mutex_unlock ( & data - > lock ) ;
return ret ;
}
2018-12-06 06:20:39 +03:00
static int sc27xx_fgu_set_property ( struct power_supply * psy ,
enum power_supply_property psp ,
const union power_supply_propval * val )
{
struct sc27xx_fgu_data * data = power_supply_get_drvdata ( psy ) ;
int ret ;
mutex_lock ( & data - > lock ) ;
2019-07-31 13:00:28 +03:00
switch ( psp ) {
case POWER_SUPPLY_PROP_CAPACITY :
ret = sc27xx_fgu_save_last_cap ( data , val - > intval ) ;
if ( ret < 0 )
dev_err ( data - > dev , " failed to save battery capacity \n " ) ;
break ;
2018-12-06 06:20:39 +03:00
2019-07-31 13:00:28 +03:00
case POWER_SUPPLY_PROP_CALIBRATE :
sc27xx_fgu_adjust_cap ( data , val - > intval ) ;
ret = 0 ;
break ;
2018-12-06 06:20:39 +03:00
2020-04-20 06:42:05 +03:00
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN :
data - > total_cap = val - > intval / 1000 ;
ret = 0 ;
break ;
2019-07-31 13:00:28 +03:00
default :
ret = - EINVAL ;
}
mutex_unlock ( & data - > lock ) ;
2018-12-06 06:20:39 +03:00
return ret ;
}
2018-11-05 10:39:11 +03:00
static void sc27xx_fgu_external_power_changed ( struct power_supply * psy )
{
struct sc27xx_fgu_data * data = power_supply_get_drvdata ( psy ) ;
power_supply_changed ( data - > battery ) ;
}
2018-12-06 06:20:39 +03:00
static int sc27xx_fgu_property_is_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
2019-07-31 13:00:28 +03:00
return psp = = POWER_SUPPLY_PROP_CAPACITY | |
2020-04-20 06:42:05 +03:00
psp = = POWER_SUPPLY_PROP_CALIBRATE | |
psp = = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN ;
2018-12-06 06:20:39 +03:00
}
2018-11-05 10:39:11 +03:00
static enum power_supply_property sc27xx_fgu_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_TEMP ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_CAPACITY ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_OCV ,
2020-04-20 06:42:06 +03:00
POWER_SUPPLY_PROP_VOLTAGE_AVG ,
2020-04-20 06:42:07 +03:00
POWER_SUPPLY_PROP_VOLTAGE_BOOT ,
2018-11-05 10:39:11 +03:00
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_CURRENT_AVG ,
2019-01-15 13:32:35 +03:00
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE ,
2019-07-31 13:00:23 +03:00
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN ,
2019-07-31 13:00:28 +03:00
POWER_SUPPLY_PROP_CALIBRATE ,
2020-02-14 08:38:58 +03:00
POWER_SUPPLY_PROP_CHARGE_NOW
2018-11-05 10:39:11 +03:00
} ;
static const struct power_supply_desc sc27xx_fgu_desc = {
. name = " sc27xx-fgu " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = sc27xx_fgu_props ,
. num_properties = ARRAY_SIZE ( sc27xx_fgu_props ) ,
. get_property = sc27xx_fgu_get_property ,
2018-12-06 06:20:39 +03:00
. set_property = sc27xx_fgu_set_property ,
2018-11-05 10:39:11 +03:00
. external_power_changed = sc27xx_fgu_external_power_changed ,
2018-12-06 06:20:39 +03:00
. property_is_writeable = sc27xx_fgu_property_is_writeable ,
2020-04-20 06:42:04 +03:00
. no_thermal = true ,
2018-11-05 10:39:11 +03:00
} ;
2018-11-14 12:07:06 +03:00
static void sc27xx_fgu_adjust_cap ( struct sc27xx_fgu_data * data , int cap )
{
2019-07-31 13:00:26 +03:00
int ret ;
2018-11-14 12:07:06 +03:00
data - > init_cap = cap ;
2019-07-31 13:00:26 +03:00
ret = sc27xx_fgu_get_clbcnt ( data , & data - > init_clbcnt ) ;
if ( ret )
dev_err ( data - > dev , " failed to get init coulomb counter \n " ) ;
2018-11-14 12:07:06 +03:00
}
2019-07-31 13:00:26 +03:00
static void sc27xx_fgu_capacity_calibration ( struct sc27xx_fgu_data * data ,
int cap , bool int_mode )
2018-11-14 12:07:06 +03:00
{
2019-07-31 13:00:26 +03:00
int ret , ocv , chg_sts , adc ;
2018-11-14 12:07:06 +03:00
2019-07-31 13:00:26 +03:00
ret = sc27xx_fgu_get_vbat_ocv ( data , & ocv ) ;
if ( ret ) {
dev_err ( data - > dev , " get battery ocv error. \n " ) ;
return ;
}
2018-11-14 12:07:06 +03:00
2019-07-31 13:00:26 +03:00
ret = sc27xx_fgu_get_status ( data , & chg_sts ) ;
if ( ret ) {
dev_err ( data - > dev , " get charger status error. \n " ) ;
return ;
}
2018-11-14 12:07:06 +03:00
/*
2019-07-31 13:00:26 +03:00
* If we are in charging mode , then we do not need to calibrate the
* lower capacity .
2018-11-14 12:07:06 +03:00
*/
2019-07-31 13:00:26 +03:00
if ( chg_sts = = POWER_SUPPLY_STATUS_CHARGING )
return ;
2018-11-14 12:07:06 +03:00
2019-07-31 13:00:26 +03:00
if ( ( ocv > data - > cap_table [ 0 ] . ocv & & cap < 100 ) | | cap > 100 ) {
/*
* If current OCV value is larger than the max OCV value in
* OCV table , or the current capacity is larger than 100 ,
* we should force the inititial capacity to 100.
*/
sc27xx_fgu_adjust_cap ( data , 100 ) ;
} else if ( ocv < = data - > cap_table [ data - > table_len - 1 ] . ocv ) {
/*
* If current OCV value is leass than the minimum OCV value in
* OCV table , we should force the inititial capacity to 0.
*/
2018-11-14 12:07:06 +03:00
sc27xx_fgu_adjust_cap ( data , 0 ) ;
2019-07-31 13:00:26 +03:00
} else if ( ( ocv > data - > cap_table [ data - > table_len - 1 ] . ocv & & cap < = 0 ) | |
( ocv > data - > min_volt & & cap < = data - > alarm_cap ) ) {
/*
* If current OCV value is not matchable with current capacity ,
* we should re - calculate current capacity by looking up the
* OCV table .
*/
int cur_cap = power_supply_ocv2cap_simple ( data - > cap_table ,
data - > table_len , ocv ) ;
sc27xx_fgu_adjust_cap ( data , cur_cap ) ;
2018-11-14 12:07:06 +03:00
} else if ( ocv < = data - > min_volt ) {
/*
* If current OCV value is less than the low alarm voltage , but
* current capacity is larger than the alarm capacity , we should
* adjust the inititial capacity to alarm capacity .
*/
if ( cap > data - > alarm_cap ) {
sc27xx_fgu_adjust_cap ( data , data - > alarm_cap ) ;
2019-07-31 13:00:26 +03:00
} else {
2018-11-14 12:07:06 +03:00
int cur_cap ;
/*
* If current capacity is equal with 0 or less than 0
* ( some error occurs ) , we should adjust inititial
* capacity to the capacity corresponding to current OCV
* value .
*/
cur_cap = power_supply_ocv2cap_simple ( data - > cap_table ,
data - > table_len ,
ocv ) ;
sc27xx_fgu_adjust_cap ( data , cur_cap ) ;
}
2019-07-31 13:00:26 +03:00
if ( ! int_mode )
return ;
2018-11-14 12:07:06 +03:00
/*
* After adjusting the battery capacity , we should set the
* lowest alarm voltage instead .
*/
data - > min_volt = data - > cap_table [ data - > table_len - 1 ] . ocv ;
2019-07-31 13:00:26 +03:00
data - > alarm_cap = power_supply_ocv2cap_simple ( data - > cap_table ,
data - > table_len ,
data - > min_volt ) ;
2018-11-14 12:07:06 +03:00
adc = sc27xx_fgu_voltage_to_adc ( data , data - > min_volt / 1000 ) ;
2019-07-31 13:00:26 +03:00
regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_LOW_OVERLOAD ,
2018-11-14 12:07:06 +03:00
SC27XX_FGU_LOW_OVERLOAD_MASK , adc ) ;
}
2019-07-31 13:00:26 +03:00
}
static irqreturn_t sc27xx_fgu_interrupt ( int irq , void * dev_id )
{
struct sc27xx_fgu_data * data = dev_id ;
int ret , cap ;
u32 status ;
mutex_lock ( & data - > lock ) ;
ret = regmap_read ( data - > regmap , data - > base + SC27XX_FGU_INT_STS ,
& status ) ;
if ( ret )
goto out ;
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_INT_CLR ,
status , status ) ;
if ( ret )
goto out ;
/*
* When low overload voltage interrupt happens , we should calibrate the
* battery capacity in lower voltage stage .
*/
if ( ! ( status & SC27XX_FGU_LOW_OVERLOAD_INT ) )
goto out ;
ret = sc27xx_fgu_get_capacity ( data , & cap ) ;
if ( ret )
goto out ;
sc27xx_fgu_capacity_calibration ( data , cap , true ) ;
2018-11-14 12:07:06 +03:00
out :
mutex_unlock ( & data - > lock ) ;
power_supply_changed ( data - > battery ) ;
return IRQ_HANDLED ;
}
2018-11-05 10:39:11 +03:00
static irqreturn_t sc27xx_fgu_bat_detection ( int irq , void * dev_id )
{
struct sc27xx_fgu_data * data = dev_id ;
int state ;
mutex_lock ( & data - > lock ) ;
state = gpiod_get_value_cansleep ( data - > gpiod ) ;
if ( state < 0 ) {
dev_err ( data - > dev , " failed to get gpio state \n " ) ;
mutex_unlock ( & data - > lock ) ;
return IRQ_RETVAL ( state ) ;
}
data - > bat_present = ! ! state ;
mutex_unlock ( & data - > lock ) ;
power_supply_changed ( data - > battery ) ;
return IRQ_HANDLED ;
}
static void sc27xx_fgu_disable ( void * _data )
{
struct sc27xx_fgu_data * data = _data ;
regmap_update_bits ( data - > regmap , SC27XX_CLK_EN0 , SC27XX_FGU_RTC_EN , 0 ) ;
regmap_update_bits ( data - > regmap , SC27XX_MODULE_EN0 , SC27XX_FGU_EN , 0 ) ;
}
static int sc27xx_fgu_cap_to_clbcnt ( struct sc27xx_fgu_data * data , int capacity )
{
/*
* Get current capacity ( mAh ) = battery total capacity ( mAh ) *
* current capacity percent ( capacity / 100 ) .
*/
int cur_cap = DIV_ROUND_CLOSEST ( data - > total_cap * capacity , 100 ) ;
/*
* Convert current capacity ( mAh ) to coulomb counter according to the
* formula : 1 mAh = 3.6 coulomb .
*/
2019-07-31 13:00:25 +03:00
return DIV_ROUND_CLOSEST ( cur_cap * 36 * data - > cur_1000ma_adc * SC27XX_FGU_SAMPLE_HZ , 10 ) ;
2018-11-05 10:39:11 +03:00
}
2018-11-14 12:07:05 +03:00
static int sc27xx_fgu_calibration ( struct sc27xx_fgu_data * data )
{
struct nvmem_cell * cell ;
int calib_data , cal_4200mv ;
void * buf ;
size_t len ;
cell = nvmem_cell_get ( data - > dev , " fgu_calib " ) ;
if ( IS_ERR ( cell ) )
return PTR_ERR ( cell ) ;
buf = nvmem_cell_read ( cell , & len ) ;
nvmem_cell_put ( cell ) ;
if ( IS_ERR ( buf ) )
return PTR_ERR ( buf ) ;
memcpy ( & calib_data , buf , min ( len , sizeof ( u32 ) ) ) ;
/*
* Get the ADC value corresponding to 4200 mV from eFuse controller
* according to below formula . Then convert to ADC values corresponding
* to 1000 mV and 1000 mA .
*/
cal_4200mv = ( calib_data & 0x1ff ) + 6963 - 4096 - 256 ;
data - > vol_1000mv_adc = DIV_ROUND_CLOSEST ( cal_4200mv * 10 , 42 ) ;
2019-12-09 06:56:25 +03:00
data - > cur_1000ma_adc =
DIV_ROUND_CLOSEST ( data - > vol_1000mv_adc * 4 * data - > calib_resist ,
SC27XX_FGU_IDEAL_RESISTANCE ) ;
2018-11-14 12:07:05 +03:00
kfree ( buf ) ;
return 0 ;
}
2018-11-05 10:39:11 +03:00
static int sc27xx_fgu_hw_init ( struct sc27xx_fgu_data * data )
{
struct power_supply_battery_info info = { } ;
struct power_supply_battery_ocv_table * table ;
2018-11-14 12:07:06 +03:00
int ret , delta_clbcnt , alarm_adc ;
2018-11-05 10:39:11 +03:00
ret = power_supply_get_battery_info ( data - > battery , & info ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to get battery information \n " ) ;
return ret ;
}
data - > total_cap = info . charge_full_design_uah / 1000 ;
data - > max_volt = info . constant_charge_voltage_max_uv / 1000 ;
data - > internal_resist = info . factory_internal_resistance_uohm / 1000 ;
2018-11-14 12:07:06 +03:00
data - > min_volt = info . voltage_min_design_uv ;
2018-11-05 10:39:11 +03:00
/*
* For SC27XX fuel gauge device , we only use one ocv - capacity
* table in normal temperature 20 Celsius .
*/
table = power_supply_find_ocv2cap_table ( & info , 20 , & data - > table_len ) ;
if ( ! table )
return - EINVAL ;
data - > cap_table = devm_kmemdup ( data - > dev , table ,
data - > table_len * sizeof ( * table ) ,
GFP_KERNEL ) ;
if ( ! data - > cap_table ) {
power_supply_put_battery_info ( data - > battery , & info ) ;
return - ENOMEM ;
}
2018-11-14 12:07:06 +03:00
data - > alarm_cap = power_supply_ocv2cap_simple ( data - > cap_table ,
data - > table_len ,
data - > min_volt ) ;
2019-07-31 13:00:27 +03:00
if ( ! data - > alarm_cap )
data - > alarm_cap + = 1 ;
2018-11-14 12:07:06 +03:00
2019-12-09 06:56:23 +03:00
data - > resist_table_len = info . resist_table_size ;
if ( data - > resist_table_len > 0 ) {
data - > resist_table = devm_kmemdup ( data - > dev , info . resist_table ,
data - > resist_table_len *
sizeof ( struct power_supply_resistance_temp_table ) ,
GFP_KERNEL ) ;
if ( ! data - > resist_table ) {
power_supply_put_battery_info ( data - > battery , & info ) ;
return - ENOMEM ;
}
}
2018-11-05 10:39:11 +03:00
power_supply_put_battery_info ( data - > battery , & info ) ;
2018-11-14 12:07:05 +03:00
ret = sc27xx_fgu_calibration ( data ) ;
if ( ret )
return ret ;
2018-11-05 10:39:11 +03:00
/* Enable the FGU module */
ret = regmap_update_bits ( data - > regmap , SC27XX_MODULE_EN0 ,
SC27XX_FGU_EN , SC27XX_FGU_EN ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to enable fgu \n " ) ;
return ret ;
}
/* Enable the FGU RTC clock to make it work */
ret = regmap_update_bits ( data - > regmap , SC27XX_CLK_EN0 ,
SC27XX_FGU_RTC_EN , SC27XX_FGU_RTC_EN ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to enable fgu RTC clock \n " ) ;
goto disable_fgu ;
}
2018-11-14 12:07:06 +03:00
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_INT_CLR ,
SC27XX_FGU_INT_MASK , SC27XX_FGU_INT_MASK ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to clear interrupt status \n " ) ;
goto disable_clk ;
}
/*
* Set the voltage low overload threshold , which means when the battery
* voltage is lower than this threshold , the controller will generate
* one interrupt to notify .
*/
alarm_adc = sc27xx_fgu_voltage_to_adc ( data , data - > min_volt / 1000 ) ;
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_LOW_OVERLOAD ,
SC27XX_FGU_LOW_OVERLOAD_MASK , alarm_adc ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to set fgu low overload \n " ) ;
goto disable_clk ;
}
/*
* Set the coulomb counter delta threshold , that means when the coulomb
* counter change is multiples of the delta threshold , the controller
* will generate one interrupt to notify the users to update the battery
* capacity . Now we set the delta threshold as a counter value of 1 %
* capacity .
*/
delta_clbcnt = sc27xx_fgu_cap_to_clbcnt ( data , 1 ) ;
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_CLBCNT_DELTL ,
SC27XX_FGU_CLBCNT_MASK , delta_clbcnt ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to set low delta coulomb counter \n " ) ;
goto disable_clk ;
}
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_CLBCNT_DELTH ,
SC27XX_FGU_CLBCNT_MASK ,
delta_clbcnt > > SC27XX_FGU_CLBCNT_SHIFT ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to set high delta coulomb counter \n " ) ;
goto disable_clk ;
}
2018-11-05 10:39:11 +03:00
/*
* Get the boot battery capacity when system powers on , which is used to
* initialize the coulomb counter . After that , we can read the coulomb
* counter to measure the battery capacity .
*/
ret = sc27xx_fgu_get_boot_capacity ( data , & data - > init_cap ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to get boot capacity \n " ) ;
goto disable_clk ;
}
/*
* Convert battery capacity to the corresponding initial coulomb counter
* and set into coulomb counter registers .
*/
data - > init_clbcnt = sc27xx_fgu_cap_to_clbcnt ( data , data - > init_cap ) ;
ret = sc27xx_fgu_set_clbcnt ( data , data - > init_clbcnt ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to initialize coulomb counter \n " ) ;
goto disable_clk ;
}
return 0 ;
disable_clk :
regmap_update_bits ( data - > regmap , SC27XX_CLK_EN0 , SC27XX_FGU_RTC_EN , 0 ) ;
disable_fgu :
regmap_update_bits ( data - > regmap , SC27XX_MODULE_EN0 , SC27XX_FGU_EN , 0 ) ;
return ret ;
}
static int sc27xx_fgu_probe ( struct platform_device * pdev )
{
2019-07-08 15:32:51 +03:00
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
2018-11-05 10:39:11 +03:00
struct power_supply_config fgu_cfg = { } ;
struct sc27xx_fgu_data * data ;
int ret , irq ;
2019-07-08 15:32:51 +03:00
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
2018-11-05 10:39:11 +03:00
if ( ! data )
return - ENOMEM ;
2019-07-08 15:32:51 +03:00
data - > regmap = dev_get_regmap ( dev - > parent , NULL ) ;
2018-11-05 10:39:11 +03:00
if ( ! data - > regmap ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get regmap \n " ) ;
2018-11-05 10:39:11 +03:00
return - ENODEV ;
}
2019-07-08 15:32:51 +03:00
ret = device_property_read_u32 ( dev , " reg " , & data - > base ) ;
2018-11-05 10:39:11 +03:00
if ( ret ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get fgu address \n " ) ;
2018-11-05 10:39:11 +03:00
return ret ;
}
2019-12-09 06:56:25 +03:00
ret = device_property_read_u32 ( & pdev - > dev ,
" sprd,calib-resistance-micro-ohms " ,
& data - > calib_resist ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" failed to get fgu calibration resistance \n " ) ;
return ret ;
}
2019-07-08 15:32:51 +03:00
data - > channel = devm_iio_channel_get ( dev , " bat-temp " ) ;
2018-11-05 10:39:11 +03:00
if ( IS_ERR ( data - > channel ) ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get IIO channel \n " ) ;
2018-11-05 10:39:11 +03:00
return PTR_ERR ( data - > channel ) ;
}
2019-07-08 15:32:51 +03:00
data - > charge_chan = devm_iio_channel_get ( dev , " charge-vol " ) ;
2019-01-15 13:32:35 +03:00
if ( IS_ERR ( data - > charge_chan ) ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get charge IIO channel \n " ) ;
2019-01-15 13:32:35 +03:00
return PTR_ERR ( data - > charge_chan ) ;
}
2019-07-08 15:32:51 +03:00
data - > gpiod = devm_gpiod_get ( dev , " bat-detect " , GPIOD_IN ) ;
2018-11-05 10:39:11 +03:00
if ( IS_ERR ( data - > gpiod ) ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get battery detection GPIO \n " ) ;
2018-11-05 10:39:11 +03:00
return PTR_ERR ( data - > gpiod ) ;
}
ret = gpiod_get_value_cansleep ( data - > gpiod ) ;
if ( ret < 0 ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to get gpio state \n " ) ;
2018-11-05 10:39:11 +03:00
return ret ;
}
data - > bat_present = ! ! ret ;
mutex_init ( & data - > lock ) ;
2019-07-08 15:32:51 +03:00
data - > dev = dev ;
2018-11-14 12:07:07 +03:00
platform_set_drvdata ( pdev , data ) ;
2018-11-05 10:39:11 +03:00
fgu_cfg . drv_data = data ;
fgu_cfg . of_node = np ;
2019-07-08 15:32:51 +03:00
data - > battery = devm_power_supply_register ( dev , & sc27xx_fgu_desc ,
2018-11-05 10:39:11 +03:00
& fgu_cfg ) ;
if ( IS_ERR ( data - > battery ) ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to register power supply \n " ) ;
2018-11-05 10:39:11 +03:00
return PTR_ERR ( data - > battery ) ;
}
ret = sc27xx_fgu_hw_init ( data ) ;
if ( ret ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to initialize fgu hardware \n " ) ;
2018-11-05 10:39:11 +03:00
return ret ;
}
2019-07-08 15:32:59 +03:00
ret = devm_add_action_or_reset ( dev , sc27xx_fgu_disable , data ) ;
2018-11-05 10:39:11 +03:00
if ( ret ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to add fgu disable action \n " ) ;
2018-11-05 10:39:11 +03:00
return ret ;
}
2018-11-14 12:07:06 +03:00
irq = platform_get_irq ( pdev , 0 ) ;
2021-08-05 15:52:33 +03:00
if ( irq < 0 )
2018-11-14 12:07:06 +03:00
return irq ;
ret = devm_request_threaded_irq ( data - > dev , irq , NULL ,
sc27xx_fgu_interrupt ,
IRQF_NO_SUSPEND | IRQF_ONESHOT ,
pdev - > name , data ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to request fgu IRQ \n " ) ;
return ret ;
}
2018-11-05 10:39:11 +03:00
irq = gpiod_to_irq ( data - > gpiod ) ;
if ( irq < 0 ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to translate GPIO to IRQ \n " ) ;
2018-11-05 10:39:11 +03:00
return irq ;
}
2019-07-08 15:32:51 +03:00
ret = devm_request_threaded_irq ( dev , irq , NULL ,
2018-11-05 10:39:11 +03:00
sc27xx_fgu_bat_detection ,
IRQF_ONESHOT | IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
pdev - > name , data ) ;
if ( ret ) {
2019-07-08 15:32:51 +03:00
dev_err ( dev , " failed to request IRQ \n " ) ;
2018-11-05 10:39:11 +03:00
return ret ;
}
return 0 ;
}
2018-11-14 12:07:07 +03:00
# ifdef CONFIG_PM_SLEEP
static int sc27xx_fgu_resume ( struct device * dev )
{
struct sc27xx_fgu_data * data = dev_get_drvdata ( dev ) ;
int ret ;
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_INT_EN ,
SC27XX_FGU_LOW_OVERLOAD_INT |
SC27XX_FGU_CLBCNT_DELTA_INT , 0 ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to disable fgu interrupts \n " ) ;
return ret ;
}
return 0 ;
}
static int sc27xx_fgu_suspend ( struct device * dev )
{
struct sc27xx_fgu_data * data = dev_get_drvdata ( dev ) ;
int ret , status , ocv ;
ret = sc27xx_fgu_get_status ( data , & status ) ;
if ( ret )
return ret ;
/*
* If we are charging , then no need to enable the FGU interrupts to
* adjust the battery capacity .
*/
2019-07-31 13:00:24 +03:00
if ( status ! = POWER_SUPPLY_STATUS_NOT_CHARGING & &
status ! = POWER_SUPPLY_STATUS_DISCHARGING )
2018-11-14 12:07:07 +03:00
return 0 ;
ret = regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_INT_EN ,
SC27XX_FGU_LOW_OVERLOAD_INT ,
SC27XX_FGU_LOW_OVERLOAD_INT ) ;
if ( ret ) {
dev_err ( data - > dev , " failed to enable low voltage interrupt \n " ) ;
return ret ;
}
ret = sc27xx_fgu_get_vbat_ocv ( data , & ocv ) ;
if ( ret )
goto disable_int ;
/*
* If current OCV is less than the minimum voltage , we should enable the
* coulomb counter threshold interrupt to notify events to adjust the
* battery capacity .
*/
if ( ocv < data - > min_volt ) {
ret = regmap_update_bits ( data - > regmap ,
data - > base + SC27XX_FGU_INT_EN ,
SC27XX_FGU_CLBCNT_DELTA_INT ,
SC27XX_FGU_CLBCNT_DELTA_INT ) ;
if ( ret ) {
dev_err ( data - > dev ,
" failed to enable coulomb threshold int \n " ) ;
goto disable_int ;
}
}
return 0 ;
disable_int :
regmap_update_bits ( data - > regmap , data - > base + SC27XX_FGU_INT_EN ,
SC27XX_FGU_LOW_OVERLOAD_INT , 0 ) ;
return ret ;
}
# endif
static const struct dev_pm_ops sc27xx_fgu_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( sc27xx_fgu_suspend , sc27xx_fgu_resume )
} ;
2018-11-05 10:39:11 +03:00
static const struct of_device_id sc27xx_fgu_of_match [ ] = {
{ . compatible = " sprd,sc2731-fgu " , } ,
{ }
} ;
2021-05-11 06:37:45 +03:00
MODULE_DEVICE_TABLE ( of , sc27xx_fgu_of_match ) ;
2018-11-05 10:39:11 +03:00
static struct platform_driver sc27xx_fgu_driver = {
. probe = sc27xx_fgu_probe ,
. driver = {
. name = " sc27xx-fgu " ,
. of_match_table = sc27xx_fgu_of_match ,
2018-11-14 12:07:07 +03:00
. pm = & sc27xx_fgu_pm_ops ,
2018-11-05 10:39:11 +03:00
}
} ;
module_platform_driver ( sc27xx_fgu_driver ) ;
MODULE_DESCRIPTION ( " Spreadtrum SC27XX PMICs Fual Gauge Unit Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;