2007-02-14 23:15:04 +03:00
/*
* adm1029 . c - Part of lm_sensors , Linux kernel modules for hardware monitoring
*
* Copyright ( C ) 2006 Corentin LABBE < corentin . labbe @ geomatys . fr >
*
* Based on LM83 Driver by Jean Delvare < khali @ linux - fr . org >
*
* 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)
# define DIV_FROM_REG(val) ( 1 << (((val) >> 6) - 1))
/* 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 ,
} ;
/*
* Functions declaration
*/
2008-07-16 21:30:09 +04:00
static int adm1029_probe ( struct i2c_client * client ,
const struct i2c_device_id * id ) ;
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 ) ;
static int adm1029_remove ( struct i2c_client * client ) ;
2007-02-14 23:15:04 +03:00
static struct adm1029_data * adm1029_update_device ( struct device * dev ) ;
static int adm1029_init_client ( struct i2c_client * client ) ;
/*
* Driver data ( common to all clients )
*/
2008-07-16 21:30:09 +04:00
static const struct i2c_device_id adm1029_id [ ] = {
2009-12-14 23:17:26 +03:00
{ " adm1029 " , 0 } ,
2008-07-16 21:30:09 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , adm1029_id ) ;
2007-02-14 23:15:04 +03:00
static struct i2c_driver adm1029_driver = {
2008-07-16 21:30:09 +04:00
. class = I2C_CLASS_HWMON ,
2007-02-14 23:15:04 +03:00
. driver = {
. name = " adm1029 " ,
} ,
2008-07-16 21:30:09 +04:00
. probe = adm1029_probe ,
. remove = adm1029_remove ,
. id_table = adm1029_id ,
. detect = adm1029_detect ,
2009-12-14 23:17:25 +03:00
. address_list = normal_i2c ,
2007-02-14 23:15:04 +03:00
} ;
/*
* Client data ( each client gets its own )
*/
struct adm1029_data {
2007-08-21 00:46:20 +04:00
struct device * hwmon_dev ;
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 ) ] ;
} ;
/*
* 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 )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct adm1029_data * data = i2c_get_clientdata ( client ) ;
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
long val = simple_strtol ( buf , NULL , 10 ) ;
u8 reg ;
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 ) ;
dev_err ( & client - > dev , " fan_div value %ld not "
" supported. Choose one of 1, 2 or 4! \n " , val ) ;
return - EINVAL ;
}
/* Update the value */
reg = ( reg & 0x3F ) | ( val < < 6 ) ;
/* Write value */
i2c_smbus_write_byte_data ( client ,
ADM1029_REG_FAN_DIV [ attr - > index ] , reg ) ;
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
/*
Access rights on sysfs , S_IRUGO stand for Is Readable by User , Group and Others
S_IWUSR stand for Is Writable by User
*/
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 ) ;
static struct attribute * adm1029_attributes [ ] = {
& 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
} ;
static const struct attribute_group adm1029_group = {
. attrs = adm1029_attributes ,
} ;
/*
* 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
/* ADM1029 doesn't have CHIP ID, check just MAN ID
* 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 ) {
/* There are no "official" CHIP ID, so actually
* we use Major / Minor revision for that */
pr_info ( " adm1029: Unknown major revision %x, "
" please let us know \n " , chip_id ) ;
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 ;
}
static int adm1029_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct adm1029_data * data ;
int err ;
data = kzalloc ( sizeof ( struct adm1029_data ) , GFP_KERNEL ) ;
if ( ! data ) {
err = - ENOMEM ;
goto exit ;
2007-02-14 23:15:04 +03:00
}
2008-07-16 21:30:09 +04:00
i2c_set_clientdata ( client , data ) ;
2007-02-14 23:15:04 +03:00
mutex_init ( & data - > update_lock ) ;
/*
* Initialize the ADM1029 chip
* Check config register
*/
2008-07-16 21:30:09 +04:00
if ( adm1029_init_client ( client ) = = 0 ) {
err = - ENODEV ;
goto exit_free ;
}
2007-02-14 23:15:04 +03:00
/* Register sysfs hooks */
if ( ( err = sysfs_create_group ( & client - > dev . kobj , & adm1029_group ) ) )
2008-07-16 21:30:09 +04:00
goto exit_free ;
2007-02-14 23:15:04 +03:00
2007-08-21 00:46:20 +04:00
data - > hwmon_dev = hwmon_device_register ( & client - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
err = PTR_ERR ( data - > hwmon_dev ) ;
2007-02-14 23:15:04 +03:00
goto exit_remove_files ;
}
return 0 ;
exit_remove_files :
sysfs_remove_group ( & client - > dev . kobj , & adm1029_group ) ;
exit_free :
kfree ( data ) ;
exit :
return err ;
}
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_remove ( struct i2c_client * client )
2007-02-14 23:15:04 +03:00
{
struct adm1029_data * data = i2c_get_clientdata ( client ) ;
2007-08-21 00:46:20 +04:00
hwmon_device_unregister ( data - > hwmon_dev ) ;
2007-02-14 23:15:04 +03:00
sysfs_remove_group ( & client - > dev . kobj , & adm1029_group ) ;
kfree ( data ) ;
return 0 ;
}
/*
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 18:09:05 +03:00
function that update the status of the chips ( temperature for example )
2007-02-14 23:15:04 +03:00
*/
static struct adm1029_data * adm1029_update_device ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct adm1029_data * data = i2c_get_clientdata ( client ) ;
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 ;
}
/*
Common module stuff
*/
static int __init sensors_adm1029_init ( void )
{
return i2c_add_driver ( & adm1029_driver ) ;
}
static void __exit sensors_adm1029_exit ( void )
{
i2c_del_driver ( & adm1029_driver ) ;
}
MODULE_AUTHOR ( " Corentin LABBE <corentin.labbe@geomatys.fr> " ) ;
MODULE_DESCRIPTION ( " adm1029 driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
module_init ( sensors_adm1029_init ) ;
module_exit ( sensors_adm1029_exit ) ;