2007-02-14 23:15:04 +03:00
/*
* adm1029 . c - Part of lm_sensors , Linux kernel modules for hardware monitoring
*
2014-05-09 16:04:01 +04:00
* Copyright ( C ) 2006 Corentin LABBE < clabbe . montjoie @ gmail . com >
2007-02-14 23:15:04 +03:00
*
2014-01-29 23:40:08 +04:00
* Based on LM83 Driver by Jean Delvare < jdelvare @ suse . de >
2007-02-14 23:15:04 +03:00
*
* Give only processor , motherboard temperatures and fan tachs
* Very rare chip please let me know if you use it
*
* http : //www.analog.com/UploadedFiles/Data_Sheets/ADM1029.pdf
*
*
* 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 version 2 of the License
*
* 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 .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/jiffies.h>
# include <linux/i2c.h>
# include <linux/hwmon-sysfs.h>
# include <linux/hwmon.h>
# include <linux/err.h>
# include <linux/mutex.h>
/*
* Addresses to scan
*/
2008-02-18 06:28:03 +03:00
static const unsigned short normal_i2c [ ] = { 0x28 , 0x29 , 0x2a , 0x2b , 0x2c , 0x2d ,
0x2e , 0x2f , I2C_CLIENT_END
2007-02-14 23:15:04 +03:00
} ;
/*
* The ADM1029 registers
* Manufacturer ID is 0x41 for Analog Devices
*/
# define ADM1029_REG_MAN_ID 0x0D
# define ADM1029_REG_CHIP_ID 0x0E
# define ADM1029_REG_CONFIG 0x01
# define ADM1029_REG_NB_FAN_SUPPORT 0x02
# define ADM1029_REG_TEMP_DEVICES_INSTALLED 0x06
# define ADM1029_REG_LOCAL_TEMP 0xA0
# define ADM1029_REG_REMOTE1_TEMP 0xA1
# define ADM1029_REG_REMOTE2_TEMP 0xA2
# define ADM1029_REG_LOCAL_TEMP_HIGH 0x90
# define ADM1029_REG_REMOTE1_TEMP_HIGH 0x91
# define ADM1029_REG_REMOTE2_TEMP_HIGH 0x92
# define ADM1029_REG_LOCAL_TEMP_LOW 0x98
# define ADM1029_REG_REMOTE1_TEMP_LOW 0x99
# define ADM1029_REG_REMOTE2_TEMP_LOW 0x9A
# define ADM1029_REG_FAN1 0x70
# define ADM1029_REG_FAN2 0x71
# define ADM1029_REG_FAN1_MIN 0x78
# define ADM1029_REG_FAN2_MIN 0x79
# define ADM1029_REG_FAN1_CONFIG 0x68
# define ADM1029_REG_FAN2_CONFIG 0x69
# define TEMP_FROM_REG(val) ((val) * 1000)
2012-01-08 22:34:18 +04:00
# define DIV_FROM_REG(val) (1 << (((val) >> 6) - 1))
2007-02-14 23:15:04 +03:00
/* Registers to be checked by adm1029_update_device() */
static const u8 ADM1029_REG_TEMP [ ] = {
ADM1029_REG_LOCAL_TEMP ,
ADM1029_REG_REMOTE1_TEMP ,
ADM1029_REG_REMOTE2_TEMP ,
ADM1029_REG_LOCAL_TEMP_HIGH ,
ADM1029_REG_REMOTE1_TEMP_HIGH ,
ADM1029_REG_REMOTE2_TEMP_HIGH ,
ADM1029_REG_LOCAL_TEMP_LOW ,
ADM1029_REG_REMOTE1_TEMP_LOW ,
ADM1029_REG_REMOTE2_TEMP_LOW ,
} ;
static const u8 ADM1029_REG_FAN [ ] = {
ADM1029_REG_FAN1 ,
ADM1029_REG_FAN2 ,
ADM1029_REG_FAN1_MIN ,
ADM1029_REG_FAN2_MIN ,
} ;
static const u8 ADM1029_REG_FAN_DIV [ ] = {
ADM1029_REG_FAN1_CONFIG ,
ADM1029_REG_FAN2_CONFIG ,
} ;
/*
* Client data ( each client gets its own )
*/
struct adm1029_data {
2014-06-29 05:42:17 +04:00
struct i2c_client * client ;
2007-02-14 23:15:04 +03:00
struct mutex update_lock ;
char valid ; /* zero until following fields are valid */
unsigned long last_updated ; /* in jiffies */
/* registers values, signed for temperature, unsigned for other stuff */
s8 temp [ ARRAY_SIZE ( ADM1029_REG_TEMP ) ] ;
u8 fan [ ARRAY_SIZE ( ADM1029_REG_FAN ) ] ;
u8 fan_div [ ARRAY_SIZE ( ADM1029_REG_FAN_DIV ) ] ;
} ;
2014-06-29 05:41:08 +04:00
/*
* function that update the status of the chips ( temperature for example )
*/
static struct adm1029_data * adm1029_update_device ( struct device * dev )
{
2014-06-29 05:42:17 +04:00
struct adm1029_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2014-06-29 05:41:08 +04:00
mutex_lock ( & data - > update_lock ) ;
/*
* Use the " cache " Luke , don ' t recheck values
* if there are already checked not a long time later
*/
if ( time_after ( jiffies , data - > last_updated + HZ * 2 )
| | ! data - > valid ) {
int nr ;
dev_dbg ( & client - > dev , " Updating adm1029 data \n " ) ;
for ( nr = 0 ; nr < ARRAY_SIZE ( ADM1029_REG_TEMP ) ; nr + + ) {
data - > temp [ nr ] =
i2c_smbus_read_byte_data ( client ,
ADM1029_REG_TEMP [ nr ] ) ;
}
for ( nr = 0 ; nr < ARRAY_SIZE ( ADM1029_REG_FAN ) ; nr + + ) {
data - > fan [ nr ] =
i2c_smbus_read_byte_data ( client ,
ADM1029_REG_FAN [ nr ] ) ;
}
for ( nr = 0 ; nr < ARRAY_SIZE ( ADM1029_REG_FAN_DIV ) ; nr + + ) {
data - > fan_div [ nr ] =
i2c_smbus_read_byte_data ( client ,
ADM1029_REG_FAN_DIV [ nr ] ) ;
}
data - > last_updated = jiffies ;
data - > valid = 1 ;
}
mutex_unlock ( & data - > update_lock ) ;
return data ;
}
2007-02-14 23:15:04 +03:00
/*
* Sysfs stuff
*/
static ssize_t
show_temp ( struct device * dev , struct device_attribute * devattr , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct adm1029_data * data = adm1029_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , TEMP_FROM_REG ( data - > temp [ attr - > index ] ) ) ;
}
static ssize_t
show_fan ( struct device * dev , struct device_attribute * devattr , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct adm1029_data * data = adm1029_update_device ( dev ) ;
u16 val ;
2008-10-17 19:51:20 +04:00
if ( data - > fan [ attr - > index ] = = 0
| | ( data - > fan_div [ attr - > index ] & 0xC0 ) = = 0
2007-02-14 23:15:04 +03:00
| | data - > fan [ attr - > index ] = = 255 ) {
return sprintf ( buf , " 0 \n " ) ;
}
val = 1880 * 120 / DIV_FROM_REG ( data - > fan_div [ attr - > index ] )
/ data - > fan [ attr - > index ] ;
return sprintf ( buf , " %d \n " , val ) ;
}
static ssize_t
show_fan_div ( struct device * dev , struct device_attribute * devattr , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct adm1029_data * data = adm1029_update_device ( dev ) ;
2008-10-17 19:51:20 +04:00
if ( ( data - > fan_div [ attr - > index ] & 0xC0 ) = = 0 )
2007-02-14 23:15:04 +03:00
return sprintf ( buf , " 0 \n " ) ;
return sprintf ( buf , " %d \n " , DIV_FROM_REG ( data - > fan_div [ attr - > index ] ) ) ;
}
static ssize_t set_fan_div ( struct device * dev ,
struct device_attribute * devattr , const char * buf , size_t count )
{
2014-06-29 05:42:17 +04:00
struct adm1029_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
2007-02-14 23:15:04 +03:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
u8 reg ;
2012-01-08 22:34:18 +04:00
long val ;
int ret = kstrtol ( buf , 10 , & val ) ;
if ( ret < 0 )
return ret ;
2007-02-14 23:15:04 +03:00
mutex_lock ( & data - > update_lock ) ;
/*Read actual config */
reg = i2c_smbus_read_byte_data ( client ,
ADM1029_REG_FAN_DIV [ attr - > index ] ) ;
switch ( val ) {
case 1 :
val = 1 ;
break ;
case 2 :
val = 2 ;
break ;
case 4 :
val = 3 ;
break ;
default :
mutex_unlock ( & data - > update_lock ) ;
2013-01-10 22:01:24 +04:00
dev_err ( & client - > dev ,
" fan_div value %ld not supported. Choose one of 1, 2 or 4! \n " ,
val ) ;
2007-02-14 23:15:04 +03:00
return - EINVAL ;
}
/* Update the value */
reg = ( reg & 0x3F ) | ( val < < 6 ) ;
2014-07-02 04:29:55 +04:00
/* Update the cache */
data - > fan_div [ attr - > index ] = reg ;
2007-02-14 23:15:04 +03:00
/* Write value */
i2c_smbus_write_byte_data ( client ,
ADM1029_REG_FAN_DIV [ attr - > index ] , reg ) ;
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
/*
2012-01-19 23:02:14 +04:00
* Access rights on sysfs . S_IRUGO : Is Readable by User , Group and Others
* S_IWUSR : Is Writable by User .
*/
2007-02-14 23:15:04 +03:00
static SENSOR_DEVICE_ATTR ( temp1_input , S_IRUGO , show_temp , NULL , 0 ) ;
static SENSOR_DEVICE_ATTR ( temp2_input , S_IRUGO , show_temp , NULL , 1 ) ;
static SENSOR_DEVICE_ATTR ( temp3_input , S_IRUGO , show_temp , NULL , 2 ) ;
static SENSOR_DEVICE_ATTR ( temp1_max , S_IRUGO , show_temp , NULL , 3 ) ;
static SENSOR_DEVICE_ATTR ( temp2_max , S_IRUGO , show_temp , NULL , 4 ) ;
static SENSOR_DEVICE_ATTR ( temp3_max , S_IRUGO , show_temp , NULL , 5 ) ;
static SENSOR_DEVICE_ATTR ( temp1_min , S_IRUGO , show_temp , NULL , 6 ) ;
static SENSOR_DEVICE_ATTR ( temp2_min , S_IRUGO , show_temp , NULL , 7 ) ;
static SENSOR_DEVICE_ATTR ( temp3_min , S_IRUGO , show_temp , NULL , 8 ) ;
static SENSOR_DEVICE_ATTR ( fan1_input , S_IRUGO , show_fan , NULL , 0 ) ;
static SENSOR_DEVICE_ATTR ( fan2_input , S_IRUGO , show_fan , NULL , 1 ) ;
static SENSOR_DEVICE_ATTR ( fan1_min , S_IRUGO , show_fan , NULL , 2 ) ;
static SENSOR_DEVICE_ATTR ( fan2_min , S_IRUGO , show_fan , NULL , 3 ) ;
static SENSOR_DEVICE_ATTR ( fan1_div , S_IRUGO | S_IWUSR ,
show_fan_div , set_fan_div , 0 ) ;
static SENSOR_DEVICE_ATTR ( fan2_div , S_IRUGO | S_IWUSR ,
show_fan_div , set_fan_div , 1 ) ;
2014-06-29 05:42:17 +04:00
static struct attribute * adm1029_attrs [ ] = {
2007-02-14 23:15:04 +03:00
& sensor_dev_attr_temp1_input . dev_attr . attr ,
& sensor_dev_attr_temp1_min . dev_attr . attr ,
& sensor_dev_attr_temp1_max . dev_attr . attr ,
& sensor_dev_attr_temp2_input . dev_attr . attr ,
& sensor_dev_attr_temp2_min . dev_attr . attr ,
& sensor_dev_attr_temp2_max . dev_attr . attr ,
& sensor_dev_attr_temp3_input . dev_attr . attr ,
& sensor_dev_attr_temp3_min . dev_attr . attr ,
& sensor_dev_attr_temp3_max . dev_attr . attr ,
& sensor_dev_attr_fan1_input . dev_attr . attr ,
& sensor_dev_attr_fan2_input . dev_attr . attr ,
& sensor_dev_attr_fan1_min . dev_attr . attr ,
& sensor_dev_attr_fan2_min . dev_attr . attr ,
& sensor_dev_attr_fan1_div . dev_attr . attr ,
& sensor_dev_attr_fan2_div . dev_attr . attr ,
NULL
} ;
2014-06-29 05:42:17 +04:00
ATTRIBUTE_GROUPS ( adm1029 ) ;
2007-02-14 23:15:04 +03:00
/*
* Real code
*/
2008-07-16 21:30:09 +04:00
/* Return 0 if detection is successful, -ENODEV otherwise */
2009-12-14 23:17:23 +03:00
static int adm1029_detect ( struct i2c_client * client ,
2008-07-16 21:30:09 +04:00
struct i2c_board_info * info )
2007-02-14 23:15:04 +03:00
{
2008-07-16 21:30:09 +04:00
struct i2c_adapter * adapter = client - > adapter ;
2009-12-09 22:35:57 +03:00
u8 man_id , chip_id , temp_devices_installed , nb_fan_support ;
2007-02-14 23:15:04 +03:00
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_BYTE_DATA ) )
2008-07-16 21:30:09 +04:00
return - ENODEV ;
2007-02-14 23:15:04 +03:00
2012-01-19 23:02:14 +04:00
/*
* ADM1029 doesn ' t have CHIP ID , check just MAN ID
2007-02-14 23:15:04 +03:00
* For better detection we check also ADM1029_TEMP_DEVICES_INSTALLED ,
* ADM1029_REG_NB_FAN_SUPPORT and compare it with possible values
* documented
*/
2009-12-09 22:35:57 +03:00
man_id = i2c_smbus_read_byte_data ( client , ADM1029_REG_MAN_ID ) ;
chip_id = i2c_smbus_read_byte_data ( client , ADM1029_REG_CHIP_ID ) ;
temp_devices_installed = i2c_smbus_read_byte_data ( client ,
2007-02-14 23:15:04 +03:00
ADM1029_REG_TEMP_DEVICES_INSTALLED ) ;
2009-12-09 22:35:57 +03:00
nb_fan_support = i2c_smbus_read_byte_data ( client ,
2007-02-14 23:15:04 +03:00
ADM1029_REG_NB_FAN_SUPPORT ) ;
2009-12-09 22:35:57 +03:00
/* 0x41 is Analog Devices */
if ( man_id ! = 0x41 | | ( temp_devices_installed & 0xf9 ) ! = 0x01
| | nb_fan_support ! = 0x03 )
return - ENODEV ;
if ( ( chip_id & 0xF0 ) ! = 0x00 ) {
2012-01-19 23:02:14 +04:00
/*
* There are no " official " CHIP ID , so actually
* we use Major / Minor revision for that
*/
2013-01-10 22:01:24 +04:00
pr_info ( " Unknown major revision %x, please let us know \n " ,
chip_id ) ;
2009-12-09 22:35:57 +03:00
return - ENODEV ;
2007-02-14 23:15:04 +03:00
}
2009-12-09 22:35:57 +03:00
2008-07-16 21:30:09 +04:00
strlcpy ( info - > type , " adm1029 " , I2C_NAME_SIZE ) ;
2007-02-14 23:15:04 +03:00
2008-07-16 21:30:09 +04:00
return 0 ;
}
2014-06-29 05:41:08 +04:00
static int adm1029_init_client ( struct i2c_client * client )
{
u8 config ;
config = i2c_smbus_read_byte_data ( client , ADM1029_REG_CONFIG ) ;
if ( ( config & 0x10 ) = = 0 ) {
i2c_smbus_write_byte_data ( client , ADM1029_REG_CONFIG ,
config | 0x10 ) ;
}
/* recheck config */
config = i2c_smbus_read_byte_data ( client , ADM1029_REG_CONFIG ) ;
if ( ( config & 0x10 ) = = 0 ) {
dev_err ( & client - > dev , " Initialization failed! \n " ) ;
return 0 ;
}
return 1 ;
}
2008-07-16 21:30:09 +04:00
static int adm1029_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
2014-06-29 05:42:17 +04:00
struct device * dev = & client - > dev ;
2008-07-16 21:30:09 +04:00
struct adm1029_data * data ;
2014-06-29 05:42:17 +04:00
struct device * hwmon_dev ;
2008-07-16 21:30:09 +04:00
2014-06-29 05:42:17 +04:00
data = devm_kzalloc ( dev , sizeof ( struct adm1029_data ) , GFP_KERNEL ) ;
2012-06-02 20:57:59 +04:00
if ( ! data )
return - ENOMEM ;
2007-02-14 23:15:04 +03:00
2014-06-29 05:42:17 +04:00
data - > client = client ;
2007-02-14 23:15:04 +03:00
mutex_init ( & data - > update_lock ) ;
/*
* Initialize the ADM1029 chip
* Check config register
*/
2012-06-02 20:57:59 +04:00
if ( adm1029_init_client ( client ) = = 0 )
return - ENODEV ;
2007-02-14 23:15:04 +03:00
2014-06-29 05:42:17 +04:00
hwmon_dev = devm_hwmon_device_register_with_groups ( dev , client - > name ,
data ,
adm1029_groups ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
2007-02-14 23:15:04 +03:00
}
2014-06-29 05:41:08 +04:00
static const struct i2c_device_id adm1029_id [ ] = {
{ " adm1029 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , adm1029_id ) ;
2007-02-14 23:15:04 +03:00
2014-06-29 05:41:08 +04:00
static struct i2c_driver adm1029_driver = {
. class = I2C_CLASS_HWMON ,
. driver = {
. name = " adm1029 " ,
} ,
. probe = adm1029_probe ,
. id_table = adm1029_id ,
. detect = adm1029_detect ,
. address_list = normal_i2c ,
} ;
2007-02-14 23:15:04 +03:00
2012-01-20 11:38:18 +04:00
module_i2c_driver ( adm1029_driver ) ;
2007-02-14 23:15:04 +03:00
2014-05-09 16:04:01 +04:00
MODULE_AUTHOR ( " Corentin LABBE <clabbe.montjoie@gmail.com> " ) ;
2007-02-14 23:15:04 +03:00
MODULE_DESCRIPTION ( " adm1029 driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;