All of lore.kernel.org
 help / color / mirror / Atom feed
* [lm-sensors] [PATCH] First cut of a adt7470 driver
@ 2007-07-06 21:11 Darrick J. Wong
  2007-07-07  8:21 ` Hans de Goede
                   ` (16 more replies)
  0 siblings, 17 replies; 18+ messages in thread
From: Darrick J. Wong @ 2007-07-06 21:11 UTC (permalink / raw)
  To: lm-sensors


[-- Attachment #1.1: Type: text/plain, Size: 35114 bytes --]

Hi there,

Below is a first draft of a driver for the ADT7470 chip.  I've tested
the driver against the adt7470 in the IBM IntelliStation Z30 and as far
as I can tell it's good for controlling the speed of the CPU heatsink
fans and monitor CPU temperature.  Please let me know what you think of
the driver.  The patch should apply cleanly against 2.6.22-rc7.

Please cc me as I'm not subscribed to lm-sensors.

Datasheets available here:
http://www.analog.com/en/prod/0,2877,ADT7470,00.html

--Darrick
---
adt7470: new hwmon driver for adt7470 chip.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---

 drivers/hwmon/Kconfig   |   10 +
 drivers/hwmon/Makefile  |    1 
 drivers/hwmon/adt7470.c |  875 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 886 insertions(+), 0 deletions(-)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 13eea47..bb57beb 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -40,6 +40,16 @@ config SENSORS_ABITUGURU
 	  This driver can also be built as a module.  If so, the module
 	  will be called abituguru.
 
+config SENSORS_ADT7470
+	tristate "Analog Devices ADT7470"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  ADT7470 temperature monitoring chips.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called adt7470.
+
 config SENSORS_AD7418
 	tristate "Analog Devices AD7416, AD7417 and AD7418"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index cfaf338..e5fc251 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_W83781D)	+= w83781d.o
 obj-$(CONFIG_SENSORS_W83791D)	+= w83791d.o
 
 obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
+obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_AD7418)	+= ad7418.o
 obj-$(CONFIG_SENSORS_ADM1021)	+= adm1021.o
 obj-$(CONFIG_SENSORS_ADM1025)	+= adm1025.o
diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c
new file mode 100644
index 0000000..cb2ac60
--- /dev/null
+++ b/drivers/hwmon/adt7470.c
@@ -0,0 +1,875 @@
+/*
+ * A hwmon driver for the Analog Devices ADT7470
+ * Copyright (C) 2007 IBM
+ *
+ * Author: Darrick J. Wong <djwong@us.ibm.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 - version 2.
+ */
+
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/log2.h>
+
+#define DRV_VERSION "0.22"
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
+
+/* Insmod parameters */
+I2C_CLIENT_INSMOD_1(adt7470);
+
+/* ADT7470 registers */
+#define ADT7470_REG_BASE		0x20
+#define ADT7470_REG_TEMP_BASE		0x20
+#define ADT7470_REG_TEMP_MAX		0x29
+#define ADT7470_REG_FAN_BASE		0x2A
+#define ADT7470_REG_FAN_MAX		0x31
+#define ADT7470_REG_PWM_BASE		0x32
+//#define ADT7470_REG_PWM_MAX		0x35
+#define ADT7470_REG_PWM_MAX_BASE	0x38
+#define ADT7470_REG_PWM_MAX_MAX		0x3B
+#define ADT7470_REG_CFG			0x40
+#define		ADT7470_FSPD_MASK	0x04
+#define ADT7470_REG_ALARM1		0x41
+#define ADT7470_REG_ALARM2		0x42
+#define ADT7470_REG_TEMP_LIMITS_BASE	0x44
+#define ADT7470_REG_TEMP_LIMITS_MAX	0x57
+#define ADT7470_REG_FAN_MIN_BASE	0x58
+#define ADT7470_REG_FAN_MIN_MAX		0x5F
+#define ADT7470_REG_FAN_MAX_BASE	0x60
+#define ADT7470_REG_FAN_MAX_MAX		0x67
+#define ADT7470_REG_PWM_CFG_BASE	0x68
+#define ADT7470_REG_PWM12_CFG		0x68
+#define		ADT7470_PWM1_AUTO_MASK	0x40
+#define		ADT7470_PWM2_AUTO_MASK	0x80
+#define ADT7470_REG_PWM34_CFG		0x69
+#define		ADT7470_PWM3_AUTO_MASK	0x40
+#define		ADT7470_PWM4_AUTO_MASK	0x80
+#define	ADT7470_REG_PWM_MIN_BASE	0x6A
+#define ADT7470_REG_PWM_MIN_MAX		0x6D
+#define ADT7470_REG_PWM_TEMP_MIN_BASE	0x6E
+#define ADT7470_REG_PWM_TEMP_MIN_MAX	0x71
+#define ADT7470_REG_ACOUSTICS12		0x75
+#define ADT7470_REG_ACOUSTICS34		0x76
+#define ADT7470_REG_DEVICE		0x3D
+#define ADT7470_REG_VENDOR		0x3E
+#define ADT7470_REG_REVISION		0x3F
+#define ADT7470_REG_ALARM1_MASK		0x72
+#define ADT7470_REG_ALARM2_MASK		0x73
+#define ADT7470_REG_PWM_AUTO_TEMP_BASE	0x7C
+#define ADT7470_REG_PWM_AUTO_TEMP_MAX	0x7D
+#define ADT7470_REG_MAX			0x81
+
+#define ADT7470_TEMP_COUNT	10
+#define ADT7470_TEMP_REG(x)	(ADT7470_REG_TEMP_BASE + (x))
+#define ADT7470_TEMP_MIN_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE + (2 * (x)))
+#define ADT7470_TEMP_MAX_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE + (2 * (x)) + 1)
+
+#define ADT7470_FAN_COUNT	4
+
+#define ADT7470_PWM_COUNT	4
+#define ADT7470_REG_PWM(x)	(ADT7470_REG_PWM_BASE + (x))
+#define ADT7470_REG_PWM_MAX(x)	(ADT7470_REG_PWM_MAX_BASE + (x))
+#define ADT7470_REG_PWM_MIN(x)	(ADT7470_REG_PWM_MIN_BASE + (x))
+#define ADT7470_REG_PWM_TMIN(x)	(ADT7470_REG_PWM_TEMP_MIN_BASE + (x))
+
+#define ADT7470_VENDOR		0x41
+#define ADT7470_DEVICE		0x70
+#define ADT7470_REVISION	0x02
+
+#define ADT7470_PWM_ALL_TEMPS	0x3FF		/* "all temps" according to hwmon sysfs interface spec */
+
+#define REFRESH_INTERVAL	(2 * HZ)
+#define TEMP_COLLECTION_TIME	1000		/* sleep 1s while gathering temperature data */
+
+#define power_of_2(x)	(((x) & ((x) - 1)) == 0)
+
+struct adt7470_data {
+	struct i2c_client	client;
+	struct class_device	*class_dev;
+	struct attribute_group	attrs;
+	enum chips		type;
+	struct mutex		lock;
+	char			valid;
+	unsigned long		last_updated;	/* In jiffies */
+
+	u8			temp[ADT7470_TEMP_COUNT];
+	u8			temp_min[ADT7470_TEMP_COUNT];
+	u8			temp_max[ADT7470_TEMP_COUNT];
+	u16			fan[ADT7470_FAN_COUNT];
+	u16			fan_min[ADT7470_FAN_COUNT];
+	u16			fan_max[ADT7470_FAN_COUNT];
+	u16			alarms, alarms_mask;
+	u8			force_pwm_max;
+	u8			pwm[ADT7470_PWM_COUNT];
+	u8			pwm_max[ADT7470_PWM_COUNT];
+	u8			pwm_automatic[ADT7470_PWM_COUNT];
+	u8			pwm_min[ADT7470_PWM_COUNT];
+	u8			pwm_tmin[ADT7470_PWM_COUNT];
+	u8			pwm_auto_temp[ADT7470_PWM_COUNT];
+
+	u8			raw[256];
+};
+
+static int adt7470_attach_adapter(struct i2c_adapter *adapter);
+static int adt7470_detect(struct i2c_adapter *adapter, int address, int kind);
+static int adt7470_detach_client(struct i2c_client *client);
+
+static struct i2c_driver adt7470_driver = {
+	.driver = {
+		.name	= "adt7470",
+	},
+	.attach_adapter	= adt7470_attach_adapter,
+	.detach_client	= adt7470_detach_client,
+};
+
+/* 16-bit registers on the ADT7470 are low-byte first. */
+static inline int adt7470_read16(struct i2c_client *client, u8 reg)
+{
+	u16 foo;
+	foo = i2c_smbus_read_byte_data(client, reg) | ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8);
+	return foo;
+}
+
+static inline int adt7470_write16(struct i2c_client *client, u8 reg, u16 value)
+{
+	return i2c_smbus_write_word_data(client, reg, cpu_to_le16(value));
+}
+
+static void adt7470_init_client(struct i2c_client *client)
+{
+	//struct adt7470_data *data = i2c_get_clientdata(client);
+
+	int reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
+	if (reg < 0) {
+		dev_err(&client->dev, "cannot read configuration register\n");
+	} else {
+		// start monitoring (and do a self-test)
+		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg | 3);
+	}
+}
+
+static u16 adt7470_read_fan_min(struct i2c_client *client, int fan)
+{
+	return adt7470_read16(client, ADT7470_REG_FAN_MIN_BASE + (2 * fan));
+}
+
+static u16 adt7470_read_fan_max(struct i2c_client *client, int fan)
+{
+	return adt7470_read16(client, ADT7470_REG_FAN_MAX_BASE + (2 * fan));
+}
+
+static u16 adt7470_read_fan(struct i2c_client *client, int fan)
+{
+	return adt7470_read16(client, ADT7470_REG_FAN_BASE + (2 * fan));
+}
+
+static struct adt7470_data *adt7470_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+
+	mutex_lock(&data->lock);
+
+	if (time_after(jiffies, data->last_updated + REFRESH_INTERVAL)
+		|| !data->valid) {
+		u8 cfg;
+		int i;
+
+		/* start reading temperature sensors */
+		cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
+		cfg |= 0x80;
+		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
+
+		/* delay is 200ms * number of tmp05 sensors */
+		udelay(TEMP_COLLECTION_TIME);
+
+		/* done reading temperature sensors */
+		cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
+		cfg &= ~0x80;
+		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
+
+		for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
+			data->temp[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_REG(i));
+			data->temp_min[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_MIN_REG(i));
+			data->temp_max[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_MAX_REG(i));
+		}
+
+		for (i = 0; i < ADT7470_FAN_COUNT; i++) {
+			data->fan[i] = adt7470_read_fan(client, i);
+			data->fan_min[i] = adt7470_read_fan_min(client, i);
+			data->fan_max[i] = adt7470_read_fan_max(client, i);
+		}
+
+		for (i = ADT7470_REG_BASE; i <= ADT7470_REG_MAX; i++) {
+			data->raw[i - ADT7470_REG_BASE] = i2c_smbus_read_byte_data(client, i);
+		}
+
+		for (i = 0; i < ADT7470_PWM_COUNT; i++) {
+			int reg = ADT7470_REG_PWM_CFG_BASE + (i / 2);
+			int reg_mask = ADT7470_PWM1_AUTO_MASK;
+			if (!(i % 2))
+				reg_mask <<= 1;
+
+			data->pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i));
+			data->pwm_max[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_MAX(i));
+			data->pwm_min[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_MIN(i));
+			data->pwm_tmin[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_TMIN(i));
+			data->pwm_automatic[i] = ((i2c_smbus_read_byte_data(client, reg) & reg_mask) == reg_mask);
+			reg = ADT7470_REG_PWM_AUTO_TEMP_BASE + (i / 2);
+			if (!(i % 2))
+				data->pwm_auto_temp[i] = i2c_smbus_read_byte_data(client, reg) >> 4;
+			else
+				data->pwm_auto_temp[i] = i2c_smbus_read_byte_data(client, reg) & 0xF;
+		}
+
+		data->force_pwm_max = ((i2c_smbus_read_byte_data(client, ADT7470_REG_CFG) & ADT7470_FSPD_MASK) == ADT7470_FSPD_MASK);
+
+		data->alarms = adt7470_read16(client, ADT7470_REG_ALARM1);
+		data->alarms_mask = adt7470_read16(client, ADT7470_REG_ALARM1_MASK);
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return data;
+}
+
+static ssize_t show_temp_min(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]);
+}
+
+static ssize_t set_temp_min(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10) / 1000;
+
+	mutex_lock(&data->lock);
+	data->temp_min[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_TEMP_MIN_REG(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_temp_max(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]);
+}
+
+static ssize_t set_temp_max(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10) / 1000;
+
+	mutex_lock(&data->lock);
+	data->temp_max[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_TEMP_MAX_REG(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+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 adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]);
+}
+
+static ssize_t show_alarms(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+
+	if (attr->index)
+		return sprintf(buf, "%x\n", data->alarms);
+	else
+		return sprintf(buf, "%x\n", data->alarms_mask);
+}
+
+static ssize_t show_fan_max(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+
+	if (data->fan_max[attr->index])
+		return sprintf(buf, "%d\n", (90000 * 60) / data->fan_max[attr->index]);
+	else
+		return sprintf(buf, "0\n");
+}
+
+static ssize_t show_fan_min(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+
+	if (data->fan_min[attr->index])
+		return sprintf(buf, "%d\n", (90000 * 60) / data->fan_min[attr->index]);
+	else
+		return sprintf(buf, "0\n");
+}
+
+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 adt7470_data *data = adt7470_update_device(dev);
+
+	if (data->fan[attr->index] && data->fan[attr->index] != 65535)
+		return sprintf(buf, "%d\n", (90000 * 60) / data->fan[attr->index]);
+	else
+		return sprintf(buf, "0\n");
+}
+
+static ssize_t show_raw(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct adt7470_data *data = adt7470_update_device(dev);
+	int i;
+	char *b = buf;
+
+	for (i = ADT7470_REG_BASE; i <= ADT7470_REG_MAX; i++)
+		b += sprintf(b, "0x%X: 0x%X\n", i, data->raw[i - ADT7470_REG_BASE]);
+
+	return b - buf;
+}
+
+static ssize_t show_force_pwm_max(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", data->force_pwm_max);
+}
+
+static ssize_t set_force_pwm_max(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10);
+	u8 reg;
+
+	mutex_lock(&data->lock);
+	data->force_pwm_max = temp;
+	reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
+	reg &= ~ADT7470_FSPD_MASK;
+	reg |= (temp ? ADT7470_FSPD_MASK : 0);
+	i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm[attr->index]);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+	data->pwm[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm_max(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm_max[attr->index]);
+}
+
+static ssize_t set_pwm_max(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+	data->pwm_max[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MAX(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm_min(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm_min[attr->index]);
+}
+
+static ssize_t set_pwm_min(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+	data->pwm_min[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MIN(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm_tmax(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1000 * (20 + data->pwm_tmin[attr->index]));
+}
+
+static ssize_t show_pwm_tmin(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1000 * data->pwm_tmin[attr->index]);
+}
+
+static ssize_t set_pwm_tmin(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10) / 1000;
+
+	mutex_lock(&data->lock);
+	data->pwm_tmin[attr->index] = temp;
+	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_TMIN(attr->index), temp);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm_auto(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]);
+}
+
+static ssize_t set_pwm_auto(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = simple_strtol(buf, NULL, 10);
+	int pwm_auto_reg = ADT7470_REG_PWM_CFG_BASE + (attr->index / 2);
+	int pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;
+	u8 reg;
+
+	if (!(attr->index % 2))
+		pwm_auto_reg_mask <<= 1;
+
+	if (temp != 2 && temp != 1)
+		return -EINVAL;
+	temp--;
+
+	mutex_lock(&data->lock);
+	data->pwm_automatic[attr->index] = temp;
+	reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);
+	reg &= ~pwm_auto_reg_mask;
+	reg |= (temp ? pwm_auto_reg_mask : 0);
+	i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_pwm_auto_temp(struct device *dev, struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct adt7470_data *data = adt7470_update_device(dev);
+	u8 ctrl = data->pwm_auto_temp[attr->index];
+
+	if (ctrl)
+		return sprintf(buf, "%d\n", 1 << (ctrl - 1));
+	else
+		return sprintf(buf, "%d\n", ADT7470_PWM_ALL_TEMPS);
+}
+
+static int cvt_auto_temp(int input)
+{
+	if (input == ADT7470_PWM_ALL_TEMPS)
+		return 0;
+	if (input < 1 || !power_of_2(input))
+		return -EINVAL;
+	return ilog2(input) + 1;
+}
+
+static ssize_t set_pwm_auto_temp(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	int temp = cvt_auto_temp(simple_strtol(buf, NULL, 10));
+	int pwm_auto_reg = ADT7470_REG_PWM_AUTO_TEMP_BASE + (attr->index / 2);
+	u8 reg;
+
+	if (temp < 0)
+		return temp;
+
+	mutex_lock(&data->lock);
+	data->pwm_automatic[attr->index] = temp;
+	reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);
+
+	if (!(attr->index % 2)) {
+		reg &= 0xF;
+		reg |= (temp << 4) & 0xF0;
+	} else {
+		reg &= 0xF0;
+		reg |= temp & 0xF;
+	}
+
+	i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL, 0);
+static SENSOR_DEVICE_ATTR(alarm_mask, S_IRUGO, show_alarms, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 4);
+static SENSOR_DEVICE_ATTR(temp6_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 5);
+static SENSOR_DEVICE_ATTR(temp7_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 6);
+static SENSOR_DEVICE_ATTR(temp8_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 7);
+static SENSOR_DEVICE_ATTR(temp9_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 8);
+static SENSOR_DEVICE_ATTR(temp10_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 9);
+
+static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp5_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 4);
+static SENSOR_DEVICE_ATTR(temp6_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 5);
+static SENSOR_DEVICE_ATTR(temp7_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 6);
+static SENSOR_DEVICE_ATTR(temp8_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 7);
+static SENSOR_DEVICE_ATTR(temp9_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 8);
+static SENSOR_DEVICE_ATTR(temp10_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 9);
+
+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(temp4_input, S_IRUGO, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5);
+static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6);
+static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7);
+static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp, NULL, 8);
+static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp, NULL, 9);
+
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO, show_fan_max, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_max, S_IRUGO, show_fan_max, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_max, S_IRUGO, show_fan_max, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_max, S_IRUGO, show_fan_max, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan_min, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan_min, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO, show_fan_min, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_min, S_IRUGO, show_fan_min, NULL, 3);
+
+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(fan3_input, S_IRUGO, show_fan, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(raw_input, S_IRUGO, show_raw, NULL, 0);
+static SENSOR_DEVICE_ATTR(force_pwm_max, S_IWUSR | S_IRUGO, show_force_pwm_max, set_force_pwm_max, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0);
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 0);
+static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm4_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 0);
+static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm4_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 0);
+static SENSOR_DEVICE_ATTR(pwm2_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 1);
+static SENSOR_DEVICE_ATTR(pwm3_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 2);
+static SENSOR_DEVICE_ATTR(pwm4_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 0);
+static SENSOR_DEVICE_ATTR(pwm2_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 1);
+static SENSOR_DEVICE_ATTR(pwm3_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 2);
+static SENSOR_DEVICE_ATTR(pwm4_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 0);
+static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 1);
+static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 2);
+static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 0);
+static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 1);
+static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 2);
+static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 3);
+
+static int adt7470_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+	return i2c_probe(adapter, &addr_data, adt7470_detect);
+}
+
+static struct attribute *adt7470_attributes[] = {
+	&sensor_dev_attr_force_pwm_max.dev_attr.attr,
+	&sensor_dev_attr_alarms.dev_attr.attr,
+	&sensor_dev_attr_alarm_mask.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp4_max.dev_attr.attr,
+	&sensor_dev_attr_temp5_max.dev_attr.attr,
+	&sensor_dev_attr_temp6_max.dev_attr.attr,
+	&sensor_dev_attr_temp7_max.dev_attr.attr,
+	&sensor_dev_attr_temp8_max.dev_attr.attr,
+	&sensor_dev_attr_temp9_max.dev_attr.attr,
+	&sensor_dev_attr_temp10_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp4_min.dev_attr.attr,
+	&sensor_dev_attr_temp5_min.dev_attr.attr,
+	&sensor_dev_attr_temp6_min.dev_attr.attr,
+	&sensor_dev_attr_temp7_min.dev_attr.attr,
+	&sensor_dev_attr_temp8_min.dev_attr.attr,
+	&sensor_dev_attr_temp9_min.dev_attr.attr,
+	&sensor_dev_attr_temp10_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp4_input.dev_attr.attr,
+	&sensor_dev_attr_temp5_input.dev_attr.attr,
+	&sensor_dev_attr_temp6_input.dev_attr.attr,
+	&sensor_dev_attr_temp7_input.dev_attr.attr,
+	&sensor_dev_attr_temp8_input.dev_attr.attr,
+	&sensor_dev_attr_temp9_input.dev_attr.attr,
+	&sensor_dev_attr_temp10_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan2_max.dev_attr.attr,
+	&sensor_dev_attr_fan3_max.dev_attr.attr,
+	&sensor_dev_attr_fan4_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan4_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_raw_input.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm4.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm4_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm4_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm4_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm4_auto_channels_temp.dev_attr.attr,
+	NULL
+};
+
+static int adt7470_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *client;
+	struct adt7470_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+				     I2C_FUNC_SMBUS_WORD_DATA))
+		goto exit;
+
+	if (!(data = kzalloc(sizeof(struct adt7470_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	client = &data->client;
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &adt7470_driver;
+
+	i2c_set_clientdata(client, data);
+
+	mutex_init(&data->lock);
+
+	if (kind <= 0) {
+		int vendor, device, revision;
+
+		vendor = i2c_smbus_read_byte_data(client, ADT7470_REG_VENDOR);
+		if (vendor != ADT7470_VENDOR) {
+			printk(KERN_ERR "%s: vendor=%x\n", __FUNCTION__, vendor);
+			err = -ENODEV;
+			goto exit_free;
+		}
+
+		device = i2c_smbus_read_byte_data(client, ADT7470_REG_DEVICE);
+		if (device != ADT7470_DEVICE) {
+			printk(KERN_ERR "%s: device=%x\n", __FUNCTION__, device);
+			err = -ENODEV;
+			goto exit_free;
+		}
+
+		revision = i2c_smbus_read_byte_data(client, ADT7470_REG_REVISION);
+		if (revision != ADT7470_REVISION) {
+			printk(KERN_ERR "%s: revision=%x\n", __FUNCTION__, revision);
+			err = -ENODEV;
+			goto exit_free;
+		}
+
+		data->type = adt7470;
+	} else {
+		dev_dbg(&adapter->dev, "detection forced\n");
+	}
+
+	if (kind > 0)
+		data->type = kind;
+
+	data->attrs.attrs = adt7470_attributes;
+	strlcpy(client->name, "adt7470", I2C_NAME_SIZE);
+
+	if ((err = i2c_attach_client(client)))
+		goto exit_free;
+
+	dev_info(&client->dev, "%s chip found\n", client->name);
+
+	/* Initialize the ADT7470 chip */
+	adt7470_init_client(client);
+
+	/* Register sysfs hooks */
+	if ((err = sysfs_create_group(&client->dev.kobj, &data->attrs)))
+		goto exit_detach;
+
+	data->class_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->class_dev)) {
+		err = PTR_ERR(data->class_dev);
+		goto exit_remove;
+	}
+
+	return 0;
+
+exit_remove:
+	sysfs_remove_group(&client->dev.kobj, &data->attrs);
+exit_detach:
+	i2c_detach_client(client);
+exit_free:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int adt7470_detach_client(struct i2c_client *client)
+{
+	struct adt7470_data *data = i2c_get_clientdata(client);
+	hwmon_device_unregister(data->class_dev);
+	sysfs_remove_group(&client->dev.kobj, &data->attrs);
+	i2c_detach_client(client);
+	kfree(data);
+	return 0;
+}
+
+static int __init adt7470_init(void)
+{
+	return i2c_add_driver(&adt7470_driver);
+}
+
+static void __exit adt7470_exit(void)
+{
+	i2c_del_driver(&adt7470_driver);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("ADT7470 driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
+module_init(adt7470_init);
+module_exit(adt7470_exit);

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
@ 2007-07-07  8:21 ` Hans de Goede
  2007-07-07  8:23 ` Hans de Goede
                   ` (15 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-07  8:21 UTC (permalink / raw)
  To: lm-sensors

Darrick J. Wong wrote:
> Hi there,
> 
> Below is a first draft of a driver for the ADT7470 chip.  I've tested
> the driver against the adt7470 in the IBM IntelliStation Z30 and as far
> as I can tell it's good for controlling the speed of the CPU heatsink
> fans and monitor CPU temperature.  Please let me know what you think of
> the driver.  The patch should apply cleanly against 2.6.22-rc7.
> 

Hi,

Below is a review of your driver.

> --- /dev/null
> +++ b/drivers/hwmon/adt7470.c
> @@ -0,0 +1,875 @@
> +/*
> + * A hwmon driver for the Analog Devices ADT7470
> + * Copyright (C) 2007 IBM
> + *
> + * Author: Darrick J. Wong <djwong@us.ibm.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 - version 2.
> + */
> +

Are you sure you want this to be GPL version 2 only? The kernel as a whole is 
GPL version 2 only, but many drivers / subsystems have the "or any later 
version" language, this will make it much easier for the kerenl to go GPL v3, 
if the kernel community ever decides todo so.

> +#include <linux/module.h>
> +#include <linux/jiffies.h>
> +#include <linux/i2c.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/err.h>
> +#include <linux/mutex.h>
> +#include <linux/delay.h>
> +#include <linux/log2.h>
> +
> +#define DRV_VERSION "0.22"
> +

No driver version defines and prints please. Once the driver is in the kernel 
it may seem fixes by others too and your own versioning + changelog will become 
stale and thus useless.

> +/* Addresses to scan */
> +static unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
> +
> +/* Insmod parameters */
> +I2C_CLIENT_INSMOD_1(adt7470);
> +
> +/* ADT7470 registers */
> +#define ADT7470_REG_BASE		0x20
> +#define ADT7470_REG_TEMP_BASE		0x20
> +#define ADT7470_REG_TEMP_MAX		0x29
> +#define ADT7470_REG_FAN_BASE		0x2A
> +#define ADT7470_REG_FAN_MAX		0x31
> +#define ADT7470_REG_PWM_BASE		0x32
> +//#define ADT7470_REG_PWM_MAX		0x35

No c++ style comments please, also even if this is not used its fine to leave 
it uncommented.

> +#define ADT7470_REG_PWM_MAX_BASE	0x38
> +#define ADT7470_REG_PWM_MAX_MAX		0x3B
> +#define ADT7470_REG_CFG			0x40
> +#define		ADT7470_FSPD_MASK	0x04
> +#define ADT7470_REG_ALARM1		0x41
> +#define ADT7470_REG_ALARM2		0x42
> +#define ADT7470_REG_TEMP_LIMITS_BASE	0x44
> +#define ADT7470_REG_TEMP_LIMITS_MAX	0x57
> +#define ADT7470_REG_FAN_MIN_BASE	0x58
> +#define ADT7470_REG_FAN_MIN_MAX		0x5F
> +#define ADT7470_REG_FAN_MAX_BASE	0x60
> +#define ADT7470_REG_FAN_MAX_MAX		0x67
> +#define ADT7470_REG_PWM_CFG_BASE	0x68
> +#define ADT7470_REG_PWM12_CFG		0x68
> +#define		ADT7470_PWM1_AUTO_MASK	0x40
> +#define		ADT7470_PWM2_AUTO_MASK	0x80
> +#define ADT7470_REG_PWM34_CFG		0x69
> +#define		ADT7470_PWM3_AUTO_MASK	0x40
> +#define		ADT7470_PWM4_AUTO_MASK	0x80
> +#define	ADT7470_REG_PWM_MIN_BASE	0x6A
> +#define ADT7470_REG_PWM_MIN_MAX		0x6D
> +#define ADT7470_REG_PWM_TEMP_MIN_BASE	0x6E
> +#define ADT7470_REG_PWM_TEMP_MIN_MAX	0x71
> +#define ADT7470_REG_ACOUSTICS12		0x75
> +#define ADT7470_REG_ACOUSTICS34		0x76
> +#define ADT7470_REG_DEVICE		0x3D
> +#define ADT7470_REG_VENDOR		0x3E
> +#define ADT7470_REG_REVISION		0x3F
> +#define ADT7470_REG_ALARM1_MASK		0x72
> +#define ADT7470_REG_ALARM2_MASK		0x73
> +#define ADT7470_REG_PWM_AUTO_TEMP_BASE	0x7C
> +#define ADT7470_REG_PWM_AUTO_TEMP_MAX	0x7D
> +#define ADT7470_REG_MAX			0x81
> +
> +#define ADT7470_TEMP_COUNT	10
> +#define ADT7470_TEMP_REG(x)	(ADT7470_REG_TEMP_BASE + (x))
> +#define ADT7470_TEMP_MIN_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE + (2 * (x)))
> +#define ADT7470_TEMP_MAX_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE + (2 * (x)) + 1)
> +
> +#define ADT7470_FAN_COUNT	4
> +
> +#define ADT7470_PWM_COUNT	4
> +#define ADT7470_REG_PWM(x)	(ADT7470_REG_PWM_BASE + (x))
> +#define ADT7470_REG_PWM_MAX(x)	(ADT7470_REG_PWM_MAX_BASE + (x))
> +#define ADT7470_REG_PWM_MIN(x)	(ADT7470_REG_PWM_MIN_BASE + (x))
> +#define ADT7470_REG_PWM_TMIN(x)	(ADT7470_REG_PWM_TEMP_MIN_BASE + (x))
> +

Why all the base defines? Why not put the base address directly in the () 
macros? You do not seem to use them anywhere else.

> +#define ADT7470_VENDOR		0x41
> +#define ADT7470_DEVICE		0x70
> +#define ADT7470_REVISION	0x02
> +

Are you sure you only want to support revision 2? What are the differences? 
Shouldn't the driver be able to support other revisions too? Maybe only print a 
warning when its used with an untested revision?

> +#define ADT7470_PWM_ALL_TEMPS	0x3FF		/* "all temps" according to hwmon sysfs interface spec */
> +
> +#define REFRESH_INTERVAL	(2 * HZ)
> +#define TEMP_COLLECTION_TIME	1000		/* sleep 1s while gathering temperature data */
> +

Keep lines within 80 chars please, put long comments above the defines.

> +#define power_of_2(x)	(((x) & ((x) - 1)) = 0)
> +
> +struct adt7470_data {
> +	struct i2c_client	client;
> +	struct class_device	*class_dev;
> +	struct attribute_group	attrs;
> +	enum chips		type;

You do not seem to have an enum chips defined and you do not use type anywhere, 
maybe this can be removed?

> +	struct mutex		lock;
> +	char			valid;
> +	unsigned long		last_updated;	/* In jiffies */
> +
> +	u8			temp[ADT7470_TEMP_COUNT];
> +	u8			temp_min[ADT7470_TEMP_COUNT];
> +	u8			temp_max[ADT7470_TEMP_COUNT];
> +	u16			fan[ADT7470_FAN_COUNT];
> +	u16			fan_min[ADT7470_FAN_COUNT];
> +	u16			fan_max[ADT7470_FAN_COUNT];
> +	u16			alarms, alarms_mask;
> +	u8			force_pwm_max;
> +	u8			pwm[ADT7470_PWM_COUNT];
> +	u8			pwm_max[ADT7470_PWM_COUNT];
> +	u8			pwm_automatic[ADT7470_PWM_COUNT];
> +	u8			pwm_min[ADT7470_PWM_COUNT];
> +	u8			pwm_tmin[ADT7470_PWM_COUNT];
> +	u8			pwm_auto_temp[ADT7470_PWM_COUNT];
> +
> +	u8			raw[256];
> +};
> +
> +static int adt7470_attach_adapter(struct i2c_adapter *adapter);
> +static int adt7470_detect(struct i2c_adapter *adapter, int address, int kind);
> +static int adt7470_detach_client(struct i2c_client *client);
> +
> +static struct i2c_driver adt7470_driver = {
> +	.driver = {
> +		.name	= "adt7470",
> +	},
> +	.attach_adapter	= adt7470_attach_adapter,
> +	.detach_client	= adt7470_detach_client,
> +};
> +
> +/* 16-bit registers on the ADT7470 are low-byte first. */
> +static inline int adt7470_read16(struct i2c_client *client, u8 reg)
> +{
> +	u16 foo;
> +	foo = i2c_smbus_read_byte_data(client, reg) | ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8);
> +	return foo;
> +}
> +
> +static inline int adt7470_write16(struct i2c_client *client, u8 reg, u16 value)
> +{
> +	return i2c_smbus_write_word_data(client, reg, cpu_to_le16(value));
> +}
> +

Why use read_byte twice in read16, but write_word in write16?

> +static void adt7470_init_client(struct i2c_client *client)
> +{
> +	//struct adt7470_data *data = i2c_get_clientdata(client);
> +

Again no c+= style comments please, and why not just remove this line?

> +	int reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);

Insert a white line between declarations and statements please

> +	if (reg < 0) {
> +		dev_err(&client->dev, "cannot read configuration register\n");
> +	} else {
> +		// start monitoring (and do a self-test)

No c++ style comments please

> +		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg | 3);
> +	}
> +}
> +
> +static u16 adt7470_read_fan_min(struct i2c_client *client, int fan)
> +{
> +	return adt7470_read16(client, ADT7470_REG_FAN_MIN_BASE + (2 * fan));
> +}
> +
> +static u16 adt7470_read_fan_max(struct i2c_client *client, int fan)
> +{
> +	return adt7470_read16(client, ADT7470_REG_FAN_MAX_BASE + (2 * fan));
> +}
> +
> +static u16 adt7470_read_fan(struct i2c_client *client, int fan)
> +{
> +	return adt7470_read16(client, ADT7470_REG_FAN_BASE + (2 * fan));
> +}
> +

Why not have ADT7470_REG_FAN()  ADT7470_REG_FAN_MIN() and ADT7470_REG_FAN_MAX() 
macros, like you have for example ADT7470_TEMP_REG(x), ADT7470_TEMP_MIN_REG()

Also notice that your not being consistent with where you put the REG please 
either put the REG always directly behind ADT7470_ or always at the end.

Also why have these functions at all, why not put the single line the consist 
of directly at the place where you call them (especially if you have 
ADT7470_REG_FAN() & friends macros

> +static struct adt7470_data *adt7470_update_device(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +
> +	mutex_lock(&data->lock);
> +
> +	if (time_after(jiffies, data->last_updated + REFRESH_INTERVAL)
> +		|| !data->valid) {
> +		u8 cfg;
> +		int i;
> +
> +		/* start reading temperature sensors */
> +		cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
> +		cfg |= 0x80;
> +		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
> +
> +		/* delay is 200ms * number of tmp05 sensors */
> +		udelay(TEMP_COLLECTION_TIME);
> +

ugh, sleep here please.

> +		/* done reading temperature sensors */
> +		cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
> +		cfg &= ~0x80;
> +		i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
> +
> +		for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
> +			data->temp[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_REG(i));
> +			data->temp_min[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_MIN_REG(i));
> +			data->temp_max[i] = i2c_smbus_read_byte_data(client, ADT7470_TEMP_MAX_REG(i));
> +		}
> +
> +		for (i = 0; i < ADT7470_FAN_COUNT; i++) {
> +			data->fan[i] = adt7470_read_fan(client, i);
> +			data->fan_min[i] = adt7470_read_fan_min(client, i);
> +			data->fan_max[i] = adt7470_read_fan_max(client, i);
> +		}
> +

again, you're inconsistent, notice how you use i2c_smbus_read_byte_data 
directly for the temp sensors, but utility functions for the fans

> +		for (i = ADT7470_REG_BASE; i <= ADT7470_REG_MAX; i++) {
> +			data->raw[i - ADT7470_REG_BASE] = i2c_smbus_read_byte_data(client, i);
> +		}
> +
> +		for (i = 0; i < ADT7470_PWM_COUNT; i++) {
> +			int reg = ADT7470_REG_PWM_CFG_BASE + (i / 2);
> +			int reg_mask = ADT7470_PWM1_AUTO_MASK;
> +			if (!(i % 2))
> +				reg_mask <<= 1;
> +
> +			data->pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i));
> +			data->pwm_max[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_MAX(i));
> +			data->pwm_min[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_MIN(i));
> +			data->pwm_tmin[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_TMIN(i));
> +			data->pwm_automatic[i] = ((i2c_smbus_read_byte_data(client, reg) & reg_mask) = reg_mask);
> +			reg = ADT7470_REG_PWM_AUTO_TEMP_BASE + (i / 2);
> +			if (!(i % 2))
> +				data->pwm_auto_temp[i] = i2c_smbus_read_byte_data(client, reg) >> 4;
> +			else
> +				data->pwm_auto_temp[i] = i2c_smbus_read_byte_data(client, reg) & 0xF;
> +		}
> +
> +		data->force_pwm_max = ((i2c_smbus_read_byte_data(client, ADT7470_REG_CFG) & ADT7470_FSPD_MASK) = ADT7470_FSPD_MASK);
> +
> +		data->alarms = adt7470_read16(client, ADT7470_REG_ALARM1);
> +		data->alarms_mask = adt7470_read16(client, ADT7470_REG_ALARM1_MASK);
> +
> +		data->last_updated = jiffies;
> +		data->valid = 1;
> +	}
> +
> +	mutex_unlock(&data->lock);
> +
> +	return data;
> +}
> +
> +static ssize_t show_temp_min(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]);
> +}
> +
> +static ssize_t set_temp_min(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10) / 1000;
> +
> +	mutex_lock(&data->lock);
> +	data->temp_min[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_TEMP_MIN_REG(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_temp_max(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]);
> +}
> +
> +static ssize_t set_temp_max(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10) / 1000;
> +
> +	mutex_lock(&data->lock);
> +	data->temp_max[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_TEMP_MAX_REG(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +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 adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]);
> +}
> +
> +static ssize_t show_alarms(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +
> +	if (attr->index)
> +		return sprintf(buf, "%x\n", data->alarms);
> +	else
> +		return sprintf(buf, "%x\n", data->alarms_mask);
> +}
> +
> +static ssize_t show_fan_max(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +
> +	if (data->fan_max[attr->index])
> +		return sprintf(buf, "%d\n", (90000 * 60) / data->fan_max[attr->index]);
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_fan_min(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +
> +	if (data->fan_min[attr->index])
> +		return sprintf(buf, "%d\n", (90000 * 60) / data->fan_min[attr->index]);
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +

This may be a stupid question (I haven't fully read the datasheet) but why no 
store functions for fan_min and fan_max?

> +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 adt7470_data *data = adt7470_update_device(dev);
> +
> +	if (data->fan[attr->index] && data->fan[attr->index] != 65535)
> +		return sprintf(buf, "%d\n", (90000 * 60) / data->fan[attr->index]);
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_raw(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	int i;
> +	char *b = buf;
> +
> +	for (i = ADT7470_REG_BASE; i <= ADT7470_REG_MAX; i++)
> +		b += sprintf(b, "0x%X: 0x%X\n", i, data->raw[i - ADT7470_REG_BASE]);
> +
> +	return b - buf;
> +}
> +

Don't do this please, we have i2cdump for this

> +static ssize_t show_force_pwm_max(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", data->force_pwm_max);
> +}
> +
> +static ssize_t set_force_pwm_max(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10);
> +	u8 reg;
> +
> +	mutex_lock(&data->lock);
> +	data->force_pwm_max = temp;
> +	reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);

> +	reg &= ~ADT7470_FSPD_MASK;
> +	reg |= (temp ? ADT7470_FSPD_MASK : 0);

I think this would be (much) easier to read as:
if (temp)
   reg |= ADT7470_FSPD_MASK;
else
   reg &= ~ADT7470_FSPD_MASK;

> +	i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", data->pwm[attr->index]);
> +}
> +
> +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10);
> +
> +	mutex_lock(&data->lock);
> +	data->pwm[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_max(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", data->pwm_max[attr->index]);
> +}
> +
> +static ssize_t set_pwm_max(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10);
> +
> +	mutex_lock(&data->lock);
> +	data->pwm_max[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MAX(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_min(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", data->pwm_min[attr->index]);
> +}
> +
> +static ssize_t set_pwm_min(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10);
> +
> +	mutex_lock(&data->lock);
> +	data->pwm_min[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MIN(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_tmax(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1000 * (20 + data->pwm_tmin[attr->index]));
> +}
> +
> +static ssize_t show_pwm_tmin(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1000 * data->pwm_tmin[attr->index]);
> +}
> +
> +static ssize_t set_pwm_tmin(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10) / 1000;
> +
> +	mutex_lock(&data->lock);
> +	data->pwm_tmin[attr->index] = temp;
> +	i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_TMIN(attr->index), temp);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_auto(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]);
> +}
> +
> +static ssize_t set_pwm_auto(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = simple_strtol(buf, NULL, 10);
> +	int pwm_auto_reg = ADT7470_REG_PWM_CFG_BASE + (attr->index / 2);

please create and use a ADT7470_REG_PWM_CFG() macro for this. Again consistency 
is key here, to make it easier for others to read your code.

> +	int pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;
> +	u8 reg;
> +
> +	if (!(attr->index % 2))
> +		pwm_auto_reg_mask <<= 1;

It took me a while to understand what you're doing here, because its a bit 
convoluted, and its wrong.

pwm1 has attr->index = 0, so the if is true, thus the mask gets shifted making 
it ADT7470_PWM2_AUTO_MASK, but for pwm1 we want ADT7470_PWM1_AUTO_MASK 
ofcourse. So the test should be inverted / the '!' should be removed.

I believe this error is caused by the current code being hard to read, I 
believe that something like this is better:
int pwm_auto_reg_mask;
u8 reg;

if (attr->index & 0x01)
   pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK;
else
   pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;

> +
> +	if (temp != 2 && temp != 1)
> +		return -EINVAL;
> +	temp--;
> +
> +	mutex_lock(&data->lock);
> +	data->pwm_automatic[attr->index] = temp;
> +	reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);

> +	reg &= ~pwm_auto_reg_mask;
> +	reg |= (temp ? pwm_auto_reg_mask : 0);
Same as with the FSPD mask.

> +	i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_auto_temp(struct device *dev, struct device_attribute *devattr,
> +			char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct adt7470_data *data = adt7470_update_device(dev);
> +	u8 ctrl = data->pwm_auto_temp[attr->index];
> +
> +	if (ctrl)
> +		return sprintf(buf, "%d\n", 1 << (ctrl - 1));
> +	else
> +		return sprintf(buf, "%d\n", ADT7470_PWM_ALL_TEMPS);
> +}
> +
> +static int cvt_auto_temp(int input)
> +{
> +	if (input = ADT7470_PWM_ALL_TEMPS)
> +		return 0;
> +	if (input < 1 || !power_of_2(input))
> +		return -EINVAL;
> +	return ilog2(input) + 1;
> +}
> +
> +static ssize_t set_pwm_auto_temp(struct device *dev, struct device_attribute *devattr,
> +			const char *buf, size_t count)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	int temp = cvt_auto_temp(simple_strtol(buf, NULL, 10));
> +	int pwm_auto_reg = ADT7470_REG_PWM_AUTO_TEMP_BASE + (attr->index / 2);
Again, for consistency use a ADT7470_REG_PWM_AUTO_TEMP() macro please

> +	u8 reg;
> +
> +	if (temp < 0)
> +		return temp;
> +
> +	mutex_lock(&data->lock);
> +	data->pwm_automatic[attr->index] = temp;
> +	reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);
> +
> +	if (!(attr->index % 2)) {
> +		reg &= 0xF;
> +		reg |= (temp << 4) & 0xF0;
> +	} else {
> +		reg &= 0xF0;
> +		reg |= temp & 0xF;
> +	}
> +
> +	i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
> +	mutex_unlock(&data->lock);
> +
> +	return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL, 0);
> +static SENSOR_DEVICE_ATTR(alarm_mask, S_IRUGO, show_alarms, NULL, 1);
> +
> +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 0);
> +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 1);
> +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 2);
> +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 3);
> +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 4);
> +static SENSOR_DEVICE_ATTR(temp6_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 5);
> +static SENSOR_DEVICE_ATTR(temp7_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 6);
> +static SENSOR_DEVICE_ATTR(temp8_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 7);
> +static SENSOR_DEVICE_ATTR(temp9_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 8);
> +static SENSOR_DEVICE_ATTR(temp10_max, S_IWUSR | S_IRUGO, show_temp_max, set_temp_max, 9);
> +
> +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 0);
> +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 1);
> +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 2);
> +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 3);
> +static SENSOR_DEVICE_ATTR(temp5_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 4);
> +static SENSOR_DEVICE_ATTR(temp6_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 5);
> +static SENSOR_DEVICE_ATTR(temp7_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 6);
> +static SENSOR_DEVICE_ATTR(temp8_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 7);
> +static SENSOR_DEVICE_ATTR(temp9_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 8);
> +static SENSOR_DEVICE_ATTR(temp10_min, S_IWUSR | S_IRUGO, show_temp_min, set_temp_min, 9);
> +
> +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(temp4_input, S_IRUGO, show_temp, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4);
> +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5);
> +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6);
> +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7);
> +static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp, NULL, 8);
> +static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp, NULL, 9);
> +
> +static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO, show_fan_max, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_max, S_IRUGO, show_fan_max, NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan3_max, S_IRUGO, show_fan_max, NULL, 2);
> +static SENSOR_DEVICE_ATTR(fan4_max, S_IRUGO, show_fan_max, NULL, 3);
> +
> +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan_min, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan_min, NULL, 1);
> +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO, show_fan_min, NULL, 2);
> +static SENSOR_DEVICE_ATTR(fan4_min, S_IRUGO, show_fan_min, NULL, 3);
> +
> +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(fan3_input, S_IRUGO, show_fan, NULL, 2);
> +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
> +
> +static SENSOR_DEVICE_ATTR(raw_input, S_IRUGO, show_raw, NULL, 0);
> +static SENSOR_DEVICE_ATTR(force_pwm_max, S_IWUSR | S_IRUGO, show_force_pwm_max, set_force_pwm_max, 0);
> +
> +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0);
> +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1);
> +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
> +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_auto_point1_pwm, S_IWUSR | S_IRUGO, show_pwm_min, set_pwm_min, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_auto_point2_pwm, S_IWUSR | S_IRUGO, show_pwm_max, set_pwm_max, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_auto_point1_temp, S_IWUSR | S_IRUGO, show_pwm_tmin, set_pwm_tmin, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_auto_point2_temp, S_IRUGO, show_pwm_tmax, NULL, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_auto, set_pwm_auto, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IWUSR | S_IRUGO, show_pwm_auto_temp, set_pwm_auto_temp, 3);
> +
> +static int adt7470_attach_adapter(struct i2c_adapter *adapter)
> +{
> +	if (!(adapter->class & I2C_CLASS_HWMON))
> +		return 0;
> +	return i2c_probe(adapter, &addr_data, adt7470_detect);
> +}
> +
> +static struct attribute *adt7470_attributes[] = {
> +	&sensor_dev_attr_force_pwm_max.dev_attr.attr,
> +	&sensor_dev_attr_alarms.dev_attr.attr,
> +	&sensor_dev_attr_alarm_mask.dev_attr.attr,
> +	&sensor_dev_attr_temp1_max.dev_attr.attr,
> +	&sensor_dev_attr_temp2_max.dev_attr.attr,
> +	&sensor_dev_attr_temp3_max.dev_attr.attr,
> +	&sensor_dev_attr_temp4_max.dev_attr.attr,
> +	&sensor_dev_attr_temp5_max.dev_attr.attr,
> +	&sensor_dev_attr_temp6_max.dev_attr.attr,
> +	&sensor_dev_attr_temp7_max.dev_attr.attr,
> +	&sensor_dev_attr_temp8_max.dev_attr.attr,
> +	&sensor_dev_attr_temp9_max.dev_attr.attr,
> +	&sensor_dev_attr_temp10_max.dev_attr.attr,
> +	&sensor_dev_attr_temp1_min.dev_attr.attr,
> +	&sensor_dev_attr_temp2_min.dev_attr.attr,
> +	&sensor_dev_attr_temp3_min.dev_attr.attr,
> +	&sensor_dev_attr_temp4_min.dev_attr.attr,
> +	&sensor_dev_attr_temp5_min.dev_attr.attr,
> +	&sensor_dev_attr_temp6_min.dev_attr.attr,
> +	&sensor_dev_attr_temp7_min.dev_attr.attr,
> +	&sensor_dev_attr_temp8_min.dev_attr.attr,
> +	&sensor_dev_attr_temp9_min.dev_attr.attr,
> +	&sensor_dev_attr_temp10_min.dev_attr.attr,
> +	&sensor_dev_attr_temp1_input.dev_attr.attr,
> +	&sensor_dev_attr_temp2_input.dev_attr.attr,
> +	&sensor_dev_attr_temp3_input.dev_attr.attr,
> +	&sensor_dev_attr_temp4_input.dev_attr.attr,
> +	&sensor_dev_attr_temp5_input.dev_attr.attr,
> +	&sensor_dev_attr_temp6_input.dev_attr.attr,
> +	&sensor_dev_attr_temp7_input.dev_attr.attr,
> +	&sensor_dev_attr_temp8_input.dev_attr.attr,
> +	&sensor_dev_attr_temp9_input.dev_attr.attr,
> +	&sensor_dev_attr_temp10_input.dev_attr.attr,
> +	&sensor_dev_attr_fan1_max.dev_attr.attr,
> +	&sensor_dev_attr_fan2_max.dev_attr.attr,
> +	&sensor_dev_attr_fan3_max.dev_attr.attr,
> +	&sensor_dev_attr_fan4_max.dev_attr.attr,
> +	&sensor_dev_attr_fan1_min.dev_attr.attr,
> +	&sensor_dev_attr_fan2_min.dev_attr.attr,
> +	&sensor_dev_attr_fan3_min.dev_attr.attr,
> +	&sensor_dev_attr_fan4_min.dev_attr.attr,
> +	&sensor_dev_attr_fan1_input.dev_attr.attr,
> +	&sensor_dev_attr_fan2_input.dev_attr.attr,
> +	&sensor_dev_attr_fan3_input.dev_attr.attr,
> +	&sensor_dev_attr_fan4_input.dev_attr.attr,
> +	&sensor_dev_attr_raw_input.dev_attr.attr,
> +	&sensor_dev_attr_pwm1.dev_attr.attr,
> +	&sensor_dev_attr_pwm2.dev_attr.attr,
> +	&sensor_dev_attr_pwm3.dev_attr.attr,
> +	&sensor_dev_attr_pwm4.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_auto_point1_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_auto_point2_pwm.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_auto_point1_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_enable.dev_attr.attr,
> +	&sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr,
> +	&sensor_dev_attr_pwm4_auto_channels_temp.dev_attr.attr,
> +	NULL
> +};
> +

Maybe just but all the sensor_attr directly into an array and use a for loop on 
that array to register / unregister instead of sysfs attribute groups?

That would save you the above array.


> +static int adt7470_detect(struct i2c_adapter *adapter, int address, int kind)
> +{
> +	struct i2c_client *client;
> +	struct adt7470_data *data;
> +	int err = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
> +				     I2C_FUNC_SMBUS_WORD_DATA))
> +		goto exit;
> +
> +	if (!(data = kzalloc(sizeof(struct adt7470_data), GFP_KERNEL))) {
> +		err = -ENOMEM;
> +		goto exit;
> +	}
> +
> +	client = &data->client;
> +	client->addr = address;
> +	client->adapter = adapter;
> +	client->driver = &adt7470_driver;
> +
> +	i2c_set_clientdata(client, data);
> +
> +	mutex_init(&data->lock);
> +
> +	if (kind <= 0) {
> +		int vendor, device, revision;
> +
> +		vendor = i2c_smbus_read_byte_data(client, ADT7470_REG_VENDOR);
> +		if (vendor != ADT7470_VENDOR) {
> +			printk(KERN_ERR "%s: vendor=%x\n", __FUNCTION__, vendor);
> +			err = -ENODEV;
> +			goto exit_free;
> +		}
> +
> +		device = i2c_smbus_read_byte_data(client, ADT7470_REG_DEVICE);
> +		if (device != ADT7470_DEVICE) {
> +			printk(KERN_ERR "%s: device=%x\n", __FUNCTION__, device);
> +			err = -ENODEV;
> +			goto exit_free;
> +		}
> +
> +		revision = i2c_smbus_read_byte_data(client, ADT7470_REG_REVISION);
> +		if (revision != ADT7470_REVISION) {
> +			printk(KERN_ERR "%s: revision=%x\n", __FUNCTION__, revision);
> +			err = -ENODEV;
> +			goto exit_free;
> +		}
> +
> +		data->type = adt7470;
> +	} else {
> +		dev_dbg(&adapter->dev, "detection forced\n");
> +	}
> +
> +	if (kind > 0)
> +		data->type = kind;
> +
> +	data->attrs.attrs = adt7470_attributes;
> +	strlcpy(client->name, "adt7470", I2C_NAME_SIZE);
> +
> +	if ((err = i2c_attach_client(client)))
> +		goto exit_free;
> +
> +	dev_info(&client->dev, "%s chip found\n", client->name);
> +
> +	/* Initialize the ADT7470 chip */
> +	adt7470_init_client(client);
> +
> +	/* Register sysfs hooks */
> +	if ((err = sysfs_create_group(&client->dev.kobj, &data->attrs)))
> +		goto exit_detach;
> +
> +	data->class_dev = hwmon_device_register(&client->dev);
> +	if (IS_ERR(data->class_dev)) {
> +		err = PTR_ERR(data->class_dev);
> +		goto exit_remove;
> +	}
> +
> +	return 0;
> +
> +exit_remove:
> +	sysfs_remove_group(&client->dev.kobj, &data->attrs);
> +exit_detach:
> +	i2c_detach_client(client);
> +exit_free:
> +	kfree(data);
> +exit:
> +	return err;
> +}
> +
> +static int adt7470_detach_client(struct i2c_client *client)
> +{
> +	struct adt7470_data *data = i2c_get_clientdata(client);
> +	hwmon_device_unregister(data->class_dev);
> +	sysfs_remove_group(&client->dev.kobj, &data->attrs);
> +	i2c_detach_client(client);
> +	kfree(data);
> +	return 0;
> +}
> +
> +static int __init adt7470_init(void)
> +{
> +	return i2c_add_driver(&adt7470_driver);
> +}
> +
> +static void __exit adt7470_exit(void)
> +{
> +	i2c_del_driver(&adt7470_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ADT7470 driver");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION(DRV_VERSION);
> +
> +module_init(adt7470_init);
> +module_exit(adt7470_exit);
> 
> 

Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
  2007-07-07  8:21 ` Hans de Goede
@ 2007-07-07  8:23 ` Hans de Goede
  2007-07-08 16:53 ` Jean Delvare
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-07  8:23 UTC (permalink / raw)
  To: lm-sensors

p.s.

I would be much obliged if in return you could review my latest driver:
http://lists.lm-sensors.org/pipermail/lm-sensors/2007-July/020297.html

Thanks,

Hans


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
  2007-07-07  8:21 ` Hans de Goede
  2007-07-07  8:23 ` Hans de Goede
@ 2007-07-08 16:53 ` Jean Delvare
  2007-07-10 18:59 ` Darrick J. Wong
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Jean Delvare @ 2007-07-08 16:53 UTC (permalink / raw)
  To: lm-sensors

Hi Darrick,

On Fri, 6 Jul 2007 14:11:45 -0700, Darrick J. Wong wrote:
> Below is a first draft of a driver for the ADT7470 chip.  I've tested
> the driver against the adt7470 in the IBM IntelliStation Z30 and as far
> as I can tell it's good for controlling the speed of the CPU heatsink
> fans and monitor CPU temperature.  Please let me know what you think of
> the driver.  The patch should apply cleanly against 2.6.22-rc7.

I've added an entry in our wiki Devices page to track this one.

Note that back in January this year, someone (Vadim Zeitlin, Cc'd)
asked for support for this chip. So maybe Vadim would be interested in
testing your driver.

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (2 preceding siblings ...)
  2007-07-08 16:53 ` Jean Delvare
@ 2007-07-10 18:59 ` Darrick J. Wong
  2007-07-10 23:33 ` Darrick J. Wong
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Darrick J. Wong @ 2007-07-10 18:59 UTC (permalink / raw)
  To: lm-sensors


[-- Attachment #1.1: Type: text/plain, Size: 2402 bytes --]

On Sat, Jul 07, 2007 at 10:21:57AM +0200, Hans de Goede wrote:

> Hi,
> 
> Below is a review of your driver.

Thanks for taking the time to review it.  In general I've found the
points you make are pretty good and have implemented them.  That said,
there were a few that I have some questions about, so I'll skip down to
those.  I intend to post a revised patch shortly, and review your driver
after that.

> Why all the base defines? Why not put the base address directly in the () 
> macros? You do not seem to use them anywhere else.

I prefer to put all the register and bit-field definitions in one place
so that later I can construct a map of all known registers after I've
forgotten which ones the driver talks to and which ones it doesn't.  The
accessor macros that come after that are merely sugar, which is why I
don't like encoding the base register address directly into the macro.

> Are you sure you only want to support revision 2? What are the differences? 
> Shouldn't the driver be able to support other revisions too? Maybe only 
> print a warning when its used with an untested revision?

I don't have a data sheet for other revisions, so the safest thing to do is
to reject that which we don't know about; an interested user can always
bypass those safety checks with force_adt7470= at a later point in time
and post a patch if the driver still works ok.

> Why use read_byte twice in read16, but write_word in write16?

A "16 bit" register is really two 8 bit register reads/writes, and the
register with the lower address has to be read/written first.  Hence the
weird helper functions.

> >+		/* delay is 200ms * number of tmp05 sensors */
> >+		udelay(TEMP_COLLECTION_TIME);
> 
> ugh, sleep here please.

Would msleep_interruptible() work here?  Some informal testing shows
that it seems to work ok, though I've not subjected it to a rigorous ^C
test to see if I can hit weird crash conditions.

> This may be a stupid question (I haven't fully read the datasheet) but why 
> no store functions for fan_min and fan_max?

Silly thinko on my part; the v2 patch will have those store functions.

> Maybe just but all the sensor_attr directly into an array and use a for 
> loop on that array to register / unregister instead of sysfs attribute 
> groups?

I implemented that, using your f71882fg driver as an example.

--D

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (3 preceding siblings ...)
  2007-07-10 18:59 ` Darrick J. Wong
@ 2007-07-10 23:33 ` Darrick J. Wong
  2007-07-11  5:23 ` Hans de Goede
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Darrick J. Wong @ 2007-07-10 23:33 UTC (permalink / raw)
  To: lm-sensors


[-- Attachment #1.1: Type: text/plain, Size: 35886 bytes --]

On Sat, Jul 07, 2007 at 10:23:45AM +0200, Hans de Goede wrote:
> p.s.
> 
> I would be much obliged if in return you could review my latest driver:
> http://lists.lm-sensors.org/pipermail/lm-sensors/2007-July/020297.html

Review follows below.

> This is a new driver for the hardware monitoring features of the Fintek
> F71882FG and F71883FG Super-I/O chips.
> 
> This version of the driver does not support the pwm part of these chips (yet).
> I'll first design a sysfs api for this and post that for discussion, and then
> implement pwm support as an incremental patch over this one.
> 
> This driver supports all sensors of this chip, except for the vid inputs. The
> vid inputs are somewhat documented in the datasheet, but I know nothing about
> vid/vrm stuff. Help with this would be much appreciated.
> 
> Signed-off-by: Hans de Goede <j.w.r.degoede@hhs.nl>

Hmm... no Signed-off-by from Mr. Edgington?

> diff -up linux-2.6.22-rc4/drivers/hwmon/f71882fg.c.newdriver linux-2.6.22-rc4/drivers/hwmon/f71882fg.c
> --- linux-2.6.22-rc4/drivers/hwmon/f71882fg.c.newdriver	2007-07-06 15:38:45.000000000 +0200
> +++ linux-2.6.22-rc4/drivers/hwmon/f71882fg.c	2007-07-06 15:38:34.000000000 +0200
> @@ -0,0 +1,949 @@
> +/***************************************************************************
> + *   Copyright (C) 2006 by Hans Edgington <hans@edgington.nl>              *
> + *   Copyright (C) 2007 by Hans de Goede  <j.w.r.degoede@hhs.nl>           *
> + *                                                                         *
> + *   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.,                                       *
> + *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
> + ***************************************************************************/
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/jiffies.h>
> +#include <linux/platform_device.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/err.h>
> +#include <linux/mutex.h>
> +#include <asm/io.h>
> +
> +#define DRVNAME "f71882fg"
> +
> +#define SIO_F71882FG_LD_HWM	0x04	/* Hardware monitor logical device*/
> +#define SIO_UNLOCK_KEY		0x87	/* Key to enable Super-I/O */
> +#define SIO_LOCK_KEY		0xAA	/* Key to diasble Super-I/O */
> +
> +#define SIO_REG_LDSEL		0x07	/* Logical device select */
> +#define SIO_REG_DEVID		0x20	/* Device ID (2 bytes) */
> +#define SIO_REG_DEVREV		0x22	/* Device revision */
> +#define SIO_REG_MANID		0x23	/* Fintek ID (2 bytes) */
> +#define SIO_REG_ENABLE		0x30	/* Logical device enable */
> +#define SIO_REG_ADDR		0x60	/* Logical device address (2 bytes) */
> +
> +#define SIO_FINTEK_ID		0x1934	/* Manufacturers ID */
> +#define SIO_F71882_ID		0x0541	/* Chipset ID */
> +
> +#define REGION_LENGTH		8
> +#define ADDR_REG_OFFSET		5
> +#define DATA_REG_OFFSET		6
> +
> +#define F71882FG_REG_PECI		0x0A
> +
> +#define F71882FG_REG_IN_STATUS		0x12
> +#define F71882FG_REG_IN_BEEP		0x13
> +#define F71882FG_REG_IN(nr)		(0x20  + (nr))
> +#define F71882FG_REG_IN1_HIGH		0x32
> +
> +#define F71882FG_REG_FAN(nr)		(0xA0 + (16 * (nr)))
> +#define F71882FG_REG_FAN_STATUS		0x92
> +#define F71882FG_REG_FAN_BEEP		0x93
> +
> +#define F71882FG_REG_TEMP(nr)		(0x72 + 2 * (nr))
> +#define F71882FG_REG_TEMP_OVT(nr)	(0x82 + 2 * (nr))
> +#define F71882FG_REG_TEMP_HIGH(nr)	(0x83 + 2 * (nr))
> +#define F71882FG_REG_TEMP_STATUS	0x62
> +#define F71882FG_REG_TEMP_BEEP		0x63
> +#define F71882FG_REG_TEMP_HYST1		0x6C
> +#define F71882FG_REG_TEMP_HYST23	0x6D
> +#define F71882FG_REG_TEMP_TYPE		0x6B
> +#define F71882FG_REG_TEMP_DIODE_OPEN	0x6F
> +
> +#define	F71882FG_REG_START		0x01
> +
> +#define FAN_MIN_DETECT			366 /* Lowest detectable fanspeed */
> +
> +static struct platform_device *f71882fg_pdev = NULL;
> +
> +/* Super-I/O Function prototypes */
> +static inline int superio_inb(int base, int reg);
> +static inline int superio_inw(int base, int reg);
> +static inline void superio_enter(int base);
> +static inline void superio_select(int base, int ld);
> +static inline void superio_exit(int base);
> +
> +static inline u16 fan_from_reg ( u16 reg );
> +
> +struct f71882fg_data {
> +	unsigned short addr;
> +	struct class_device *class_dev;
> +
> +	struct mutex update_lock;
> +	char valid;			/* !=0 if following fields are valid */
> +	unsigned long last_updated;	/* In jiffies */
> +	unsigned long last_limits;	/* In jiffies */
> +
> +	/* Register Values */
> +	u8	in[9];
> +	u8	in1_max;
> +	u8	in_status;
> +	u8	in_beep;
> +	u16	fan[4];
> +	u8	fan_status;
> +	u8	fan_beep;
> +	u8	temp[3];
> +	u8	temp_ovt[3];
> +	u8	temp_high[3];
> +	u8	temp_hyst[3];
> +	u8	temp_type[3];
> +	u8	temp_status;
> +	u8	temp_beep;
> +	u8	temp_diode_open;
> +};
> +
> +static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
> +static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
> +static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
> +
> +/* Sysfs in*/
> +static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
> +	char *buf);
> +static ssize_t show_in_max(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_in_max(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_in_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_in_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_in_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +/* Sysfs Fan */
> +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
> +	char *buf);
> +static ssize_t show_fan_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_fan_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +/* Sysfs Temp */
> +static ssize_t show_temp(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t show_temp_max(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_temp_max(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_temp_crit(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_temp_crit(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t show_temp_type(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t show_temp_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t store_temp_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count);
> +static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +static ssize_t show_temp_fault(struct device *dev, struct device_attribute
> +	*devattr, char *buf);
> +/* Sysfs misc */
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> +	char *buf);
> +
> +static int __devinit f71882fg_probe(struct platform_device * pdev);
> +static int __devexit f71882fg_remove(struct platform_device *pdev);
> +static int __init f71882fg_init(void);
> +static int __init f71882fg_find(int sioaddr, unsigned short *address);
> +static int __init f71882fg_device_add(unsigned short address);
> +static void __exit f71882fg_exit(void);
> +
> +static struct platform_driver f71882fg_driver = {
> +	.driver = {
> +		.owner	= THIS_MODULE,
> +		.name	= DRVNAME,
> +	},
> +	.probe		= f71882fg_probe,
> +	.remove		= __devexit_p(f71882fg_remove),
> +};
> +
> +static struct device_attribute f71882fg_dev_attr[] =
> +{
> +	__ATTR( name, S_IRUGO, show_name, NULL ),
> +};
> +
> +static struct sensor_device_attribute f71882fg_in_temp_attr[] =
> +{
> +	SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0),
> +	SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1),
> +	SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1),
> +	SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1),
> +	SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1),
> +	SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
> +	SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
> +	SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
> +	SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
> +	SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
> +	SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
> +	SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
> +	SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
> +	SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 0),
> +	SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 0),
> +	SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 0),
> +	SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
> +	SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
> +	SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 0),
> +	SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
> +	SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
> +	SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
> +	SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 1),
> +	SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 1),
> +	SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 1),
> +	SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
> +	SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
> +	SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 1),
> +	SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
> +	SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
> +	SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
> +	SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 2),
> +	SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 2),
> +	SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 2),
> +	SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
> +	SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
> +	SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 2),
> +	SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
> +	SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
> +};
> +
> +static struct sensor_device_attribute f71882fg_fan_attr[] =
> +{
> +	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
> +	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> +		store_fan_beep, 0),
> +	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
> +	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
> +	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> +		store_fan_beep, 1),
> +	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
> +	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
> +	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> +		store_fan_beep, 2),
> +	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
> +	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
> +	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> +		store_fan_beep, 3),
> +	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
> +};

