2017-06-15 12:53:17 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* System Control and Management Interface ( SCMI ) based hwmon sensor driver
*
2021-03-16 15:48:49 +03:00
* Copyright ( C ) 2018 - 2021 ARM Ltd .
2017-06-15 12:53:17 +03:00
* 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>
2021-03-16 15:48:49 +03:00
static const struct scmi_sensor_proto_ops * sensor_ops ;
2017-06-15 12:53:17 +03:00
struct scmi_sensors {
2021-03-16 15:48:49 +03:00
const struct scmi_protocol_handle * ph ;
2017-06-15 12:53:17 +03:00
const struct scmi_sensor_info * * info [ hwmon_max ] ;
} ;
2019-05-08 21:46:35 +03:00
static inline u64 __pow10 ( u8 x )
{
u64 r = 1 ;
while ( x - - )
r * = 10 ;
return r ;
}
static int scmi_hwmon_scale ( const struct scmi_sensor_info * sensor , u64 * value )
{
2020-11-19 20:49:03 +03:00
int scale = sensor - > scale ;
2019-05-08 21:46:35 +03:00
u64 f ;
switch ( sensor - > type ) {
case TEMPERATURE_C :
case VOLTAGE :
case CURRENT :
scale + = 3 ;
break ;
case POWER :
case ENERGY :
scale + = 6 ;
break ;
default :
break ;
}
if ( scale = = 0 )
return 0 ;
if ( abs ( scale ) > 19 )
return - E2BIG ;
f = __pow10 ( abs ( scale ) ) ;
if ( scale > 0 )
* value * = f ;
else
* value = div64_u64 ( * value , f ) ;
return 0 ;
}
2017-06-15 12:53:17 +03:00
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 ) ;
sensor = * ( scmi_sensors - > info [ type ] + channel ) ;
2021-03-16 15:48:49 +03:00
ret = sensor_ops - > reading_get ( scmi_sensors - > ph , sensor - > id , & value ) ;
2019-05-08 21:46:35 +03:00
if ( ret )
return ret ;
ret = scmi_hwmon_scale ( sensor , & value ) ;
2017-06-15 12:53:17 +03:00
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 ,
} ;
2020-07-15 15:13:38 +03:00
static u32 hwmon_attributes [ hwmon_max ] = {
2017-06-15 12:53:17 +03:00
[ 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 ;
2021-03-16 15:48:49 +03:00
struct scmi_protocol_handle * ph ;
2017-06-15 12:53:17 +03:00
2021-03-16 15:48:49 +03:00
if ( ! handle )
2017-06-15 12:53:17 +03:00
return - ENODEV ;
2021-03-16 15:48:49 +03:00
sensor_ops = handle - > devm_protocol_get ( sdev , SCMI_PROTOCOL_SENSOR , & ph ) ;
if ( IS_ERR ( sensor_ops ) )
return PTR_ERR ( sensor_ops ) ;
nr_sensors = sensor_ops - > count_get ( ph ) ;
2017-06-15 12:53:17 +03:00
if ( ! nr_sensors )
return - EIO ;
scmi_sensors = devm_kzalloc ( dev , sizeof ( * scmi_sensors ) , GFP_KERNEL ) ;
if ( ! scmi_sensors )
return - ENOMEM ;
2021-03-16 15:48:49 +03:00
scmi_sensors - > ph = ph ;
2017-06-15 12:53:17 +03:00
for ( i = 0 ; i < nr_sensors ; i + + ) {
2021-03-16 15:48:49 +03:00
sensor = sensor_ops - > info_get ( ph , i ) ;
2017-06-15 12:53:17 +03:00
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 ;
}
}
2020-08-25 07:56:08 +03:00
if ( nr_count [ hwmon_temp ] ) {
nr_count [ hwmon_chip ] + + ;
nr_types + + ;
}
2017-06-15 12:53:17 +03:00
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 - - ) {
2021-03-16 15:48:49 +03:00
sensor = sensor_ops - > info_get ( ph , i ) ;
2017-06-15 12:53:17 +03:00
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 [ ] = {
2019-11-06 20:57:30 +03:00
{ SCMI_PROTOCOL_SENSOR , " hwmon " } ,
2017-06-15 12:53:17 +03:00
{ } ,
} ;
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 " ) ;