2015-05-21 17:08:45 +03:00
/*
* System Control and Power Interface ( SCPI ) based hwmon sensor driver
*
* Copyright ( C ) 2015 ARM Ltd .
* Punit Agrawal < punit . agrawal @ arm . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/hwmon.h>
# include <linux/module.h>
2017-05-30 12:05:07 +03:00
# include <linux/of_device.h>
2015-05-21 17:08:45 +03:00
# include <linux/platform_device.h>
# include <linux/scpi_protocol.h>
# include <linux/slab.h>
# include <linux/sysfs.h>
2015-06-24 21:03:16 +03:00
# include <linux/thermal.h>
2015-05-21 17:08:45 +03:00
struct sensor_data {
2017-05-30 12:05:07 +03:00
unsigned int scale ;
2015-05-21 17:08:45 +03:00
struct scpi_sensor_info info ;
struct device_attribute dev_attr_input ;
struct device_attribute dev_attr_label ;
char input [ 20 ] ;
char label [ 20 ] ;
} ;
2015-06-24 21:03:16 +03:00
struct scpi_thermal_zone {
int sensor_id ;
struct scpi_sensors * scpi_sensors ;
} ;
2015-05-21 17:08:45 +03:00
struct scpi_sensors {
struct scpi_ops * scpi_ops ;
struct sensor_data * data ;
2015-06-24 21:03:16 +03:00
struct list_head thermal_zones ;
2015-05-21 17:08:45 +03:00
struct attribute * * attrs ;
struct attribute_group group ;
const struct attribute_group * groups [ 2 ] ;
} ;
2017-05-30 12:05:07 +03:00
static const u32 gxbb_scpi_scale [ ] = {
[ TEMPERATURE ] = 1 , /* (celsius) */
[ VOLTAGE ] = 1000 , /* (millivolts) */
[ CURRENT ] = 1000 , /* (milliamperes) */
[ POWER ] = 1000000 , /* (microwatts) */
[ ENERGY ] = 1000000 , /* (microjoules) */
} ;
static const u32 scpi_scale [ ] = {
[ TEMPERATURE ] = 1000 , /* (millicelsius) */
[ VOLTAGE ] = 1000 , /* (millivolts) */
[ CURRENT ] = 1000 , /* (milliamperes) */
[ POWER ] = 1000000 , /* (microwatts) */
[ ENERGY ] = 1000000 , /* (microjoules) */
} ;
static void scpi_scale_reading ( u64 * value , struct sensor_data * sensor )
{
if ( scpi_scale [ sensor - > info . class ] ! = sensor - > scale ) {
* value * = scpi_scale [ sensor - > info . class ] ;
do_div ( * value , sensor - > scale ) ;
}
}
2015-06-24 21:03:16 +03:00
static int scpi_read_temp ( void * dev , int * temp )
{
struct scpi_thermal_zone * zone = dev ;
struct scpi_sensors * scpi_sensors = zone - > scpi_sensors ;
struct scpi_ops * scpi_ops = scpi_sensors - > scpi_ops ;
struct sensor_data * sensor = & scpi_sensors - > data [ zone - > sensor_id ] ;
2016-01-14 20:58:02 +03:00
u64 value ;
2015-06-24 21:03:16 +03:00
int ret ;
ret = scpi_ops - > sensor_get_value ( sensor - > info . sensor_id , & value ) ;
if ( ret )
return ret ;
2017-05-30 12:05:07 +03:00
scpi_scale_reading ( & value , sensor ) ;
2015-06-24 21:03:16 +03:00
* temp = value ;
return 0 ;
}
2015-05-21 17:08:45 +03:00
/* hwmon callback functions */
static ssize_t
scpi_show_sensor ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct scpi_sensors * scpi_sensors = dev_get_drvdata ( dev ) ;
struct scpi_ops * scpi_ops = scpi_sensors - > scpi_ops ;
struct sensor_data * sensor ;
2016-01-14 20:58:02 +03:00
u64 value ;
2015-05-21 17:08:45 +03:00
int ret ;
sensor = container_of ( attr , struct sensor_data , dev_attr_input ) ;
ret = scpi_ops - > sensor_get_value ( sensor - > info . sensor_id , & value ) ;
if ( ret )
return ret ;
2017-05-30 12:05:07 +03:00
scpi_scale_reading ( & value , sensor ) ;
2016-01-14 20:58:02 +03:00
return sprintf ( buf , " %llu \n " , value ) ;
2015-05-21 17:08:45 +03:00
}
static ssize_t
scpi_show_label ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_data * sensor ;
sensor = container_of ( attr , struct sensor_data , dev_attr_label ) ;
return sprintf ( buf , " %s \n " , sensor - > info . name ) ;
}
2015-06-24 21:03:16 +03:00
static struct thermal_zone_of_device_ops scpi_sensor_ops = {
. get_temp = scpi_read_temp ,
} ;
2017-05-30 12:05:07 +03:00
static const struct of_device_id scpi_of_match [ ] = {
{ . compatible = " arm,scpi-sensors " , . data = & scpi_scale } ,
{ . compatible = " amlogic,meson-gxbb-scpi-sensors " , . data = & gxbb_scpi_scale } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , scpi_of_match ) ;
2015-05-21 17:08:45 +03:00
static int scpi_hwmon_probe ( struct platform_device * pdev )
{
u16 nr_sensors , i ;
2017-05-30 12:05:07 +03:00
const u32 * scale ;
2015-05-21 17:08:45 +03:00
int num_temp = 0 , num_volt = 0 , num_current = 0 , num_power = 0 ;
2016-01-25 13:53:38 +03:00
int num_energy = 0 ;
2015-05-21 17:08:45 +03:00
struct scpi_ops * scpi_ops ;
struct device * hwdev , * dev = & pdev - > dev ;
struct scpi_sensors * scpi_sensors ;
2017-05-30 12:05:07 +03:00
const struct of_device_id * of_id ;
2016-03-10 00:03:17 +03:00
int idx , ret ;
2015-05-21 17:08:45 +03:00
scpi_ops = get_scpi_ops ( ) ;
if ( ! scpi_ops )
return - EPROBE_DEFER ;
ret = scpi_ops - > sensor_get_capability ( & nr_sensors ) ;
if ( ret )
return ret ;
if ( ! nr_sensors )
return - ENODEV ;
scpi_sensors = devm_kzalloc ( dev , sizeof ( * scpi_sensors ) , GFP_KERNEL ) ;
if ( ! scpi_sensors )
return - ENOMEM ;
scpi_sensors - > data = devm_kcalloc ( dev , nr_sensors ,
sizeof ( * scpi_sensors - > data ) , GFP_KERNEL ) ;
if ( ! scpi_sensors - > data )
return - ENOMEM ;
scpi_sensors - > attrs = devm_kcalloc ( dev , ( nr_sensors * 2 ) + 1 ,
sizeof ( * scpi_sensors - > attrs ) , GFP_KERNEL ) ;
if ( ! scpi_sensors - > attrs )
return - ENOMEM ;
scpi_sensors - > scpi_ops = scpi_ops ;
2017-05-30 12:05:07 +03:00
of_id = of_match_device ( scpi_of_match , & pdev - > dev ) ;
if ( ! of_id ) {
dev_err ( & pdev - > dev , " Unable to initialize scpi-hwmon data \n " ) ;
return - ENODEV ;
}
scale = of_id - > data ;
2015-10-28 20:17:31 +03:00
for ( i = 0 , idx = 0 ; i < nr_sensors ; i + + ) {
struct sensor_data * sensor = & scpi_sensors - > data [ idx ] ;
2015-05-21 17:08:45 +03:00
ret = scpi_ops - > sensor_get_info ( i , & sensor - > info ) ;
if ( ret )
return ret ;
switch ( sensor - > info . class ) {
case TEMPERATURE :
snprintf ( sensor - > input , sizeof ( sensor - > input ) ,
" temp%d_input " , num_temp + 1 ) ;
snprintf ( sensor - > label , sizeof ( sensor - > input ) ,
" temp%d_label " , num_temp + 1 ) ;
num_temp + + ;
break ;
case VOLTAGE :
snprintf ( sensor - > input , sizeof ( sensor - > input ) ,
" in%d_input " , num_volt ) ;
snprintf ( sensor - > label , sizeof ( sensor - > input ) ,
" in%d_label " , num_volt ) ;
num_volt + + ;
break ;
case CURRENT :
snprintf ( sensor - > input , sizeof ( sensor - > input ) ,
" curr%d_input " , num_current + 1 ) ;
snprintf ( sensor - > label , sizeof ( sensor - > input ) ,
" curr%d_label " , num_current + 1 ) ;
num_current + + ;
break ;
case POWER :
snprintf ( sensor - > input , sizeof ( sensor - > input ) ,
" power%d_input " , num_power + 1 ) ;
snprintf ( sensor - > label , sizeof ( sensor - > input ) ,
" power%d_label " , num_power + 1 ) ;
num_power + + ;
break ;
2016-01-25 13:53:38 +03:00
case ENERGY :
snprintf ( sensor - > input , sizeof ( sensor - > input ) ,
" energy%d_input " , num_energy + 1 ) ;
snprintf ( sensor - > label , sizeof ( sensor - > input ) ,
" energy%d_label " , num_energy + 1 ) ;
num_energy + + ;
break ;
2015-05-21 17:08:45 +03:00
default :
2015-10-28 20:17:31 +03:00
continue ;
2015-05-21 17:08:45 +03:00
}
2017-05-30 12:05:07 +03:00
sensor - > scale = scale [ sensor - > info . class ] ;
2015-05-21 17:08:45 +03:00
sensor - > dev_attr_input . attr . mode = S_IRUGO ;
sensor - > dev_attr_input . show = scpi_show_sensor ;
sensor - > dev_attr_input . attr . name = sensor - > input ;
sensor - > dev_attr_label . attr . mode = S_IRUGO ;
sensor - > dev_attr_label . show = scpi_show_label ;
sensor - > dev_attr_label . attr . name = sensor - > label ;
2015-10-28 20:17:31 +03:00
scpi_sensors - > attrs [ idx < < 1 ] = & sensor - > dev_attr_input . attr ;
scpi_sensors - > attrs [ ( idx < < 1 ) + 1 ] = & sensor - > dev_attr_label . attr ;
2015-05-21 17:08:45 +03:00
2015-10-28 20:17:31 +03:00
sysfs_attr_init ( scpi_sensors - > attrs [ idx < < 1 ] ) ;
sysfs_attr_init ( scpi_sensors - > attrs [ ( idx < < 1 ) + 1 ] ) ;
idx + + ;
2015-05-21 17:08:45 +03:00
}
scpi_sensors - > group . attrs = scpi_sensors - > attrs ;
scpi_sensors - > groups [ 0 ] = & scpi_sensors - > group ;
2015-06-24 21:03:16 +03:00
platform_set_drvdata ( pdev , scpi_sensors ) ;
2015-05-21 17:08:45 +03:00
hwdev = devm_hwmon_device_register_with_groups ( dev ,
" scpi_sensors " , scpi_sensors , scpi_sensors - > groups ) ;
2015-06-24 21:03:16 +03:00
if ( IS_ERR ( hwdev ) )
return PTR_ERR ( hwdev ) ;
/*
* Register the temperature sensors with the thermal framework
* to allow their usage in setting up the thermal zones from
* device tree .
*
* NOTE : Not all temperature sensors maybe used for thermal
* control
*/
INIT_LIST_HEAD ( & scpi_sensors - > thermal_zones ) ;
for ( i = 0 ; i < nr_sensors ; i + + ) {
struct sensor_data * sensor = & scpi_sensors - > data [ i ] ;
2016-03-10 00:03:17 +03:00
struct thermal_zone_device * z ;
2015-06-24 21:03:16 +03:00
struct scpi_thermal_zone * zone ;
if ( sensor - > info . class ! = TEMPERATURE )
continue ;
zone = devm_kzalloc ( dev , sizeof ( * zone ) , GFP_KERNEL ) ;
2016-03-10 00:03:17 +03:00
if ( ! zone )
return - ENOMEM ;
2015-06-24 21:03:16 +03:00
zone - > sensor_id = i ;
zone - > scpi_sensors = scpi_sensors ;
2016-03-10 00:03:17 +03:00
z = devm_thermal_zone_of_sensor_register ( dev ,
sensor - > info . sensor_id ,
zone ,
& scpi_sensor_ops ) ;
2015-06-24 21:03:16 +03:00
/*
* The call to thermal_zone_of_sensor_register returns
* an error for sensors that are not associated with
* any thermal zones or if the thermal subsystem is
* not configured .
*/
2016-03-10 00:03:17 +03:00
if ( IS_ERR ( z ) ) {
2015-06-24 21:03:16 +03:00
devm_kfree ( dev , zone ) ;
continue ;
}
}
return 0 ;
2015-05-21 17:08:45 +03:00
}
static struct platform_driver scpi_hwmon_platdrv = {
. driver = {
. name = " scpi-hwmon " ,
. of_match_table = scpi_of_match ,
} ,
. probe = scpi_hwmon_probe ,
} ;
module_platform_driver ( scpi_hwmon_platdrv ) ;
MODULE_AUTHOR ( " Punit Agrawal <punit.agrawal@arm.com> " ) ;
MODULE_DESCRIPTION ( " ARM SCPI HWMON interface driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;