If you moved these structs to the bottom, you could avoid having to
declare the function prototypes for static functions, thus making
your driver shorter.  gcc will still catch error cases where the static
function's signature doesn't match whatever SENSOR_ATTR wants.

It doesn't really matter to me, but I'm curious to learn your
reasons for choosing this method.

> +/* Super I/O functions */
> +static inline int superio_inb(int base, int reg)
> +{
> +	outb(reg, base);
> +	return inb(base + 1);
> +}
> +
> +static int superio_inw(int base, int reg)
> +{
> +	int val;
> +	outb(reg++, base);
> +	val = inb(base + 1) << 8;
> +	outb(reg, base);
> +	val |= inb(base + 1);
> +	return val;
> +}
> +
> +static inline void superio_enter(int base)
> +{
> +	outb( SIO_UNLOCK_KEY, base);
> +	outb( SIO_UNLOCK_KEY, base);

Are you deliberately doing this twice?  If so, a comment to that
effect would be a good idea.

> +}
> +
> +static inline void superio_select( int base, int ld)
> +{
> +	outb(SIO_REG_LDSEL, base);
> +	outb(ld, base + 1);
> +}
> +
> +static inline void superio_exit(int base)
> +{
> +	outb(SIO_LOCK_KEY, base);
> +}
> +
> +static inline u16 fan_from_reg(u16 reg)
> +{
> +	return (1500000 / reg);
> +}

