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>
# define MAX_ATTR_LEN 32
/* 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 ,
} ;
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 ;
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 ) ;
}
2014-11-05 14:15:14 +03:00
static int get_sensor_index_attr ( const char * name , u32 * index ,
2014-07-08 13:08:38 +04:00
char * attr )
{
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 ;
for ( type = 0 ; type < MAX_SENSOR_TYPE ; type + + ) {
if ( of_device_is_compatible ( np , sensor_groups [ type ] . compatible ) )
return type ;
}
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 ;
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 ;
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 ) {
if ( np - > name = = NULL )
continue ;
2015-03-19 20:44:42 +03:00
type = get_sensor_type ( np ) ;
if ( type ! = MAX_SENSOR_TYPE )
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 ;
}
/*
* 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 ;
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 ;
2014-08-01 08:38:47 +04:00
if ( of_property_read_u32 ( np , " sensor-id " , & 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
attr_name = parse_opal_node_name ( np - > name , type , & opal_index ) ;
if ( IS_ERR ( attr_name ) ) {
dev_err ( & pdev - > dev , " Sensor device node name '%s' is invalid \n " ,
np - > name ) ;
err = PTR_ERR ( attr_name ) ;
2014-07-08 13:08:38 +04:00
goto exit_put_node ;
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-03-19 20:44:44 +03:00
snprintf ( sdata [ count ] . name , MAX_ATTR_LEN , " %s%d_%s " ,
2015-03-19 20:44:45 +03:00
sensor_groups [ type ] . name , sdata [ count ] . hwmon_index ,
attr_name ) ;
2014-07-08 13:08:38 +04:00
sysfs_attr_init ( & sdata [ count ] . dev_attr . attr ) ;
sdata [ count ] . dev_attr . attr . name = sdata [ count ] . name ;
sdata [ count ] . dev_attr . attr . mode = S_IRUGO ;
sdata [ count ] . dev_attr . show = show_sensor ;
pgroups [ type ] - > attrs [ sensor_groups [ type ] . attr_count + + ] =
& sdata [ count + + ] . dev_attr . attr ;
}
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 ) ;
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 ,
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 " ) ;