2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2011-03-21 19:59:36 +03:00
/***************************************************************************
2012-03-18 16:05:08 +04:00
* Copyright ( C ) 2010 - 2012 Hans de Goede < hdegoede @ redhat . com > *
2011-03-21 19:59:36 +03:00
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/jiffies.h>
# include <linux/platform_device.h>
# include <linux/hwmon.h>
# include <linux/err.h>
# include <linux/mutex.h>
2011-07-25 23:46:09 +04:00
# include "sch56xx-common.h"
2011-03-21 19:59:36 +03:00
# define DRVNAME "sch5627"
# define DEVNAME DRVNAME /* We only support one model */
# define SCH5627_HWMON_ID 0xa5
# define SCH5627_COMPANY_ID 0x5c
# define SCH5627_PRIMARY_ID 0xa0
# define SCH5627_REG_BUILD_CODE 0x39
# define SCH5627_REG_BUILD_ID 0x3a
# define SCH5627_REG_HWMON_ID 0x3c
# define SCH5627_REG_HWMON_REV 0x3d
# define SCH5627_REG_COMPANY_ID 0x3e
# define SCH5627_REG_PRIMARY_ID 0x3f
# define SCH5627_REG_CTRL 0x40
# define SCH5627_NO_TEMPS 8
# define SCH5627_NO_FANS 4
# define SCH5627_NO_IN 5
static const u16 SCH5627_REG_TEMP_MSB [ SCH5627_NO_TEMPS ] = {
0x2B , 0x26 , 0x27 , 0x28 , 0x29 , 0x2A , 0x180 , 0x181 } ;
static const u16 SCH5627_REG_TEMP_LSN [ SCH5627_NO_TEMPS ] = {
0xE2 , 0xE1 , 0xE1 , 0xE5 , 0xE5 , 0xE6 , 0x182 , 0x182 } ;
static const u16 SCH5627_REG_TEMP_HIGH_NIBBLE [ SCH5627_NO_TEMPS ] = {
0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 } ;
static const u16 SCH5627_REG_TEMP_HIGH [ SCH5627_NO_TEMPS ] = {
0x61 , 0x57 , 0x59 , 0x5B , 0x5D , 0x5F , 0x184 , 0x186 } ;
static const u16 SCH5627_REG_TEMP_ABS [ SCH5627_NO_TEMPS ] = {
0x9B , 0x96 , 0x97 , 0x98 , 0x99 , 0x9A , 0x1A8 , 0x1A9 } ;
static const u16 SCH5627_REG_FAN [ SCH5627_NO_FANS ] = {
0x2C , 0x2E , 0x30 , 0x32 } ;
static const u16 SCH5627_REG_FAN_MIN [ SCH5627_NO_FANS ] = {
0x62 , 0x64 , 0x66 , 0x68 } ;
static const u16 SCH5627_REG_IN_MSB [ SCH5627_NO_IN ] = {
0x22 , 0x23 , 0x24 , 0x25 , 0x189 } ;
static const u16 SCH5627_REG_IN_LSN [ SCH5627_NO_IN ] = {
0xE4 , 0xE4 , 0xE3 , 0xE3 , 0x18A } ;
static const u16 SCH5627_REG_IN_HIGH_NIBBLE [ SCH5627_NO_IN ] = {
1 , 0 , 1 , 0 , 1 } ;
static const u16 SCH5627_REG_IN_FACTOR [ SCH5627_NO_IN ] = {
10745 , 3660 , 9765 , 10745 , 3660 } ;
static const char * const SCH5627_IN_LABELS [ SCH5627_NO_IN ] = {
" VCC " , " VTT " , " VBAT " , " VTR " , " V_IN " } ;
struct sch5627_data {
unsigned short addr ;
2011-05-25 22:43:33 +04:00
u8 control ;
2011-03-21 19:59:36 +03:00
u8 temp_max [ SCH5627_NO_TEMPS ] ;
u8 temp_crit [ SCH5627_NO_TEMPS ] ;
u16 fan_min [ SCH5627_NO_FANS ] ;
struct mutex update_lock ;
2011-05-25 22:43:33 +04:00
unsigned long last_battery ; /* In jiffies */
2021-04-11 19:42:25 +03:00
char temp_valid ; /* !=0 if following fields are valid */
char fan_valid ;
char in_valid ;
unsigned long temp_last_updated ; /* In jiffies */
unsigned long fan_last_updated ;
unsigned long in_last_updated ;
2011-03-21 19:59:36 +03:00
u16 temp [ SCH5627_NO_TEMPS ] ;
u16 fan [ SCH5627_NO_FANS ] ;
u16 in [ SCH5627_NO_IN ] ;
} ;
2021-04-11 19:42:25 +03:00
static int sch5627_update_temp ( struct sch5627_data * data )
2011-03-21 19:59:36 +03:00
{
2021-04-11 19:42:25 +03:00
int ret = 0 ;
2011-03-21 19:59:36 +03:00
int i , val ;
mutex_lock ( & data - > update_lock ) ;
/* Cache the values for 1 second */
2021-04-11 19:42:25 +03:00
if ( time_after ( jiffies , data - > temp_last_updated + HZ ) | | ! data - > temp_valid ) {
2011-03-21 19:59:36 +03:00
for ( i = 0 ; i < SCH5627_NO_TEMPS ; i + + ) {
2021-04-11 19:42:25 +03:00
val = sch56xx_read_virtual_reg12 ( data - > addr , SCH5627_REG_TEMP_MSB [ i ] ,
SCH5627_REG_TEMP_LSN [ i ] ,
SCH5627_REG_TEMP_HIGH_NIBBLE [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( unlikely ( val < 0 ) ) {
2021-04-11 19:42:25 +03:00
ret = val ;
2011-03-21 19:59:36 +03:00
goto abort ;
}
data - > temp [ i ] = val ;
}
2021-04-11 19:42:25 +03:00
data - > temp_last_updated = jiffies ;
data - > temp_valid = 1 ;
}
abort :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
static int sch5627_update_fan ( struct sch5627_data * data )
{
int ret = 0 ;
int i , val ;
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:25 +03:00
mutex_lock ( & data - > update_lock ) ;
/* Cache the values for 1 second */
if ( time_after ( jiffies , data - > fan_last_updated + HZ ) | | ! data - > fan_valid ) {
2011-03-21 19:59:36 +03:00
for ( i = 0 ; i < SCH5627_NO_FANS ; i + + ) {
2021-04-11 19:42:25 +03:00
val = sch56xx_read_virtual_reg16 ( data - > addr , SCH5627_REG_FAN [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( unlikely ( val < 0 ) ) {
2021-04-11 19:42:25 +03:00
ret = val ;
2011-03-21 19:59:36 +03:00
goto abort ;
}
data - > fan [ i ] = val ;
}
2021-04-11 19:42:25 +03:00
data - > fan_last_updated = jiffies ;
data - > fan_valid = 1 ;
}
abort :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
static int sch5627_update_in ( struct sch5627_data * data )
{
int ret = 0 ;
int i , val ;
mutex_lock ( & data - > update_lock ) ;
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:25 +03:00
/* Trigger a Vbat voltage measurement every 5 minutes */
if ( time_after ( jiffies , data - > last_battery + 300 * HZ ) ) {
sch56xx_write_virtual_reg ( data - > addr , SCH5627_REG_CTRL , data - > control | 0x10 ) ;
data - > last_battery = jiffies ;
}
/* Cache the values for 1 second */
if ( time_after ( jiffies , data - > in_last_updated + HZ ) | | ! data - > in_valid ) {
2011-03-21 19:59:36 +03:00
for ( i = 0 ; i < SCH5627_NO_IN ; i + + ) {
2021-04-11 19:42:25 +03:00
val = sch56xx_read_virtual_reg12 ( data - > addr , SCH5627_REG_IN_MSB [ i ] ,
SCH5627_REG_IN_LSN [ i ] ,
SCH5627_REG_IN_HIGH_NIBBLE [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( unlikely ( val < 0 ) ) {
2021-04-11 19:42:25 +03:00
ret = val ;
2011-03-21 19:59:36 +03:00
goto abort ;
}
data - > in [ i ] = val ;
}
2021-04-11 19:42:25 +03:00
data - > in_last_updated = jiffies ;
data - > in_valid = 1 ;
2011-03-21 19:59:36 +03:00
}
abort :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
2012-11-19 22:22:35 +04:00
static int sch5627_read_limits ( struct sch5627_data * data )
2011-03-21 19:59:36 +03:00
{
int i , val ;
for ( i = 0 ; i < SCH5627_NO_TEMPS ; i + + ) {
/*
* Note what SMSC calls ABS , is what lm_sensors calls max
* ( aka high ) , and HIGH is what lm_sensors calls crit .
*/
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr ,
SCH5627_REG_TEMP_ABS [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( val < 0 )
return val ;
data - > temp_max [ i ] = val ;
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr ,
SCH5627_REG_TEMP_HIGH [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( val < 0 )
return val ;
data - > temp_crit [ i ] = val ;
}
for ( i = 0 ; i < SCH5627_NO_FANS ; i + + ) {
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg16 ( data - > addr ,
SCH5627_REG_FAN_MIN [ i ] ) ;
2011-03-21 19:59:36 +03:00
if ( val < 0 )
return val ;
data - > fan_min [ i ] = val ;
}
return 0 ;
}
static int reg_to_temp ( u16 reg )
{
return ( reg * 625 ) / 10 - 64000 ;
}
static int reg_to_temp_limit ( u8 reg )
{
return ( reg - 64 ) * 1000 ;
}
static int reg_to_rpm ( u16 reg )
{
if ( reg = = 0 )
return - EIO ;
if ( reg = = 0xffff )
return 0 ;
return 5400540 / reg ;
}
2021-04-11 19:42:24 +03:00
static umode_t sch5627_is_visible ( const void * drvdata , enum hwmon_sensor_types type , u32 attr ,
int channel )
2011-03-21 19:59:36 +03:00
{
2021-04-11 19:42:24 +03:00
return 0444 ;
2011-03-21 19:59:36 +03:00
}
2021-04-11 19:42:24 +03:00
static int sch5627_read ( struct device * dev , enum hwmon_sensor_types type , u32 attr , int channel ,
long * val )
2011-03-21 19:59:36 +03:00
{
2021-04-11 19:42:25 +03:00
struct sch5627_data * data = dev_get_drvdata ( dev ) ;
2021-04-11 19:42:24 +03:00
int ret ;
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:24 +03:00
switch ( type ) {
case hwmon_temp :
2021-04-11 19:42:25 +03:00
ret = sch5627_update_temp ( data ) ;
if ( ret < 0 )
return ret ;
2021-04-11 19:42:24 +03:00
switch ( attr ) {
case hwmon_temp_input :
* val = reg_to_temp ( data - > temp [ channel ] ) ;
return 0 ;
case hwmon_temp_max :
* val = reg_to_temp_limit ( data - > temp_max [ channel ] ) ;
return 0 ;
case hwmon_temp_crit :
* val = reg_to_temp_limit ( data - > temp_crit [ channel ] ) ;
return 0 ;
case hwmon_temp_fault :
* val = ( data - > temp [ channel ] = = 0 ) ;
return 0 ;
default :
break ;
}
break ;
case hwmon_fan :
2021-04-11 19:42:25 +03:00
ret = sch5627_update_fan ( data ) ;
if ( ret < 0 )
return ret ;
2021-04-11 19:42:24 +03:00
switch ( attr ) {
case hwmon_fan_input :
ret = reg_to_rpm ( data - > fan [ channel ] ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return 0 ;
case hwmon_fan_min :
ret = reg_to_rpm ( data - > fan_min [ channel ] ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
return 0 ;
case hwmon_fan_fault :
* val = ( data - > fan [ channel ] = = 0xffff ) ;
return 0 ;
default :
break ;
}
break ;
case hwmon_in :
2021-04-11 19:42:25 +03:00
ret = sch5627_update_in ( data ) ;
if ( ret < 0 )
return ret ;
2021-04-11 19:42:24 +03:00
switch ( attr ) {
case hwmon_in_input :
* val = DIV_ROUND_CLOSEST ( data - > in [ channel ] * SCH5627_REG_IN_FACTOR [ channel ] ,
10000 ) ;
return 0 ;
default :
break ;
}
break ;
default :
break ;
}
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:24 +03:00
return - EOPNOTSUPP ;
2011-03-21 19:59:36 +03:00
}
2021-04-11 19:42:24 +03:00
static int sch5627_read_string ( struct device * dev , enum hwmon_sensor_types type , u32 attr ,
int channel , const char * * str )
2011-03-21 19:59:36 +03:00
{
2021-04-11 19:42:24 +03:00
switch ( type ) {
case hwmon_in :
switch ( attr ) {
case hwmon_in_label :
* str = SCH5627_IN_LABELS [ channel ] ;
return 0 ;
default :
break ;
}
break ;
default :
break ;
}
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:24 +03:00
return - EOPNOTSUPP ;
2011-03-21 19:59:36 +03:00
}
2021-04-11 19:42:24 +03:00
static const struct hwmon_ops sch5627_ops = {
. is_visible = sch5627_is_visible ,
. read = sch5627_read ,
. read_string = sch5627_read_string ,
} ;
2011-03-21 19:59:36 +03:00
2021-04-11 19:42:24 +03:00
static const struct hwmon_channel_info * sch5627_info [ ] = {
HWMON_CHANNEL_INFO ( chip , HWMON_C_REGISTER_TZ ) ,
HWMON_CHANNEL_INFO ( temp ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT
) ,
HWMON_CHANNEL_INFO ( fan ,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT ,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT ,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT ,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT
) ,
HWMON_CHANNEL_INFO ( in ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LABEL ,
HWMON_I_INPUT
) ,
2011-03-21 19:59:36 +03:00
NULL
} ;
2021-04-11 19:42:24 +03:00
static const struct hwmon_chip_info sch5627_chip_info = {
. ops = & sch5627_ops ,
. info = sch5627_info ,
2011-03-21 19:59:36 +03:00
} ;
2012-11-19 22:22:35 +04:00
static int sch5627_probe ( struct platform_device * pdev )
2011-03-21 19:59:36 +03:00
{
struct sch5627_data * data ;
2021-04-18 00:09:19 +03:00
struct device * hwmon_dev ;
2011-03-21 19:59:36 +03:00
int err , build_code , build_id , hwmon_rev , val ;
2012-06-02 22:20:21 +04:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct sch5627_data ) ,
GFP_KERNEL ) ;
2011-03-21 19:59:36 +03:00
if ( ! data )
return - ENOMEM ;
data - > addr = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) - > start ;
mutex_init ( & data - > update_lock ) ;
platform_set_drvdata ( pdev , data ) ;
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr , SCH5627_REG_HWMON_ID ) ;
2021-04-18 00:09:20 +03:00
if ( val < 0 )
return val ;
2011-03-21 19:59:36 +03:00
if ( val ! = SCH5627_HWMON_ID ) {
pr_err ( " invalid %s id: 0x%02X (expected 0x%02X) \n " , " hwmon " ,
val , SCH5627_HWMON_ID ) ;
2021-04-18 00:09:20 +03:00
return - ENODEV ;
2011-03-21 19:59:36 +03:00
}
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr , SCH5627_REG_COMPANY_ID ) ;
2021-04-18 00:09:20 +03:00
if ( val < 0 )
return val ;
2011-03-21 19:59:36 +03:00
if ( val ! = SCH5627_COMPANY_ID ) {
pr_err ( " invalid %s id: 0x%02X (expected 0x%02X) \n " , " company " ,
val , SCH5627_COMPANY_ID ) ;
2021-04-18 00:09:20 +03:00
return - ENODEV ;
2011-03-21 19:59:36 +03:00
}
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr , SCH5627_REG_PRIMARY_ID ) ;
2021-04-18 00:09:20 +03:00
if ( val < 0 )
return val ;
2011-03-21 19:59:36 +03:00
if ( val ! = SCH5627_PRIMARY_ID ) {
pr_err ( " invalid %s id: 0x%02X (expected 0x%02X) \n " , " primary " ,
val , SCH5627_PRIMARY_ID ) ;
2021-04-18 00:09:20 +03:00
return - ENODEV ;
2011-03-21 19:59:36 +03:00
}
2011-07-25 23:46:09 +04:00
build_code = sch56xx_read_virtual_reg ( data - > addr ,
SCH5627_REG_BUILD_CODE ) ;
2021-04-18 00:09:20 +03:00
if ( build_code < 0 )
return build_code ;
2011-03-21 19:59:36 +03:00
2011-07-25 23:46:09 +04:00
build_id = sch56xx_read_virtual_reg16 ( data - > addr ,
SCH5627_REG_BUILD_ID ) ;
2021-04-18 00:09:20 +03:00
if ( build_id < 0 )
return build_id ;
2011-03-21 19:59:36 +03:00
2011-07-25 23:46:09 +04:00
hwmon_rev = sch56xx_read_virtual_reg ( data - > addr ,
SCH5627_REG_HWMON_REV ) ;
2021-04-18 00:09:20 +03:00
if ( hwmon_rev < 0 )
return hwmon_rev ;
2011-03-21 19:59:36 +03:00
2011-07-25 23:46:09 +04:00
val = sch56xx_read_virtual_reg ( data - > addr , SCH5627_REG_CTRL ) ;
2021-04-18 00:09:20 +03:00
if ( val < 0 )
return val ;
2011-05-25 22:43:33 +04:00
data - > control = val ;
if ( ! ( data - > control & 0x01 ) ) {
2011-03-21 19:59:36 +03:00
pr_err ( " hardware monitoring not enabled \n " ) ;
2021-04-18 00:09:20 +03:00
return - ENODEV ;
2011-03-21 19:59:36 +03:00
}
2011-05-25 22:43:33 +04:00
/* Trigger a Vbat voltage measurement, so that we get a valid reading
the first time we read Vbat */
2011-07-25 23:46:09 +04:00
sch56xx_write_virtual_reg ( data - > addr , SCH5627_REG_CTRL ,
2011-05-25 22:43:33 +04:00
data - > control | 0x10 ) ;
data - > last_battery = jiffies ;
2011-03-21 19:59:36 +03:00
/*
* Read limits , we do this only once as reading a register on
* the sch5627 is quite expensive ( and they don ' t change ) .
*/
err = sch5627_read_limits ( data ) ;
if ( err )
2021-04-18 00:09:20 +03:00
return err ;
2011-03-21 19:59:36 +03:00
2011-07-25 23:46:09 +04:00
pr_info ( " found %s chip at %#hx \n " , DEVNAME , data - > addr ) ;
2011-03-21 19:59:36 +03:00
pr_info ( " firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X \n " ,
build_code , build_id , hwmon_rev ) ;
2021-04-18 00:09:19 +03:00
hwmon_dev = devm_hwmon_device_register_with_info ( & pdev - > dev , DEVNAME , data ,
& sch5627_chip_info , NULL ) ;
2021-04-18 00:09:20 +03:00
if ( IS_ERR ( hwmon_dev ) )
return PTR_ERR ( hwmon_dev ) ;
2011-03-21 19:59:36 +03:00
2012-03-18 16:05:08 +04:00
/* Note failing to register the watchdog is not a fatal error */
2021-05-08 16:14:54 +03:00
sch56xx_watchdog_register ( & pdev - > dev , data - > addr ,
( build_code < < 24 ) | ( build_id < < 8 ) | hwmon_rev ,
& data - > update_lock , 1 ) ;
2012-03-18 16:05:08 +04:00
2011-03-21 19:59:36 +03:00
return 0 ;
}
static struct platform_driver sch5627_driver = {
. driver = {
. name = DRVNAME ,
} ,
. probe = sch5627_probe ,
} ;
2011-11-25 11:31:00 +04:00
module_platform_driver ( sch5627_driver ) ;
2011-03-21 19:59:36 +03:00
MODULE_DESCRIPTION ( " SMSC SCH5627 Hardware Monitoring Driver " ) ;
2011-07-03 15:32:53 +04:00
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
2011-03-21 19:59:36 +03:00
MODULE_LICENSE ( " GPL " ) ;