I've not read the spec sheet, but are you sure reg != 0?  I've observed
overheating conditions with the adt7470 where it starts returning bogus
values that the data sheets say it shouldn't ever return, and crashing
the kernel would not be a good way to react to that.

> +static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg)
> +{
> +	u8 val;
> +
> +	outb(reg, data->addr + ADDR_REG_OFFSET);
> +	val = inb(data->addr + DATA_REG_OFFSET);
> +
> +	return val;
> +}
> +
> +static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg)
> +{
> +	u16 val;
> +
> +	outb(reg, data->addr + ADDR_REG_OFFSET);
> +	val = inb(data->addr + DATA_REG_OFFSET) << 8;
> +	outb(++reg, data->addr + ADDR_REG_OFFSET);

superio_inw() used "reg++" and then "reg"; here you use "reg" then
"++reg".  Though the two are functionally equivalent here, I wonder
about the inconsistency.

> +	val |= inb(data->addr + DATA_REG_OFFSET);
> +
> +	return val;
> +}
> +
> +static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val)
> +{
> +	outb(reg, data->addr + ADDR_REG_OFFSET);
> +	outb(val, data->addr + DATA_REG_OFFSET);	
> +}
> +
> +static struct f71882fg_data *f71882fg_update_device(struct device * dev)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr, reg, reg2;
> +
> +	mutex_lock(&data->update_lock);
> +	
> +	/* Update once every 60 seconds */
> +	if ( time_after(jiffies, data->last_updated + 60 * HZ ) ||
> +			!data->valid) {

You test last_updated here but update last_limits below.  Is that
really what you intended?  last_limits isn't used anywhere in
the driver.

> +		data->in1_max = f71882fg_read8(data, F71882FG_REG_IN1_HIGH);
> +		data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP);
> +			
> +		/* Get High & boundary temps*/
> +		for (nr = 0; nr < 3; nr++) {
> +			data->temp_ovt[nr] = f71882fg_read8(data,
> +						F71882FG_REG_TEMP_OVT(nr));
> +			data->temp_high[nr] = f71882fg_read8(data,
> +						F71882FG_REG_TEMP_HIGH(nr));
> +		}
> +
> +		/* Have to hardcode hyst*/
> +		data->temp_hyst[0] = f71882fg_read8(data,
> +						F71882FG_REG_TEMP_HYST1) >> 4;
> +		/* Hyst temps 2 & 3 stored in same register */
> +		reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST23);
> +		data->temp_hyst[1] = reg & 0x0F;
> +		data->temp_hyst[2] = reg >> 4;
> +
> +		/* Have to hardcode type, because temp1 is special */
> +		reg  = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE);
> +		reg2 = f71882fg_read8(data, F71882FG_REG_PECI);
> +		if ((reg2 & 0x03) == 0x01) 
> +			data->temp_type[0] = 6 /* PECI */;
> +		else if ((reg2 & 0x03) == 0x02)
> +			data->temp_type[0] = 5 /* AMDSI */;
> +		else
> +			data->temp_type[0] = (reg & 0x02) ? 2 : 4;
> +		
> +		data->temp_type[1] = (reg & 0x04) ? 2 : 4;
> +		data->temp_type[2] = (reg & 0x08) ? 2 : 4;
> +		
> +		data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP);
> +		
> +		data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP);
> +
> +		data->last_limits = jiffies;
> +	}
> +	
> +	/* Update every second */
> +	if (time_after(jiffies, data->last_updated + HZ) || !data->valid)  {
> +		data->temp_status = f71882fg_read8(data,
> +						F71882FG_REG_TEMP_STATUS);
> +		data->temp_diode_open = f71882fg_read8(data,
> +						F71882FG_REG_TEMP_DIODE_OPEN);
> +		for (nr = 0; nr < 3; nr++)
> +			data->temp[nr] = f71882fg_read8(data,
> +						F71882FG_REG_TEMP(nr));
> +
> +		data->fan_status = f71882fg_read8(data,
> +						F71882FG_REG_FAN_STATUS);
> +		for (nr = 0; nr < 4; nr++)
> +			data->fan[nr] = f71882fg_read16(data,
> +						F71882FG_REG_FAN(nr));
> +
> +		data->in_status = f71882fg_read8(data,
> +						F71882FG_REG_IN_STATUS);
> +		for (nr = 0; nr < 9; nr++)
> +			data->in[nr] = f71882fg_read8(data,
> +						F71882FG_REG_IN(nr));
> +
> +		data->last_updated = jiffies;
> +		data->valid = 1;
> +	}
> +
> +	mutex_unlock(&data->update_lock);
> +
> +	return data;
> +}
> +
> +/* Sysfs Interface */
> +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
> +	char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int speed = fan_from_reg(data->fan[nr]);
> +
> +	if (speed == FAN_MIN_DETECT)
> +		speed = 0;
> +
> +	return sprintf(buf, "%d\n", speed);
> +}
> +
> +static ssize_t show_fan_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->fan_beep & (1 << nr))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t store_fan_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10);
> +	
> +	mutex_lock(&data->update_lock);
> +	if (val)
> +		data->fan_beep |= 1 << nr;
> +	else
> +		data->fan_beep &= ~(1 << nr);
> +	
> +	f71882fg_write8(data, F71882FG_REG_FAN_BEEP, data->fan_beep);
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->fan_status & (1 << nr))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
> +	char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n", data->in[nr] * 8);
> +}
> +
> +static ssize_t show_in_max(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +
> +	return sprintf(buf, "%d\n", data->in1_max * 8);
> +}
> +
> +static ssize_t store_in_max(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int val = simple_strtoul(buf, NULL, 10) / 8;
> +	
> +	if (val > 255)
> +		val = 255;

This check won't catch the case where I feed your sysfs file "-4096"
and the value inside f71882fg_data won't match what gets written to the
port.  Granted, the maxim "If you write garbage to the control systems
you deserve what you get" might apply here too.

> +	
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_IN1_HIGH, val);
> +	data->in1_max = val;
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_in_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->in_beep & (1 << nr))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t store_in_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10);
> +	
> +	mutex_lock(&data->update_lock);
> +	if (val)
> +		data->in_beep |= 1 << nr;
> +	else
> +		data->in_beep &= ~(1 << nr);
> +	
> +	f71882fg_write8(data, F71882FG_REG_IN_BEEP, data->in_beep);
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_in_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->in_status & (1 << nr))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
> +	char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n", data->temp[nr] * 1000);
> +}
> +
> +static ssize_t show_temp_max(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
> +}
> +
> +static ssize_t store_temp_max(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10) / 1000;
> +	
> +	if (val > 255)
> +		val = 255;
> +	
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_TEMP_HIGH(nr), val);
> +	data->temp_high[nr] = val;
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n",
> +		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
> +}
> +
> +static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10) / 1000;
> +	ssize_t ret = count;
> +	
> +	mutex_lock(&data->update_lock);
> +
> +	/* convert abs to relative and check */
> +	val = data->temp_high[nr] - val;
> +	if (val < 0 || val > 15) {
> +		ret = -EINVAL;
> +		goto store_temp_max_hyst_exit;
> +	}
> +	
> +	data->temp_hyst[nr] = val;
> +	
> +	/* convert value to register contents */
> +	switch(nr) {
> +		case 0:
> +			val = val << 4;
> +			break;
> +		case 1:
> +			val = val | (data->temp_hyst[2] << 4);
> +			break;
> +		case 2:
> +			val = data->temp_hyst[1] | (val << 4);
> +			break;
> +	}
> +
> +	f71882fg_write8(data, nr ? F71882FG_REG_TEMP_HYST23 :
> +		F71882FG_REG_TEMP_HYST1, val);
> +	
> +store_temp_max_hyst_exit:
> +	mutex_unlock(&data->update_lock);
> +	return ret;
> +}
> +
> +static ssize_t show_temp_crit(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
> +}
> +
> +static ssize_t store_temp_crit(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10) / 1000;
> +	
> +	if (val > 255)
> +		val = 255;
> +	
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_TEMP_OVT(nr), val);
> +	data->temp_ovt[nr] = val;
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n",
> +		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
> +}
> +
> +static ssize_t show_temp_type(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	return sprintf(buf, "%d\n", data->temp_type[nr]);
> +}
> +
> +static ssize_t show_temp_beep(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->temp_beep & (1 << (nr + 1)))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t store_temp_beep(struct device *dev, struct device_attribute
> +	*devattr, const char *buf, size_t count)
> +{
> +	struct f71882fg_data *data = dev_get_drvdata(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +	int val = simple_strtoul(buf, NULL, 10);
> +	
> +	mutex_lock(&data->update_lock);
> +	if (val)
> +		data->temp_beep |= 1 << (nr + 1);
> +	else
> +		data->temp_beep &= ~(1 << (nr + 1));
> +	
> +	f71882fg_write8(data, F71882FG_REG_TEMP_BEEP, data->temp_beep);
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->temp_status & (1 << (nr + 1)))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_temp_fault(struct device *dev, struct device_attribute
> +	*devattr, char *buf)
> +{
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr(devattr)->index;
> +
> +	if (data->temp_diode_open & (1 << (nr + 1)))
> +		return sprintf(buf, "1\n");
> +	else
> +		return sprintf(buf, "0\n");
> +}
> +
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> +	char *buf)
> +{
> +	return sprintf(buf, DRVNAME "\n");
> +}
> +
> +
> +static int __devinit f71882fg_probe(struct platform_device * pdev)
> +{
> +	struct f71882fg_data *data;
> +	int err, i;
> +	u8 start_reg;
> +
> +	if(!(data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL)))
> +		return -ENOMEM;
> +
> +	data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
> +	mutex_init(&data->update_lock);
> +	platform_set_drvdata(pdev, data);
> +
> +	/* Register sysfs interface files */
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) {
> +		err = device_create_file(&pdev->dev, &f71882fg_dev_attr[i]);
> +		if (err)
> +			goto exit_unregister_sysfs;
> +	}
> +	
> +	start_reg = f71882fg_read8(data, F71882FG_REG_START);
> +	if (start_reg & 0x01) {
> +		for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) {
> +			err = device_create_file(&pdev->dev,
> +					&f71882fg_in_temp_attr[i].dev_attr);
> +			if (err)
> +				goto exit_unregister_sysfs;
> +		}
> +	}
> +
> +	if (start_reg & 0x02) {
> +		for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) {
> +			err = device_create_file(&pdev->dev,
> +					&f71882fg_fan_attr[i].dev_attr);
> +			if (err)
> +				goto exit_unregister_sysfs;
> +		}
> +	}
> +	
> +	data->class_dev = hwmon_device_register(&pdev->dev);
> +	if (IS_ERR(data->class_dev)) {
> +		err = PTR_ERR(data->class_dev);
> +		goto exit_unregister_sysfs;
> +	}
> +
> +	return 0;
> +
> +exit_unregister_sysfs:
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++)
> +		device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]);
> +	
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
> +		device_remove_file(&pdev->dev,
> +					&f71882fg_in_temp_attr[i].dev_attr);
> +	
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
> +		device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
> +
> +	kfree(data);
> +
> +	return err;
> +}
> +
> +static int __devexit f71882fg_remove(struct platform_device *pdev)
> +{
> +	int i;
> +	struct f71882fg_data *data = platform_get_drvdata(pdev);
> +
> +	platform_set_drvdata(pdev, NULL);
> +	hwmon_device_unregister(data->class_dev);
> +
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++)
> +		device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]);
> +	
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
> +		device_remove_file(&pdev->dev,
> +					&f71882fg_in_temp_attr[i].dev_attr);
> +	
> +	for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
> +		device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
> +
> +	kfree(data);
> +
> +	return 0;
> +}
> +
> +static int __init f71882fg_find(int sioaddr, unsigned short *address)
> +{
> +	int err = -ENODEV;
> +	u16 devid;
> +	u8 start_reg;
> +	struct f71882fg_data data;
> +
> +	superio_enter(sioaddr);
> +	
> +	devid = superio_inw(sioaddr, SIO_REG_MANID);
> +	if (devid != SIO_FINTEK_ID) {
> +		printk(KERN_INFO DRVNAME ": Not a Fintek device\n");
> +		goto exit;
> +	}
> +
> +	devid = superio_inw(sioaddr, SIO_REG_DEVID);
> +	if (devid != SIO_F71882_ID) {
> +		printk(KERN_INFO DRVNAME ": Unsupported Fintek device\n");
> +		goto exit;
> +	}
> +
> +	superio_select(sioaddr, SIO_F71882FG_LD_HWM);
> +	if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01))  {
> +		printk(KERN_WARNING DRVNAME ": Device not activated\n");
> +		goto exit;
> +	}
> +
> +	*address = superio_inw(sioaddr, SIO_REG_ADDR);
> +	if (*address == 0) 
> +	{
> +		printk(KERN_WARNING DRVNAME ": Base address not set\n");
> +		goto exit;
> +	}
> +	*address &= ~(REGION_LENGTH - 1);	/* Ignore 3 LSB */
> +	
> +	data.addr = *address;
> +	start_reg = f71882fg_read8(&data, F71882FG_REG_START);
> +	if (!(start_reg & 0x03)) {
> +		printk(KERN_WARNING DRVNAME
> +			": Hardware monitoring not activated\n");
> +		goto exit;
> +	}
> +
> +	err = 0;
> +	printk(KERN_INFO DRVNAME ": Found F71882FG chip at %#x, revision %d\n",
> +		(unsigned int)*address,
> +		(int)superio_inb(sioaddr, SIO_REG_DEVREV));
> +exit:
> +	superio_exit(sioaddr);
> +	return err;
> +}
> +
> +static int __init f71882fg_device_add(unsigned short address)
> +{
> +	struct resource res = {
> +		.start	= address,
> +		.end	= address + REGION_LENGTH - 1,
> +		.flags	= IORESOURCE_IO,
> +	};
> +	int err;
> +
> +	f71882fg_pdev = platform_device_alloc(DRVNAME, address);
> +	if(!f71882fg_pdev)
> +		return -ENOMEM;
> +
> +	res.name = f71882fg_pdev->name;
> +	err = platform_device_add_resources(f71882fg_pdev, &res, 1);
> +	if(err) {
> +		printk(KERN_ERR DRVNAME ": Device resource addition failed\n");
> +		goto exit_device_put;
> +	}
> +
> +	err = platform_device_add(f71882fg_pdev);
> +	if(err) {
> +		printk(KERN_ERR DRVNAME ": Device addition failed\n");
> +		goto exit_device_put;
> +	}
> +	
> +	return 0;
> +
> +exit_device_put:
> +	platform_device_put(f71882fg_pdev);
> +
> +	return err;
> +}
> +
> +static int __init f71882fg_init(void)
> +{
> +	int err = -ENODEV;
> +	unsigned short address;
> +
> +	if (f71882fg_find(0x2e, &address) && f71882fg_find(0x4e, &address))
> +		goto exit;
> +
> +	if ((err = platform_driver_register(&f71882fg_driver)))
> +		goto exit;
> +	
> +	if ((err = f71882fg_device_add(address)))
> +		goto exit_driver;
> +
> +	return 0;
> +
> +exit_driver:
> +	platform_driver_unregister(&f71882fg_driver);
> +exit:
> +	return err;
> +}
> +
> +static void __exit f71882fg_exit(void)
> +{
> +	platform_device_unregister(f71882fg_pdev);
> +	platform_driver_unregister(&f71882fg_driver);
> +}
> +
> +MODULE_DESCRIPTION("F71882FG Hardware Monitoring Driver");
> +MODULE_AUTHOR("Hans Edgington (hans@edgington.nl)");
> +MODULE_LICENSE("GPL");
> +
> +module_init(f71882fg_init);
> +module_exit(f71882fg_exit);
> diff -up linux-2.6.22-rc4/drivers/hwmon/Kconfig.newdriver linux-2.6.22-rc4/drivers/hwmon/Kconfig
> --- linux-2.6.22-rc4/drivers/hwmon/Kconfig.newdriver	2007-07-06 15:39:00.000000000 +0200
> +++ linux-2.6.22-rc4/drivers/hwmon/Kconfig	2007-07-06 15:41:07.000000000 +0200
> @@ -199,6 +199,16 @@ config SENSORS_F71805F
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called f71805f.
>  
> +config SENSORS_F71882FG
> +	tristate "Fintek F71882FG and F71883FG"
> +	depends on EXPERIMENTAL
> +	help
> +	  If you say yes here you get support for hardware monitoring
> +	  features of the Fintek F71882FG and F71883FG Super-I/O chips.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called f71882fg.
> +
>  config SENSORS_FSCHER
>  	tristate "FSC Hermes"
>  	depends on I2C
> diff -up linux-2.6.22-rc4/drivers/hwmon/Makefile.newdriver linux-2.6.22-rc4/drivers/hwmon/Makefile
> --- linux-2.6.22-rc4/drivers/hwmon/Makefile.newdriver	2007-07-06 15:39:10.000000000 +0200
> +++ linux-2.6.22-rc4/drivers/hwmon/Makefile	2007-07-06 15:41:52.000000000 +0200
> @@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
>  obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
>  obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
>  obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
> +obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
>  obj-$(CONFIG_SENSORS_FSCHER)	+= fscher.o
>  obj-$(CONFIG_SENSORS_FSCPOS)	+= fscpos.o
>  obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (4 preceding siblings ...)
  2007-07-10 23:33 ` Darrick J. Wong
@ 2007-07-11  5:23 ` Hans de Goede
  2007-07-11 12:47 ` Hans de Goede
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-11  5:23 UTC (permalink / raw)
  To: lm-sensors

Darrick J. Wong wrote:
> On Sat, Jul 07, 2007 at 10:21:57AM +0200, Hans de Goede wrote:
> 
>> Why all the base defines? Why not put the base address directly in the () 
>> macros? You do not seem to use them anywhere else.
> 
> I prefer to put all the register and bit-field definitions in one place
> so that later I can construct a map of all known registers after I've
> forgotten which ones the driver talks to and which ones it doesn't.  The
> accessor macros that come after that are merely sugar, which is why I
> don't like encoding the base register address directly into the macro.
> 

Ok, leave them in then.

>> Are you sure you only want to support revision 2? What are the differences? 
>> Shouldn't the driver be able to support other revisions too? Maybe only 
>> print a warning when its used with an untested revision?
> 
> I don't have a data sheet for other revisions, so the safest thing to do is
> to reject that which we don't know about; an interested user can always
> bypass those safety checks with force_adt7470= at a later point in time
> and post a patch if the driver still works ok.
> 

Good point about using the force parameter, ok.

>> Why use read_byte twice in read16, but write_word in write16?
> 
> A "16 bit" register is really two 8 bit register reads/writes, and the
> register with the lower address has to be read/written first.  Hence the
> weird helper functions.
> 




>>> +		/* delay is 200ms * number of tmp05 sensors */
>>> +		udelay(TEMP_COLLECTION_TIME);
>> ugh, sleep here please.
> 
> Would msleep_interruptible() work here?  Some informal testing shows
> that it seems to work ok, though I've not subjected it to a rigorous ^C
> test to see if I can hit weird crash conditions.
> 

Well msleep_interruptible() can be interrupted (you check the return value for 
this) so if a user presses control-C, then your sleep will be cut short. So 
either you must be prepared to handle this, or sleep uninterruptible. I would 
sleep uninterruptible if I were you, using regular msleep.


Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (5 preceding siblings ...)
  2007-07-11  5:23 ` Hans de Goede
