2019-04-18 20:24:04 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Battery driver for the Ingenic JZ47xx SoCs
* Copyright ( c ) 2019 Artur Rojek < contact @ artur - rojek . eu >
*
* based on drivers / power / supply / jz4740 - battery . c
*/
# include <linux/iio/consumer.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/property.h>
struct ingenic_battery {
struct device * dev ;
struct iio_channel * channel ;
struct power_supply_desc desc ;
struct power_supply * battery ;
2021-12-15 02:01:18 +01:00
struct power_supply_battery_info * info ;
2019-04-18 20:24:04 +02:00
} ;
static int ingenic_battery_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct ingenic_battery * bat = power_supply_get_drvdata ( psy ) ;
2021-12-15 02:01:18 +01:00
struct power_supply_battery_info * info = bat - > info ;
2019-04-18 20:24:04 +02:00
int ret ;
switch ( psp ) {
case POWER_SUPPLY_PROP_HEALTH :
ret = iio_read_channel_processed ( bat - > channel , & val - > intval ) ;
val - > intval * = 1000 ;
if ( val - > intval < info - > voltage_min_design_uv )
val - > intval = POWER_SUPPLY_HEALTH_DEAD ;
else if ( val - > intval > info - > voltage_max_design_uv )
val - > intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE ;
else
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
return ret ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
ret = iio_read_channel_processed ( bat - > channel , & val - > intval ) ;
val - > intval * = 1000 ;
return ret ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = info - > voltage_min_design_uv ;
return 0 ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN :
val - > intval = info - > voltage_max_design_uv ;
return 0 ;
default :
return - EINVAL ;
2020-11-01 06:09:10 -08:00
}
2019-04-18 20:24:04 +02:00
}
/* Set the most appropriate IIO channel voltage reference scale
* based on the battery ' s max voltage .
*/
static int ingenic_battery_set_scale ( struct ingenic_battery * bat )
{
const int * scale_raw ;
int scale_len , scale_type , best_idx = - 1 , best_mV , max_raw , i , ret ;
u64 max_mV ;
ret = iio_read_max_channel_raw ( bat - > channel , & max_raw ) ;
if ( ret ) {
dev_err ( bat - > dev , " Unable to read max raw channel value \n " ) ;
return ret ;
}
ret = iio_read_avail_channel_attribute ( bat - > channel , & scale_raw ,
& scale_type , & scale_len ,
IIO_CHAN_INFO_SCALE ) ;
if ( ret < 0 ) {
dev_err ( bat - > dev , " Unable to read channel avail scale \n " ) ;
return ret ;
}
if ( ret ! = IIO_AVAIL_LIST | | scale_type ! = IIO_VAL_FRACTIONAL_LOG2 )
return - EINVAL ;
2021-12-15 02:01:18 +01:00
max_mV = bat - > info - > voltage_max_design_uv / 1000 ;
2019-04-18 20:24:04 +02:00
for ( i = 0 ; i < scale_len ; i + = 2 ) {
u64 scale_mV = ( max_raw * scale_raw [ i ] ) > > scale_raw [ i + 1 ] ;
if ( scale_mV < max_mV )
continue ;
if ( best_idx > = 0 & & scale_mV > best_mV )
continue ;
best_mV = scale_mV ;
best_idx = i ;
}
if ( best_idx < 0 ) {
dev_err ( bat - > dev , " Unable to find matching voltage scale \n " ) ;
return - EINVAL ;
}
2019-11-16 14:56:19 +01:00
/* Only set scale if there is more than one (fractional) entry */
if ( scale_len > 2 ) {
ret = iio_write_channel_attribute ( bat - > channel ,
scale_raw [ best_idx ] ,
scale_raw [ best_idx + 1 ] ,
IIO_CHAN_INFO_SCALE ) ;
if ( ret )
return ret ;
}
return 0 ;
2019-04-18 20:24:04 +02:00
}
static enum power_supply_property ingenic_battery_properties [ ] = {
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
} ;
static int ingenic_battery_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct ingenic_battery * bat ;
struct power_supply_config psy_cfg = { } ;
struct power_supply_desc * desc ;
int ret ;
bat = devm_kzalloc ( dev , sizeof ( * bat ) , GFP_KERNEL ) ;
if ( ! bat )
return - ENOMEM ;
bat - > dev = dev ;
bat - > channel = devm_iio_channel_get ( dev , " battery " ) ;
if ( IS_ERR ( bat - > channel ) )
return PTR_ERR ( bat - > channel ) ;
desc = & bat - > desc ;
desc - > name = " jz-battery " ;
desc - > type = POWER_SUPPLY_TYPE_BATTERY ;
desc - > properties = ingenic_battery_properties ;
desc - > num_properties = ARRAY_SIZE ( ingenic_battery_properties ) ;
desc - > get_property = ingenic_battery_get_property ;
psy_cfg . drv_data = bat ;
psy_cfg . of_node = dev - > of_node ;
bat - > battery = devm_power_supply_register ( dev , desc , & psy_cfg ) ;
2020-08-26 16:48:57 +02:00
if ( IS_ERR ( bat - > battery ) )
return dev_err_probe ( dev , PTR_ERR ( bat - > battery ) ,
" Unable to register battery \n " ) ;
2019-04-18 20:24:04 +02:00
ret = power_supply_get_battery_info ( bat - > battery , & bat - > info ) ;
if ( ret ) {
dev_err ( dev , " Unable to get battery info: %d \n " , ret ) ;
return ret ;
}
2021-12-15 02:01:18 +01:00
if ( bat - > info - > voltage_min_design_uv < 0 ) {
2019-04-18 20:24:04 +02:00
dev_err ( dev , " Unable to get voltage min design \n " ) ;
2021-12-15 02:01:18 +01:00
return bat - > info - > voltage_min_design_uv ;
2019-04-18 20:24:04 +02:00
}
2021-12-15 02:01:18 +01:00
if ( bat - > info - > voltage_max_design_uv < 0 ) {
2019-04-18 20:24:04 +02:00
dev_err ( dev , " Unable to get voltage max design \n " ) ;
2021-12-15 02:01:18 +01:00
return bat - > info - > voltage_max_design_uv ;
2019-04-18 20:24:04 +02:00
}
return ingenic_battery_set_scale ( bat ) ;
}
# ifdef CONFIG_OF
static const struct of_device_id ingenic_battery_of_match [ ] = {
{ . compatible = " ingenic,jz4740-battery " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ingenic_battery_of_match ) ;
# endif
static struct platform_driver ingenic_battery_driver = {
. driver = {
. name = " ingenic-battery " ,
. of_match_table = of_match_ptr ( ingenic_battery_of_match ) ,
} ,
. probe = ingenic_battery_probe ,
} ;
module_platform_driver ( ingenic_battery_driver ) ;
MODULE_DESCRIPTION ( " Battery driver for Ingenic JZ47xx SoCs " ) ;
MODULE_AUTHOR ( " Artur Rojek <contact@artur-rojek.eu> " ) ;
MODULE_LICENSE ( " GPL " ) ;