2021-01-07 15:32:48 +01:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Analog Devices ( Linear Technology ) LTC4162 - L charger IC .
* Copyright ( C ) 2020 , Topic Embedded Products
*/
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/of_device.h>
# include <linux/pm_runtime.h>
# include <linux/power_supply.h>
# include <linux/i2c.h>
# include <linux/regmap.h>
/* Registers (names based on what datasheet uses) */
# define LTC4162L_EN_LIMIT_ALERTS_REG 0x0D
# define LTC4162L_EN_CHARGER_STATE_ALERTS_REG 0x0E
# define LTC4162L_EN_CHARGE_STATUS_ALERTS_REG 0x0F
# define LTC4162L_CONFIG_BITS_REG 0x14
# define LTC4162L_IIN_LIMIT_TARGET 0x15
# define LTC4162L_ARM_SHIP_MODE 0x19
# define LTC4162L_CHARGE_CURRENT_SETTING 0X1A
# define LTC4162L_VCHARGE_SETTING 0X1B
# define LTC4162L_C_OVER_X_THRESHOLD 0x1C
# define LTC4162L_MAX_CV_TIME 0X1D
# define LTC4162L_MAX_CHARGE_TIME 0X1E
# define LTC4162L_CHARGER_CONFIG_BITS 0x29
# define LTC4162L_CHARGER_STATE 0x34
# define LTC4162L_CHARGE_STATUS 0x35
# define LTC4162L_LIMIT_ALERTS_REG 0x36
# define LTC4162L_CHARGER_STATE_ALERTS_REG 0x37
# define LTC4162L_CHARGE_STATUS_ALERTS_REG 0x38
# define LTC4162L_SYSTEM_STATUS_REG 0x39
# define LTC4162L_VBAT 0x3A
# define LTC4162L_VIN 0x3B
# define LTC4162L_VOUT 0x3C
# define LTC4162L_IBAT 0x3D
# define LTC4162L_IIN 0x3E
# define LTC4162L_DIE_TEMPERATURE 0x3F
# define LTC4162L_THERMISTOR_VOLTAGE 0x40
# define LTC4162L_BSR 0x41
# define LTC4162L_JEITA_REGION 0x42
# define LTC4162L_CHEM_CELLS_REG 0x43
# define LTC4162L_ICHARGE_DAC 0x44
# define LTC4162L_VCHARGE_DAC 0x45
# define LTC4162L_IIN_LIMIT_DAC 0x46
# define LTC4162L_VBAT_FILT 0x47
# define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B
/* Enumeration as in datasheet. Individual bits are mutually exclusive. */
enum ltc4162l_state {
battery_detection = 2048 ,
charger_suspended = 256 ,
precharge = 128 , /* trickle on low bat voltage */
cc_cv_charge = 64 , /* normal charge */
ntc_pause = 32 ,
timer_term = 16 ,
c_over_x_term = 8 , /* battery is full */
max_charge_time_fault = 4 ,
bat_missing_fault = 2 ,
bat_short_fault = 1
} ;
/* Individual bits are mutually exclusive. Only active in charging states.*/
enum ltc4162l_charge_status {
ilim_reg_active = 32 ,
thermal_reg_active = 16 ,
vin_uvcl_active = 8 ,
iin_limit_active = 4 ,
constant_current = 2 ,
constant_voltage = 1 ,
charger_off = 0
} ;
/* Magic number to write to ARM_SHIP_MODE register */
# define LTC4162L_ARM_SHIP_MODE_MAGIC 21325
struct ltc4162l_info {
struct i2c_client * client ;
struct regmap * regmap ;
struct power_supply * charger ;
u32 rsnsb ; /* Series resistor that sets charge current, microOhm */
u32 rsnsi ; /* Series resistor to measure input current, microOhm */
u8 cell_count ; /* Number of connected cells, 0 while unknown */
} ;
static u8 ltc4162l_get_cell_count ( struct ltc4162l_info * info )
{
int ret ;
unsigned int val ;
/* Once read successfully */
if ( info - > cell_count )
return info - > cell_count ;
ret = regmap_read ( info - > regmap , LTC4162L_CHEM_CELLS_REG , & val ) ;
if ( ret )
return 0 ;
/* Lower 4 bits is the cell count, or 0 if the chip doesn't know yet */
val & = 0x0f ;
if ( ! val )
return 0 ;
/* Once determined, keep the value */
info - > cell_count = val ;
return val ;
} ;
/* Convert enum value to POWER_SUPPLY_STATUS value */
static int ltc4162l_state_decode ( enum ltc4162l_state value )
{
switch ( value ) {
case precharge :
case cc_cv_charge :
return POWER_SUPPLY_STATUS_CHARGING ;
case c_over_x_term :
return POWER_SUPPLY_STATUS_FULL ;
case bat_missing_fault :
case bat_short_fault :
return POWER_SUPPLY_STATUS_UNKNOWN ;
default :
return POWER_SUPPLY_STATUS_NOT_CHARGING ;
}
} ;
static int ltc4162l_get_status ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CHARGER_STATE , & regval ) ;
if ( ret ) {
dev_err ( & info - > client - > dev , " Failed to read CHARGER_STATE \n " ) ;
return ret ;
}
val - > intval = ltc4162l_state_decode ( regval ) ;
return 0 ;
}
static int ltc4162l_charge_status_decode ( enum ltc4162l_charge_status value )
{
if ( ! value )
return POWER_SUPPLY_CHARGE_TYPE_NONE ;
/* constant voltage/current and input_current limit are "fast" modes */
if ( value < = iin_limit_active )
return POWER_SUPPLY_CHARGE_TYPE_FAST ;
/* Anything that's not fast we'll return as trickle */
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE ;
}
static int ltc4162l_get_charge_type ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CHARGE_STATUS , & regval ) ;
if ( ret )
return ret ;
val - > intval = ltc4162l_charge_status_decode ( regval ) ;
return 0 ;
}
static int ltc4162l_state_to_health ( enum ltc4162l_state value )
{
switch ( value ) {
case ntc_pause :
return POWER_SUPPLY_HEALTH_OVERHEAT ;
case timer_term :
return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE ;
case max_charge_time_fault :
return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE ;
case bat_missing_fault :
return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
case bat_short_fault :
return POWER_SUPPLY_HEALTH_DEAD ;
default :
return POWER_SUPPLY_HEALTH_GOOD ;
}
}
static int ltc4162l_get_health ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CHARGER_STATE , & regval ) ;
if ( ret )
return ret ;
val - > intval = ltc4162l_state_to_health ( regval ) ;
return 0 ;
}
static int ltc4162l_get_online ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_SYSTEM_STATUS_REG , & regval ) ;
if ( ret )
return ret ;
/* BIT(2) indicates if input voltage is sufficient to charge */
val - > intval = ! ! ( regval & BIT ( 2 ) ) ;
return 0 ;
}
static int ltc4162l_get_vbat ( struct ltc4162l_info * info ,
unsigned int reg ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , reg , & regval ) ;
if ( ret )
return ret ;
/* cell_count × 192.4μV/LSB */
regval * = 1924 ;
regval * = ltc4162l_get_cell_count ( info ) ;
regval / = 10 ;
val - > intval = regval ;
return 0 ;
}
static int ltc4162l_get_ibat ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_IBAT , & regval ) ;
if ( ret )
return ret ;
/* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */
ret = ( s16 ) ( regval & 0xFFFF ) ;
val - > intval = 100 * mult_frac ( ret , 14660 , ( int ) info - > rsnsb ) ;
return 0 ;
}
static int ltc4162l_get_input_voltage ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_VIN , & regval ) ;
if ( ret )
return ret ;
/* 1.649mV/LSB */
val - > intval = regval * 1694 ;
return 0 ;
}
static int ltc4162l_get_input_current ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_IIN , & regval ) ;
if ( ret )
return ret ;
/* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */
ret = ( s16 ) ( regval & 0xFFFF ) ;
ret * = 14660 ;
ret / = info - > rsnsi ;
ret * = 100 ;
val - > intval = ret ;
return 0 ;
}
static int ltc4162l_get_icharge ( struct ltc4162l_info * info ,
unsigned int reg ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , reg , & regval ) ;
if ( ret )
return ret ;
regval & = BIT ( 6 ) - 1 ; /* Only the lower 5 bits */
/* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */
+ + regval ;
val - > intval = 10000u * mult_frac ( regval , 100000u , info - > rsnsb ) ;
return 0 ;
}
static int ltc4162l_set_icharge ( struct ltc4162l_info * info ,
unsigned int reg ,
unsigned int value )
{
value = mult_frac ( value , info - > rsnsb , 100000u ) ;
value / = 10000u ;
/* Round to lowest possible */
if ( value )
- - value ;
if ( value > 31 )
return - EINVAL ;
return regmap_write ( info - > regmap , reg , value ) ;
}
static int ltc4162l_get_vcharge ( struct ltc4162l_info * info ,
unsigned int reg ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
u32 voltage ;
ret = regmap_read ( info - > regmap , reg , & regval ) ;
if ( ret )
return ret ;
regval & = BIT ( 6 ) - 1 ; /* Only the lower 5 bits */
/*
* charge voltage setting can be computed from
* cell_count × ( vcharge_setting × 12.5 mV + 3.8125 V )
* where vcharge_setting ranges from 0 to 31 ( 4.2 V max ) .
*/
voltage = 3812500 + ( regval * 12500 ) ;
voltage * = ltc4162l_get_cell_count ( info ) ;
val - > intval = voltage ;
return 0 ;
}
static int ltc4162l_set_vcharge ( struct ltc4162l_info * info ,
unsigned int reg ,
unsigned int value )
{
u8 cell_count = ltc4162l_get_cell_count ( info ) ;
if ( ! cell_count )
return - EBUSY ; /* Not available yet, try again later */
value / = cell_count ;
if ( value < 3812500 )
return - EINVAL ;
value - = 3812500 ;
value / = 12500 ;
if ( value > 31 )
return - EINVAL ;
return regmap_write ( info - > regmap , reg , value ) ;
}
static int ltc4162l_get_iin_limit_dac ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_IIN_LIMIT_DAC , & regval ) ;
if ( ret )
return ret ;
regval & = BIT ( 6 ) - 1 ; /* Only 6 bits */
/* (iin_limit_dac + 1) × 500μV / RSNSI */
+ + regval ;
regval * = 5000000u ;
regval / = info - > rsnsi ;
val - > intval = 100u * regval ;
return 0 ;
}
static int ltc4162l_set_iin_limit ( struct ltc4162l_info * info ,
unsigned int value )
{
unsigned int regval ;
regval = mult_frac ( value , info - > rsnsi , 50000u ) ;
regval / = 10000u ;
if ( regval )
- - regval ;
if ( regval > 63 )
regval = 63 ;
return regmap_write ( info - > regmap , LTC4162L_IIN_LIMIT_TARGET , regval ) ;
}
static int ltc4162l_get_die_temp ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_DIE_TEMPERATURE , & regval ) ;
if ( ret )
return ret ;
/* die_temp × 0.0215°C/LSB - 264.4°C */
ret = ( s16 ) ( regval & 0xFFFF ) ;
ret * = 215 ;
ret / = 100 ; /* Centidegrees scale */
ret - = 26440 ;
val - > intval = ret ;
return 0 ;
}
static int ltc4162l_get_term_current ( struct ltc4162l_info * info ,
union power_supply_propval * val )
{
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CHARGER_CONFIG_BITS , & regval ) ;
if ( ret )
return ret ;
/* Check if C_OVER_X_THRESHOLD is enabled */
if ( ! ( regval & BIT ( 2 ) ) ) {
val - > intval = 0 ;
return 0 ;
}
ret = regmap_read ( info - > regmap , LTC4162L_C_OVER_X_THRESHOLD , & regval ) ;
if ( ret )
return ret ;
/* 1.466μV / RSNSB amperes/LSB */
regval * = 14660u ;
regval / = info - > rsnsb ;
val - > intval = 100 * regval ;
return 0 ;
}
static int ltc4162l_set_term_current ( struct ltc4162l_info * info ,
unsigned int value )
{
int ret ;
unsigned int regval ;
if ( ! value ) {
/* Disable en_c_over_x_term when set to zero */
return regmap_update_bits ( info - > regmap ,
LTC4162L_CHARGER_CONFIG_BITS ,
BIT ( 2 ) , 0 ) ;
}
regval = mult_frac ( value , info - > rsnsb , 14660u ) ;
regval / = 100u ;
ret = regmap_write ( info - > regmap , LTC4162L_C_OVER_X_THRESHOLD , regval ) ;
if ( ret )
return ret ;
/* Set en_c_over_x_term after changing the threshold value */
return regmap_update_bits ( info - > regmap , LTC4162L_CHARGER_CONFIG_BITS ,
BIT ( 2 ) , BIT ( 2 ) ) ;
}
/* Custom properties */
static const char * const ltc4162l_charge_status_name [ ] = {
" ilim_reg_active " , /* 32 */
" thermal_reg_active " ,
" vin_uvcl_active " ,
" iin_limit_active " ,
" constant_current " ,
" constant_voltage " ,
" charger_off " /* 0 */
} ;
static ssize_t charge_status_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
const char * result = ltc4162l_charge_status_name [
ARRAY_SIZE ( ltc4162l_charge_status_name ) - 1 ] ;
unsigned int regval ;
unsigned int mask ;
unsigned int index ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CHARGE_STATUS , & regval ) ;
if ( ret )
return ret ;
/* Only one bit is set according to datasheet, let's be safe here */
for ( mask = 32 , index = 0 ; mask ! = 0 ; mask > > = 1 , + + index ) {
if ( regval & mask ) {
result = ltc4162l_charge_status_name [ index ] ;
break ;
}
}
return sprintf ( buf , " %s \n " , result ) ;
}
static DEVICE_ATTR_RO ( charge_status ) ;
static ssize_t vbat_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
union power_supply_propval val ;
int ret ;
ret = ltc4162l_get_vbat ( info , LTC4162L_VBAT , & val ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %d \n " , val . intval ) ;
}
static DEVICE_ATTR_RO ( vbat ) ;
static ssize_t vbat_avg_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
union power_supply_propval val ;
int ret ;
ret = ltc4162l_get_vbat ( info , LTC4162L_VBAT_FILT , & val ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %d \n " , val . intval ) ;
}
static DEVICE_ATTR_RO ( vbat_avg ) ;
static ssize_t ibat_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
union power_supply_propval val ;
int ret ;
ret = ltc4162l_get_ibat ( info , & val ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %d \n " , val . intval ) ;
}
static DEVICE_ATTR_RO ( ibat ) ;
static ssize_t force_telemetry_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_CONFIG_BITS_REG , & regval ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %u \n " , regval & BIT ( 2 ) ? 1 : 0 ) ;
}
static ssize_t force_telemetry_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf ,
size_t count )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
int ret ;
unsigned int value ;
ret = kstrtouint ( buf , 0 , & value ) ;
if ( ret < 0 )
return ret ;
ret = regmap_update_bits ( info - > regmap , LTC4162L_CONFIG_BITS_REG ,
BIT ( 2 ) , value ? BIT ( 2 ) : 0 ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_RW ( force_telemetry ) ;
static ssize_t arm_ship_mode_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
unsigned int regval ;
int ret ;
ret = regmap_read ( info - > regmap , LTC4162L_ARM_SHIP_MODE , & regval ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %u \n " ,
regval = = LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0 ) ;
}
static ssize_t arm_ship_mode_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf ,
size_t count )
{
struct power_supply * psy = to_power_supply ( dev ) ;
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
int ret ;
unsigned int value ;
ret = kstrtouint ( buf , 0 , & value ) ;
if ( ret < 0 )
return ret ;
ret = regmap_write ( info - > regmap , LTC4162L_ARM_SHIP_MODE ,
value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0 ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_RW ( arm_ship_mode ) ;
static struct attribute * ltc4162l_sysfs_entries [ ] = {
& dev_attr_charge_status . attr ,
& dev_attr_ibat . attr ,
& dev_attr_vbat . attr ,
& dev_attr_vbat_avg . attr ,
& dev_attr_force_telemetry . attr ,
& dev_attr_arm_ship_mode . attr ,
NULL ,
} ;
2021-01-13 21:32:42 +01:00
static const struct attribute_group ltc4162l_attr_group = {
2021-01-07 15:32:48 +01:00
. name = NULL , /* put in device directory */
. attrs = ltc4162l_sysfs_entries ,
} ;
static const struct attribute_group * ltc4162l_attr_groups [ ] = {
& ltc4162l_attr_group ,
NULL ,
} ;
static int ltc4162l_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
return ltc4162l_get_status ( info , val ) ;
case POWER_SUPPLY_PROP_CHARGE_TYPE :
return ltc4162l_get_charge_type ( info , val ) ;
case POWER_SUPPLY_PROP_HEALTH :
return ltc4162l_get_health ( info , val ) ;
case POWER_SUPPLY_PROP_ONLINE :
return ltc4162l_get_online ( info , val ) ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
return ltc4162l_get_input_voltage ( info , val ) ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
return ltc4162l_get_input_current ( info , val ) ;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT :
return ltc4162l_get_icharge ( info ,
LTC4162L_ICHARGE_DAC , val ) ;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
return ltc4162l_get_icharge ( info ,
LTC4162L_CHARGE_CURRENT_SETTING , val ) ;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE :
return ltc4162l_get_vcharge ( info ,
LTC4162L_VCHARGE_DAC , val ) ;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX :
return ltc4162l_get_vcharge ( info ,
LTC4162L_VCHARGE_SETTING , val ) ;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT :
return ltc4162l_get_iin_limit_dac ( info , val ) ;
case POWER_SUPPLY_PROP_TEMP :
return ltc4162l_get_die_temp ( info , val ) ;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT :
return ltc4162l_get_term_current ( info , val ) ;
default :
return - EINVAL ;
}
}
static int ltc4162l_set_property ( struct power_supply * psy ,
enum power_supply_property psp ,
const union power_supply_propval * val )
{
struct ltc4162l_info * info = power_supply_get_drvdata ( psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
return ltc4162l_set_icharge ( info ,
LTC4162L_CHARGE_CURRENT_SETTING , val - > intval ) ;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX :
return ltc4162l_set_vcharge ( info ,
LTC4162L_VCHARGE_SETTING , val - > intval ) ;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT :
return ltc4162l_set_iin_limit ( info , val - > intval ) ;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT :
return ltc4162l_set_term_current ( info , val - > intval ) ;
default :
return - EINVAL ;
}
}
static int ltc4162l_property_is_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX :
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT :
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT :
return 1 ;
default :
return 0 ;
}
}
/* Charger power supply property routines */
static enum power_supply_property ltc4162l_properties [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_CHARGE_TYPE ,
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX ,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE ,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX ,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT ,
POWER_SUPPLY_PROP_TEMP ,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT ,
} ;
static const struct power_supply_desc ltc4162l_desc = {
. name = " ltc4162-l " ,
. type = POWER_SUPPLY_TYPE_MAINS ,
. properties = ltc4162l_properties ,
. num_properties = ARRAY_SIZE ( ltc4162l_properties ) ,
. get_property = ltc4162l_get_property ,
. set_property = ltc4162l_set_property ,
. property_is_writeable = ltc4162l_property_is_writeable ,
} ;
static bool ltc4162l_is_writeable_reg ( struct device * dev , unsigned int reg )
{
/* all registers up to this one are writeable */
if ( reg < = LTC4162L_CHARGER_CONFIG_BITS )
return true ;
/* The ALERTS registers can be written to clear alerts */
if ( reg > = LTC4162L_LIMIT_ALERTS_REG & &
reg < = LTC4162L_CHARGE_STATUS_ALERTS_REG )
return true ;
return false ;
}
static bool ltc4162l_is_volatile_reg ( struct device * dev , unsigned int reg )
{
/* all registers after this one are read-only status registers */
return reg > LTC4162L_CHARGER_CONFIG_BITS ;
}
static const struct regmap_config ltc4162l_regmap_config = {
. reg_bits = 8 ,
. val_bits = 16 ,
. val_format_endian = REGMAP_ENDIAN_LITTLE ,
. writeable_reg = ltc4162l_is_writeable_reg ,
. volatile_reg = ltc4162l_is_volatile_reg ,
. max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC ,
. cache_type = REGCACHE_RBTREE ,
} ;
static void ltc4162l_clear_interrupts ( struct ltc4162l_info * info )
{
/* Acknowledge interrupt to chip by clearing all events */
regmap_write ( info - > regmap , LTC4162L_LIMIT_ALERTS_REG , 0 ) ;
regmap_write ( info - > regmap , LTC4162L_CHARGER_STATE_ALERTS_REG , 0 ) ;
regmap_write ( info - > regmap , LTC4162L_CHARGE_STATUS_ALERTS_REG , 0 ) ;
}
static int ltc4162l_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct i2c_adapter * adapter = client - > adapter ;
struct device * dev = & client - > dev ;
struct ltc4162l_info * info ;
struct power_supply_config ltc4162l_config = { } ;
u32 value ;
int ret ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_WORD_DATA ) ) {
dev_err ( dev , " No support for SMBUS_WORD_DATA \n " ) ;
return - ENODEV ;
}
info = devm_kzalloc ( dev , sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info )
return - ENOMEM ;
info - > client = client ;
i2c_set_clientdata ( client , info ) ;
info - > regmap = devm_regmap_init_i2c ( client , & ltc4162l_regmap_config ) ;
if ( IS_ERR ( info - > regmap ) ) {
dev_err ( dev , " Failed to initialize register map \n " ) ;
return PTR_ERR ( info - > regmap ) ;
}
ret = device_property_read_u32 ( dev , " lltc,rsnsb-micro-ohms " ,
& info - > rsnsb ) ;
if ( ret ) {
dev_err ( dev , " Missing lltc,rsnsb-micro-ohms property \n " ) ;
return ret ;
}
if ( ! info - > rsnsb )
return - EINVAL ;
ret = device_property_read_u32 ( dev , " lltc,rsnsi-micro-ohms " ,
& info - > rsnsi ) ;
if ( ret ) {
dev_err ( dev , " Missing lltc,rsnsi-micro-ohms property \n " ) ;
return ret ;
}
if ( ! info - > rsnsi )
return - EINVAL ;
if ( ! device_property_read_u32 ( dev , " lltc,cell-count " , & value ) )
info - > cell_count = value ;
ltc4162l_config . of_node = dev - > of_node ;
ltc4162l_config . drv_data = info ;
ltc4162l_config . attr_grp = ltc4162l_attr_groups ;
info - > charger = devm_power_supply_register ( dev , & ltc4162l_desc ,
& ltc4162l_config ) ;
if ( IS_ERR ( info - > charger ) ) {
dev_err ( dev , " Failed to register charger \n " ) ;
return PTR_ERR ( info - > charger ) ;
}
/* Disable the threshold alerts, we're not using them */
regmap_write ( info - > regmap , LTC4162L_EN_LIMIT_ALERTS_REG , 0 ) ;
/* Enable interrupts on all status changes */
regmap_write ( info - > regmap , LTC4162L_EN_CHARGER_STATE_ALERTS_REG ,
0x1fff ) ;
regmap_write ( info - > regmap , LTC4162L_EN_CHARGE_STATUS_ALERTS_REG , 0x1f ) ;
ltc4162l_clear_interrupts ( info ) ;
return 0 ;
}
static void ltc4162l_alert ( struct i2c_client * client ,
enum i2c_alert_protocol type , unsigned int flag )
{
struct ltc4162l_info * info = i2c_get_clientdata ( client ) ;
if ( type ! = I2C_PROTOCOL_SMBUS_ALERT )
return ;
ltc4162l_clear_interrupts ( info ) ;
power_supply_changed ( info - > charger ) ;
}
static const struct i2c_device_id ltc4162l_i2c_id_table [ ] = {
{ " ltc4162-l " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , ltc4162l_i2c_id_table ) ;
static const struct of_device_id ltc4162l_of_match [ ] = {
{ . compatible = " lltc,ltc4162-l " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ltc4162l_of_match ) ;
static struct i2c_driver ltc4162l_driver = {
. probe = ltc4162l_probe ,
. alert = ltc4162l_alert ,
. id_table = ltc4162l_i2c_id_table ,
. driver = {
. name = " ltc4162-l-charger " ,
. of_match_table = of_match_ptr ( ltc4162l_of_match ) ,
} ,
} ;
module_i2c_driver ( ltc4162l_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Mike Looijmans <mike.looijmans@topic.nl> " ) ;
MODULE_DESCRIPTION ( " LTC4162-L charger driver " ) ;