@ 2007-07-11 12:47 ` Hans de Goede
  2007-07-13 12:08 ` Hans de Goede
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-11 12:47 UTC (permalink / raw)
  To: lm-sensors

Darrick J. Wong wrote:

I just realized I failed to respond to this point, here is my response:

>> Why use read_byte twice in read16, but write_word in write16?
> 
> A "16 bit" register is really two 8 bit register reads/writes, and the
> register with the lower address has to be read/written first.  Hence the
> weird helper functions.
> 

Here is the code you use for this:

+static inline int adt7470_read_word_data(struct i2c_client *client, u8 reg)
+{
+	u16 foo;
+	foo = i2c_smbus_read_byte_data(client, reg);
+	foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8);
+	return foo;
+}
+
+static inline int adt7470_write_word_data(struct i2c_client *client, u8 reg,
+					  u16 value)
+{
+	u16 x = cpu_to_le16(value);
+
+	return i2c_smbus_write_word_data(client, reg, x & 0xFF)
+	       && i2c_smbus_write_word_data(client, reg + 1, x >> 8);
+}
+

I understand that you need to somehow read / write a byte 2 times to get a 
word, but why in adt7470_read_word_data() call i2c_smbus_read_byte_data twice? 
while in adt7470_write_word_data() you use cpu_to_le16() and then 
i2c_smbus_write_word_data(). Why not use i2c_smbus_read_word_data() in 
adt7470_read_word_data() instead of calling i2c_smbus_read_byte_data twice?

