2014-07-08 13:08:38 +04:00
/*
* IBM PowerNV platform sensors for temperature / fan / voltage / power
* Copyright ( C ) 2014 IBM
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program .
*/
# define DRVNAME "ibmpowernv"
# define pr_fmt(fmt) DRVNAME ": " fmt
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/of.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <asm/opal.h>
# include <linux/err.h>
2015-04-08 20:19:50 +03:00
# include <asm/cputhreads.h>
2015-04-09 07:02:47 +03:00
# include <asm/smp.h>
2014-07-08 13:08:38 +04:00
# define MAX_ATTR_LEN 32
2015-04-08 20:19:49 +03:00
# define MAX_LABEL_LEN 64
2014-07-08 13:08:38 +04:00
/* Sensor suffix name from DT */
# define DT_FAULT_ATTR_SUFFIX "faulted"
# define DT_DATA_ATTR_SUFFIX "data"
# define DT_THRESHOLD_ATTR_SUFFIX "thrs"
/*
* Enumerates all the types of sensors in the POWERNV platform and does index
* into ' struct sensor_group '
*/
enum sensors {
FAN ,
2015-03-19 20:44:41 +03:00
TEMP ,
2014-07-08 13:08:38 +04:00
POWER_SUPPLY ,
POWER_INPUT ,
MAX_SENSOR_TYPE ,
} ;
2015-04-08 20:19:48 +03:00
# define INVALID_INDEX (-1U)
2014-07-08 13:08:38 +04:00
static struct sensor_group {
const char * name ;
const char * compatible ;
struct attribute_group group ;
u32 attr_count ;
2015-03-19 20:44:45 +03:00
u32 hwmon_index ;
2014-07-08 13:08:38 +04:00
} sensor_groups [ ] = {
{ " fan " , " ibm,opal-sensor-cooling-fan " } ,
{ " temp " , " ibm,opal-sensor-amb-temp " } ,
{ " in " , " ibm,opal-sensor-power-supply " } ,
{ " power " , " ibm,opal-sensor-power " }
} ;
struct sensor_data {
u32 id ; /* An opaque id of the firmware for each sensor */
2015-03-19 20:44:45 +03:00
u32 hwmon_index ;
u32 opal_index ;
2014-07-08 13:08:38 +04:00
enum sensors type ;
2015-04-08 20:19:49 +03:00
char label [ MAX_LABEL_LEN ] ;
2014-07-08 13:08:38 +04:00
char name [ MAX_ATTR_LEN ] ;
struct device_attribute dev_attr ;
} ;
struct platform_data {
const struct attribute_group * attr_groups [ MAX_SENSOR_TYPE + 1 ] ;
u32 sensors_count ; /* Total count of sensors from each group */
} ;
static ssize_t show_sensor ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
struct sensor_data * sdata = container_of ( devattr , struct sensor_data ,
dev_attr ) ;
ssize_t ret ;
u32 x ;
ret = opal_get_sensor_data ( sdata - > id , & x ) ;
if ( ret )
return ret ;
/* Convert temperature to milli-degrees */
2015-03-19 20:44:41 +03:00
if ( sdata - > type = = TEMP )
2014-07-08 13:08:38 +04:00
x * = 1000 ;
/* Convert power to micro-watts */
else if ( sdata - > type = = POWER_INPUT )
x * = 1000000 ;
return sprintf ( buf , " %u \n " , x ) ;
}
2015-04-08 20:19:49 +03:00
static ssize_t show_label ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
struct sensor_data * sdata = container_of ( devattr , struct sensor_data ,
dev_attr ) ;
return sprintf ( buf , " %s \n " , sdata - > label ) ;
}
2015-04-08 20:19:50 +03:00
static int __init get_logical_cpu ( int hwcpu )
{
int cpu ;
for_each_possible_cpu ( cpu )
if ( get_hard_smp_processor_id ( cpu ) = = hwcpu )
return cpu ;
return - ENOENT ;
}
2015-04-08 20:19:49 +03:00
static void __init make_sensor_label ( struct device_node * np ,
struct sensor_data * sdata ,
const char * label )
{
2015-04-08 20:19:50 +03:00
u32 id ;
2015-04-08 20:19:49 +03:00
size_t n ;
n = snprintf ( sdata - > label , sizeof ( sdata - > label ) , " %s " , label ) ;
2015-04-08 20:19:50 +03:00
/*
* Core temp pretty print
*/
if ( ! of_property_read_u32 ( np , " ibm,pir " , & id ) ) {
int cpuid = get_logical_cpu ( id ) ;
if ( cpuid > = 0 )
/*
* The digital thermal sensors are associated
* with a core . Let ' s print out the range of
* cpu ids corresponding to the hardware
* threads of the core .
*/
n + = snprintf ( sdata - > label + n ,
sizeof ( sdata - > label ) - n , " %d-%d " ,
cpuid , cpuid + threads_per_core - 1 ) ;
else
n + = snprintf ( sdata - > label + n ,
sizeof ( sdata - > label ) - n , " phy%d " , id ) ;
}
/*
* Membuffer pretty print
*/
if ( ! of_property_read_u32 ( np , " ibm,chip-id " , & id ) )
n + = snprintf ( sdata - > label + n , sizeof ( sdata - > label ) - n ,
" %d " , id & 0xffff ) ;
2015-04-08 20:19:49 +03:00
}
static int get_sensor_index_attr ( const char * name , u32 * index , char * attr )
2014-07-08 13:08:38 +04:00
{
char * hash_pos = strchr ( name , ' # ' ) ;
char buf [ 8 ] = { 0 } ;
char * dash_pos ;
u32 copy_len ;
int err ;
if ( ! hash_pos )
return - EINVAL ;
dash_pos = strchr ( hash_pos , ' - ' ) ;
if ( ! dash_pos )
return - EINVAL ;
copy_len = dash_pos - hash_pos - 1 ;
if ( copy_len > = sizeof ( buf ) )
return - EINVAL ;
strncpy ( buf , hash_pos + 1 , copy_len ) ;
err = kstrtou32 ( buf , 10 , index ) ;
if ( err )
return err ;
strncpy ( attr , dash_pos + 1 , MAX_ATTR_LEN ) ;
return 0 ;
}
2015-03-19 20:44:43 +03:00
static const char * convert_opal_attr_name ( enum sensors type ,
const char * opal_attr )
{
const char * attr_name = NULL ;
if ( ! strcmp ( opal_attr , DT_FAULT_ATTR_SUFFIX ) ) {
attr_name = " fault " ;
} else if ( ! strcmp ( opal_attr , DT_DATA_ATTR_SUFFIX ) ) {
attr_name = " input " ;
} else if ( ! strcmp ( opal_attr , DT_THRESHOLD_ATTR_SUFFIX ) ) {
if ( type = = TEMP )
attr_name = " max " ;
else if ( type = = FAN )
attr_name = " min " ;
}
return attr_name ;
}
2014-07-08 13:08:38 +04:00
/*
* This function translates the DT node name into the ' hwmon ' attribute name .
* IBMPOWERNV device node appear like cooling - fan # 2 - data , amb - temp # 1 - thrs etc .
* which need to be mapped as fan2_input , temp1_max respectively before
* populating them inside hwmon device class .
*/
2015-03-19 20:44:44 +03:00
static const char * parse_opal_node_name ( const char * node_name ,
enum sensors type , u32 * index )
2014-07-08 13:08:38 +04:00
{
char attr_suffix [ MAX_ATTR_LEN ] ;
2015-03-19 20:44:43 +03:00
const char * attr_name ;
2014-07-08 13:08:38 +04:00
int err ;
2015-03-19 20:44:44 +03:00
err = get_sensor_index_attr ( node_name , index , attr_suffix ) ;
if ( err )
return ERR_PTR ( err ) ;
2014-07-08 13:08:38 +04:00
2015-03-19 20:44:43 +03:00
attr_name = convert_opal_attr_name ( type , attr_suffix ) ;
if ( ! attr_name )
2015-03-19 20:44:44 +03:00
return ERR_PTR ( - ENOENT ) ;
2014-07-08 13:08:38 +04:00
2015-03-19 20:44:44 +03:00
return attr_name ;
2014-07-08 13:08:38 +04:00
}
2015-03-19 20:44:42 +03:00
static int get_sensor_type ( struct device_node * np )
{
enum sensors type ;
2015-04-08 20:19:48 +03:00
const char * str ;
2015-03-19 20:44:42 +03:00
for ( type = 0 ; type < MAX_SENSOR_TYPE ; type + + ) {
if ( of_device_is_compatible ( np , sensor_groups [ type ] . compatible ) )
return type ;
}
2015-04-08 20:19:48 +03:00
/*
* Let ' s check if we have a newer device tree
*/
if ( ! of_device_is_compatible ( np , " ibm,opal-sensor " ) )
return MAX_SENSOR_TYPE ;
if ( of_property_read_string ( np , " sensor-type " , & str ) )
return MAX_SENSOR_TYPE ;
for ( type = 0 ; type < MAX_SENSOR_TYPE ; type + + )
if ( ! strcmp ( str , sensor_groups [ type ] . name ) )
return type ;
2015-03-19 20:44:42 +03:00
return MAX_SENSOR_TYPE ;
}
2015-03-19 20:44:45 +03:00
static u32 get_sensor_hwmon_index ( struct sensor_data * sdata ,
struct sensor_data * sdata_table , int count )
{
int i ;
2015-04-08 20:19:48 +03:00
/*
* We don ' t use the OPAL index on newer device trees
*/
if ( sdata - > opal_index ! = INVALID_INDEX ) {
for ( i = 0 ; i < count ; i + + )
if ( sdata_table [ i ] . opal_index = = sdata - > opal_index & &
sdata_table [ i ] . type = = sdata - > type )
return sdata_table [ i ] . hwmon_index ;
}
2015-03-19 20:44:45 +03:00
return + + sensor_groups [ sdata - > type ] . hwmon_index ;
}
2014-11-05 14:15:14 +03:00
static int populate_attr_groups ( struct platform_device * pdev )
2014-07-08 13:08:38 +04:00
{
struct platform_data * pdata = platform_get_drvdata ( pdev ) ;
const struct attribute_group * * pgroups = pdata - > attr_groups ;
struct device_node * opal , * np ;
enum sensors type ;
opal = of_find_node_by_path ( " /ibm,opal/sensors " ) ;
for_each_child_of_node ( opal , np ) {
2015-04-08 20:19:49 +03:00
const char * label ;
2014-07-08 13:08:38 +04:00
if ( np - > name = = NULL )
continue ;
2015-03-19 20:44:42 +03:00
type = get_sensor_type ( np ) ;
2015-04-08 20:19:49 +03:00
if ( type = = MAX_SENSOR_TYPE )
continue ;
sensor_groups [ type ] . attr_count + + ;
/*
* add a new attribute for labels
*/
if ( ! of_property_read_string ( np , " label " , & label ) )
2015-03-19 20:44:42 +03:00
sensor_groups [ type ] . attr_count + + ;
2014-07-08 13:08:38 +04:00
}
of_node_put ( opal ) ;
for ( type = 0 ; type < MAX_SENSOR_TYPE ; type + + ) {
sensor_groups [ type ] . group . attrs = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct attribute * ) *
( sensor_groups [ type ] . attr_count + 1 ) ,
GFP_KERNEL ) ;
if ( ! sensor_groups [ type ] . group . attrs )
return - ENOMEM ;
pgroups [ type ] = & sensor_groups [ type ] . group ;
pdata - > sensors_count + = sensor_groups [ type ] . attr_count ;
sensor_groups [ type ] . attr_count = 0 ;
}
return 0 ;
}
2015-04-08 20:19:47 +03:00
static void create_hwmon_attr ( struct sensor_data * sdata , const char * attr_name ,
ssize_t ( * show ) ( struct device * dev ,
struct device_attribute * attr ,
char * buf ) )
{
snprintf ( sdata - > name , MAX_ATTR_LEN , " %s%d_%s " ,
sensor_groups [ sdata - > type ] . name , sdata - > hwmon_index ,
attr_name ) ;
sysfs_attr_init ( & sdata - > dev_attr . attr ) ;
sdata - > dev_attr . attr . name = sdata - > name ;
sdata - > dev_attr . attr . mode = S_IRUGO ;
sdata - > dev_attr . show = show ;
}
2014-07-08 13:08:38 +04:00
/*
* Iterate through the device tree for each child of ' sensors ' node , create
* a sysfs attribute file , the file is named by translating the DT node name
* to the name required by the higher ' hwmon ' driver like fan1_input , temp1_max
* etc . .
*/
2014-11-05 14:15:14 +03:00
static int create_device_attrs ( struct platform_device * pdev )
2014-07-08 13:08:38 +04:00
{
struct platform_data * pdata = platform_get_drvdata ( pdev ) ;
const struct attribute_group * * pgroups = pdata - > attr_groups ;
struct device_node * opal , * np ;
struct sensor_data * sdata ;
2014-08-01 08:38:47 +04:00
u32 sensor_id ;
2014-07-08 13:08:38 +04:00
enum sensors type ;
u32 count = 0 ;
int err = 0 ;
opal = of_find_node_by_path ( " /ibm,opal/sensors " ) ;
sdata = devm_kzalloc ( & pdev - > dev , pdata - > sensors_count * sizeof ( * sdata ) ,
GFP_KERNEL ) ;
if ( ! sdata ) {
err = - ENOMEM ;
goto exit_put_node ;
}
for_each_child_of_node ( opal , np ) {
2015-03-19 20:44:44 +03:00
const char * attr_name ;
u32 opal_index ;
2015-04-08 20:19:49 +03:00
const char * label ;
2015-03-19 20:44:44 +03:00
2014-07-08 13:08:38 +04:00
if ( np - > name = = NULL )
continue ;
2015-03-19 20:44:42 +03:00
type = get_sensor_type ( np ) ;
2014-07-08 13:08:38 +04:00
if ( type = = MAX_SENSOR_TYPE )
continue ;
2015-04-08 20:19:48 +03:00
/*
* Newer device trees use a " sensor-data " property
* name for input .
*/
if ( of_property_read_u32 ( np , " sensor-id " , & sensor_id ) & &
of_property_read_u32 ( np , " sensor-data " , & sensor_id ) ) {
2014-07-08 13:08:38 +04:00
dev_info ( & pdev - > dev ,
" 'sensor-id' missing in the node '%s' \n " ,
np - > name ) ;
continue ;
}
2014-08-01 08:38:47 +04:00
sdata [ count ] . id = sensor_id ;
2014-07-08 13:08:38 +04:00
sdata [ count ] . type = type ;
2015-03-19 20:44:44 +03:00
2015-04-08 20:19:48 +03:00
/*
* If we can not parse the node name , it means we are
* running on a newer device tree . We can just forget
* about the OPAL index and use a defaut value for the
* hwmon attribute name
*/
2015-03-19 20:44:44 +03:00
attr_name = parse_opal_node_name ( np - > name , type , & opal_index ) ;
if ( IS_ERR ( attr_name ) ) {
2015-04-08 20:19:48 +03:00
attr_name = " input " ;
opal_index = INVALID_INDEX ;
2015-03-19 20:44:44 +03:00
}
2015-03-19 20:44:45 +03:00
sdata [ count ] . opal_index = opal_index ;
sdata [ count ] . hwmon_index =
get_sensor_hwmon_index ( & sdata [ count ] , sdata , count ) ;
2015-04-08 20:19:47 +03:00
create_hwmon_attr ( & sdata [ count ] , attr_name , show_sensor ) ;
2014-07-08 13:08:38 +04:00
pgroups [ type ] - > attrs [ sensor_groups [ type ] . attr_count + + ] =
& sdata [ count + + ] . dev_attr . attr ;
2015-04-08 20:19:49 +03:00
if ( ! of_property_read_string ( np , " label " , & label ) ) {
/*
* For the label attribute , we can reuse the
* " properties " of the previous " input "
* attribute . They are related to the same
* sensor .
*/
sdata [ count ] . type = type ;
sdata [ count ] . opal_index = sdata [ count - 1 ] . opal_index ;
sdata [ count ] . hwmon_index = sdata [ count - 1 ] . hwmon_index ;
make_sensor_label ( np , & sdata [ count ] , label ) ;
create_hwmon_attr ( & sdata [ count ] , " label " , show_label ) ;
pgroups [ type ] - > attrs [ sensor_groups [ type ] . attr_count + + ] =
& sdata [ count + + ] . dev_attr . attr ;
}
2014-07-08 13:08:38 +04:00
}
exit_put_node :
of_node_put ( opal ) ;
return err ;
}
2014-11-05 14:15:14 +03:00
static int ibmpowernv_probe ( struct platform_device * pdev )
2014-07-08 13:08:38 +04:00
{
struct platform_data * pdata ;
struct device * hwmon_dev ;
int err ;
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
platform_set_drvdata ( pdev , pdata ) ;
pdata - > sensors_count = 0 ;
err = populate_attr_groups ( pdev ) ;
if ( err )
return err ;
/* Create sysfs attribute data for each sensor found in the DT */
err = create_device_attrs ( pdev ) ;
if ( err )
return err ;
/* Finally, register with hwmon */
hwmon_dev = devm_hwmon_device_register_with_groups ( & pdev - > dev , DRVNAME ,
pdata ,
pdata - > attr_groups ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
}
2014-11-05 14:15:14 +03:00
static const struct platform_device_id opal_sensor_driver_ids [ ] = {
{
. name = " opal-sensor " ,
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , opal_sensor_driver_ids ) ;
2015-09-23 15:44:48 +03:00
static const struct of_device_id opal_sensor_match [ ] = {
{ . compatible = " ibm,opal-sensor " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , opal_sensor_match ) ;
2014-07-08 13:08:38 +04:00
static struct platform_driver ibmpowernv_driver = {
2014-11-05 14:15:14 +03:00
. probe = ibmpowernv_probe ,
. id_table = opal_sensor_driver_ids ,
. driver = {
. name = DRVNAME ,
2015-09-23 15:44:48 +03:00
. of_match_table = opal_sensor_match ,
2014-07-08 13:08:38 +04:00
} ,
} ;
2014-11-12 11:36:47 +03:00
module_platform_driver ( ibmpowernv_driver ) ;
2014-07-08 13:08:38 +04:00
MODULE_AUTHOR ( " Neelesh Gupta <neelegup@linux.vnet.ibm.com> " ) ;
MODULE_DESCRIPTION ( " IBM POWERNV platform sensors " ) ;
MODULE_LICENSE ( " GPL " ) ;