From be77f5a4ce0be90c4b64026ba395d66b217af574 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 3 Jun 2004 21:32:19 +0000 Subject: [PATCH] Initial ADM1030 and ADM1031 support. Contributed by Alexandre d'Alton. git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@2573 7894878c-1315-0410-8ee3-d5d059ff63e0 --- doc/chips/adm1031 | 109 +++++++ etc/sensors.conf.eg | 35 +++ kernel/chips/Module.mk | 1 + kernel/chips/adm1031.c | 658 +++++++++++++++++++++++++++++++++++++++++ lib/chips.c | 115 +++++++ lib/chips.h | 32 ++ prog/sensors/chips.c | 93 ++++++ prog/sensors/chips.h | 1 + prog/sensors/main.c | 2 + 9 files changed, 1046 insertions(+) create mode 100644 doc/chips/adm1031 create mode 100644 kernel/chips/adm1031.c diff --git a/doc/chips/adm1031 b/doc/chips/adm1031 new file mode 100644 index 00000000..06390df3 --- /dev/null +++ b/doc/chips/adm1031 @@ -0,0 +1,109 @@ +Kernel driver `adm1031.o' +========================= + +Status: Driver supports basic temp and fan readings, min/max/crit settings + alarms. This driver does not support auto fan control. + +Supported chips: + * Analog Devices ADM1030 + Prefix: `adm1030' + Addresses scanned: I2C 0x2c to 0x2e + Datasheet: Publicly available at the Analog Devices website + http://products.analog.com/products/info.asp?product=ADM1030 + + * Analog Devices ADM1031 + Prefix: `adm1031' + Addresses scanned: I2C 0x2c to 0x2e + Datasheet: Publicly available at the Analog Devices website + http://products.analog.com/products/info.asp?product=ADM1031 + + +Author: Alexandre d'Alton + +License: GPL + + +Module Parameters +----------------- + +* force: short array (min = 1, max = 48) + List of adapter,address pairs to boldly assume to be present +* force_adm1030: short array (min = 1, max = 48) + List of adapter,address pairs which are unquestionably assumed to contain + a `adm1030' chip +* force_adm1031: short array (min = 1, max = 48) + List of adapter,address pairs which are unquestionably assumed to contain + a `adm1031' chip +* probe: short array (min = 1, max = 48) + List of adapter,address pairs to scan additionally +* probe_range: short array (min = 1, max = 48) + List of adapter,start-addr,end-addr triples to scan additionally +* ignore: short array (min = 1, max = 48) + List of adapter,address pairs not to scan +* ignore_range: short array (min = 1, max = 48) + List of adapter,start-addr,end-addr triples not to scan + + +Description +----------- + +ADM1030 and ADM1031 are digital temperature sensors and fan controllers. +They sense their own temperature as well as the temperature of up to one +(ADM1030) or two (ADM1031) external diodes. + +All temperature values are given in degrees Celcius. Resolution +is 0.5 degree for the local temperature, 0.125 degree for the remote +temperatures. + +Each temperature channel has its own high and low limits, plus a critical +limit. + +The ADM1030 monitors a single fan speed, while the ADM1031 monitors up to +two. Each fan channel has its own low speed limit. + + +Chip Features +------------- + +Chip 'adm1030' + +LABEL LABEL CLASS COMPUTE CLASS MODE MAGN +temp1 - - R- 3 +temp1_high temp1 temp1 RW 3 +temp1_low temp1 temp1 RW 3 +temp1_crit temp1 temp1 RW 3 +temp2 - - R- 3 +temp2_high temp2 temp2 RW 3 +temp2_low temp2 temp2 RW 3 +temp2_crit temp2 temp2 RW 3 + +fan1_input - - R- 0 +fan1_min fan1 fan1 RW 0 +fan1_div fan1 - RW 0 + +alarms - - R- 0 + +Chip 'adm1031' + +LABEL LABEL CLASS COMPUTE CLASS MODE MAGN +temp1 - - R- 3 +temp1_high temp1 temp1 RW 3 +temp1_low temp1 temp1 RW 3 +temp1_crit temp1 temp1 RW 3 +temp2 - - R- 3 +temp2_high temp2 temp2 RW 3 +temp2_low temp2 temp2 RW 3 +temp2_crit temp2 temp2 RW 3 +temp3 - - R- 3 +temp3_high temp3 temp3 RW 3 +temp3_low temp3 temp3 RW 3 +temp3_crit temp3 temp3 RW 3 + +fan1_input - - R- 0 +fan1_min fan1 fan1 RW 0 +fan1_div fan1 - RW 0 +fan2_input - - R- 0 +fan2_min fan2 fan2 RW 0 +fan2_div fan2 - RW 0 + +alarms - - R- 0 diff --git a/etc/sensors.conf.eg b/etc/sensors.conf.eg index d2198df2..4bf204e8 100644 --- a/etc/sensors.conf.eg +++ b/etc/sensors.conf.eg @@ -2150,3 +2150,38 @@ chip "pc87366-*" set fan1_min 2000 set fan2_min 2000 set fan3_min 2000 + + +chip "adm1030-*" + + label temp1 "SYS Temp" + label temp2 "CPU Temp" + label temp1_crit "SYS Crit" + label temp2_crit "CPU Crit" + + set temp1_max 50000 + set temp2_max 65000 + + label fan1 "CPU Fan" + + set fan1_min 2000 + + +chip "adm1031-*" + + label temp1 "SYS Temp" + label temp2 "CPU Temp" + label temp3 "AUX Temp" + label temp1_crit "SYS Crit" + label temp2_crit "CPU Crit" + label temp3_crit "AUX Crit" + + set temp1_max 50000 + set temp2_max 65000 + set temp3_max 65000 + + label fan1 "CPU Fan" + label fan2 "Case Fan" + + set fan1_min 2000 + set fan2_min 2000 diff --git a/kernel/chips/Module.mk b/kernel/chips/Module.mk index 0d99d46f..6e5ef283 100644 --- a/kernel/chips/Module.mk +++ b/kernel/chips/Module.mk @@ -25,6 +25,7 @@ KERNELCHIPSDIR := $(MODULE_DIR) # defined value verbatim into the command-list of rules... # These targets are NOT included in 'mkpatch' ... KERNELCHIPSTARGETS := +KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1031.o ifeq ($(shell if grep -q '^CONFIG_IPMI_HANDLER=' $(LINUX)/.config; then echo 1; fi),1) KERNELCHIPSTARGETS += $(MODULE_DIR)/bmcsensors.o endif diff --git a/kernel/chips/adm1031.c b/kernel/chips/adm1031.c new file mode 100644 index 00000000..1eab784c --- /dev/null +++ b/kernel/chips/adm1031.c @@ -0,0 +1,658 @@ +/* + adm1031.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Based on lm75.c and lm85.c + Supports Analog Devices ADM1030 and ADM1031 + Copyright (C) 2004 Alexandre d'Alton + + 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 +#include +#include +#include +#include +#include "version.h" + +/* Following macros take channel parameter starting from 0 */ +#define ADM1031_REG_FAN_SPEED(nr) (0x08 + (nr)) + +#define ADM1031_REG_FAN_DIV(nr) (0x20 + (nr)) +#define ADM1031_REG_FAN_MIN(nr) (0x10 + (nr)) + +#define ADM1031_REG_TEMP_MAX(nr) (0x14 + 4 * (nr)) +#define ADM1031_REG_TEMP_MIN(nr) (0x15 + 4 * (nr)) +#define ADM1031_REG_TEMP_CRIT(nr) (0x16 + 4 * (nr)) + +#define ADM1031_REG_TEMP(nr) (0x0a + (nr)) +#define ADM1031_REG_AUTO_TEMP(nr) (0x24 + (nr)) + +#define ADM1031_REG_STATUS(nr) (0x02 + (nr)) +#define ADM1031_REG_FAN_PWM 0x22 + +#define ADM1031_REG_CONF1 0x00 +#define ADM1031_REG_CONF2 0x01 +#define ADM1031_REG_EXT_TEMP 0x06 + +#define ADM1031_CONF1_MONITOR_ENABLE 0x01 /* Monitoring enable */ +#define ADM1031_CONF1_PWM_INVERT 0x08 /* PWM Invert (unused) */ +#define ADM1031_CONF1_AUTO_MODE 0x80 /* Auto fan mode */ + +#define ADM1031_CONF2_PWM1_ENABLE 0x01 +#define ADM1031_CONF2_PWM2_ENABLE 0x02 +#define ADM1031_CONF2_TACH1_ENABLE 0x04 +#define ADM1031_CONF2_TACH2_ENABLE 0x08 +#define ADM1031_CONF2_TEMP_ENABLE(chan) (0x10 << (chan)) + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_2(adm1030, adm1031); + +/* + * Proc entries + * These files are created for each detected ADM1031. + */ + +/* -- SENSORS SYSCTL START -- */ + +#define ADM1031_SYSCTL_TEMP1 1200 +#define ADM1031_SYSCTL_TEMP2 1201 +#define ADM1031_SYSCTL_TEMP3 1202 + +#define ADM1031_SYSCTL_FAN1 1210 +#define ADM1031_SYSCTL_FAN2 1211 + +#define ADM1031_SYSCTL_FAN_DIV 1220 + +#define ADM1031_SYSCTL_ALARMS 1250 + +#define ADM1031_ALARM_FAN1_MIN 0x0001 +#define ADM1031_ALARM_FAN1_FLT 0x0002 +#define ADM1031_ALARM_TEMP2_HIGH 0x0004 +#define ADM1031_ALARM_TEMP2_LOW 0x0008 +#define ADM1031_ALARM_TEMP2_CRIT 0x0010 +#define ADM1031_ALARM_TEMP2_DIODE 0x0020 +#define ADM1031_ALARM_TEMP1_HIGH 0x0040 +#define ADM1031_ALARM_TEMP1_LOW 0x0080 +#define ADM1031_ALARM_FAN2_MIN 0x0100 +#define ADM1031_ALARM_FAN2_FLT 0x0200 +#define ADM1031_ALARM_TEMP3_HIGH 0x0400 +#define ADM1031_ALARM_TEMP3_LOW 0x0800 +#define ADM1031_ALARM_TEMP3_CRIT 0x1000 +#define ADM1031_ALARM_TEMP3_DIODE 0x2000 +#define ADM1031_ALARM_TEMP1_CRIT 0x4000 +#define ADM1031_ALARM_THERMAL 0x8000 + + +/* -- SENSORS SYSCTL END -- */ +static void adm1031_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1031_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static ctl_table adm1031_dir_table_template[] = { + {ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan_div}, + {ADM1031_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_alarms}, + {0} +}; + +static ctl_table adm1030_dir_table_template[] = { + {ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_temp}, + {ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan}, + {ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_fan_div}, + {ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, + &i2c_proc_real, + &i2c_sysctl_real, NULL, &adm1031_alarms}, + {0} +}; + + +/* Each client has this additional data */ +struct adm1031_data { + struct i2c_client client; + int sysctl_id; + struct semaphore update_lock; + int chip_type; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 alarms; + u8 conf1; + u8 conf2; + u8 fan[2]; + u8 fan_min[2]; + u8 fan_pwm[2]; + u8 fan_div[2]; + s8 temp[3]; + u8 auto_temp[3]; + u8 ext_temp[3]; + s8 temp_min[3]; + s8 temp_max[3]; + s8 temp_crit[3]; +}; + +static int adm1031_attach_adapter(struct i2c_adapter *adapter); +static int adm1031_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void adm1031_init_client(struct i2c_client *client); +static int adm1031_detach_client(struct i2c_client *client); + +static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg); +static inline int adm1031_write_value(struct i2c_client *client, u8 reg, + unsigned int value); + +static void adm1031_update_client(struct i2c_client *client); + +/* This is the driver that will be inserted */ +static struct i2c_driver adm1031_driver = { + .owner = THIS_MODULE, + .name = "ADM1031/ADM1030 sensor driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = adm1031_attach_adapter, + .detach_client = adm1031_detach_client, +}; + +static int adm1031_id = 0; + +#define TEMP_TO_REG(val) ((val) < 0 ? (((val) - 500) / 1000) : \ + (((val) + 500) / 1000)) + +#define TEMP_FROM_REG(val) ((val) * 1000) + +#define TEMP_FROM_REG_EXT(val,ext) (TEMP_FROM_REG(val) + (ext) * 125) + +#define FAN_FROM_REG(reg,div) ((reg) ? \ + (11250 * 60) / ((reg) * (div)) : 0) + +#define FAN_TO_REG(reg,div) FAN_FROM_REG(SENSORS_LIMIT(reg, 0, \ + 65535), div) + +#define FAN_DIV_TO_REG(val) ((val) == 8 ? 0xc0 : \ + (val) == 4 ? 0x80 : \ + (val) == 1 ? 0x00 : 0x40) + +#define FAN_DIV_FROM_REG(reg) (1 << (((reg)&0xc0) >> 6)) + +#define AUTO_TEMP_OFF_FROM_REG(reg) (((((reg) >> 3) & 0x1f) << 2) - 5) + + +static int adm1031_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, adm1031_detect); +} + +/* This function is called by i2c_detect */ +static int +adm1031_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *new_client; + struct adm1031_data *data; + int err = 0; + const char *type_name = ""; + const char *client_name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto exit; + + if (!(data = kmalloc(sizeof(struct adm1031_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + memset(data, 0, sizeof(struct adm1031_data)); + + new_client = &data->client; + new_client->data = data; + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &adm1031_driver; + new_client->flags = 0; + + if (kind < 0) { + int id, co; + id = i2c_smbus_read_byte_data(new_client, 0x3d); + co = i2c_smbus_read_byte_data(new_client, 0x3e); + + if (((id != 0x31) || (id != 0x30)) && (co != 0x41)) + goto exit_free; + kind = (id == 0x30) ? adm1030 : adm1031; + } + + if (kind <= 0) + kind = adm1031; + + /* Given the detected chip type, set the chip name and the + * auto fan control helper table. */ + if (kind == adm1030) { + type_name = "adm1030"; + client_name = "ADM1030 chip"; + + } else if (kind == adm1031) { + type_name = "adm1031"; + client_name = "ADM1031 chip"; + } + data->chip_type = kind; + + strcpy(new_client->name, client_name); + + new_client->id = adm1031_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; + + + if (kind == adm1030) { + if ((err = i2c_register_entry(new_client, type_name, + adm1030_dir_table_template)) < 0) + goto exit_detach; + } else if (kind == adm1031) { + if ((err = i2c_register_entry(new_client, type_name, + adm1031_dir_table_template)) < 0) + goto exit_detach; + } + + data->sysctl_id = err; + + /* Initialize the ADM1031 chip */ + adm1031_init_client(new_client); + + return 0; + +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(new_client); +exit: + return err; +} + +static int adm1031_detach_client(struct i2c_client *client) +{ + int ret; + struct adm1031_data *data = client->data; + + i2c_deregister_entry(data->sysctl_id); + if ((ret = i2c_detach_client(client)) != 0) { + return ret; + } + + kfree(client); + return 0; +} + +static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int adm1031_write_value(struct i2c_client *client, u8 reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void adm1031_init_client(struct i2c_client *client) +{ + unsigned int read_val; + unsigned int mask; + struct adm1031_data *data = client->data; + + mask = (ADM1031_CONF2_PWM1_ENABLE | ADM1031_CONF2_TACH1_ENABLE); + if (data->chip_type == adm1031) { + mask |= (ADM1031_CONF2_PWM2_ENABLE | + ADM1031_CONF2_TACH2_ENABLE); + } + /* Initialize the ADM1031 chip (enable fan speed reading) */ + read_val = adm1031_read_value(client, ADM1031_REG_CONF2); + if ((read_val | mask) != read_val) { + adm1031_write_value(client, ADM1031_REG_CONF2, + read_val | mask); + } + + read_val = adm1031_read_value(client, ADM1031_REG_CONF1); + if ((read_val | ADM1031_CONF1_MONITOR_ENABLE) != read_val) { + adm1031_write_value(client, ADM1031_REG_CONF1, read_val | + ADM1031_CONF1_MONITOR_ENABLE); + } +} + +static void adm1031_update_client(struct i2c_client *client) +{ + struct adm1031_data *data = client->data; + int chan; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { +#ifdef DEBUG + printk(KERN_INFO "adm1031.o: Starting chip update\n"); +#endif + for (chan = 0; + chan < ((data->chip_type == adm1031) ? 3 : 2); + chan++) { + u8 oldh, newh; + + oldh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + data->ext_temp[chan] = adm1031_read_value(client, + ADM1031_REG_EXT_TEMP); + newh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + if (newh != oldh) { + data->ext_temp[chan] = adm1031_read_value(client, + ADM1031_REG_EXT_TEMP); +#ifdef DEBUG + oldh = adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + + /* oldh is actually newer */ + if (newh != oldh) + printk(KERN_INFO "adm1031.o: Remote " + "temperature may be wrong.\n"); +#endif + } + data->temp[chan] = newh; + + data->temp_min[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_MIN(chan)); + data->temp_max[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_MAX(chan)); + data->temp_crit[chan] = adm1031_read_value(client, + ADM1031_REG_TEMP_CRIT(chan)); + data->auto_temp[chan] = adm1031_read_value(client, + ADM1031_REG_AUTO_TEMP(chan)); + } + + data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1); + + data->alarms = adm1031_read_value(client, ADM1031_REG_STATUS(0)) + | (adm1031_read_value(client, ADM1031_REG_STATUS(1)) + << 8); + if (data->chip_type == adm1030) { + data->alarms &= 0xc0ff; + } + + for (chan = 0; + chan < (data->chip_type == adm1030 ? 1 : 2); + chan++) { + data->fan_div[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_DIV(chan)); + data->fan_min[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_MIN(chan)); + data->fan[chan] = adm1031_read_value(client, + ADM1031_REG_FAN_SPEED(chan)); + data->fan_pwm[chan] = (adm1031_read_value(client, + ADM1031_REG_FAN_PWM) >> (4 * chan)) + & 0x0f; + } + + data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1); + data->conf2 = adm1031_read_value(client, ADM1031_REG_CONF2); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static int __init sensors_adm1031_init(void) +{ + printk(KERN_INFO "adm1031.o version %s (%s)\n", LM_VERSION, LM_DATE); + return i2c_add_driver(&adm1031_driver); +} + +static void __exit sensors_adm1031_exit(void) +{ + i2c_del_driver(&adm1031_driver); +} + + +static void +adm1031_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + int ext; + struct adm1031_data *data = client->data; + int tmp; + int nr = ctl_name - ADM1031_SYSCTL_TEMP1; + + ext = nr == 0 ? + ((data->ext_temp[nr] >> 6) & 0x3) * 2 : + (((data->ext_temp[nr] >> ((nr - 1) * 3)) & 7)); + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = TEMP_FROM_REG(data->temp_max[nr]); + results[1] = TEMP_FROM_REG(data->temp_min[nr]); + results[2] = TEMP_FROM_REG(data->temp_crit[nr]); + results[3] = TEMP_FROM_REG_EXT(data->temp[nr], ext); + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + tmp = SENSORS_LIMIT(results[0], -55000, 127000); + data->temp_max[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_MAX(nr), + data->temp_max[nr]); + } + if (*nrels_mag >= 2) { + tmp = SENSORS_LIMIT(results[1], -55000, 127000); + data->temp_min[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_MIN(nr), + data->temp_min[nr]); + } + if (*nrels_mag >= 3) { + tmp = SENSORS_LIMIT(results[2], -55000, 127000); + data->temp_crit[nr] = TEMP_TO_REG(tmp); + adm1031_write_value(client, + ADM1031_REG_TEMP_CRIT(nr), + data->temp_crit[nr]); + } + } +} + +/* + * That function checks the cases where the fan reading is not + * relevant. It is used to provide 0 as fan reading when the fan is + * not supposed to run. + */ +static int trust_fan_readings(struct adm1031_data * data, int chan) +{ + int res = 0; + + if (data->conf1 & ADM1031_CONF1_AUTO_MODE) { + switch(data->conf1 & 0x60) { + case 0x00: /* remote temp1 controls fan1 remote temp2 + controls fan2 */ + res = chan == 0 ? data->temp[1] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[1]) : + data->temp[2] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[2]); + break; + case 0x20: /* remote temp1 controls both fans*/ + if (data->chip_type == adm1030) + res = 0; + else + res = data->temp[1] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[1]); + break; + case 0x40: /* remote temp2 controls both fans*/ + if (data->chip_type == adm1030) + res = 0; + else + res = data->temp[2] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[2]); + break; + case 0x60: /* max controls both fans */ + if (data->chip_type == adm1030){ + res = data->temp[0] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[0]) + || data->temp[1] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[1]); + } else { + res = data->temp[0] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[0]) + || data->temp[1] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[1]) + || data->temp[2] > + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[2]); + } + break; + default: + res = 0; + break; + } + } else { + res = data->fan_pwm[chan] > 0; + } + + return res; +} + +static void adm1031_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + int nr = ctl_name - ADM1031_SYSCTL_FAN1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])); + results[1] = trust_fan_readings(data, nr) ? + FAN_FROM_REG(data->fan[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])) : 0; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + data->fan_min[nr] = FAN_TO_REG(results[0], + FAN_DIV_FROM_REG(data->fan_div[nr])); + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(nr), + data->fan_min[nr]); + } + } +} + +static void adm1031_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = FAN_DIV_FROM_REG(data->fan_div[0]); + *nrels_mag = 1; + if(data->chip_type == adm1031) { + results[1] = FAN_DIV_FROM_REG(data->fan_div[1]); + *nrels_mag = 2; + } + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + int old_div = FAN_DIV_FROM_REG(data->fan_div[0]); + data->fan_div[0] = FAN_DIV_TO_REG(results[0]); + adm1031_write_value(client, + ADM1031_REG_FAN_DIV(0), + data->fan_div[0]); + data->fan_min[0] = data->fan_min[0] * old_div / + FAN_DIV_FROM_REG(data->fan_div[0]); + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(0), + data->fan_min[0]); + } + if (*nrels_mag >= 2) { + int old_div = FAN_DIV_FROM_REG(data->fan_div[1]); + data->fan_div[1] = FAN_DIV_TO_REG(results[1]); + adm1031_write_value(client, + ADM1031_REG_FAN_DIV(1), + data->fan_div[1]); + data->fan_min[1] = data->fan_min[1] * old_div / + FAN_DIV_FROM_REG(data->fan_div[1]); + adm1031_write_value(client, + ADM1031_REG_FAN_MIN(1), + data->fan_min[1]); + } + } +} + +static void adm1031_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct adm1031_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + adm1031_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +MODULE_AUTHOR("Alexandre d'Alton "); +MODULE_DESCRIPTION("ADM1031/ADM1030 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_adm1031_init); +module_exit(sensors_adm1031_exit); diff --git a/lib/chips.c b/lib/chips.c index 8ee8916f..849d9e99 100644 --- a/lib/chips.c +++ b/lib/chips.c @@ -360,6 +360,119 @@ static sensors_chip_feature adm1023_features[] = { 0 } }; + +static sensors_chip_feature adm1030_features[] = + { + { SENSORS_ADM1031_TEMP1, "temp1", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_TEMP1, VALUE(4), 3 }, + { SENSORS_ADM1031_TEMP1_MAX, "temp1_max", + SENSORS_ADM1031_TEMP1, SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(1), 3 }, + { SENSORS_ADM1031_TEMP1_MIN, "temp1_min", + SENSORS_ADM1031_TEMP1, SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(2), 3 }, + { SENSORS_ADM1031_TEMP1_CRIT, "temp1_crit", SENSORS_ADM1031_TEMP1, + SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(3), 3 }, + + { SENSORS_ADM1031_TEMP2, "temp2", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_TEMP2, VALUE(4), 3 }, + { SENSORS_ADM1031_TEMP2_MAX, "temp2_max", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(1), 3 }, + { SENSORS_ADM1031_TEMP2_MIN, "temp2_min", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(2), 3 }, + { SENSORS_ADM1031_TEMP2_CRIT, "temp2_crit", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(3), 3 }, + + { SENSORS_ADM1031_FAN1, "fan1", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_FAN1, VALUE(2), 0 }, + { SENSORS_ADM1031_FAN1_MIN, "fan1_min", + SENSORS_ADM1031_FAN1, SENSORS_ADM1031_FAN1, RW, + ADM1031_SYSCTL_FAN1, VALUE(1), 0 }, + { SENSORS_ADM1031_FAN1_DIV, "fan1_div", + SENSORS_ADM1031_FAN1, NOMAP, + RW, ADM1031_SYSCTL_FAN_DIV, VALUE(1), 0 }, + + { SENSORS_ADM1031_ALARMS, "alarms", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_ALARMS, VALUE(1), 0 }, + { 0 } + }; + + +static sensors_chip_feature adm1031_features[] = + { + { SENSORS_ADM1031_TEMP1, "temp1", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_TEMP1, VALUE(4), 3 }, + { SENSORS_ADM1031_TEMP1_MAX, "temp1_max", + SENSORS_ADM1031_TEMP1, SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(1), 3 }, + { SENSORS_ADM1031_TEMP1_MIN, "temp1_min", + SENSORS_ADM1031_TEMP1, SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(2), 3 }, + { SENSORS_ADM1031_TEMP1_CRIT, "temp1_crit", + SENSORS_ADM1031_TEMP1, SENSORS_ADM1031_TEMP1, + RW, ADM1031_SYSCTL_TEMP1, VALUE(3), 3 }, + + { SENSORS_ADM1031_TEMP2, "temp2", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_TEMP2, VALUE(4), 3 }, + { SENSORS_ADM1031_TEMP2_MAX, "temp2_max", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(1), 3 }, + { SENSORS_ADM1031_TEMP2_MIN, "temp2_min", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(2), 3 }, + { SENSORS_ADM1031_TEMP2_CRIT, "temp2_crit", + SENSORS_ADM1031_TEMP2, SENSORS_ADM1031_TEMP2, + RW, ADM1031_SYSCTL_TEMP2, VALUE(3), 3 }, + + { SENSORS_ADM1031_TEMP3, "temp3", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_TEMP3, VALUE(4), 3 }, + { SENSORS_ADM1031_TEMP3_MAX, "temp3_max", + SENSORS_ADM1031_TEMP3, SENSORS_ADM1031_TEMP3, + RW, ADM1031_SYSCTL_TEMP3, VALUE(1), 3 }, + { SENSORS_ADM1031_TEMP3_MIN, "temp3_min", + SENSORS_ADM1031_TEMP3, SENSORS_ADM1031_TEMP3, + RW, ADM1031_SYSCTL_TEMP3, VALUE(2), 3 }, + { SENSORS_ADM1031_TEMP3_CRIT, "temp3_crit", + SENSORS_ADM1031_TEMP3, SENSORS_ADM1031_TEMP3, + RW, ADM1031_SYSCTL_TEMP3, VALUE(3), 3 }, + + { SENSORS_ADM1031_FAN1, "fan1", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_FAN1, VALUE(2), 0 }, + { SENSORS_ADM1031_FAN1_MIN, "fan1_min", + SENSORS_ADM1031_FAN1, SENSORS_ADM1031_FAN1, + RW, ADM1031_SYSCTL_FAN1, VALUE(1), 0 }, + { SENSORS_ADM1031_FAN1_DIV, "fan1_div", + SENSORS_ADM1031_FAN1, NOMAP, + RW, ADM1031_SYSCTL_FAN_DIV, VALUE(1), 0 }, + + { SENSORS_ADM1031_FAN2, "fan2", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_FAN2, VALUE(2), 0 }, + { SENSORS_ADM1031_FAN2_MIN, "fan2_min", + SENSORS_ADM1031_FAN2, SENSORS_ADM1031_FAN2, + RW, ADM1031_SYSCTL_FAN2, VALUE(1), 0 }, + { SENSORS_ADM1031_FAN2_DIV, "fan2_div", + SENSORS_ADM1031_FAN2, NOMAP, + RW, ADM1031_SYSCTL_FAN_DIV, VALUE(2), 0 }, + + { SENSORS_ADM1031_ALARMS, "alarms", + NOMAP, NOMAP, + R, ADM1031_SYSCTL_ALARMS, VALUE(1), 0 }, + { 0 } + }; + static sensors_chip_feature max1617_features[] = { { SENSORS_MAX1617_TEMP, "temp", NOMAP, NOMAP, @@ -5100,5 +5213,7 @@ sensors_chip_features sensors_chip_features_list[] = { SENSORS_MAX1619_PREFIX, max1619_features }, { SENSORS_XEONTEMP_PREFIX, xeontemp_features }, { SENSORS_MAX6650_PREFIX, max6650_features }, + { SENSORS_ADM1030_PREFIX, adm1030_features }, + { SENSORS_ADM1031_PREFIX, adm1031_features }, { 0 } }; diff --git a/lib/chips.h b/lib/chips.h index c5865fc2..5f6f4d1f 100644 --- a/lib/chips.h +++ b/lib/chips.h @@ -184,6 +184,38 @@ #define SENSORS_ADM1021_ALARMS 81 /* R */ #define SENSORS_ADM1021_DIE_CODE 90 /* R */ + +/* ADM1030 and ADM1031 chips */ + +#define SENSORS_ADM1030_PREFIX "adm1030" +#define SENSORS_ADM1031_PREFIX "adm1031" + +#define SENSORS_ADM1031_TEMP1 51 /* R */ +#define SENSORS_ADM1031_TEMP1_MIN 52 /* RW */ +#define SENSORS_ADM1031_TEMP1_MAX 53 /* RW */ +#define SENSORS_ADM1031_TEMP1_CRIT 54 /* RW */ + +#define SENSORS_ADM1031_TEMP2 61 /* R */ +#define SENSORS_ADM1031_TEMP2_MIN 62 /* RW */ +#define SENSORS_ADM1031_TEMP2_MAX 63 /* RW */ +#define SENSORS_ADM1031_TEMP2_CRIT 64 /* RW */ + +#define SENSORS_ADM1031_TEMP3 71 /* R */ +#define SENSORS_ADM1031_TEMP3_MIN 72 /* RW */ +#define SENSORS_ADM1031_TEMP3_MAX 73 /* RW */ +#define SENSORS_ADM1031_TEMP3_CRIT 74 /* RW */ + + +#define SENSORS_ADM1031_FAN1 80 /* R */ +#define SENSORS_ADM1031_FAN1_MIN 81 /* RW */ +#define SENSORS_ADM1031_FAN1_DIV 82 /* RW */ +#define SENSORS_ADM1031_FAN2 90 /* R */ +#define SENSORS_ADM1031_FAN2_MIN 91 /* RW */ +#define SENSORS_ADM1031_FAN2_DIV 92 /* RW */ + +#define SENSORS_ADM1031_ALARMS 100 /* R */ + + /* MAX1617 chips. */ #define SENSORS_MAX1617_PREFIX "max1617" diff --git a/prog/sensors/chips.c b/prog/sensors/chips.c index 992c0bc4..ea4d2edd 100644 --- a/prog/sensors/chips.c +++ b/prog/sensors/chips.c @@ -4726,6 +4726,99 @@ void print_lm90(const sensors_chip_name *name) free_the_label(&label); } + +void print_adm1031(const sensors_chip_name *name) +{ + char *label; + double cur, high, low, crit, div; + int valid, alarms, i; + int is_1031 = !strncmp("adm1031", name->prefix, 7); + + if (!sensors_get_feature(*name, SENSORS_ADM1031_ALARMS, &cur)) + alarms = cur; + else { + printf("ERROR: Can't get alarm data!\n"); + alarms = 0; + } + + for(i=0; i<(is_1031?2:1);i++) { + if (!sensors_get_label_and_valid(*name, SENSORS_ADM1031_FAN1+i*10, + &label, &valid) + && !sensors_get_feature(*name, SENSORS_ADM1031_FAN1+i*10, &cur) + && !sensors_get_feature(*name, SENSORS_ADM1031_FAN1_MIN+i*10, &low) + && !sensors_get_feature(*name, SENSORS_ADM1031_FAN1_DIV+i*10, &div)) { + if (valid) { + print_label(label, 10); + printf("%4.0f RPM (min = %4.0f RPM, div = %1.0f)", cur, low, div); + printf(" %s\n", + alarms&(ADM1031_ALARM_FAN1_FLT<<(i*8))?"FAN_FAULT": + alarms&((ADM1031_ALARM_FAN1_MIN<<(i*8)))?"ALARM":""); + } + } else + printf("ERROR: Can't get fan%d data!\n", i+1); + free_the_label(&label); + } + + if (!sensors_get_label_and_valid(*name, SENSORS_ADM1031_TEMP1, + &label, &valid) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP1, &cur) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP1_MIN, &low) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP1_MAX, &high) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP1_CRIT, &crit)) { + if (valid) { + print_label(label, 10); + print_temp_info(cur, high, low, MINMAX, 1, 0); + printf(" %s\n", + alarms&(ADM1031_ALARM_TEMP1_HIGH|ADM1031_ALARM_TEMP1_LOW)?"ALARM":""); + } + } else + printf("ERROR: Can't get temp1 temperature data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name, SENSORS_ADM1031_TEMP1_CRIT, + &label, &valid) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP1_CRIT, &cur)) { + if (valid) { + print_label(label, 10); + print_temp_info(cur, 0, 0, SINGLE, 0, 0); + printf(" %s\n", + alarms&ADM1031_ALARM_TEMP1_CRIT?"CRITICAL":""); + } + } else + printf("ERROR: Can't get temp1 temperature data!\n"); + free_the_label(&label); + + for (i=0; i < (is_1031 ? 2 : 1); i++) { + if (!sensors_get_label_and_valid(*name, SENSORS_ADM1031_TEMP2+i*10, + &label, &valid) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP2+i*10, &cur) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP2_MIN+i*10, &low) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP2_MAX+i*10, &high)) { + if (valid) { + print_label(label, 10); + print_temp_info(cur, high, low, MINMAX, 1, 0); + printf(" %s\n", + alarms&((ADM1031_ALARM_TEMP2_DIODE<<(i*8)))?"DISCONNECT": + alarms&((ADM1031_ALARM_TEMP2_HIGH<<(i*8))| + (ADM1031_ALARM_TEMP2_LOW<<(i*8)))?"ALARM":""); + } + } else + printf("ERROR: Can't get temp%d temperature data!\n", i+2); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name, SENSORS_ADM1031_TEMP2_CRIT+i*10, + &label, &valid) + && !sensors_get_feature(*name, SENSORS_ADM1031_TEMP2_CRIT+i*10, &cur)) { + if (valid) { + print_label(label, 10); + print_temp_info(cur, 0, 0, SINGLE, 0, 0); + printf(" %s\n", + alarms&((ADM1031_ALARM_TEMP2_CRIT<<(i*8)))?"CRITICAL":""); + } + } else + printf("ERROR: Can't get temp%d crit temperature data!\n", i+2); + free_the_label(&label); + } +} + void print_xeontemp(const sensors_chip_name *name) { char *label; diff --git a/prog/sensors/chips.h b/prog/sensors/chips.h index 6efde09d..977f5a52 100644 --- a/prog/sensors/chips.h +++ b/prog/sensors/chips.h @@ -63,5 +63,6 @@ extern void print_lm90(const sensors_chip_name *name); extern void print_max1619(const sensors_chip_name *name); extern void print_xeontemp(const sensors_chip_name *name); extern void print_max6650(const sensors_chip_name *name); +extern void print_adm1031(const sensors_chip_name *name); #endif /* def PROG_SENSORS_CHIPS_H */ diff --git a/prog/sensors/main.c b/prog/sensors/main.c index 86d3229c..276d9510 100644 --- a/prog/sensors/main.c +++ b/prog/sensors/main.c @@ -427,6 +427,8 @@ struct match matches[] = { { "xeontemp", print_xeontemp }, { "max6650", print_max6650 }, { "asb100", print_asb100 }, + { "adm1030", print_adm1031 }, + { "adm1031", print_adm1031 }, { NULL, NULL } };