BTW I have serious doubts about you calling cpu_to_le16(), I would expect that 
i2c_smbus_write_word_data() wants the data to be in cpu endianness.

Regards,

Hans



_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (6 preceding siblings ...)
  2007-07-11 12:47 ` Hans de Goede
@ 2007-07-13 12:08 ` Hans de Goede
  2007-07-14 14:23 ` Vadim Zeitlin
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-13 12:08 UTC (permalink / raw)
  To: lm-sensors

Hi All,

While actually updating the driver I realized I didn't respond to all points 
raised (I think), so here is my reply to the other points.

>> +static struct f71882fg_data *f71882fg_update_device(struct device * dev)
>> +{
>> +	struct f71882fg_data *data = dev_get_drvdata(dev);
>> +	int nr, reg, reg2;
>> +
>> +	mutex_lock(&data->update_lock);
>> +	
>> +	/* Update once every 60 seconds */
>> +	if ( time_after(jiffies, data->last_updated + 60 * HZ ) ||
>> +			!data->valid) {
> 
> You test last_updated here but update last_limits below.  Is that
> really what you intended?  last_limits isn't used anywhere in
> the driver.
> 

Good catch, will fix.

>> +static ssize_t store_in_max(struct device *dev, struct device_attribute
>> +	*devattr, const char *buf, size_t count)
>> +{
>> +	struct f71882fg_data *data = dev_get_drvdata(dev);
>> +	int val = simple_strtoul(buf, NULL, 10) / 8;
>> +	
>> +	if (val > 255)
>> +		val = 255;
> 
> This check won't catch the case where I feed your sysfs file "-4096"
> and the value inside f71882fg_data won't match what gets written to the
> port.  Granted, the maxim "If you write garbage to the control systems
> you deserve what you get" might apply here too.
> 

Notice that I use simple_strtoul, so val will never be < 0, if you write -4096 
strtoul will not recognize the - and return 0.

I know it looks strange to store the return value in an int then, but in some 
of the other store methods I need val to be signed, and for consistency I've 
thus stored it into an int everywhere.


Regards,

Hans


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (7 preceding siblings ...)
  2007-07-13 12:08 ` Hans de Goede
