2019-06-01 11:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-07-16 05:38:08 +04:00
/*
2012-01-19 23:02:18 +04:00
* hwmon . c - part of lm_sensors , Linux kernel modules for hardware monitoring
*
* This file defines the sysfs class " hwmon " , for use by sensors drivers .
*
* Copyright ( C ) 2005 Mark M . Hoffman < mhoffman @ lightlink . com >
*/
2005-07-16 05:38:08 +04:00
2010-10-20 10:51:37 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2015-08-27 05:38:11 +03:00
# include <linux/bitops.h>
2005-07-16 05:38:08 +04:00
# include <linux/device.h>
# include <linux/err.h>
2005-11-07 11:59:43 +03:00
# include <linux/gfp.h>
2016-07-10 19:43:21 +03:00
# include <linux/hwmon.h>
# include <linux/idr.h>
2020-05-28 17:28:04 +03:00
# include <linux/list.h>
2016-07-10 19:43:21 +03:00
# include <linux/module.h>
2009-06-15 20:39:50 +04:00
# include <linux/pci.h>
2016-07-10 19:43:21 +03:00
# include <linux/slab.h>
2014-02-28 22:37:55 +04:00
# include <linux/string.h>
2015-08-27 05:38:11 +03:00
# include <linux/thermal.h>
2005-07-16 05:38:08 +04:00
2018-10-10 00:42:19 +03:00
# define CREATE_TRACE_POINTS
# include <trace/events/hwmon.h>
2005-07-16 05:38:08 +04:00
# define HWMON_ID_PREFIX "hwmon"
# define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d"
2013-07-07 00:57:23 +04:00
struct hwmon_device {
const char * name ;
struct device dev ;
2015-08-27 05:38:11 +03:00
const struct hwmon_chip_info * chip ;
2020-05-28 17:28:04 +03:00
struct list_head tzdata ;
2015-08-27 05:38:11 +03:00
struct attribute_group group ;
const struct attribute_group * * groups ;
2013-07-07 00:57:23 +04:00
} ;
2015-08-27 05:38:11 +03:00
2013-07-07 00:57:23 +04:00
# define to_hwmon_device(d) container_of(d, struct hwmon_device, dev)
2016-10-16 20:52:04 +03:00
# define MAX_SYSFS_ATTR_NAME_LENGTH 32
2015-08-27 05:38:11 +03:00
struct hwmon_device_attribute {
struct device_attribute dev_attr ;
const struct hwmon_ops * ops ;
enum hwmon_sensor_types type ;
u32 attr ;
int index ;
2016-10-16 20:52:04 +03:00
char name [ MAX_SYSFS_ATTR_NAME_LENGTH ] ;
2015-08-27 05:38:11 +03:00
} ;
# define to_hwmon_attr(d) \
container_of ( d , struct hwmon_device_attribute , dev_attr )
2020-01-16 21:44:17 +03:00
# define to_dev_attr(a) container_of(a, struct device_attribute, attr)
2015-08-27 05:38:11 +03:00
/*
* Thermal zone information
*/
struct hwmon_thermal_data {
2020-05-28 17:28:04 +03:00
struct list_head node ; /* hwmon tzdata list entry */
2020-01-16 21:44:17 +03:00
struct device * dev ; /* Reference to hwmon device */
2015-08-27 05:38:11 +03:00
int index ; /* sensor index */
2020-05-28 17:28:04 +03:00
struct thermal_zone_device * tzd ; /* thermal zone device */
2015-08-27 05:38:11 +03:00
} ;
2013-07-07 00:57:23 +04:00
static ssize_t
2016-12-22 15:04:45 +03:00
name_show ( struct device * dev , struct device_attribute * attr , char * buf )
2013-07-07 00:57:23 +04:00
{
return sprintf ( buf , " %s \n " , to_hwmon_device ( dev ) - > name ) ;
}
2016-12-22 15:04:45 +03:00
static DEVICE_ATTR_RO ( name ) ;
2013-07-07 00:57:23 +04:00
static struct attribute * hwmon_dev_attrs [ ] = {
& dev_attr_name . attr ,
NULL
} ;
static umode_t hwmon_dev_name_is_visible ( struct kobject * kobj ,
struct attribute * attr , int n )
{
2021-02-23 12:14:27 +03:00
struct device * dev = kobj_to_dev ( kobj ) ;
2013-07-07 00:57:23 +04:00
if ( to_hwmon_device ( dev ) - > name = = NULL )
return 0 ;
return attr - > mode ;
}
2017-07-05 08:01:05 +03:00
static const struct attribute_group hwmon_dev_attr_group = {
2013-07-07 00:57:23 +04:00
. attrs = hwmon_dev_attrs ,
. is_visible = hwmon_dev_name_is_visible ,
} ;
static const struct attribute_group * hwmon_dev_attr_groups [ ] = {
& hwmon_dev_attr_group ,
NULL
} ;
2020-01-16 21:44:17 +03:00
static void hwmon_free_attrs ( struct attribute * * attrs )
{
int i ;
for ( i = 0 ; attrs [ i ] ; i + + ) {
struct device_attribute * dattr = to_dev_attr ( attrs [ i ] ) ;
struct hwmon_device_attribute * hattr = to_hwmon_attr ( dattr ) ;
kfree ( hattr ) ;
}
kfree ( attrs ) ;
}
2013-07-07 00:57:23 +04:00
static void hwmon_dev_release ( struct device * dev )
{
2020-01-16 21:44:17 +03:00
struct hwmon_device * hwdev = to_hwmon_device ( dev ) ;
if ( hwdev - > group . attrs )
hwmon_free_attrs ( hwdev - > group . attrs ) ;
kfree ( hwdev - > groups ) ;
kfree ( hwdev ) ;
2013-07-07 00:57:23 +04:00
}
static struct class hwmon_class = {
. name = " hwmon " ,
. owner = THIS_MODULE ,
. dev_groups = hwmon_dev_attr_groups ,
. dev_release = hwmon_dev_release ,
} ;
2005-07-16 05:38:08 +04:00
2011-11-01 04:10:09 +04:00
static DEFINE_IDA ( hwmon_ida ) ;
2005-07-16 05:38:08 +04:00
2015-08-27 05:38:11 +03:00
/* Thermal zone handling */
2016-08-12 16:28:15 +03:00
/*
* The complex conditional is necessary to avoid a cyclic dependency
* between hwmon and thermal_sys modules .
*/
2019-04-02 19:12:50 +03:00
# ifdef CONFIG_THERMAL_OF
2015-08-27 05:38:11 +03:00
static int hwmon_thermal_get_temp ( void * data , int * temp )
{
struct hwmon_thermal_data * tdata = data ;
2020-01-16 21:44:17 +03:00
struct hwmon_device * hwdev = to_hwmon_device ( tdata - > dev ) ;
2015-08-27 05:38:11 +03:00
int ret ;
long t ;
2020-01-16 21:44:17 +03:00
ret = hwdev - > chip - > ops - > read ( tdata - > dev , hwmon_temp , hwmon_temp_input ,
2015-08-27 05:38:11 +03:00
tdata - > index , & t ) ;
if ( ret < 0 )
return ret ;
* temp = t ;
return 0 ;
}
2021-06-23 07:22:31 +03:00
static int hwmon_thermal_set_trips ( void * data , int low , int high )
{
struct hwmon_thermal_data * tdata = data ;
struct hwmon_device * hwdev = to_hwmon_device ( tdata - > dev ) ;
const struct hwmon_chip_info * chip = hwdev - > chip ;
const struct hwmon_channel_info * * info = chip - > info ;
unsigned int i ;
int err ;
if ( ! chip - > ops - > write )
return 0 ;
for ( i = 0 ; info [ i ] & & info [ i ] - > type ! = hwmon_temp ; i + + )
continue ;
if ( ! info [ i ] )
return 0 ;
if ( info [ i ] - > config [ tdata - > index ] & HWMON_T_MIN ) {
err = chip - > ops - > write ( tdata - > dev , hwmon_temp ,
hwmon_temp_min , tdata - > index , low ) ;
if ( err & & err ! = - EOPNOTSUPP )
return err ;
}
if ( info [ i ] - > config [ tdata - > index ] & HWMON_T_MAX ) {
err = chip - > ops - > write ( tdata - > dev , hwmon_temp ,
hwmon_temp_max , tdata - > index , high ) ;
if ( err & & err ! = - EOPNOTSUPP )
return err ;
}
return 0 ;
}
2017-08-08 18:09:02 +03:00
static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
2015-08-27 05:38:11 +03:00
. get_temp = hwmon_thermal_get_temp ,
2021-06-23 07:22:31 +03:00
. set_trips = hwmon_thermal_set_trips ,
2015-08-27 05:38:11 +03:00
} ;
2020-05-28 17:28:04 +03:00
static void hwmon_thermal_remove_sensor ( void * data )
{
list_del ( data ) ;
}
2020-01-16 21:44:17 +03:00
static int hwmon_thermal_add_sensor ( struct device * dev , int index )
2015-08-27 05:38:11 +03:00
{
2020-05-28 17:28:04 +03:00
struct hwmon_device * hwdev = to_hwmon_device ( dev ) ;
2015-08-27 05:38:11 +03:00
struct hwmon_thermal_data * tdata ;
2017-12-05 11:36:14 +03:00
struct thermal_zone_device * tzd ;
2020-05-28 17:28:04 +03:00
int err ;
2015-08-27 05:38:11 +03:00
tdata = devm_kzalloc ( dev , sizeof ( * tdata ) , GFP_KERNEL ) ;
if ( ! tdata )
return - ENOMEM ;
2020-01-16 21:44:17 +03:00
tdata - > dev = dev ;
2015-08-27 05:38:11 +03:00
tdata - > index = index ;
2020-01-16 21:44:17 +03:00
tzd = devm_thermal_zone_of_sensor_register ( dev , index , tdata ,
2017-12-05 11:36:14 +03:00
& hwmon_thermal_ops ) ;
/*
* If CONFIG_THERMAL_OF is disabled , this returns - ENODEV ,
* so ignore that error but forward any other error .
*/
if ( IS_ERR ( tzd ) & & ( PTR_ERR ( tzd ) ! = - ENODEV ) )
return PTR_ERR ( tzd ) ;
2015-08-27 05:38:11 +03:00
2020-05-28 17:28:04 +03:00
err = devm_add_action ( dev , hwmon_thermal_remove_sensor , & tdata - > node ) ;
if ( err )
return err ;
tdata - > tzd = tzd ;
list_add ( & tdata - > node , & hwdev - > tzdata ) ;
2015-08-27 05:38:11 +03:00
return 0 ;
}
2020-05-04 17:57:44 +03:00
static int hwmon_thermal_register_sensors ( struct device * dev )
{
struct hwmon_device * hwdev = to_hwmon_device ( dev ) ;
const struct hwmon_chip_info * chip = hwdev - > chip ;
const struct hwmon_channel_info * * info = chip - > info ;
void * drvdata = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 1 ; info [ i ] ; i + + ) {
int j ;
if ( info [ i ] - > type ! = hwmon_temp )
continue ;
for ( j = 0 ; info [ i ] - > config [ j ] ; j + + ) {
int err ;
if ( ! ( info [ i ] - > config [ j ] & HWMON_T_INPUT ) | |
! chip - > ops - > is_visible ( drvdata , hwmon_temp ,
hwmon_temp_input , j ) )
continue ;
err = hwmon_thermal_add_sensor ( dev , j ) ;
if ( err )
return err ;
}
}
return 0 ;
}
2020-05-28 17:28:04 +03:00
static void hwmon_thermal_notify ( struct device * dev , int index )
{
struct hwmon_device * hwdev = to_hwmon_device ( dev ) ;
struct hwmon_thermal_data * tzdata ;
list_for_each_entry ( tzdata , & hwdev - > tzdata , node ) {
if ( tzdata - > index = = index ) {
thermal_zone_device_update ( tzdata - > tzd ,
THERMAL_EVENT_UNSPECIFIED ) ;
}
}
}
2015-08-27 05:38:11 +03:00
# else
2020-05-04 17:57:44 +03:00
static int hwmon_thermal_register_sensors ( struct device * dev )
2015-08-27 05:38:11 +03:00
{
return 0 ;
}
2020-05-28 17:28:04 +03:00
static void hwmon_thermal_notify ( struct device * dev , int index ) { }
2016-08-12 16:28:15 +03:00
# endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
2015-08-27 05:38:11 +03:00
2018-10-10 00:42:19 +03:00
static int hwmon_attr_base ( enum hwmon_sensor_types type )
{
2019-11-24 23:20:29 +03:00
if ( type = = hwmon_in | | type = = hwmon_intrusion )
2018-10-10 00:42:19 +03:00
return 0 ;
return 1 ;
}
2015-08-27 05:38:11 +03:00
/* sysfs attribute management */
static ssize_t hwmon_attr_show ( struct device * dev ,
struct device_attribute * devattr , char * buf )
{
struct hwmon_device_attribute * hattr = to_hwmon_attr ( devattr ) ;
long val ;
int ret ;
ret = hattr - > ops - > read ( dev , hattr - > type , hattr - > attr , hattr - > index ,
& val ) ;
if ( ret < 0 )
return ret ;
2018-10-10 00:42:19 +03:00
trace_hwmon_attr_show ( hattr - > index + hwmon_attr_base ( hattr - > type ) ,
hattr - > name , val ) ;
2015-08-27 05:38:11 +03:00
return sprintf ( buf , " %ld \n " , val ) ;
}
2016-08-08 06:51:25 +03:00
static ssize_t hwmon_attr_show_string ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct hwmon_device_attribute * hattr = to_hwmon_attr ( devattr ) ;
2018-10-10 00:42:19 +03:00
enum hwmon_sensor_types type = hattr - > type ;
2017-03-07 17:15:15 +03:00
const char * s ;
2016-08-08 06:51:25 +03:00
int ret ;
ret = hattr - > ops - > read_string ( dev , hattr - > type , hattr - > attr ,
hattr - > index , & s ) ;
if ( ret < 0 )
return ret ;
2018-10-10 00:42:19 +03:00
trace_hwmon_attr_show_string ( hattr - > index + hwmon_attr_base ( type ) ,
hattr - > name , s ) ;
2016-08-08 06:51:25 +03:00
return sprintf ( buf , " %s \n " , s ) ;
}
2015-08-27 05:38:11 +03:00
static ssize_t hwmon_attr_store ( struct device * dev ,
struct device_attribute * devattr ,
const char * buf , size_t count )
{
struct hwmon_device_attribute * hattr = to_hwmon_attr ( devattr ) ;
long val ;
int ret ;
ret = kstrtol ( buf , 10 , & val ) ;
if ( ret < 0 )
return ret ;
ret = hattr - > ops - > write ( dev , hattr - > type , hattr - > attr , hattr - > index ,
val ) ;
if ( ret < 0 )
return ret ;
2018-10-10 00:42:19 +03:00
trace_hwmon_attr_store ( hattr - > index + hwmon_attr_base ( hattr - > type ) ,
hattr - > name , val ) ;
2015-08-27 05:38:11 +03:00
2018-10-10 00:42:19 +03:00
return count ;
2015-08-27 05:38:11 +03:00
}
2016-08-08 06:51:25 +03:00
static bool is_string_attr ( enum hwmon_sensor_types type , u32 attr )
{
return ( type = = hwmon_temp & & attr = = hwmon_temp_label ) | |
( type = = hwmon_in & & attr = = hwmon_in_label ) | |
( type = = hwmon_curr & & attr = = hwmon_curr_label ) | |
( type = = hwmon_power & & attr = = hwmon_power_label ) | |
( type = = hwmon_energy & & attr = = hwmon_energy_label ) | |
( type = = hwmon_humidity & & attr = = hwmon_humidity_label ) | |
( type = = hwmon_fan & & attr = = hwmon_fan_label ) ;
}
2020-01-16 21:44:17 +03:00
static struct attribute * hwmon_genattr ( const void * drvdata ,
2015-08-27 05:38:11 +03:00
enum hwmon_sensor_types type ,
u32 attr ,
int index ,
const char * template ,
const struct hwmon_ops * ops )
{
struct hwmon_device_attribute * hattr ;
struct device_attribute * dattr ;
struct attribute * a ;
umode_t mode ;
2018-10-27 01:30:59 +03:00
const char * name ;
2016-08-08 06:51:25 +03:00
bool is_string = is_string_attr ( type , attr ) ;
2015-08-27 05:38:11 +03:00
/* The attribute is invisible if there is no template string */
if ( ! template )
return ERR_PTR ( - ENOENT ) ;
mode = ops - > is_visible ( drvdata , type , attr , index ) ;
if ( ! mode )
return ERR_PTR ( - ENOENT ) ;
2018-12-11 01:02:08 +03:00
if ( ( mode & 0444 ) & & ( ( is_string & & ! ops - > read_string ) | |
2016-08-08 06:51:25 +03:00
( ! is_string & & ! ops - > read ) ) )
2015-08-27 05:38:11 +03:00
return ERR_PTR ( - EINVAL ) ;
2018-12-11 01:02:08 +03:00
if ( ( mode & 0222 ) & & ! ops - > write )
2015-08-27 05:38:11 +03:00
return ERR_PTR ( - EINVAL ) ;
2020-01-16 21:44:17 +03:00
hattr = kzalloc ( sizeof ( * hattr ) , GFP_KERNEL ) ;
2016-10-16 20:52:04 +03:00
if ( ! hattr )
return ERR_PTR ( - ENOMEM ) ;
2015-08-27 05:38:11 +03:00
if ( type = = hwmon_chip ) {
2018-10-27 01:30:59 +03:00
name = template ;
2015-08-27 05:38:11 +03:00
} else {
2016-10-16 20:52:04 +03:00
scnprintf ( hattr - > name , sizeof ( hattr - > name ) , template ,
2015-08-27 05:38:11 +03:00
index + hwmon_attr_base ( type ) ) ;
2016-10-16 20:52:04 +03:00
name = hattr - > name ;
2015-08-27 05:38:11 +03:00
}
hattr - > type = type ;
hattr - > attr = attr ;
hattr - > index = index ;
hattr - > ops = ops ;
dattr = & hattr - > dev_attr ;
2016-08-08 06:51:25 +03:00
dattr - > show = is_string ? hwmon_attr_show_string : hwmon_attr_show ;
2015-08-27 05:38:11 +03:00
dattr - > store = hwmon_attr_store ;
a = & dattr - > attr ;
sysfs_attr_init ( a ) ;
a - > name = name ;
a - > mode = mode ;
return a ;
}
2016-10-16 20:38:52 +03:00
/*
* Chip attributes are not attribute templates but actual sysfs attributes .
* See hwmon_genattr ( ) for special handling .
*/
static const char * const hwmon_chip_attrs [ ] = {
2015-08-27 05:38:11 +03:00
[ hwmon_chip_temp_reset_history ] = " temp_reset_history " ,
2016-06-20 21:01:57 +03:00
[ hwmon_chip_in_reset_history ] = " in_reset_history " ,
2016-06-20 21:10:33 +03:00
[ hwmon_chip_curr_reset_history ] = " curr_reset_history " ,
2016-06-20 21:27:36 +03:00
[ hwmon_chip_power_reset_history ] = " power_reset_history " ,
2015-08-27 05:38:11 +03:00
[ hwmon_chip_update_interval ] = " update_interval " ,
[ hwmon_chip_alarms ] = " alarms " ,
2019-04-15 23:23:48 +03:00
[ hwmon_chip_samples ] = " samples " ,
[ hwmon_chip_curr_samples ] = " curr_samples " ,
[ hwmon_chip_in_samples ] = " in_samples " ,
[ hwmon_chip_power_samples ] = " power_samples " ,
[ hwmon_chip_temp_samples ] = " temp_samples " ,
2015-08-27 05:38:11 +03:00
} ;
static const char * const hwmon_temp_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_temp_enable ] = " temp%d_enable " ,
2015-08-27 05:38:11 +03:00
[ hwmon_temp_input ] = " temp%d_input " ,
[ hwmon_temp_type ] = " temp%d_type " ,
[ hwmon_temp_lcrit ] = " temp%d_lcrit " ,
[ hwmon_temp_lcrit_hyst ] = " temp%d_lcrit_hyst " ,
[ hwmon_temp_min ] = " temp%d_min " ,
[ hwmon_temp_min_hyst ] = " temp%d_min_hyst " ,
[ hwmon_temp_max ] = " temp%d_max " ,
[ hwmon_temp_max_hyst ] = " temp%d_max_hyst " ,
[ hwmon_temp_crit ] = " temp%d_crit " ,
[ hwmon_temp_crit_hyst ] = " temp%d_crit_hyst " ,
[ hwmon_temp_emergency ] = " temp%d_emergency " ,
[ hwmon_temp_emergency_hyst ] = " temp%d_emergency_hyst " ,
[ hwmon_temp_alarm ] = " temp%d_alarm " ,
[ hwmon_temp_lcrit_alarm ] = " temp%d_lcrit_alarm " ,
[ hwmon_temp_min_alarm ] = " temp%d_min_alarm " ,
[ hwmon_temp_max_alarm ] = " temp%d_max_alarm " ,
[ hwmon_temp_crit_alarm ] = " temp%d_crit_alarm " ,
[ hwmon_temp_emergency_alarm ] = " temp%d_emergency_alarm " ,
[ hwmon_temp_fault ] = " temp%d_fault " ,
[ hwmon_temp_offset ] = " temp%d_offset " ,
[ hwmon_temp_label ] = " temp%d_label " ,
[ hwmon_temp_lowest ] = " temp%d_lowest " ,
[ hwmon_temp_highest ] = " temp%d_highest " ,
[ hwmon_temp_reset_history ] = " temp%d_reset_history " ,
2020-07-31 22:37:16 +03:00
[ hwmon_temp_rated_min ] = " temp%d_rated_min " ,
[ hwmon_temp_rated_max ] = " temp%d_rated_max " ,
2015-08-27 05:38:11 +03:00
} ;
2016-06-20 21:01:57 +03:00
static const char * const hwmon_in_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_in_enable ] = " in%d_enable " ,
2016-06-20 21:01:57 +03:00
[ hwmon_in_input ] = " in%d_input " ,
[ hwmon_in_min ] = " in%d_min " ,
[ hwmon_in_max ] = " in%d_max " ,
[ hwmon_in_lcrit ] = " in%d_lcrit " ,
[ hwmon_in_crit ] = " in%d_crit " ,
[ hwmon_in_average ] = " in%d_average " ,
[ hwmon_in_lowest ] = " in%d_lowest " ,
[ hwmon_in_highest ] = " in%d_highest " ,
[ hwmon_in_reset_history ] = " in%d_reset_history " ,
[ hwmon_in_label ] = " in%d_label " ,
[ hwmon_in_alarm ] = " in%d_alarm " ,
[ hwmon_in_min_alarm ] = " in%d_min_alarm " ,
[ hwmon_in_max_alarm ] = " in%d_max_alarm " ,
[ hwmon_in_lcrit_alarm ] = " in%d_lcrit_alarm " ,
[ hwmon_in_crit_alarm ] = " in%d_crit_alarm " ,
2020-07-31 22:37:16 +03:00
[ hwmon_in_rated_min ] = " in%d_rated_min " ,
[ hwmon_in_rated_max ] = " in%d_rated_max " ,
2016-06-20 21:01:57 +03:00
} ;
2016-06-20 21:10:33 +03:00
static const char * const hwmon_curr_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_curr_enable ] = " curr%d_enable " ,
2016-06-20 21:10:33 +03:00
[ hwmon_curr_input ] = " curr%d_input " ,
[ hwmon_curr_min ] = " curr%d_min " ,
[ hwmon_curr_max ] = " curr%d_max " ,
[ hwmon_curr_lcrit ] = " curr%d_lcrit " ,
[ hwmon_curr_crit ] = " curr%d_crit " ,
[ hwmon_curr_average ] = " curr%d_average " ,
[ hwmon_curr_lowest ] = " curr%d_lowest " ,
[ hwmon_curr_highest ] = " curr%d_highest " ,
[ hwmon_curr_reset_history ] = " curr%d_reset_history " ,
[ hwmon_curr_label ] = " curr%d_label " ,
[ hwmon_curr_alarm ] = " curr%d_alarm " ,
[ hwmon_curr_min_alarm ] = " curr%d_min_alarm " ,
[ hwmon_curr_max_alarm ] = " curr%d_max_alarm " ,
[ hwmon_curr_lcrit_alarm ] = " curr%d_lcrit_alarm " ,
[ hwmon_curr_crit_alarm ] = " curr%d_crit_alarm " ,
2020-07-31 22:37:16 +03:00
[ hwmon_curr_rated_min ] = " curr%d_rated_min " ,
[ hwmon_curr_rated_max ] = " curr%d_rated_max " ,
2016-06-20 21:10:33 +03:00
} ;
2016-06-20 21:27:36 +03:00
static const char * const hwmon_power_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_power_enable ] = " power%d_enable " ,
2016-06-20 21:27:36 +03:00
[ hwmon_power_average ] = " power%d_average " ,
[ hwmon_power_average_interval ] = " power%d_average_interval " ,
[ hwmon_power_average_interval_max ] = " power%d_interval_max " ,
[ hwmon_power_average_interval_min ] = " power%d_interval_min " ,
[ hwmon_power_average_highest ] = " power%d_average_highest " ,
[ hwmon_power_average_lowest ] = " power%d_average_lowest " ,
[ hwmon_power_average_max ] = " power%d_average_max " ,
[ hwmon_power_average_min ] = " power%d_average_min " ,
[ hwmon_power_input ] = " power%d_input " ,
[ hwmon_power_input_highest ] = " power%d_input_highest " ,
[ hwmon_power_input_lowest ] = " power%d_input_lowest " ,
[ hwmon_power_reset_history ] = " power%d_reset_history " ,
[ hwmon_power_accuracy ] = " power%d_accuracy " ,
[ hwmon_power_cap ] = " power%d_cap " ,
[ hwmon_power_cap_hyst ] = " power%d_cap_hyst " ,
[ hwmon_power_cap_max ] = " power%d_cap_max " ,
[ hwmon_power_cap_min ] = " power%d_cap_min " ,
2018-07-17 22:48:11 +03:00
[ hwmon_power_min ] = " power%d_min " ,
2016-06-20 21:27:36 +03:00
[ hwmon_power_max ] = " power%d_max " ,
2018-07-17 22:48:11 +03:00
[ hwmon_power_lcrit ] = " power%d_lcrit " ,
2016-06-20 21:27:36 +03:00
[ hwmon_power_crit ] = " power%d_crit " ,
[ hwmon_power_label ] = " power%d_label " ,
[ hwmon_power_alarm ] = " power%d_alarm " ,
[ hwmon_power_cap_alarm ] = " power%d_cap_alarm " ,
2018-07-17 22:48:11 +03:00
[ hwmon_power_min_alarm ] = " power%d_min_alarm " ,
2016-06-20 21:27:36 +03:00
[ hwmon_power_max_alarm ] = " power%d_max_alarm " ,
2018-07-17 22:48:11 +03:00
[ hwmon_power_lcrit_alarm ] = " power%d_lcrit_alarm " ,
2016-06-20 21:27:36 +03:00
[ hwmon_power_crit_alarm ] = " power%d_crit_alarm " ,
2020-07-31 22:37:16 +03:00
[ hwmon_power_rated_min ] = " power%d_rated_min " ,
[ hwmon_power_rated_max ] = " power%d_rated_max " ,
2016-06-20 21:27:36 +03:00
} ;
2016-06-20 21:38:37 +03:00
static const char * const hwmon_energy_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_energy_enable ] = " energy%d_enable " ,
2016-06-20 21:38:37 +03:00
[ hwmon_energy_input ] = " energy%d_input " ,
[ hwmon_energy_label ] = " energy%d_label " ,
} ;
static const char * const hwmon_humidity_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_humidity_enable ] = " humidity%d_enable " ,
2016-06-20 21:38:37 +03:00
[ hwmon_humidity_input ] = " humidity%d_input " ,
[ hwmon_humidity_label ] = " humidity%d_label " ,
[ hwmon_humidity_min ] = " humidity%d_min " ,
[ hwmon_humidity_min_hyst ] = " humidity%d_min_hyst " ,
[ hwmon_humidity_max ] = " humidity%d_max " ,
[ hwmon_humidity_max_hyst ] = " humidity%d_max_hyst " ,
[ hwmon_humidity_alarm ] = " humidity%d_alarm " ,
[ hwmon_humidity_fault ] = " humidity%d_fault " ,
2020-07-31 22:37:16 +03:00
[ hwmon_humidity_rated_min ] = " humidity%d_rated_min " ,
[ hwmon_humidity_rated_max ] = " humidity%d_rated_max " ,
2016-06-20 21:38:37 +03:00
} ;
2016-06-26 05:52:13 +03:00
static const char * const hwmon_fan_attr_templates [ ] = {
2018-07-17 20:17:19 +03:00
[ hwmon_fan_enable ] = " fan%d_enable " ,
2016-06-26 05:52:13 +03:00
[ hwmon_fan_input ] = " fan%d_input " ,
[ hwmon_fan_label ] = " fan%d_label " ,
[ hwmon_fan_min ] = " fan%d_min " ,
[ hwmon_fan_max ] = " fan%d_max " ,
[ hwmon_fan_div ] = " fan%d_div " ,
[ hwmon_fan_pulses ] = " fan%d_pulses " ,
[ hwmon_fan_target ] = " fan%d_target " ,
[ hwmon_fan_alarm ] = " fan%d_alarm " ,
[ hwmon_fan_min_alarm ] = " fan%d_min_alarm " ,
[ hwmon_fan_max_alarm ] = " fan%d_max_alarm " ,
[ hwmon_fan_fault ] = " fan%d_fault " ,
} ;
2016-06-26 22:20:46 +03:00
static const char * const hwmon_pwm_attr_templates [ ] = {
[ hwmon_pwm_input ] = " pwm%d " ,
[ hwmon_pwm_enable ] = " pwm%d_enable " ,
[ hwmon_pwm_mode ] = " pwm%d_mode " ,
[ hwmon_pwm_freq ] = " pwm%d_freq " ,
} ;
2019-11-24 23:20:29 +03:00
static const char * const hwmon_intrusion_attr_templates [ ] = {
[ hwmon_intrusion_alarm ] = " intrusion%d_alarm " ,
[ hwmon_intrusion_beep ] = " intrusion%d_beep " ,
} ;
2015-08-27 05:38:11 +03:00
static const char * const * __templates [ ] = {
2016-10-16 20:38:52 +03:00
[ hwmon_chip ] = hwmon_chip_attrs ,
2015-08-27 05:38:11 +03:00
[ hwmon_temp ] = hwmon_temp_attr_templates ,
2016-06-20 21:01:57 +03:00
[ hwmon_in ] = hwmon_in_attr_templates ,
2016-06-20 21:10:33 +03:00
[ hwmon_curr ] = hwmon_curr_attr_templates ,
2016-06-20 21:27:36 +03:00
[ hwmon_power ] = hwmon_power_attr_templates ,
2016-06-20 21:38:37 +03:00
[ hwmon_energy ] = hwmon_energy_attr_templates ,
[ hwmon_humidity ] = hwmon_humidity_attr_templates ,
2016-06-26 05:52:13 +03:00
[ hwmon_fan ] = hwmon_fan_attr_templates ,
2016-06-26 22:20:46 +03:00
[ hwmon_pwm ] = hwmon_pwm_attr_templates ,
2019-11-24 23:20:29 +03:00
[ hwmon_intrusion ] = hwmon_intrusion_attr_templates ,
2015-08-27 05:38:11 +03:00
} ;
static const int __templates_size [ ] = {
2016-10-16 20:38:52 +03:00
[ hwmon_chip ] = ARRAY_SIZE ( hwmon_chip_attrs ) ,
2015-08-27 05:38:11 +03:00
[ hwmon_temp ] = ARRAY_SIZE ( hwmon_temp_attr_templates ) ,
2016-06-20 21:01:57 +03:00
[ hwmon_in ] = ARRAY_SIZE ( hwmon_in_attr_templates ) ,
2016-06-20 21:10:33 +03:00
[ hwmon_curr ] = ARRAY_SIZE ( hwmon_curr_attr_templates ) ,
2016-06-20 21:27:36 +03:00
[ hwmon_power ] = ARRAY_SIZE ( hwmon_power_attr_templates ) ,
2016-06-20 21:38:37 +03:00
[ hwmon_energy ] = ARRAY_SIZE ( hwmon_energy_attr_templates ) ,
[ hwmon_humidity ] = ARRAY_SIZE ( hwmon_humidity_attr_templates ) ,
2016-06-26 05:52:13 +03:00
[ hwmon_fan ] = ARRAY_SIZE ( hwmon_fan_attr_templates ) ,
2016-06-26 22:20:46 +03:00
[ hwmon_pwm ] = ARRAY_SIZE ( hwmon_pwm_attr_templates ) ,
2019-11-24 23:20:29 +03:00
[ hwmon_intrusion ] = ARRAY_SIZE ( hwmon_intrusion_attr_templates ) ,
2015-08-27 05:38:11 +03:00
} ;
2020-05-28 17:28:04 +03:00
int hwmon_notify_event ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel )
{
char sattr [ MAX_SYSFS_ATTR_NAME_LENGTH ] ;
const char * const * templates ;
const char * template ;
int base ;
if ( type > = ARRAY_SIZE ( __templates ) )
return - EINVAL ;
if ( attr > = __templates_size [ type ] )
return - EINVAL ;
templates = __templates [ type ] ;
template = templates [ attr ] ;
base = hwmon_attr_base ( type ) ;
scnprintf ( sattr , MAX_SYSFS_ATTR_NAME_LENGTH , template , base + channel ) ;
sysfs_notify ( & dev - > kobj , NULL , sattr ) ;
kobject_uevent ( & dev - > kobj , KOBJ_CHANGE ) ;
if ( type = = hwmon_temp )
hwmon_thermal_notify ( dev , channel ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( hwmon_notify_event ) ;
2015-08-27 05:38:11 +03:00
static int hwmon_num_channel_attrs ( const struct hwmon_channel_info * info )
{
int i , n ;
for ( i = n = 0 ; info - > config [ i ] ; i + + )
n + = hweight32 ( info - > config [ i ] ) ;
return n ;
}
2020-01-16 21:44:17 +03:00
static int hwmon_genattrs ( const void * drvdata ,
2015-08-27 05:38:11 +03:00
struct attribute * * attrs ,
const struct hwmon_ops * ops ,
const struct hwmon_channel_info * info )
{
const char * const * templates ;
int template_size ;
int i , aindex = 0 ;
if ( info - > type > = ARRAY_SIZE ( __templates ) )
return - EINVAL ;
templates = __templates [ info - > type ] ;
template_size = __templates_size [ info - > type ] ;
for ( i = 0 ; info - > config [ i ] ; i + + ) {
u32 attr_mask = info - > config [ i ] ;
u32 attr ;
while ( attr_mask ) {
struct attribute * a ;
attr = __ffs ( attr_mask ) ;
attr_mask & = ~ BIT ( attr ) ;
if ( attr > = template_size )
return - EINVAL ;
2020-01-16 21:44:17 +03:00
a = hwmon_genattr ( drvdata , info - > type , attr , i ,
2015-08-27 05:38:11 +03:00
templates [ attr ] , ops ) ;
if ( IS_ERR ( a ) ) {
if ( PTR_ERR ( a ) ! = - ENOENT )
return PTR_ERR ( a ) ;
continue ;
}
attrs [ aindex + + ] = a ;
}
}
return aindex ;
}
static struct attribute * *
2020-01-16 21:44:17 +03:00
__hwmon_create_attrs ( const void * drvdata , const struct hwmon_chip_info * chip )
2015-08-27 05:38:11 +03:00
{
int ret , i , aindex = 0 , nattrs = 0 ;
struct attribute * * attrs ;
for ( i = 0 ; chip - > info [ i ] ; i + + )
nattrs + = hwmon_num_channel_attrs ( chip - > info [ i ] ) ;
if ( nattrs = = 0 )
return ERR_PTR ( - EINVAL ) ;
2020-01-16 21:44:17 +03:00
attrs = kcalloc ( nattrs + 1 , sizeof ( * attrs ) , GFP_KERNEL ) ;
2015-08-27 05:38:11 +03:00
if ( ! attrs )
return ERR_PTR ( - ENOMEM ) ;
for ( i = 0 ; chip - > info [ i ] ; i + + ) {
2020-01-16 21:44:17 +03:00
ret = hwmon_genattrs ( drvdata , & attrs [ aindex ] , chip - > ops ,
2015-08-27 05:38:11 +03:00
chip - > info [ i ] ) ;
2020-01-16 21:44:17 +03:00
if ( ret < 0 ) {
hwmon_free_attrs ( attrs ) ;
2015-08-27 05:38:11 +03:00
return ERR_PTR ( ret ) ;
2020-01-16 21:44:17 +03:00
}
2015-08-27 05:38:11 +03:00
aindex + = ret ;
}
return attrs ;
}
static struct device *
__hwmon_device_register ( struct device * dev , const char * name , void * drvdata ,
const struct hwmon_chip_info * chip ,
const struct attribute_group * * groups )
2005-07-16 05:38:08 +04:00
{
2013-07-07 00:57:23 +04:00
struct hwmon_device * hwdev ;
2015-08-27 05:38:11 +03:00
struct device * hdev ;
2020-05-04 17:57:44 +03:00
int i , err , id ;
2006-03-06 01:13:47 +03:00
2017-01-28 06:35:57 +03:00
/* Complain about invalid characters in hwmon name attribute */
2014-02-28 22:37:55 +04:00
if ( name & & ( ! strlen ( name ) | | strpbrk ( name , " -* \t \n " ) ) )
2017-01-28 06:35:57 +03:00
dev_warn ( dev ,
" hwmon: '%s' is not a valid name attribute, please fix \n " ,
name ) ;
2014-02-28 22:37:55 +04:00
2011-11-01 04:10:09 +04:00
id = ida_simple_get ( & hwmon_ida , 0 , 0 , GFP_KERNEL ) ;
if ( id < 0 )
return ERR_PTR ( id ) ;
2005-07-16 05:38:08 +04:00
2013-07-07 00:57:23 +04:00
hwdev = kzalloc ( sizeof ( * hwdev ) , GFP_KERNEL ) ;
if ( hwdev = = NULL ) {
err = - ENOMEM ;
goto ida_remove ;
}
2005-07-16 05:38:08 +04:00
2015-08-27 05:38:11 +03:00
hdev = & hwdev - > dev ;
2016-10-17 03:06:20 +03:00
if ( chip ) {
2015-08-27 05:38:11 +03:00
struct attribute * * attrs ;
2016-10-17 03:11:52 +03:00
int ngroups = 2 ; /* terminating NULL plus &hwdev->groups */
2015-08-27 05:38:11 +03:00
if ( groups )
for ( i = 0 ; groups [ i ] ; i + + )
ngroups + + ;
2020-01-16 21:44:17 +03:00
hwdev - > groups = kcalloc ( ngroups , sizeof ( * groups ) , GFP_KERNEL ) ;
2016-10-23 23:56:08 +03:00
if ( ! hwdev - > groups ) {
err = - ENOMEM ;
goto free_hwmon ;
}
2015-08-27 05:38:11 +03:00
2020-01-16 21:44:17 +03:00
attrs = __hwmon_create_attrs ( drvdata , chip ) ;
2015-08-27 05:38:11 +03:00
if ( IS_ERR ( attrs ) ) {
err = PTR_ERR ( attrs ) ;
goto free_hwmon ;
}
hwdev - > group . attrs = attrs ;
ngroups = 0 ;
hwdev - > groups [ ngroups + + ] = & hwdev - > group ;
if ( groups ) {
for ( i = 0 ; groups [ i ] ; i + + )
hwdev - > groups [ ngroups + + ] = groups [ i ] ;
}
hdev - > groups = hwdev - > groups ;
} else {
hdev - > groups = groups ;
}
2013-07-07 00:57:23 +04:00
hwdev - > name = name ;
2015-08-27 05:38:11 +03:00
hdev - > class = & hwmon_class ;
hdev - > parent = dev ;
hdev - > of_node = dev ? dev - > of_node : NULL ;
hwdev - > chip = chip ;
dev_set_drvdata ( hdev , drvdata ) ;
dev_set_name ( hdev , HWMON_ID_FORMAT , id ) ;
err = device_register ( hdev ) ;
2021-10-12 14:27:58 +03:00
if ( err ) {
put_device ( hdev ) ;
goto ida_remove ;
}
2015-08-27 05:38:11 +03:00
2020-05-28 17:28:04 +03:00
INIT_LIST_HEAD ( & hwdev - > tzdata ) ;
hwmon: (core) add thermal sensors only if dev->of_node is present
Drivers may register to hwmon and request for also registering
with the thermal subsystem (HWMON_C_REGISTER_TZ). However,
some of these driver, e.g. marvell phy, may be probed from
Device Tree or being dynamically allocated, and in the later
case, it will not have a dev->of_node entry.
Registering with hwmon without the dev->of_node may result in
different outcomes depending on the device tree, which may
be a bit misleading. If the device tree blob has no 'thermal-zones'
node, the *hwmon_device_register*() family functions are going
to gracefully succeed, because of-thermal,
*thermal_zone_of_sensor_register() return -ENODEV in this case,
and the hwmon error path handles this error code as success to
cover for the case where CONFIG_THERMAL_OF is not set.
However, if the device tree blob has the 'thermal-zones'
entry, the *hwmon_device_register*() will always fail on callers
with no dev->of_node, propagating -EINVAL.
If dev->of_node is not present, calling of-thermal does not
make sense. For this reason, this patch checks first if the
device has a of_node before going over the process of registering
with the thermal subsystem of-thermal interface. And in this case,
when a caller of *hwmon_device_register*() with HWMON_C_REGISTER_TZ
and no dev->of_node will still register with hwmon, but not with
the thermal subsystem. If all the hwmon part bits are in place,
the registration will succeed.
Fixes: d560168b5d0f ("hwmon: (core) New hwmon registration API")
Cc: Jean Delvare <jdelvare@suse.com>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: linux-hwmon@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Eduardo Valentin <eduval@amazon.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2019-05-30 05:56:04 +03:00
if ( dev & & dev - > of_node & & chip & & chip - > ops - > read & &
2015-08-27 05:38:11 +03:00
chip - > info [ 0 ] - > type = = hwmon_chip & &
( chip - > info [ 0 ] - > config [ 0 ] & HWMON_C_REGISTER_TZ ) ) {
2020-05-04 17:57:44 +03:00
err = hwmon_thermal_register_sensors ( hdev ) ;
if ( err ) {
device_unregister ( hdev ) ;
/*
* Don ' t worry about hwdev ; hwmon_dev_release ( ) , called
* from device_unregister ( ) , will free it .
*/
goto ida_remove ;
2015-08-27 05:38:11 +03:00
}
}
2013-07-07 00:57:23 +04:00
2015-08-27 05:38:11 +03:00
return hdev ;
2013-07-07 00:57:23 +04:00
2015-08-27 05:38:11 +03:00
free_hwmon :
2020-01-16 21:44:17 +03:00
hwmon_dev_release ( hdev ) ;
2013-07-07 00:57:23 +04:00
ida_remove :
ida_simple_remove ( & hwmon_ida , id ) ;
return ERR_PTR ( err ) ;
}
2015-08-27 05:38:11 +03:00
/**
* hwmon_device_register_with_groups - register w / hwmon
* @ dev : the parent device
* @ name : hwmon name attribute
* @ drvdata : driver data to attach to created device
* @ groups : List of attribute groups to create
*
* hwmon_device_unregister ( ) must be called when the device is no
* longer needed .
*
* Returns the pointer to the new device .
*/
struct device *
hwmon_device_register_with_groups ( struct device * dev , const char * name ,
void * drvdata ,
const struct attribute_group * * groups )
{
2017-01-24 17:32:57 +03:00
if ( ! name )
return ERR_PTR ( - EINVAL ) ;
2015-08-27 05:38:11 +03:00
return __hwmon_device_register ( dev , name , drvdata , NULL , groups ) ;
}
2013-07-07 00:57:23 +04:00
EXPORT_SYMBOL_GPL ( hwmon_device_register_with_groups ) ;
2005-07-16 05:38:08 +04:00
2015-08-27 05:38:11 +03:00
/**
* hwmon_device_register_with_info - register w / hwmon
* @ dev : the parent device
* @ name : hwmon name attribute
* @ drvdata : driver data to attach to created device
2017-12-04 02:15:00 +03:00
* @ chip : pointer to hwmon chip information
2016-10-17 03:20:43 +03:00
* @ extra_groups : pointer to list of additional non - standard attribute groups
2015-08-27 05:38:11 +03:00
*
* hwmon_device_unregister ( ) must be called when the device is no
* longer needed .
*
* Returns the pointer to the new device .
*/
struct device *
hwmon_device_register_with_info ( struct device * dev , const char * name ,
void * drvdata ,
const struct hwmon_chip_info * chip ,
2016-10-17 03:20:43 +03:00
const struct attribute_group * * extra_groups )
2015-08-27 05:38:11 +03:00
{
2017-01-24 17:32:57 +03:00
if ( ! name )
return ERR_PTR ( - EINVAL ) ;
2016-10-17 03:06:20 +03:00
if ( chip & & ( ! chip - > ops | | ! chip - > ops - > is_visible | | ! chip - > info ) )
2015-08-27 05:38:11 +03:00
return ERR_PTR ( - EINVAL ) ;
2018-05-08 14:43:33 +03:00
if ( chip & & ! dev )
return ERR_PTR ( - EINVAL ) ;
2016-10-17 03:20:43 +03:00
return __hwmon_device_register ( dev , name , drvdata , chip , extra_groups ) ;
2015-08-27 05:38:11 +03:00
}
EXPORT_SYMBOL_GPL ( hwmon_device_register_with_info ) ;
2013-07-07 00:57:23 +04:00
/**
* hwmon_device_register - register w / hwmon
* @ dev : the device to register
*
* hwmon_device_unregister ( ) must be called when the device is no
* longer needed .
*
* Returns the pointer to the new device .
*/
struct device * hwmon_device_register ( struct device * dev )
{
2016-10-16 21:31:08 +03:00
dev_warn ( dev ,
" hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info(). \n " ) ;
2017-01-24 17:32:57 +03:00
return __hwmon_device_register ( dev , NULL , NULL , NULL , NULL ) ;
2005-07-16 05:38:08 +04:00
}
2012-01-08 22:34:14 +04:00
EXPORT_SYMBOL_GPL ( hwmon_device_register ) ;
2005-07-16 05:38:08 +04:00
/**
* hwmon_device_unregister - removes the previously registered class device
*
2007-08-21 00:46:20 +04:00
* @ dev : the class device to destroy
2005-07-16 05:38:08 +04:00
*/
2007-08-21 00:46:20 +04:00
void hwmon_device_unregister ( struct device * dev )
2005-07-16 05:38:08 +04:00
{
int id ;
2009-01-06 21:44:41 +03:00
if ( likely ( sscanf ( dev_name ( dev ) , HWMON_ID_FORMAT , & id ) = = 1 ) ) {
2007-08-21 00:46:20 +04:00
device_unregister ( dev ) ;
2011-11-01 04:10:09 +04:00
ida_simple_remove ( & hwmon_ida , id ) ;
2005-07-16 05:38:08 +04:00
} else
2007-08-21 00:46:20 +04:00
dev_dbg ( dev - > parent ,
2005-07-16 05:38:08 +04:00
" hwmon_device_unregister() failed: bad class ID! \n " ) ;
}
2012-01-08 22:34:14 +04:00
EXPORT_SYMBOL_GPL ( hwmon_device_unregister ) ;
2005-07-16 05:38:08 +04:00
2013-07-12 07:00:12 +04:00
static void devm_hwmon_release ( struct device * dev , void * res )
{
struct device * hwdev = * ( struct device * * ) res ;
hwmon_device_unregister ( hwdev ) ;
}
/**
* devm_hwmon_device_register_with_groups - register w / hwmon
* @ dev : the parent device
* @ name : hwmon name attribute
* @ drvdata : driver data to attach to created device
* @ groups : List of attribute groups to create
*
* Returns the pointer to the new device . The new device is automatically
* unregistered with the parent device .
*/
struct device *
devm_hwmon_device_register_with_groups ( struct device * dev , const char * name ,
void * drvdata ,
const struct attribute_group * * groups )
{
struct device * * ptr , * hwdev ;
if ( ! dev )
return ERR_PTR ( - EINVAL ) ;
ptr = devres_alloc ( devm_hwmon_release , sizeof ( * ptr ) , GFP_KERNEL ) ;
if ( ! ptr )
return ERR_PTR ( - ENOMEM ) ;
hwdev = hwmon_device_register_with_groups ( dev , name , drvdata , groups ) ;
if ( IS_ERR ( hwdev ) )
goto error ;
* ptr = hwdev ;
devres_add ( dev , ptr ) ;
return hwdev ;
error :
devres_free ( ptr ) ;
return hwdev ;
}
EXPORT_SYMBOL_GPL ( devm_hwmon_device_register_with_groups ) ;
2015-08-27 05:38:11 +03:00
/**
* devm_hwmon_device_register_with_info - register w / hwmon
2017-12-04 02:15:00 +03:00
* @ dev : the parent device
* @ name : hwmon name attribute
* @ drvdata : driver data to attach to created device
* @ chip : pointer to hwmon chip information
* @ groups : pointer to list of driver specific attribute groups
2015-08-27 05:38:11 +03:00
*
* Returns the pointer to the new device . The new device is automatically
* unregistered with the parent device .
*/
struct device *
devm_hwmon_device_register_with_info ( struct device * dev , const char * name ,
void * drvdata ,
const struct hwmon_chip_info * chip ,
const struct attribute_group * * groups )
{
struct device * * ptr , * hwdev ;
if ( ! dev )
return ERR_PTR ( - EINVAL ) ;
ptr = devres_alloc ( devm_hwmon_release , sizeof ( * ptr ) , GFP_KERNEL ) ;
if ( ! ptr )
return ERR_PTR ( - ENOMEM ) ;
hwdev = hwmon_device_register_with_info ( dev , name , drvdata , chip ,
groups ) ;
if ( IS_ERR ( hwdev ) )
goto error ;
* ptr = hwdev ;
devres_add ( dev , ptr ) ;
return hwdev ;
error :
devres_free ( ptr ) ;
return hwdev ;
}
EXPORT_SYMBOL_GPL ( devm_hwmon_device_register_with_info ) ;
2013-07-12 07:00:12 +04:00
static int devm_hwmon_match ( struct device * dev , void * res , void * data )
{
struct device * * hwdev = res ;
return * hwdev = = data ;
}
/**
* devm_hwmon_device_unregister - removes a previously registered hwmon device
*
* @ dev : the parent device of the device to unregister
*/
void devm_hwmon_device_unregister ( struct device * dev )
{
WARN_ON ( devres_release ( dev , devm_hwmon_release , devm_hwmon_match , dev ) ) ;
}
EXPORT_SYMBOL_GPL ( devm_hwmon_device_unregister ) ;
2009-06-15 20:39:50 +04:00
static void __init hwmon_pci_quirks ( void )
{
# if defined CONFIG_X86 && defined CONFIG_PCI
struct pci_dev * sb ;
u16 base ;
u8 enable ;
/* Open access to 0x295-0x296 on MSI MS-7031 */
sb = pci_get_device ( PCI_VENDOR_ID_ATI , 0x436c , NULL ) ;
2012-12-20 01:16:59 +04:00
if ( sb ) {
if ( sb - > subsystem_vendor = = 0x1462 & & /* MSI */
sb - > subsystem_device = = 0x0031 ) { /* MS-7031 */
pci_read_config_byte ( sb , 0x48 , & enable ) ;
pci_read_config_word ( sb , 0x64 , & base ) ;
if ( base = = 0 & & ! ( enable & BIT ( 2 ) ) ) {
dev_info ( & sb - > dev ,
" Opening wide generic port at 0x295 \n " ) ;
pci_write_config_word ( sb , 0x64 , 0x295 ) ;
pci_write_config_byte ( sb , 0x48 ,
enable | BIT ( 2 ) ) ;
}
2009-06-15 20:39:50 +04:00
}
2012-12-20 01:16:59 +04:00
pci_dev_put ( sb ) ;
2009-06-15 20:39:50 +04:00
}
# endif
}
2005-07-16 05:38:08 +04:00
static int __init hwmon_init ( void )
{
2013-07-07 00:57:23 +04:00
int err ;
2009-06-15 20:39:50 +04:00
hwmon_pci_quirks ( ) ;
2013-07-07 00:57:23 +04:00
err = class_register ( & hwmon_class ) ;
if ( err ) {
pr_err ( " couldn't register hwmon sysfs class \n " ) ;
return err ;
2005-07-16 05:38:08 +04:00
}
return 0 ;
}
static void __exit hwmon_exit ( void )
{
2013-07-07 00:57:23 +04:00
class_unregister ( & hwmon_class ) ;
2005-07-16 05:38:08 +04:00
}
2007-02-14 23:15:04 +03:00
subsys_initcall ( hwmon_init ) ;
2005-07-16 05:38:08 +04:00
module_exit ( hwmon_exit ) ;
MODULE_AUTHOR ( " Mark M. Hoffman <mhoffman@lightlink.com> " ) ;
MODULE_DESCRIPTION ( " hardware monitoring sysfs/class support " ) ;
MODULE_LICENSE ( " GPL " ) ;