2007-07-09 00:43:00 +04:00
/*
2012-01-15 10:33:01 +04:00
* thmc50 . c - Part of lm_sensors , Linux kernel modules for hardware
* monitoring
* Copyright ( C ) 2007 Krzysztof Helt < krzysztof . h1 @ wp . pl >
* Based on 2.4 driver by Frodo Looijaard < frodol @ dds . nl > and
* Philip Edelbrock < phil @ netroedge . com >
*
* 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 ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
2007-07-09 00:43:00 +04:00
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/err.h>
# include <linux/mutex.h>
2012-10-10 17:25:56 +04:00
# include <linux/jiffies.h>
2007-07-09 00:43:00 +04:00
MODULE_LICENSE ( " GPL " ) ;
/* Addresses to scan */
2008-02-18 06:28:03 +03:00
static const unsigned short normal_i2c [ ] = { 0x2c , 0x2d , 0x2e , I2C_CLIENT_END } ;
2007-07-09 00:43:00 +04:00
/* Insmod parameters */
2009-12-14 23:17:27 +03:00
enum chips { thmc50 , adm1022 } ;
2009-12-09 22:35:59 +03:00
static unsigned short adm1022_temp3 [ 16 ] ;
static unsigned int adm1022_temp3_num ;
module_param_array ( adm1022_temp3 , ushort , & adm1022_temp3_num , 0 ) ;
2013-01-10 22:01:24 +04:00
MODULE_PARM_DESC ( adm1022_temp3 ,
" List of adapter,address pairs to enable 3rd temperature (ADM1022 only) " ) ;
2007-07-09 00:43:00 +04:00
/* Many THMC50 constants specified below */
/* The THMC50 registers */
# define THMC50_REG_CONF 0x40
# define THMC50_REG_COMPANY_ID 0x3E
# define THMC50_REG_DIE_CODE 0x3F
# define THMC50_REG_ANALOG_OUT 0x19
2007-08-22 22:05:22 +04:00
/*
2007-09-09 19:35:34 +04:00
* The mirror status register cannot be used as
* reading it does not clear alarms .
2007-08-22 22:05:22 +04:00
*/
2007-09-09 19:35:34 +04:00
# define THMC50_REG_INTR 0x41
2007-07-09 00:43:00 +04:00
2008-02-17 17:39:24 +03:00
static const u8 THMC50_REG_TEMP [ ] = { 0x27 , 0x26 , 0x20 } ;
static const u8 THMC50_REG_TEMP_MIN [ ] = { 0x3A , 0x38 , 0x2C } ;
static const u8 THMC50_REG_TEMP_MAX [ ] = { 0x39 , 0x37 , 0x2B } ;
2008-08-07 00:41:05 +04:00
static const u8 THMC50_REG_TEMP_CRITICAL [ ] = { 0x13 , 0x14 , 0x14 } ;
static const u8 THMC50_REG_TEMP_DEFAULT [ ] = { 0x17 , 0x18 , 0x18 } ;
2007-07-09 00:43:00 +04:00
# define THMC50_REG_CONF_nFANOFF 0x20
2008-08-07 00:41:05 +04:00
# define THMC50_REG_CONF_PROGRAMMED 0x08
2007-07-09 00:43:00 +04:00
/* Each client has this additional data */
struct thmc50_data {
2014-07-17 15:06:08 +04:00
struct i2c_client * client ;
const struct attribute_group * groups [ 3 ] ;
2007-07-09 00:43:00 +04:00
struct mutex update_lock ;
enum chips type ;
unsigned long last_updated ; /* In jiffies */
char has_temp3 ; /* !=0 if it is ADM1022 in temp3 mode */
char valid ; /* !=0 if following fields are valid */
/* Register values */
s8 temp_input [ 3 ] ;
s8 temp_max [ 3 ] ;
s8 temp_min [ 3 ] ;
2008-08-07 00:41:05 +04:00
s8 temp_critical [ 3 ] ;
2007-07-09 00:43:00 +04:00
u8 analog_out ;
2007-08-22 22:05:22 +04:00
u8 alarms ;
2007-07-09 00:43:00 +04:00
} ;
2014-07-17 15:05:23 +04:00
static struct thmc50_data * thmc50_update_device ( struct device * dev )
{
2014-07-17 15:06:08 +04:00
struct thmc50_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2014-07-17 15:05:23 +04:00
int timeout = HZ / 5 + ( data - > type = = thmc50 ? HZ : 0 ) ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:05:23 +04:00
mutex_lock ( & data - > update_lock ) ;
2008-07-16 21:30:16 +04:00
2014-07-17 15:05:23 +04:00
if ( time_after ( jiffies , data - > last_updated + timeout )
| | ! data - > valid ) {
int temps = data - > has_temp3 ? 3 : 2 ;
int i ;
int prog = i2c_smbus_read_byte_data ( client , THMC50_REG_CONF ) ;
prog & = THMC50_REG_CONF_PROGRAMMED ;
for ( i = 0 ; i < temps ; i + + ) {
data - > temp_input [ i ] = i2c_smbus_read_byte_data ( client ,
THMC50_REG_TEMP [ i ] ) ;
data - > temp_max [ i ] = i2c_smbus_read_byte_data ( client ,
THMC50_REG_TEMP_MAX [ i ] ) ;
data - > temp_min [ i ] = i2c_smbus_read_byte_data ( client ,
THMC50_REG_TEMP_MIN [ i ] ) ;
data - > temp_critical [ i ] =
i2c_smbus_read_byte_data ( client ,
prog ? THMC50_REG_TEMP_CRITICAL [ i ]
: THMC50_REG_TEMP_DEFAULT [ i ] ) ;
}
data - > analog_out =
i2c_smbus_read_byte_data ( client , THMC50_REG_ANALOG_OUT ) ;
data - > alarms =
i2c_smbus_read_byte_data ( client , THMC50_REG_INTR ) ;
data - > last_updated = jiffies ;
data - > valid = 1 ;
}
mutex_unlock ( & data - > update_lock ) ;
return data ;
}
2007-07-09 00:43:00 +04:00
static ssize_t show_analog_out ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > analog_out ) ;
}
static ssize_t set_analog_out ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
2014-07-17 15:06:08 +04:00
struct thmc50_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2007-07-09 00:43:00 +04:00
int config ;
2012-01-15 10:33:01 +04:00
unsigned long tmp ;
int err ;
err = kstrtoul ( buf , 10 , & tmp ) ;
if ( err )
return err ;
2007-07-09 00:43:00 +04:00
mutex_lock ( & data - > update_lock ) ;
2013-01-09 20:09:34 +04:00
data - > analog_out = clamp_val ( tmp , 0 , 255 ) ;
2007-07-09 00:43:00 +04:00
i2c_smbus_write_byte_data ( client , THMC50_REG_ANALOG_OUT ,
data - > analog_out ) ;
config = i2c_smbus_read_byte_data ( client , THMC50_REG_CONF ) ;
if ( data - > analog_out = = 0 )
config & = ~ THMC50_REG_CONF_nFANOFF ;
else
config | = THMC50_REG_CONF_nFANOFF ;
i2c_smbus_write_byte_data ( client , THMC50_REG_CONF , config ) ;
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
/* There is only one PWM mode = DC */
static ssize_t show_pwm_mode ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
return sprintf ( buf , " 0 \n " ) ;
}
/* Temperatures */
static ssize_t show_temp ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > temp_input [ nr ] * 1000 ) ;
}
static ssize_t show_temp_min ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > temp_min [ nr ] * 1000 ) ;
}
static ssize_t set_temp_min ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
2014-07-17 15:06:08 +04:00
struct thmc50_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2012-01-15 10:33:01 +04:00
long val ;
int err ;
err = kstrtol ( buf , 10 , & val ) ;
if ( err )
return err ;
2007-07-09 00:43:00 +04:00
mutex_lock ( & data - > update_lock ) ;
2013-01-09 20:09:34 +04:00
data - > temp_min [ nr ] = clamp_val ( val / 1000 , - 128 , 127 ) ;
2007-07-09 00:43:00 +04:00
i2c_smbus_write_byte_data ( client , THMC50_REG_TEMP_MIN [ nr ] ,
data - > temp_min [ nr ] ) ;
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
static ssize_t show_temp_max ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > temp_max [ nr ] * 1000 ) ;
}
static ssize_t set_temp_max ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
2014-07-17 15:06:08 +04:00
struct thmc50_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2012-01-15 10:33:01 +04:00
long val ;
int err ;
err = kstrtol ( buf , 10 , & val ) ;
if ( err )
return err ;
2007-07-09 00:43:00 +04:00
mutex_lock ( & data - > update_lock ) ;
2013-01-09 20:09:34 +04:00
data - > temp_max [ nr ] = clamp_val ( val / 1000 , - 128 , 127 ) ;
2007-07-09 00:43:00 +04:00
i2c_smbus_write_byte_data ( client , THMC50_REG_TEMP_MAX [ nr ] ,
data - > temp_max [ nr ] ) ;
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
2008-08-07 00:41:05 +04:00
static ssize_t show_temp_critical ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
int nr = to_sensor_dev_attr ( attr ) - > index ;
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > temp_critical [ nr ] * 1000 ) ;
}
2007-08-22 22:05:22 +04:00
static ssize_t show_alarm ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int index = to_sensor_dev_attr ( attr ) - > index ;
struct thmc50_data * data = thmc50_update_device ( dev ) ;
return sprintf ( buf , " %u \n " , ( data - > alarms > > index ) & 1 ) ;
}
2007-07-09 00:43:00 +04:00
# define temp_reg(offset) \
static SENSOR_DEVICE_ATTR ( temp # # offset # # _input , S_IRUGO , show_temp , \
NULL , offset - 1 ) ; \
static SENSOR_DEVICE_ATTR ( temp # # offset # # _min , S_IRUGO | S_IWUSR , \
show_temp_min , set_temp_min , offset - 1 ) ; \
static SENSOR_DEVICE_ATTR ( temp # # offset # # _max , S_IRUGO | S_IWUSR , \
2008-08-07 00:41:05 +04:00
show_temp_max , set_temp_max , offset - 1 ) ; \
static SENSOR_DEVICE_ATTR ( temp # # offset # # _crit , S_IRUGO , \
show_temp_critical , NULL , offset - 1 ) ;
2007-07-09 00:43:00 +04:00
temp_reg ( 1 ) ;
temp_reg ( 2 ) ;
temp_reg ( 3 ) ;
2007-08-22 22:05:22 +04:00
static SENSOR_DEVICE_ATTR ( temp1_alarm , S_IRUGO , show_alarm , NULL , 0 ) ;
static SENSOR_DEVICE_ATTR ( temp2_alarm , S_IRUGO , show_alarm , NULL , 5 ) ;
static SENSOR_DEVICE_ATTR ( temp3_alarm , S_IRUGO , show_alarm , NULL , 1 ) ;
static SENSOR_DEVICE_ATTR ( temp2_fault , S_IRUGO , show_alarm , NULL , 7 ) ;
static SENSOR_DEVICE_ATTR ( temp3_fault , S_IRUGO , show_alarm , NULL , 2 ) ;
2007-07-09 00:43:00 +04:00
static SENSOR_DEVICE_ATTR ( pwm1 , S_IRUGO | S_IWUSR , show_analog_out ,
set_analog_out , 0 ) ;
static SENSOR_DEVICE_ATTR ( pwm1_mode , S_IRUGO , show_pwm_mode , NULL , 0 ) ;
static struct attribute * thmc50_attributes [ ] = {
& sensor_dev_attr_temp1_max . dev_attr . attr ,
& sensor_dev_attr_temp1_min . dev_attr . attr ,
& sensor_dev_attr_temp1_input . dev_attr . attr ,
2008-08-07 00:41:05 +04:00
& sensor_dev_attr_temp1_crit . dev_attr . attr ,
2007-08-22 22:05:22 +04:00
& sensor_dev_attr_temp1_alarm . dev_attr . attr ,
2007-07-09 00:43:00 +04:00
& sensor_dev_attr_temp2_max . dev_attr . attr ,
& sensor_dev_attr_temp2_min . dev_attr . attr ,
& sensor_dev_attr_temp2_input . dev_attr . attr ,
2008-08-07 00:41:05 +04:00
& sensor_dev_attr_temp2_crit . dev_attr . attr ,
2007-08-22 22:05:22 +04:00
& sensor_dev_attr_temp2_alarm . dev_attr . attr ,
& sensor_dev_attr_temp2_fault . dev_attr . attr ,
2007-07-09 00:43:00 +04:00
& sensor_dev_attr_pwm1 . dev_attr . attr ,
& sensor_dev_attr_pwm1_mode . dev_attr . attr ,
NULL
} ;
static const struct attribute_group thmc50_group = {
. attrs = thmc50_attributes ,
} ;
/* for ADM1022 3rd temperature mode */
2007-09-18 20:44:42 +04:00
static struct attribute * temp3_attributes [ ] = {
2007-07-09 00:43:00 +04:00
& sensor_dev_attr_temp3_max . dev_attr . attr ,
& sensor_dev_attr_temp3_min . dev_attr . attr ,
& sensor_dev_attr_temp3_input . dev_attr . attr ,
2008-08-07 00:41:05 +04:00
& sensor_dev_attr_temp3_crit . dev_attr . attr ,
2007-08-22 22:05:22 +04:00
& sensor_dev_attr_temp3_alarm . dev_attr . attr ,
& sensor_dev_attr_temp3_fault . dev_attr . attr ,
2007-07-09 00:43:00 +04:00
NULL
} ;
2007-09-18 20:44:42 +04:00
static const struct attribute_group temp3_group = {
. attrs = temp3_attributes ,
2007-07-09 00:43:00 +04:00
} ;
2008-07-16 21:30:16 +04:00
/* Return 0 if detection is successful, -ENODEV otherwise */
2009-12-14 23:17:23 +03:00
static int thmc50_detect ( struct i2c_client * client ,
2008-07-16 21:30:16 +04:00
struct i2c_board_info * info )
2007-07-09 00:43:00 +04:00
{
unsigned company ;
unsigned revision ;
unsigned config ;
2008-07-16 21:30:16 +04:00
struct i2c_adapter * adapter = client - > adapter ;
2007-09-18 20:48:16 +04:00
const char * type_name ;
2007-07-09 00:43:00 +04:00
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_BYTE_DATA ) ) {
2013-01-10 22:01:24 +04:00
pr_debug ( " thmc50: detect failed, smbus byte data not supported! \n " ) ;
2008-07-16 21:30:16 +04:00
return - ENODEV ;
2007-07-09 00:43:00 +04:00
}
pr_debug ( " thmc50: Probing for THMC50 at 0x%2X on bus %d \n " ,
client - > addr , i2c_adapter_id ( client - > adapter ) ) ;
company = i2c_smbus_read_byte_data ( client , THMC50_REG_COMPANY_ID ) ;
revision = i2c_smbus_read_byte_data ( client , THMC50_REG_DIE_CODE ) ;
config = i2c_smbus_read_byte_data ( client , THMC50_REG_CONF ) ;
2009-12-09 22:35:57 +03:00
if ( revision < 0xc0 | | ( config & 0x10 ) )
return - ENODEV ;
2007-07-09 00:43:00 +04:00
2009-12-09 22:35:57 +03:00
if ( company = = 0x41 ) {
2007-07-09 00:43:00 +04:00
int id = i2c_adapter_id ( client - > adapter ) ;
int i ;
type_name = " adm1022 " ;
for ( i = 0 ; i + 1 < adm1022_temp3_num ; i + = 2 )
if ( adm1022_temp3 [ i ] = = id & &
2008-07-16 21:30:16 +04:00
adm1022_temp3 [ i + 1 ] = = client - > addr ) {
2007-07-09 00:43:00 +04:00
/* enable 2nd remote temp */
2008-07-16 21:30:16 +04:00
config | = ( 1 < < 7 ) ;
i2c_smbus_write_byte_data ( client ,
THMC50_REG_CONF ,
config ) ;
2007-07-09 00:43:00 +04:00
break ;
}
2009-12-09 22:35:57 +03:00
} else if ( company = = 0x49 ) {
2007-09-18 20:48:16 +04:00
type_name = " thmc50 " ;
2009-12-09 22:35:57 +03:00
} else {
pr_debug ( " thmc50: Detection of THMC50/ADM1022 failed \n " ) ;
return - ENODEV ;
2007-07-09 00:43:00 +04:00
}
2009-12-09 22:35:57 +03:00
2007-09-18 20:48:16 +04:00
pr_debug ( " thmc50: Detected %s (version %x, revision %x) \n " ,
type_name , ( revision > > 4 ) - 0xc , revision & 0xf ) ;
2007-07-09 00:43:00 +04:00
2008-07-16 21:30:16 +04:00
strlcpy ( info - > type , type_name , I2C_NAME_SIZE ) ;
2007-07-09 00:43:00 +04:00
2008-07-16 21:30:16 +04:00
return 0 ;
}
2014-07-17 15:06:08 +04:00
static void thmc50_init_client ( struct thmc50_data * data )
2014-07-17 15:05:23 +04:00
{
2014-07-17 15:06:08 +04:00
struct i2c_client * client = data - > client ;
2014-07-17 15:05:23 +04:00
int config ;
data - > analog_out = i2c_smbus_read_byte_data ( client ,
THMC50_REG_ANALOG_OUT ) ;
/* set up to at least 1 */
if ( data - > analog_out = = 0 ) {
data - > analog_out = 1 ;
i2c_smbus_write_byte_data ( client , THMC50_REG_ANALOG_OUT ,
data - > analog_out ) ;
}
config = i2c_smbus_read_byte_data ( client , THMC50_REG_CONF ) ;
config | = 0x1 ; /* start the chip if it is in standby mode */
if ( data - > type = = adm1022 & & ( config & ( 1 < < 7 ) ) )
data - > has_temp3 = 1 ;
i2c_smbus_write_byte_data ( client , THMC50_REG_CONF , config ) ;
}
2008-07-16 21:30:16 +04:00
static int thmc50_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
2014-07-17 15:06:08 +04:00
struct device * dev = & client - > dev ;
2008-07-16 21:30:16 +04:00
struct thmc50_data * data ;
2014-07-17 15:06:08 +04:00
struct device * hwmon_dev ;
int idx = 0 ;
2008-07-16 21:30:16 +04:00
2014-07-17 15:06:08 +04:00
data = devm_kzalloc ( dev , sizeof ( struct thmc50_data ) , GFP_KERNEL ) ;
2012-06-02 22:20:23 +04:00
if ( ! data )
return - ENOMEM ;
2008-07-16 21:30:16 +04:00
2014-07-17 15:06:08 +04:00
data - > client = client ;
2008-07-16 21:30:16 +04:00
data - > type = id - > driver_data ;
mutex_init ( & data - > update_lock ) ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:06:08 +04:00
thmc50_init_client ( data ) ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:06:08 +04:00
/* sysfs hooks */
data - > groups [ idx + + ] = & thmc50_group ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:06:08 +04:00
/* Register additional ADM1022 sysfs hooks */
2007-09-18 20:44:42 +04:00
if ( data - > has_temp3 )
2014-07-17 15:06:08 +04:00
data - > groups [ idx + + ] = & temp3_group ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:06:08 +04:00
hwmon_dev = devm_hwmon_device_register_with_groups ( dev , client - > name ,
data , data - > groups ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
2007-07-09 00:43:00 +04:00
}
2014-07-17 15:05:23 +04:00
static const struct i2c_device_id thmc50_id [ ] = {
{ " adm1022 " , adm1022 } ,
{ " thmc50 " , thmc50 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , thmc50_id ) ;
2007-07-09 00:43:00 +04:00
2014-07-17 15:05:23 +04:00
static struct i2c_driver thmc50_driver = {
. class = I2C_CLASS_HWMON ,
. driver = {
. name = " thmc50 " ,
} ,
. probe = thmc50_probe ,
. id_table = thmc50_id ,
. detect = thmc50_detect ,
. address_list = normal_i2c ,
} ;
2007-07-09 00:43:00 +04:00
2012-01-20 11:38:18 +04:00
module_i2c_driver ( thmc50_driver ) ;
2007-07-09 00:43:00 +04:00
MODULE_AUTHOR ( " Krzysztof Helt <krzysztof.h1@wp.pl> " ) ;
MODULE_DESCRIPTION ( " THMC50 driver " ) ;