2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-12-20 18:31:13 +03:00
/*
* Copyright ( c ) 2016 , Prodys S . L .
*
* This adds support for sbs - charger compilant chips as defined here :
* http : //sbs-forum.org/specs/sbc110.pdf
*
* Implemetation based on sbs - battery . c
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/err.h>
# include <linux/power_supply.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/gpio.h>
# include <linux/regmap.h>
# include <linux/of_gpio.h>
# include <linux/bitops.h>
# define SBS_CHARGER_REG_SPEC_INFO 0x11
# define SBS_CHARGER_REG_STATUS 0x13
# define SBS_CHARGER_REG_ALARM_WARNING 0x16
# define SBS_CHARGER_STATUS_CHARGE_INHIBITED BIT(1)
# define SBS_CHARGER_STATUS_RES_COLD BIT(9)
# define SBS_CHARGER_STATUS_RES_HOT BIT(10)
# define SBS_CHARGER_STATUS_BATTERY_PRESENT BIT(14)
# define SBS_CHARGER_STATUS_AC_PRESENT BIT(15)
# define SBS_CHARGER_POLL_TIME 500
struct sbs_info {
struct i2c_client * client ;
struct power_supply * power_supply ;
struct regmap * regmap ;
struct delayed_work work ;
unsigned int last_state ;
} ;
static int sbs_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct sbs_info * chip = power_supply_get_drvdata ( psy ) ;
unsigned int reg ;
reg = chip - > last_state ;
switch ( psp ) {
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = ! ! ( reg & SBS_CHARGER_STATUS_BATTERY_PRESENT ) ;
break ;
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = ! ! ( reg & SBS_CHARGER_STATUS_AC_PRESENT ) ;
break ;
case POWER_SUPPLY_PROP_STATUS :
val - > intval = POWER_SUPPLY_STATUS_UNKNOWN ;
if ( ! ( reg & SBS_CHARGER_STATUS_BATTERY_PRESENT ) )
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
else if ( reg & SBS_CHARGER_STATUS_AC_PRESENT & &
! ( reg & SBS_CHARGER_STATUS_CHARGE_INHIBITED ) )
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
if ( reg & SBS_CHARGER_STATUS_RES_COLD )
val - > intval = POWER_SUPPLY_HEALTH_COLD ;
if ( reg & SBS_CHARGER_STATUS_RES_HOT )
val - > intval = POWER_SUPPLY_HEALTH_OVERHEAT ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int sbs_check_state ( struct sbs_info * chip )
{
unsigned int reg ;
int ret ;
ret = regmap_read ( chip - > regmap , SBS_CHARGER_REG_STATUS , & reg ) ;
if ( ! ret & & reg ! = chip - > last_state ) {
chip - > last_state = reg ;
power_supply_changed ( chip - > power_supply ) ;
return 1 ;
}
return 0 ;
}
static void sbs_delayed_work ( struct work_struct * work )
{
struct sbs_info * chip = container_of ( work , struct sbs_info , work . work ) ;
sbs_check_state ( chip ) ;
schedule_delayed_work ( & chip - > work ,
msecs_to_jiffies ( SBS_CHARGER_POLL_TIME ) ) ;
}
static irqreturn_t sbs_irq_thread ( int irq , void * data )
{
struct sbs_info * chip = data ;
int ret ;
ret = sbs_check_state ( chip ) ;
return ret ? IRQ_HANDLED : IRQ_NONE ;
}
static enum power_supply_property sbs_properties [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_HEALTH ,
} ;
static bool sbs_readable_reg ( struct device * dev , unsigned int reg )
{
2017-02-15 18:46:21 +03:00
return reg > = SBS_CHARGER_REG_SPEC_INFO ;
2016-12-20 18:31:13 +03:00
}
static bool sbs_volatile_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case SBS_CHARGER_REG_STATUS :
return true ;
}
return false ;
}
static const struct regmap_config sbs_regmap = {
. reg_bits = 8 ,
. val_bits = 16 ,
. max_register = SBS_CHARGER_REG_ALARM_WARNING ,
. readable_reg = sbs_readable_reg ,
. volatile_reg = sbs_volatile_reg ,
. val_format_endian = REGMAP_ENDIAN_LITTLE , /* since based on SMBus */
} ;
static const struct power_supply_desc sbs_desc = {
. name = " sbs-charger " ,
. type = POWER_SUPPLY_TYPE_MAINS ,
. properties = sbs_properties ,
. num_properties = ARRAY_SIZE ( sbs_properties ) ,
. get_property = sbs_get_property ,
} ;
static int sbs_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct power_supply_config psy_cfg = { } ;
struct sbs_info * chip ;
int ret , val ;
chip = devm_kzalloc ( & client - > dev , sizeof ( struct sbs_info ) , GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
chip - > client = client ;
psy_cfg . of_node = client - > dev . of_node ;
psy_cfg . drv_data = chip ;
i2c_set_clientdata ( client , chip ) ;
chip - > regmap = devm_regmap_init_i2c ( client , & sbs_regmap ) ;
if ( IS_ERR ( chip - > regmap ) )
return PTR_ERR ( chip - > regmap ) ;
/*
* Before we register , we need to make sure we can actually talk
* to the battery .
*/
ret = regmap_read ( chip - > regmap , SBS_CHARGER_REG_STATUS , & val ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to get device status \n " ) ;
return ret ;
}
chip - > last_state = val ;
chip - > power_supply = devm_power_supply_register ( & client - > dev , & sbs_desc ,
& psy_cfg ) ;
if ( IS_ERR ( chip - > power_supply ) ) {
dev_err ( & client - > dev , " Failed to register power supply \n " ) ;
return PTR_ERR ( chip - > power_supply ) ;
}
/*
* The sbs - charger spec doesn ' t impose the use of an interrupt . So in
* the case it wasn ' t provided we use polling in order get the charger ' s
* status .
*/
if ( client - > irq ) {
ret = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL , sbs_irq_thread ,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT ,
dev_name ( & client - > dev ) , chip ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to request irq, %d \n " , ret ) ;
return ret ;
}
} else {
INIT_DELAYED_WORK ( & chip - > work , sbs_delayed_work ) ;
schedule_delayed_work ( & chip - > work ,
msecs_to_jiffies ( SBS_CHARGER_POLL_TIME ) ) ;
}
dev_info ( & client - > dev ,
" %s: smart charger device registered \n " , client - > name ) ;
return 0 ;
}
static int sbs_remove ( struct i2c_client * client )
{
struct sbs_info * chip = i2c_get_clientdata ( client ) ;
cancel_delayed_work_sync ( & chip - > work ) ;
return 0 ;
}
# ifdef CONFIG_OF
static const struct of_device_id sbs_dt_ids [ ] = {
{ . compatible = " sbs,sbs-charger " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , sbs_dt_ids ) ;
# endif
static const struct i2c_device_id sbs_id [ ] = {
{ " sbs-charger " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , sbs_id ) ;
static struct i2c_driver sbs_driver = {
. probe = sbs_probe ,
. remove = sbs_remove ,
. id_table = sbs_id ,
. driver = {
. name = " sbs-charger " ,
. of_match_table = of_match_ptr ( sbs_dt_ids ) ,
} ,
} ;
module_i2c_driver ( sbs_driver ) ;
MODULE_AUTHOR ( " Nicolas Saenz Julienne <nicolassaenzj@gmail.com> " ) ;
MODULE_DESCRIPTION ( " SBS smart charger driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;