@ 2007-07-14 14:23 ` Vadim Zeitlin
  2007-07-14 20:05 ` Jean Delvare
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Vadim Zeitlin @ 2007-07-14 14:23 UTC (permalink / raw)
  To: lm-sensors

On Sun, 8 Jul 2007 18:53:12 +0200 Jean Delvare <khali@linux-fr.org> wrote:

JD> On Fri, 6 Jul 2007 14:11:45 -0700, Darrick J. Wong wrote:
JD> > Below is a first draft of a driver for the ADT7470 chip.  I've tested
JD> > the driver against the adt7470 in the IBM IntelliStation Z30 and as far
JD> > as I can tell it's good for controlling the speed of the CPU heatsink
JD> > fans and monitor CPU temperature.  Please let me know what you think of
JD> > the driver.  The patch should apply cleanly against 2.6.22-rc7.
JD> 
JD> I've added an entry in our wiki Devices page to track this one.
JD> 
JD> Note that back in January this year, someone (Vadim Zeitlin, Cc'd)
JD> asked for support for this chip. So maybe Vadim would be interested in
JD> testing your driver.

 Hello,

 Thanks a lot Jean for cc'ing this to me and, of course, thanks a lot to
Darrick for writing the driver (and sorry for never getting around to
writing it myself...)! I was away during this week so I couldn't test it
before but now I got back and so I've just compiled the driver for my
current 2.6.20 kernel. The module loads (accompanied by a brief switch to
maximal fan speed judging by the noise) and creates its directory under
/sys/bus/i2c/drivers/adt7470. I don't really know what do I have to do to
make it work with lm-sensors (do I need the svn version? Debian only has
2.10.1 currently) so for now I just tried to manually check the files in
this directory.


 The first thing I noticed is that accessing any of them takes almost 3
seconds:

# time cat fan1_input
2001
cat fan1_input  0.00s user 0.00s system 0% cpu 2.760 total

I wonder if this is normal? I see a 1 sec sleep in adt7470_update_device()
but the delay happens not only for the temperatures and it's also much
bigger than 1 second.

 Next, the fan speed detection seems to work as expected, i.e. the number
it gives (like 2000 above) seem to be RPM. But reading the temperature
files produces strange results:

# grep . temp?_input
temp1_input:0
temp2_input:228000
temp3_input:59000
temp4_input:54000
temp5_input:226000
temp6_input:228000
temp7_input:58000
temp8_input:54000
temp9_input:49000

Again, I see the factor of 1000 in adt7470.c and, accounting for it, all
numbers except 227000 may be correct -- but what about those latter ones?
To confuse the matters (or at least me) further, temp_min files contain
129000 while temp_max files have 127000 (no typo) so something seems wrong
here... At the very least, the factor of 1000 seems to be unnecessary:

# grep . pwm1*
pwm1:0
pwm1_auto_channels_temp:1023
pwm1_auto_point1_pwm:128
pwm1_auto_point1_temp:90000
pwm1_auto_point2_pwm:255
pwm1_auto_point2_temp:110000
pwm1_enable:2

The values here clearly should be 90 and 110 degrees, shouldn't they?

 Please let me know what else should I test, thanks in advance!
VZ


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (8 preceding siblings ...)
  2007-07-14 14:23 ` Vadim Zeitlin
@ 2007-07-14 20:05 ` Jean Delvare
  2007-07-14 20:17 ` Hans de Goede
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Jean Delvare @ 2007-07-14 20:05 UTC (permalink / raw)
  To: lm-sensors

Hi Hans,

On Fri, 13 Jul 2007 14:08:53 +0200, Hans de Goede wrote:
> Notice that I use simple_strtoul, so val will never be < 0, if you write -4096 
> strtoul will not recognize the - and return 0.
> 
> I know it looks strange to store the return value in an int then, but in some 
> of the other store methods I need val to be signed, and for consistency I've 
> thus stored it into an int everywhere.

But wouldn't then a large input value (fitting in a long but not in an
int) be silently turned into a negative value, possibly doing crazy
things in the rest of the code?

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (9 preceding siblings ...)
  2007-07-14 20:05 ` Jean Delvare
@ 2007-07-14 20:17 ` Hans de Goede
  2007-07-16 20:18 ` Darrick J. Wong
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-14 20:17 UTC (permalink / raw)
  To: lm-sensors

Jean Delvare wrote:
> Hi Hans,
> 
> On Fri, 13 Jul 2007 14:08:53 +0200, Hans de Goede wrote:
>> Notice that I use simple_strtoul, so val will never be < 0, if you write -4096 
>> strtoul will not recognize the - and return 0.
>>
>> I know it looks strange to store the return value in an int then, but in some 
>> of the other store methods I need val to be signed, and for consistency I've 
>> thus stored it into an int everywhere.
> 
> But wouldn't then a large input value (fitting in a long but not in an
> int) be silently turned into a negative value, possibly doing crazy
> things in the rest of the code?
> 

Yes,

That could happen, but then people are _really_ asking for it. Notice that most 
hwmon drivers store functions do even less checking then this. We really need 
to discuss this and make a decision on it for all hwmon drivers, starting with 
the question wether or not to check if the user input actually is a number?

