Hi everybody, Here is the first version of the adm1030 sensor driver I've made. It is tested on my MSI MegaPC 651 which is SiS chipset based. The driver is written for the Linux 2.6.4 kernel, if you want to compile it just add obj-m += adm1030.o to the drivers/i2c/chips/Makefile. It's ugly, but if you do not see a lot of issues in the code, I'll prepare a patch for the latest 2.6.5-mm kernel. What the driver can do : - Read system temperature ( temp_local_in ) - Read CPU Temperature ( temp_remote_in ) - Set minimum CPU fan speed ( fan_rpm ) - Read CPU fan speed (pwm_input) What is it supposed to do but not verified correctly: - temp_local_min, temp_local_range are the values to start the fan and the range to let the fan active. - temp_remote_min, temp_remote_range are the values to start the fan and the range to let the fan active. Please give me your feedbacks, Thanks, Alex. The code is following inlined : ++++++++++++++++++++++++++ adm1030.c +++++++++++++++++++++++++++++++++++ /* adm1030.c - Part of lm_sensors, Linux kernel modules for hardware monitoring Based on lm75.c Copyright (c) 1998, 1999 Frodo Looijaard <frodol at dds.nl> Copyright (c) 2004 Alexandre d'Alton <alex at alexdalton.org> 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 02139, USA. */ #include <linux/config.h> #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/i2c.h> #include <linux/i2c-sensor.h> #include "adm1030.h" /* Addresses to scan */ static unsigned short normal_i2c[] = { I2C_CLIENT_END }; static unsigned short normal_i2c_range[] = { 0x2e, I2C_CLIENT_END }; static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END }; static unsigned int normal_isa_range[] = { I2C_CLIENT_ISA_END }; /* Insmod parameters */ SENSORS_INSMOD_1(adm1030); /* Each client has this additional data */ struct adm1030_data { struct semaphore update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ u16 temp_local_in; /* Register values */ u16 temp_remote_in; u16 fan_rpm; u16 conf1; u16 conf2; u16 ftac; u16 pwm; u16 temp_local_min; u16 temp_local_range; u16 temp_remote_min; u16 temp_remote_range; }; static int adm1030_attach_adapter(struct i2c_adapter *adapter); static int adm1030_detect(struct i2c_adapter *adapter, int address, int kind); static void adm1030_init_client(struct i2c_client *client); static int adm1030_detach_client(struct i2c_client *client); static int adm1030_read_value(struct i2c_client *client, u8 reg); static int adm1030_write_value(struct i2c_client *client, u8 reg, u16 value); static struct adm1030_data *adm1030_update_device(struct device *dev); /* This is the driver that will be inserted */ static struct i2c_driver adm1030_driver = { .owner = THIS_MODULE, .name = "adm1030", .id = I2C_DRIVERID_ADM1030, .flags = I2C_DF_NOTIFY, .attach_adapter = adm1030_attach_adapter, .detach_client = adm1030_detach_client, }; static int adm1030_id = 0; #define show(value) \ static ssize_t show_##value(struct device *dev, char *buf) \ { \ struct adm1030_data *data = adm1030_update_device(dev); \ return sprintf(buf, "%d\n", data->value); \ } show(temp_local_in); show(temp_remote_in); show(conf1); show(conf2); show(ftac); show(pwm); show(temp_local_min); show(temp_local_range); show(temp_remote_min); show(temp_remote_range); static ssize_t show_fan_rpm(struct device *dev, char *buf) { int fan_speed; struct adm1030_data *data = adm1030_update_device(dev); fan_speed = (11250 * 60) / (data->fan_rpm * 8); return sprintf(buf, "%d\n", fan_speed); } #define set(value, reg) \ static ssize_t set_##value(struct device *dev, const char *buf, size_t count) \ { \ struct i2c_client *client = to_i2c_client(dev); \ struct adm1030_data *data = i2c_get_clientdata(client); \ int temp = simple_strtoul(buf, NULL, 10); \ data->value = temp; \ adm1030_write_value(client, reg, data->value); \ return count; \ } set(conf1, ADM1030_REG_CONF1); set(conf2, ADM1030_REG_CONF2); set(ftac, ADM1030_REG_FTAC); set(pwm, ADM1030_REG_FSP); void set_temp_min(struct i2c_client *client, int value, int reg) { int val = adm1030_read_value(client, reg); val = ( ( ( value >> 2 ) & 0x1f ) << 3 ) | ( val & 0x7 ); adm1030_write_value(client, reg, val); } void set_temp_range(struct i2c_client *client, int value, int reg) { int val = adm1030_read_value(client, reg); switch (value) { case 5: value = 0; break; case 10: value = 1; break; case 20: value = 2; break; case 40: value = 3; break; case 80: value = 4; break; default: return; } val = (val&0xf8) | (value & 0x7); adm1030_write_value(client, reg, val); } static ssize_t set_temp_local_min(struct device *dev, const char *buf, size_t count) { struct i2c_client * client = to_i2c_client(dev); int temp = simple_strtoul(buf, NULL, 10); set_temp_min(client, temp, ADM1030_REG_LTR); return count; } static ssize_t set_temp_local_range(struct device *dev, const char *buf, size_t count) { struct i2c_client * client = to_i2c_client(dev); int temp = simple_strtoul(buf, NULL, 10); set_temp_range(client, temp, ADM1030_REG_LTR); return count; } static ssize_t set_temp_remote_min(struct device *dev, const char *buf, size_t count) { struct i2c_client * client = to_i2c_client(dev); int temp = simple_strtoul(buf, NULL, 10); set_temp_min(client, temp, ADM1030_REG_RTR); return count; } static ssize_t set_temp_remote_range(struct device *dev, const char *buf, size_t count) { struct i2c_client * client = to_i2c_client(dev); int temp = simple_strtoul(buf, NULL, 10); set_temp_range(client, temp, ADM1030_REG_RTR); return count; } //static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max); //static DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp_hyst, set_temp_hyst); static DEVICE_ATTR(conf1, S_IWUSR | S_IRUGO, show_conf1, set_conf1); static DEVICE_ATTR(conf2, S_IWUSR | S_IRUGO, show_conf2, set_conf2); static DEVICE_ATTR(ftac, S_IWUSR | S_IRUGO, show_ftac, set_ftac); static DEVICE_ATTR(pwm, S_IWUSR | S_IRUGO, show_pwm, set_pwm); static DEVICE_ATTR(temp_local_in, S_IRUGO, show_temp_local_in, NULL); static DEVICE_ATTR(temp_remote_in, S_IRUGO, show_temp_remote_in, NULL); static DEVICE_ATTR(fan_rpm, S_IRUGO , show_fan_rpm, NULL); static DEVICE_ATTR(temp_local_min, S_IRUGO | S_IWUSR, show_temp_local_min, set_temp_local_min); static DEVICE_ATTR(temp_local_range, S_IRUGO | S_IWUSR, show_temp_local_range, set_temp_local_range); static DEVICE_ATTR(temp_remote_min, S_IRUGO | S_IWUSR, show_temp_remote_min, set_temp_remote_min); static DEVICE_ATTR(temp_remote_range, S_IRUGO | S_IWUSR, show_temp_remote_range, set_temp_remote_range); static int adm1030_attach_adapter(struct i2c_adapter *adapter) { if (!(adapter->class & I2C_ADAP_CLASS_SMBUS)) return 0; return i2c_detect(adapter, &addr_data, adm1030_detect); } /* This function is called by i2c_detect */ static int adm1030_detect(struct i2c_adapter *adapter, int address, int kind) { int id, co; struct i2c_client *new_client; struct adm1030_data *data; int err = 0; const char *name = ""; /* Make sure we aren't probing the ISA bus!! This is just a safety check at this moment; i2c_detect really won't call us. */ #ifdef DEBUG if (i2c_is_isa_adapter(adapter)) { dev_dbg(&adapter->dev, "adm1030_detect called for an ISA bus adapter?!?\n"); goto exit; } #endif if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) goto exit; /* OK. For now, we presume we have a valid client. We now create the client structure, even though we cannot fill it completely yet. But it allows us to access lm75_{read,write}_value. */ if (!(new_client = kmalloc(sizeof(struct i2c_client) + sizeof(struct adm1030_data), GFP_KERNEL))) { err = -ENOMEM; goto exit; } memset(new_client, 0x00, sizeof(struct i2c_client) + sizeof(struct adm1030_data)); data = (struct adm1030_data *) (new_client + 1); i2c_set_clientdata(new_client, data); new_client->addr = address; new_client->adapter = adapter; new_client->driver = &adm1030_driver; new_client->flags = 0; /* Now, we do the remaining detection. It is lousy. */ if (kind < 0) { id = i2c_smbus_read_byte_data(new_client, 0x3d); co = i2c_smbus_read_byte_data(new_client, 0x3e); // printk("read values@%x : %x %x\n", address, id, co); if((id != 0x30) && (co != 0x41)) goto exit_free; } /* Determine the chip type - only one kind supported! */ if (kind <= 0) kind = adm1030; if (kind == adm1030) { name = "adm1030"; } /* Fill in the remaining client fields and put it into the global list */ strlcpy(new_client->name, name, I2C_NAME_SIZE); new_client->id = adm1030_id++; data->valid = 0; init_MUTEX(&data->update_lock); /* Tell the I2C layer a new client has arrived */ if ((err = i2c_attach_client(new_client))) goto exit_free; /* Initialize the LM75 chip */ adm1030_init_client(new_client); /* Register sysfs hooks */ device_create_file(&new_client->dev, &dev_attr_temp_local_in); device_create_file(&new_client->dev, &dev_attr_temp_remote_in); device_create_file(&new_client->dev, &dev_attr_fan_rpm); /* Following entries are for debugging purpose only, commented * out. These are access to HW configuration registers. */ /* device_create_file(&new_client->dev, &dev_attr_conf1); device_create_file(&new_client->dev, &dev_attr_conf2); device_create_file(&new_client->dev, &dev_attr_ftac); */ device_create_file(&new_client->dev, &dev_attr_pwm); device_create_file(&new_client->dev, &dev_attr_temp_local_min); device_create_file(&new_client->dev, &dev_attr_temp_local_range); device_create_file(&new_client->dev, &dev_attr_temp_remote_min); device_create_file(&new_client->dev, &dev_attr_temp_remote_range); return 0; exit_free: kfree(new_client); exit: return err; } static int adm1030_detach_client(struct i2c_client *client) { i2c_detach_client(client); kfree(client); return 0; } static int adm1030_read_value(struct i2c_client *client, u8 reg) { return i2c_smbus_read_byte_data(client, reg); } static int adm1030_write_value(struct i2c_client *client, u8 reg, u16 value) { return i2c_smbus_write_byte_data(client, reg, value); } static void adm1030_init_client(struct i2c_client *client) { int read_val; /* Initialize the ADM1030 chip (enables fan speed reading )*/ adm1030_write_value(client, ADM1030_REG_CONF2, 0x3f); /* Increase the accuracy on rpm measurements */ read_val = adm1030_read_value(client, ADM1030_REG_FCH); adm1030_write_value(client, ADM1030_REG_FCH, 0xc0 | read_val); } static struct adm1030_data *adm1030_update_device(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct adm1030_data *data = i2c_get_clientdata(client); int temp_range; static unsigned short values[5] = {5, 10, 20, 40, 80}; down(&data->update_lock); if ((jiffies - data->last_updated > HZ + HZ / 2) || (jiffies < data->last_updated) || !data->valid) { dev_dbg(&client->dev, "Starting adm1030 update\n"); data->temp_local_in = adm1030_read_value(client, ADM1030_REG_TEMP_INT); data->temp_remote_in = adm1030_read_value(client, ADM1030_REG_TEMP_EXT); data->fan_rpm = adm1030_read_value(client, ADM1030_REG_FAN_SPEED); temp_range = adm1030_read_value(client, ADM1030_REG_LTR); data->temp_local_min = ((temp_range >> 3) & 0x1f) * 4; data->temp_local_range = (values[temp_range&0x7]); temp_range = adm1030_read_value(client, ADM1030_REG_RTR); data->temp_remote_min = ((temp_range >> 3) & 0x1f) * 4; data->temp_remote_range = (values[temp_range&0x7]); data->conf1 = adm1030_read_value(client, ADM1030_REG_CONF1); data->conf2 = adm1030_read_value(client, ADM1030_REG_CONF2); data->ftac = adm1030_read_value(client, ADM1030_REG_FTAC); data->pwm = 0xf & adm1030_read_value(client, ADM1030_REG_FSP); data->last_updated = jiffies; data->valid = 1; } up(&data->update_lock); return data; } static int __init sensors_adm1030_init(void) { return i2c_add_driver(&adm1030_driver); } static void __exit sensors_adm1030_exit(void) { i2c_del_driver(&adm1030_driver); } MODULE_AUTHOR("Alexandre d'Alton <alex at alexdalton.org>"); MODULE_DESCRIPTION("ADM1030 driver"); MODULE_LICENSE("GPL"); module_init(sensors_adm1030_init); module_exit(sensors_adm1030_exit); +++++++++++++++++++++++++ end adm1030.c ++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ adm1030.h ++++++++++++++++++++++++++++++++ /* adm1030.h - Part of lm_sensors, Linux kernel modules for hardware monitoring Copyright (c) 2004 Alexandre d'Alton <alex at alexdalton.org> 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 02139, USA. */ /* This file contains common code for encoding/decoding LM75 type temperature readings, which are emulated by many of the chips we support. As the user is unlikely to load more than one driver which contains this code, we don't worry about the wasted space. */ #include <linux/i2c-sensor.h> /* Many ADM1030 constants specified below */ /* The ADM1030 registers */ /* Configuration register 1 */ #define ADM1030_REG_CONF1 0x00 #define ADM1030_CONF1_MEN 0x01 /* monitoring enable */ #define ADM1030_CONF1_IEN 0x02 /* INT enable */ #define ADM1030_CONF1_TMODE 0x04 /* Tach/Ain mode */ #define ADM1030_CONF1_PINV 0x08 /* PWM Invert */ #define ADM1030_CONF1_FFE 0x10 /* Fan Fault Enable */ #define ADM1030_CONF1_PMODE 0x60 /* PWM Mode 00 = Remote control Fan (Select PWM when manual) 11 = Fastest Calc speed controls fan (Select RPM when manual)*/ #define ADM1030_CONF1_AEN 0x80 /* Auto / SW Control */ /* Configuration register 2 */ #define ADM1030_REG_CONF2 0x01 #define ADM1030_CONF2_PEN 0x01 /* PWM1 Enable */ #define ADM1030_CONF2_UN1 0x02 /* Unused */ #define ADM1030_CONF2_TIEN 0x04 /* TACH input Enable */ #define ADM1030_CONF2_UN2 0x08 /* Unused */ #define ADM1030_CONF2_LTE 0x10 /* Local temp enable */ #define ADM1030_CONF2_RTE 0x20 /* Remote temp enable */ #define ADM1030_CONF2_UN3 0x40 /* Unused */ #define ADM1030_CONF2_SWR 0x80 /* SW Reset */ /* STATUS register 1 */ #define ADM1030_REG_STAT1 0x02 #define ADM1030_STAT1_ASP 0x01 /* Alarm Speed */ #define ADM1030_STAT1_FF 0x02 /* Fan Fault */ #define ADM1030_STAT1_RTH 0x04 /* Remote Temp High */ #define ADM1030_STAT1_RTL 0x08 /* Remote Temp Low */ #define ADM1030_STAT1_RTT 0x10 /* Remote Temp Therm */ #define ADM1030_STAT1_RDE 0x20 /* Remote Diode Error */ #define ADM1030_STAT1_LTH 0x40 /* Local Temp High */ #define ADM1030_STAT1_LTL 0x80 /* Loc Temp Low */ /* STATUS register 2 */ #define ADM1030_REG_STAT2 0x03 #define ADM1030_STAT2_UN1 0x3f /* Unused */ #define ADM1030_STAT2_LTH 0x40 /* Local Temp Therm */ #define ADM1030_STAT2_LTL 0x80 /* Therm Status */ /* Extended Temp register */ #define ADM1030_REG_EXT 0x06 #define ADM1030_EXT_ASP 0x07 /* Remote extended temp range */ #define ADM1030_EXT_LTH 0x38 /* Unused */ #define ADM1030_EXT_LTL 0xc0 /* Local extended temp range */ /* Fan charasteristic register */ #define ADM1030_REG_FCH 0x20 #define ADM1030_FCH_FSU 0x07 /* Fan spin up */ #define ADM1030_EXT_PWMF 0x38 /* PWM Freq */ #define ADM1030_EXT_SPR 0xc0 /* Speed range */ /* Fan Speed register */ #define ADM1030_REG_FSP 0x22 #define ADM1030_FSP_NSP 0x0f /* Fan min speed */ /* Fan Filter register */ #define ADM1030_REG_FLT 0x23 #define ADM1030_FLT_SUD 0x80 /* Spin up disable */ #define ADM1030_FLT_RR 0x60 /* Ramp rate */ #define ADM1030_FLT_ASR 0x1c /* ADC Sample rate */ #define ADM1030_FLT_UN1 0x02 /* Unused */ #define ADM1030_FLT_FFE 0x01 /* Fan Filter enable */ /* Local Temp Tmin / Trange */ #define ADM1030_REG_LTR 0x24 /* Remote Temp Tmin / Trange */ #define ADM1030_REG_RTR 0x25 #define ADM1030_TR_TM 0xf8 /* Local Temp Tmin */ #define ADM1030_TR_TR 0x60 /* Local Temp Trange */ /* Therm to Fan */ #define ADM1030_REG_TTF 0x3f #define ADM1030_TTF_EN 0x80 /* Therm to fan enable */ #define ADM1030_REG_FAN_SPEED 0x08 #define ADM1030_REG_FTAC 0x10 #define ADM1030_REG_TEMP_INT 0x0a #define ADM1030_REG_TEMP_EXT 0x0b +++++++++++++++++++++++++ end adm1030.h ++++++++++++++++++++++++++++++++