2017-06-15 12:53:17 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface ( SCMI ) based hwmon sensor driver
*
* Copyright ( C ) 2018 ARM Ltd .
* Sudeep Holla < sudeep . holla @ arm . com >
*/
# include <linux/hwmon.h>
# include <linux/module.h>
# include <linux/scmi_protocol.h>
# include <linux/slab.h>
# include <linux/sysfs.h>
# include <linux/thermal.h>
struct scmi_sensors {
const struct scmi_handle * handle ;
const struct scmi_sensor_info * * info [ hwmon_max ] ;
} ;
static int scmi_hwmon_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
int ret ;
u64 value ;
const struct scmi_sensor_info * sensor ;
struct scmi_sensors * scmi_sensors = dev_get_drvdata ( dev ) ;
const struct scmi_handle * h = scmi_sensors - > handle ;
sensor = * ( scmi_sensors - > info [ type ] + channel ) ;
ret = h - > sensor_ops - > reading_get ( h , sensor - > id , false , & value ) ;
if ( ! ret )
* val = value ;
return ret ;
}
static int
scmi_hwmon_read_string ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , const char * * str )
{
const struct scmi_sensor_info * sensor ;
struct scmi_sensors * scmi_sensors = dev_get_drvdata ( dev ) ;
sensor = * ( scmi_sensors - > info [ type ] + channel ) ;
* str = sensor - > name ;
return 0 ;
}
static umode_t
scmi_hwmon_is_visible ( const void * drvdata , enum hwmon_sensor_types type ,
u32 attr , int channel )
{
const struct scmi_sensor_info * sensor ;
const struct scmi_sensors * scmi_sensors = drvdata ;
sensor = * ( scmi_sensors - > info [ type ] + channel ) ;
2018-09-16 03:05:07 +03:00
if ( sensor )
2018-12-11 01:02:19 +03:00
return 0444 ;
2017-06-15 12:53:17 +03:00
return 0 ;
}
static const struct hwmon_ops scmi_hwmon_ops = {
. is_visible = scmi_hwmon_is_visible ,
. read = scmi_hwmon_read ,
. read_string = scmi_hwmon_read_string ,
} ;
static struct hwmon_chip_info scmi_chip_info = {
. ops = & scmi_hwmon_ops ,
. info = NULL ,
} ;
static int scmi_hwmon_add_chan_info ( struct hwmon_channel_info * scmi_hwmon_chan ,
struct device * dev , int num ,
enum hwmon_sensor_types type , u32 config )
{
int i ;
u32 * cfg = devm_kcalloc ( dev , num + 1 , sizeof ( * cfg ) , GFP_KERNEL ) ;
if ( ! cfg )
return - ENOMEM ;
scmi_hwmon_chan - > type = type ;
scmi_hwmon_chan - > config = cfg ;
for ( i = 0 ; i < num ; i + + , cfg + + )
* cfg = config ;
return 0 ;
}
static enum hwmon_sensor_types scmi_types [ ] = {
[ TEMPERATURE_C ] = hwmon_temp ,
[ VOLTAGE ] = hwmon_in ,
[ CURRENT ] = hwmon_curr ,
[ POWER ] = hwmon_power ,
[ ENERGY ] = hwmon_energy ,
} ;
static u32 hwmon_attributes [ ] = {
[ hwmon_chip ] = HWMON_C_REGISTER_TZ ,
[ hwmon_temp ] = HWMON_T_INPUT | HWMON_T_LABEL ,
[ hwmon_in ] = HWMON_I_INPUT | HWMON_I_LABEL ,
[ hwmon_curr ] = HWMON_C_INPUT | HWMON_C_LABEL ,
[ hwmon_power ] = HWMON_P_INPUT | HWMON_P_LABEL ,
[ hwmon_energy ] = HWMON_E_INPUT | HWMON_E_LABEL ,
} ;
static int scmi_hwmon_probe ( struct scmi_device * sdev )
{
int i , idx ;
u16 nr_sensors ;
enum hwmon_sensor_types type ;
struct scmi_sensors * scmi_sensors ;
const struct scmi_sensor_info * sensor ;
int nr_count [ hwmon_max ] = { 0 } , nr_types = 0 ;
const struct hwmon_chip_info * chip_info ;
struct device * hwdev , * dev = & sdev - > dev ;
struct hwmon_channel_info * scmi_hwmon_chan ;
const struct hwmon_channel_info * * ptr_scmi_ci ;
const struct scmi_handle * handle = sdev - > handle ;
if ( ! handle | | ! handle - > sensor_ops )
return - ENODEV ;
nr_sensors = handle - > sensor_ops - > count_get ( handle ) ;
if ( ! nr_sensors )
return - EIO ;
scmi_sensors = devm_kzalloc ( dev , sizeof ( * scmi_sensors ) , GFP_KERNEL ) ;
if ( ! scmi_sensors )
return - ENOMEM ;
scmi_sensors - > handle = handle ;
for ( i = 0 ; i < nr_sensors ; i + + ) {
sensor = handle - > sensor_ops - > info_get ( handle , i ) ;
if ( ! sensor )
2018-03-15 19:54:18 +03:00
return - EINVAL ;
2017-06-15 12:53:17 +03:00
switch ( sensor - > type ) {
case TEMPERATURE_C :
case VOLTAGE :
case CURRENT :
case POWER :
case ENERGY :
type = scmi_types [ sensor - > type ] ;
if ( ! nr_count [ type ] )
nr_types + + ;
nr_count [ type ] + + ;
break ;
}
}
if ( nr_count [ hwmon_temp ] )
nr_count [ hwmon_chip ] + + , nr_types + + ;
scmi_hwmon_chan = devm_kcalloc ( dev , nr_types , sizeof ( * scmi_hwmon_chan ) ,
GFP_KERNEL ) ;
if ( ! scmi_hwmon_chan )
return - ENOMEM ;
ptr_scmi_ci = devm_kcalloc ( dev , nr_types + 1 , sizeof ( * ptr_scmi_ci ) ,
GFP_KERNEL ) ;
if ( ! ptr_scmi_ci )
return - ENOMEM ;
scmi_chip_info . info = ptr_scmi_ci ;
chip_info = & scmi_chip_info ;
2018-04-06 18:30:47 +03:00
for ( type = 0 ; type < hwmon_max ; type + + ) {
if ( ! nr_count [ type ] )
continue ;
2017-06-15 12:53:17 +03:00
scmi_hwmon_add_chan_info ( scmi_hwmon_chan , dev , nr_count [ type ] ,
type , hwmon_attributes [ type ] ) ;
* ptr_scmi_ci + + = scmi_hwmon_chan + + ;
scmi_sensors - > info [ type ] =
devm_kcalloc ( dev , nr_count [ type ] ,
sizeof ( * scmi_sensors - > info ) , GFP_KERNEL ) ;
if ( ! scmi_sensors - > info [ type ] )
return - ENOMEM ;
}
for ( i = nr_sensors - 1 ; i > = 0 ; i - - ) {
sensor = handle - > sensor_ops - > info_get ( handle , i ) ;
if ( ! sensor )
continue ;
switch ( sensor - > type ) {
case TEMPERATURE_C :
case VOLTAGE :
case CURRENT :
case POWER :
case ENERGY :
type = scmi_types [ sensor - > type ] ;
idx = - - nr_count [ type ] ;
* ( scmi_sensors - > info [ type ] + idx ) = sensor ;
break ;
}
}
hwdev = devm_hwmon_device_register_with_info ( dev , " scmi_sensors " ,
scmi_sensors , chip_info ,
NULL ) ;
return PTR_ERR_OR_ZERO ( hwdev ) ;
}
static const struct scmi_device_id scmi_id_table [ ] = {
{ SCMI_PROTOCOL_SENSOR } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( scmi , scmi_id_table ) ;
static struct scmi_driver scmi_hwmon_drv = {
. name = " scmi-hwmon " ,
. probe = scmi_hwmon_probe ,
. id_table = scmi_id_table ,
} ;
module_scmi_driver ( scmi_hwmon_drv ) ;
MODULE_AUTHOR ( " Sudeep Holla <sudeep.holla@arm.com> " ) ;
MODULE_DESCRIPTION ( " ARM SCMI HWMON interface driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;