Currently a user can do:
echo -n foo > /sys/class/hwmon/hwmon0/in0_max
and not get an error (instead in0_max typically gets set to 0

Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (10 preceding siblings ...)
  2007-07-14 20:17 ` Hans de Goede
@ 2007-07-16 20:18 ` Darrick J. Wong
  2007-07-16 22:19 ` Philip Pokorny
                   ` (4 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Darrick J. Wong @ 2007-07-16 20:18 UTC (permalink / raw)
  To: lm-sensors


[-- Attachment #1.1: Type: text/plain, Size: 2322 bytes --]

On Sat, Jul 14, 2007 at 04:23:16PM +0200, Vadim Zeitlin wrote:
>  The first thing I noticed is that accessing any of them takes almost 3
> seconds:
> 
> # time cat fan1_input
> 2001
> cat fan1_input  0.00s user 0.00s system 0% cpu 2.760 total

Hm.... I see this with the v3 patch:
root@elm3a181:/usr/local/src/lm_sensors-2.10.3/prog# time cat
/sys/class/hwmon/hwmon0/device/temp1_input 
47000

real    0m1.701s
user    0m0.000s
sys     0m0.000s

Not sure why it takes 700ms to read the sensors after the 1s sleep,
perhaps that could just be the cost of reading the registers?  (I'd hope
not...)

> I wonder if this is normal? I see a 1 sec sleep in adt7470_update_device()
> but the delay happens not only for the temperatures and it's also much
> bigger than 1 second.
> 
>  Next, the fan speed detection seems to work as expected, i.e. the number
> it gives (like 2000 above) seem to be RPM. But reading the temperature
> files produces strange results:
> 
> # grep . temp?_input
> temp1_input:0
> temp2_input:228000
> temp3_input:59000
> temp4_input:54000
> temp5_input:226000
> temp6_input:228000
> temp7_input:58000
> temp8_input:54000
> temp9_input:49000
> 
> Again, I see the factor of 1000 in adt7470.c and, accounting for it, all

Documentation/hwmon/sysfs-interface says that the units for temperatures
are millidegrees C, hence the 1000 factor both here and for the pwm
controls below.

> numbers except 227000 may be correct -- but what about those latter ones?
> To confuse the matters (or at least me) further, temp_min files contain
> 129000 while temp_max files have 127000 (no typo) so something seems wrong

Mine are programmed like that too.  I suppose it's possible that the
datasheet could be wrong?  Page 14 of the adt7470 datasheet says that
the default temp low limit is 0x81 and the default temp high limit is
0x7F.  Weird...unless I'm misinterpreting the data sheet?

> here... At the very least, the factor of 1000 seems to be unnecessary:
> 
> # grep . pwm1*
> pwm1:0
> pwm1_auto_channels_temp:1023
> pwm1_auto_point1_pwm:128
> pwm1_auto_point1_temp:90000
> pwm1_auto_point2_pwm:255
> pwm1_auto_point2_temp:110000
> pwm1_enable:2
> 
> The values here clearly should be 90 and 110 degrees, shouldn't they?

(See above)

--D

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (11 preceding siblings ...)
  2007-07-16 20:18 ` Darrick J. Wong
@ 2007-07-16 22:19 ` Philip Pokorny
  2007-07-19 16:47 ` Jean Delvare
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Philip Pokorny @ 2007-07-16 22:19 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 215 bytes --]

--===============9088171502961478393==
Content-class: urn:content-classes:message
Content-Type: multipart/alternative;
	boundary="----_=_NextPart_001_01C7C7F7.5181AC1C"

This is a multi-part message in MIME format.

[-- Attachment #2: Type: text/plain, Size: 2790 bytes --]

The values 0x81 and 0x7f are *signed* 8 bit quantities.  0x81 whould be considered -127

:v)

-- 
Philip Pokorny, RHCE
Director of Field Engineering 
Penguin Computing  http://www.penguincomputing.com

 -----Original Message-----
From: 	Darrick J. Wong [mailto:djwong@us.ibm.com]
Sent:	Monday, July 16, 2007 01:21 PM Pacific Standard Time
To:	Vadim Zeitlin
Cc:	lm-sensors@lm-sensors.org
Subject:	Re: [lm-sensors] [PATCH] First cut of a adt7470 driver

On Sat, Jul 14, 2007 at 04:23:16PM +0200, Vadim Zeitlin wrote:
>  The first thing I noticed is that accessing any of them takes almost 3
> seconds:
> 
> # time cat fan1_input
> 2001
> cat fan1_input  0.00s user 0.00s system 0% cpu 2.760 total

Hm.... I see this with the v3 patch:
root@elm3a181:/usr/local/src/lm_sensors-2.10.3/prog# time cat
/sys/class/hwmon/hwmon0/device/temp1_input 
47000

real    0m1.701s
user    0m0.000s
sys     0m0.000s

Not sure why it takes 700ms to read the sensors after the 1s sleep,
perhaps that could just be the cost of reading the registers?  (I'd hope
not...)

> I wonder if this is normal? I see a 1 sec sleep in adt7470_update_device()
> but the delay happens not only for the temperatures and it's also much
> bigger than 1 second.
> 
>  Next, the fan speed detection seems to work as expected, i.e. the number
> it gives (like 2000 above) seem to be RPM. But reading the temperature
> files produces strange results:
> 
> # grep . temp?_input
> temp1_input:0
> temp2_input:228000
> temp3_input:59000
> temp4_input:54000
> temp5_input:226000
> temp6_input:228000
> temp7_input:58000
> temp8_input:54000
> temp9_input:49000
> 
> Again, I see the factor of 1000 in adt7470.c and, accounting for it, all

Documentation/hwmon/sysfs-interface says that the units for temperatures
are millidegrees C, hence the 1000 factor both here and for the pwm
controls below.

> numbers except 227000 may be correct -- but what about those latter ones?
> To confuse the matters (or at least me) further, temp_min files contain
> 129000 while temp_max files have 127000 (no typo) so something seems wrong

Mine are programmed like that too.  I suppose it's possible that the
datasheet could be wrong?  Page 14 of the adt7470 datasheet says that
the default temp low limit is 0x81 and the default temp high limit is
0x7F.  Weird...unless I'm misinterpreting the data sheet?

> here... At the very least, the factor of 1000 seems to be unnecessary:
> 
> # grep . pwm1*
> pwm1:0
> pwm1_auto_channels_temp:1023
> pwm1_auto_point1_pwm:128
> pwm1_auto_point1_temp:90000
> pwm1_auto_point2_pwm:255
> pwm1_auto_point2_temp:110000
> pwm1_enable:2
> 
> The values here clearly should be 90 and 110 degrees, shouldn't they?

(See above)

--D

[-- Attachment #3: Type: text/html, Size: 3794 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (12 preceding siblings ...)
  2007-07-16 22:19 ` Philip Pokorny
@ 2007-07-19 16:47 ` Jean Delvare
  2007-07-19 16:57 ` Jean Delvare
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Jean Delvare @ 2007-07-19 16:47 UTC (permalink / raw)
  To: lm-sensors

Hi Vadim,

On Sat, 14 Jul 2007 16:23:16 +0200, Vadim Zeitlin wrote:
> # grep . temp?_input
> temp1_input:0
> temp2_input:228000
> temp3_input:59000
> temp4_input:54000
> temp5_input:226000
> temp6_input:228000
> temp7_input:58000
> temp8_input:54000
> temp9_input:49000

Thanks for this tip. I've always wondered how to easily list sysfs
files and their content all at once, and this grep trick works really
well :)

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (13 preceding siblings ...)
  2007-07-19 16:47 ` Jean Delvare
@ 2007-07-19 16:57 ` Jean Delvare
  2007-07-19 17:11 ` Hans de Goede
  2007-07-21 20:07 ` Jean Delvare
  16 siblings, 0 replies; 18+ messages in thread
From: Jean Delvare @ 2007-07-19 16:57 UTC (permalink / raw)
  To: lm-sensors

Hi Hans,

Sorry for the late answer, I'm swamped :(

On Sat, 14 Jul 2007 22:17:34 +0200, Hans de Goede wrote:
> Jean Delvare wrote:
> > On Fri, 13 Jul 2007 14:08:53 +0200, Hans de Goede wrote:
> >> I know it looks strange to store the return value in an int then, but in some 
> >> of the other store methods I need val to be signed, and for consistency I've 
> >> thus stored it into an int everywhere.
> > 
> > But wouldn't then a large input value (fitting in a long but not in an
> > int) be silently turned into a negative value, possibly doing crazy
> > things in the rest of the code?
> 
> That could happen, but then people are _really_ asking for it. Notice that most 
> hwmon drivers store functions do even less checking then this. We really need 
> to discuss this and make a decision on it for all hwmon drivers, starting with 
> the question wether or not to check if the user input actually is a number?
> 
> Currently a user can do:
> echo -n foo > /sys/class/hwmon/hwmon0/in0_max
> and not get an error (instead in0_max typically gets set to 0

I am totally fine handling really invalid values as 0. As you said,
people doing that are asking for trouble, and handling this case would
slow down and/or enlarge our drivers quite a bit.

OTOH, the reason why I think that numbers should always be treated
correctly, even if out of bounds, is that they may be written by
libsensors rather than directly by a human. And libsensors may have
done some arithmetic operations on the values based
on /etc/sensors.conf, in particular for voltages. This can easily
result in a negative value being written to a voltage sysfs file. This
is really important that we correctly trim the negative value to 0 mV
and not to 4080 mV, otherwise we trigger an unwanted alarm. This is
only an example but it shows why we have to be careful.

That being said, this is kind of low priority and I am fine fixing the
drivers when people report such problems. I really don't have the time
to go thought all drivers to make sure that they are always correct
(but wouldn't mind if someone else does, of course.)

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (14 preceding siblings ...)
  2007-07-19 16:57 ` Jean Delvare
@ 2007-07-19 17:11 ` Hans de Goede
  2007-07-21 20:07 ` Jean Delvare
  16 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2007-07-19 17:11 UTC (permalink / raw)
  To: lm-sensors

Jean Delvare wrote:
> Hi Hans,
> 
> Sorry for the late answer, I'm swamped :(
> 

No problem, I'm very happy that you've found the time eventually, thanks!

> On Sat, 14 Jul 2007 22:17:34 +0200, Hans de Goede wrote:
>> Jean Delvare wrote:
>>> On Fri, 13 Jul 2007 14:08:53 +0200, Hans de Goede wrote:
>>>> I know it looks strange to store the return value in an int then, but in some 
>>>> of the other store methods I need val to be signed, and for consistency I've 
>>>> thus stored it into an int everywhere.
>>> But wouldn't then a large input value (fitting in a long but not in an
>>> int) be silently turned into a negative value, possibly doing crazy
>>> things in the rest of the code?
>> That could happen, but then people are _really_ asking for it. Notice that most 
>> hwmon drivers store functions do even less checking then this. We really need 
>> to discuss this and make a decision on it for all hwmon drivers, starting with 
>> the question wether or not to check if the user input actually is a number?
>>
>> Currently a user can do:
>> echo -n foo > /sys/class/hwmon/hwmon0/in0_max
>> and not get an error (instead in0_max typically gets set to 0
> 
> I am totally fine handling really invalid values as 0. As you said,
> people doing that are asking for trouble, and handling this case would
> slow down and/or enlarge our drivers quite a bit.
> 
> OTOH, the reason why I think that numbers should always be treated
> correctly, even if out of bounds, is that they may be written by
> libsensors rather than directly by a human. And libsensors may have
> done some arithmetic operations on the values based
> on /etc/sensors.conf, in particular for voltages. This can easily
> result in a negative value being written to a voltage sysfs file. This
> is really important that we correctly trim the negative value to 0 mV
> and not to 4080 mV, otherwise we trigger an unwanted alarm. This is
> only an example but it shows why we have to be careful.
> 

I agree that we should do some sanity checking, but not too much as to not 
clutter the drivers. However, I think we first need to define some kind of 
standard as to what sanity checking we will do, and what not, and how to handle 
out of range values (clamp versus EINVAL), actually I've written and send a 
proposal for this to the list already as our discussion got me thinking about this.

> That being said, this is kind of low priority and I am fine fixing the
> drivers when people report such problems. I really don't have the time
> to go thought all drivers to make sure that they are always correct
> (but wouldn't mind if someone else does, of course.)
> 

I agree that this is somewhat of a minor issue, and I do not advocate to go in 
and fix everything immediately once my proposal is accepted, one of the main 
purposes of my proposal is to have some guidelines / rules for input checking 
when reviewing patches. If we find the time and energy for it we could also fix 
existing drivers, but thats not high priority.

Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [lm-sensors] [PATCH] First cut of a adt7470 driver
  2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
                   ` (15 preceding siblings ...)
  2007-07-19 17:11 ` Hans de Goede
@ 2007-07-21 20:07 ` Jean Delvare
  16 siblings, 0 replies; 18+ messages in thread
From: Jean Delvare @ 2007-07-21 20:07 UTC (permalink / raw)
  To: lm-sensors

On Thu, 19 Jul 2007 19:11:04 +0200, Hans de Goede wrote:
> I agree that we should do some sanity checking, but not too much as to not 
> clutter the drivers. However, I think we first need to define some kind of 
> standard as to what sanity checking we will do, and what not, and how to handle 
> out of range values (clamp versus EINVAL), actually I've written and send a 
> proposal for this to the list already as our discussion got me thinking about this.

What I've been telling contributors so far is: clamp for "continuous"
values those boundaries aren't known to the user, -EINVAL for the rest.
The rationale is essentially the same I mentioned earlier in this
thread: limits can be computed by libsensors, and we do not want a
slightly out-of-bound values to be ignored or trigger errors. But for
pretty much all the rest, returning an error on invalid values seems to
be the sane thing to do.

I agree that having these rules written somewhere would probably save
some question from new driver contributors.

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2007-07-21 20:07 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-07-06 21:11 [lm-sensors] [PATCH] First cut of a adt7470 driver Darrick J. Wong
2007-07-07  8:21 ` Hans de Goede
2007-07-07  8:23 ` Hans de Goede
2007-07-08 16:53 ` Jean Delvare
2007-07-10 18:59 ` Darrick J. Wong
2007-07-10 23:33 ` Darrick J. Wong
2007-07-11  5:23 ` Hans de Goede
2007-07-11 12:47 ` Hans de Goede
2007-07-13 12:08 ` Hans de Goede
2007-07-14 14:23 ` Vadim Zeitlin
2007-07-14 20:05 ` Jean Delvare
2007-07-14 20:17 ` Hans de Goede
2007-07-16 20:18 ` Darrick J. Wong
2007-07-16 22:19 ` Philip Pokorny
2007-07-19 16:47 ` Jean Delvare
2007-07-19 16:57 ` Jean Delvare
2007-07-19 17:11 ` Hans de Goede
2007-07-21 20:07 ` Jean Delvare

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.