All of lore.kernel.org
 help / color / mirror / Atom feed
* [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal
@ 2008-06-10 19:48 Jordan Crouse
  2008-06-13  7:10 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
                   ` (13 more replies)
  0 siblings, 14 replies; 15+ messages in thread
From: Jordan Crouse @ 2008-06-10 19:48 UTC (permalink / raw)
  To: lm-sensors

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

Hi Mark -

Attached is a patch for the ADT7475 that has been bouncing around on
the list for some time now.  It is suitable for the testing
GIT tree, and I think it is a good candidate for the 2.6.26 push.

Jordan

-- 
Jordan Crouse
Systems Software Development Engineer 
Advanced Micro Devices, Inc.

[-- Attachment #2: adt7475.patch --]
[-- Type: text/x-diff, Size: 35802 bytes --]

[PATCH] hwmon:  Add a driver for the ADT7475 thermal sensor

From: Jordan Crouse <jordan.crouse@amd.com>

HWMON driver for the ADT7475 thermal sensor.

Signed-off-by: Jordan Crouse <jordan.crouse@amd.com>
---

 Documentation/hwmon/adt7475 |  104 +++++
 drivers/hwmon/Kconfig       |   10 
 drivers/hwmon/Makefile      |    2 
 drivers/hwmon/adt7475.c     |  938 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1054 insertions(+), 0 deletions(-)

diff --git a/Documentation/hwmon/adt7475 b/Documentation/hwmon/adt7475
new file mode 100644
index 0000000..64eb78c
--- /dev/null
+++ b/Documentation/hwmon/adt7475
@@ -0,0 +1,104 @@
+This describes the interface for the ADT7475 driver:
+
+(there are 4 fans, numbered fan1 to fan4):
+
+fanX_input              Read the current speed of the fan (in RPMs)
+fanX_min                Read/write the minimum speed of the fan.  Dropping
+	   		below this sets an alarm.
+
+(there are three PWMs, numbered pwm1 to pwm3):
+
+pwmX                    Read/write the current duty cycle of the PWM.  Writes
+     			only have effect when auto mode is turned off (see
+			below).
+
+pwmX_enable             Read/write the PWM configuration based on the following
+                        table:
+
+		        0 - Remote1 temp controls PWMx (auto mode)
+		        1 - local temp controls PWMx (auto mode)
+	 	        2 - remote2 temp controls PWMx (auto mode)
+                        3 - PWMx runs at full speed
+                        4 - PWMx is disabled
+                        5 - Use fastest speed calculated by local and remote2
+                        6 - Use fastest speed calculated by all three channels
+                        7 - Manual mode
+
+pwmX_freq               Read/write the PWM frequency.  The value returned is
+	  	        an index into the following table:
+
+		        0x0 - 11.0 Hz
+		        0x1 - 14.7 Hz
+		        0x2 - 22.1 Hz
+		        0x3 - 29.4 Hz
+		        0x4 - 35.3 Hz
+		        0x5 - 44.1 Hz
+		        0x6 - 58.8 Hz
+		        0x7 - 88.2 Hz
+
+pwmX_max                Read/write the maximum PWM duty cycle.  The PWM
+                        duty cycle will never exceed this.
+
+pwmX_min                Read/write the minimum PWM duty cycle in automatic mode
+
+(there are three temperature settings numbered temp1 to temp3):
+
+tempX_input             Read the current temperature.  The value is in milli
+			degrees of Celsius.
+
+tempX_max               Read/write the upper temperature limit - exceeding this
+	                will cause an alarm.
+
+tempX_min               Read/write the lower temperature limit - exceeding this
+                        will cause an alarm.
+
+tempX_offset            Read/write the temperature adjustment offset
+
+tempX_crit_max        Read/write the THERM limit for remote1.  Exceeding this
+                        causes the chip to force the processor off.
+
+tempX_auto_min   	Read/write the minimum temperature where the fans will
+                        turn on in automatic mode.
+
+tempX_auto_range  	Read/write the range over which the automatic fan
+			control will be executed.  The value returned is a
+			index into the following table:
+
+			0x0 - 2 C
+			0x1 - 2.5 C
+			0x2 - 3.33 C
+			0x3 - 4 C
+			0x4 - 5 C
+			0x5 - 6.67 C
+			0x6 - 8 C
+			0x7 - 10 C
+			0x8 - 13.33 C
+			0x9 - 16 C
+			0xA - 20 C
+			0xB - 26.67 C
+			0xC - 32 C
+			0xD - 40 C
+			0xE - 53.33 C
+			0xF - 80 C
+
+tempX_crit_hyst 	set the temperature range below crit_max where the
+                        fans will stay on - this helps drive the temperature
+                        low enough so it doesn't stay near the edge and
+                        cause THERM to keep tripping.
+
+tempX_alarm		Read a 1 if the max/min alarm is set
+tempX_crit_alarm	Read a 1 if the critical limit is exceeded
+tempX_fault		Read a 1 if either temp1 or temp3 diode has a fault
+
+(There are two voltage settings, in1 and in2):
+
+inX_input               Read the current voltage on VCC.  Value is in
+			millivolts.
+
+inX_min                 read/write the minimum voltage limit.
+			Dropping below this causes an alarm.
+
+inX_max   		read/write the maximum voltage limit.
+			Exceeding this causes an alarm.
+
+inX_alarm		Read a 1 if the max/min alarm is set.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 00ff533..9e5b883 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -153,6 +153,16 @@ config SENSORS_ADT7473
 	  This driver can also be built as a module. If so, the module
 	  will be called adt7473.
 
+config SENSORS_ADT7475
+	tristate "Analog Devices ADT7475"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  ADT7475 temperature monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called adt7475.
+
 config SENSORS_K8TEMP
 	tristate "AMD Athlon64/FX or Opteron temperature sensor"
 	depends on X86 && PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d098677..6e426d4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -25,6 +25,8 @@ obj-$(CONFIG_SENSORS_ADM9240)	+= adm9240.o
 obj-$(CONFIG_SENSORS_ADS7828)	+= ads7828.o
 obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
+obj-$(CONFIG_SENSORS_ADT7475)   += adt7475.o
+
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c
new file mode 100644
index 0000000..891687d
--- /dev/null
+++ b/drivers/hwmon/adt7475.c
@@ -0,0 +1,938 @@
+/*
+ * adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives
+ * Copyright (C) 2007-2008, Advanced Micro Devices, Inc.
+ *
+ * Derived from the lm83 driver by Jean Delvare
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+
+/* Indexes for the sysfs hooks */
+
+#define INPUT     0
+#define MIN       1
+#define MAX       2
+#define CONTROL   3
+#define OFFSET    3
+#define AUTOMIN   4
+#define FREQ      4
+#define THERM     5
+#define HYSTERSIS 6
+#define RANGE     7
+#define ALARM     9
+#define CRIT_ALARM 10
+#define FAULT      11
+
+
+/* 7475 Common Registers */
+
+#define REG_VOLTAGE_BASE        0x21
+#define REG_TEMP_BASE           0x25
+#define REG_TACH_BASE		0x28
+#define REG_PWM_BASE            0x30
+#define REG_PWM_MAX_BASE        0x38
+
+#define REG_DEVID               0x3D
+#define REG_VENDID              0x3E
+
+#define REG_CONFIG1             0x40
+#define REG_STATUS1             0x41
+#define REG_STATUS2             0x42
+
+/* Not all the alarm bits are enabled - mask the ones we don't use */
+#define ALARM_MASK              0xFE76
+
+#define REG_VOLTAGE_MIN_BASE    0x46
+#define REG_VOLTAGE_MAX_BASE    0x47
+
+#define REG_TEMP_MIN_BASE       0x4E
+#define REG_TEMP_MAX_BASE	0x4F
+
+#define REG_TACH_MIN_BASE	0x54
+
+#define REG_PWM_CONFIG_BASE     0x5C
+
+#define REG_TEMP_TRANGE_BASE    0x5F
+
+#define REG_ACOUSTICS1          0x62
+#define REG_ACOUSTICS2          0x63
+
+#define REG_PWM_MIN_BASE        0x64
+
+#define REG_TEMP_TMIN_BASE      0x67
+#define REG_TEMP_THERM_BASE     0x6A
+
+#define REG_REMOTE1_HYSTERSIS   0x6D
+#define REG_REMOTE2_HYSTERSIS   0x6E
+
+#define REG_TEMP_OFFSET_BASE    0x70
+
+#define REG_CONFIG2             0x73
+#define REG_INTERRUPT_MASK1     0x74
+#define REG_INTERRUPT_MASK2     0x75
+#define REG_EXTEND1             0x76
+#define REG_EXTEND2             0x77
+#define REG_CONFIG3             0x78
+#define REG_THERM_TIMER_STATUS  0x79
+#define REG_THERM_TIMER_LIMIT   0x7A
+#define REG_TACH_PULSES         0x7B
+#define REG_CONFIG5             0x7C
+
+#define CONFIG5_TWOSCOMP        0x01
+#define CONFIG5_TEMPOFFSET      0x02
+
+#define REG_CONFIG4             0x7D
+
+/* ADT7475 Settings */
+
+#define ADT7475_VOLTAGE_COUNT   2
+#define ADT7475_TEMP_COUNT      3
+#define ADT7475_TACH_COUNT	4
+#define ADT7475_PWM_COUNT       3
+
+/* 7475 specific registers */
+
+#define REG_CONFIG6             0x10
+#define REG_CONFIG7             0x11
+
+
+/* Macro to read the registers */
+
+#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg))
+
+/* Macros to easily index the registers */
+
+#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2))
+#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2))
+
+#define PWM_REG(idx) (REG_PWM_BASE + (idx))
+#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx))
+#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx))
+#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx))
+
+#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx))
+#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2))
+#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2))
+
+#define TEMP_REG(idx) (REG_TEMP_BASE + (idx))
+#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2))
+#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2))
+#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx))
+#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx))
+#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx))
+#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx))
+
+/* Convert the tach reading into RPMs */
+
+#define TACH2RPM(val) ((90000 * 60) / (val))
+#define RPM2TACH(val) ((90000 * 60) / (val))
+
+/* Convert the voltage registers into mW */
+
+#define REG2VCC(val) ((42 * (int)(val)) / 10)
+#define REG2VCCP(val) ((293 * (int)(val)) / 100)
+
+#define VCC2REG(val)  (((val) * 10) / 42)
+#define VCCP2REG(val) (((val) * 100) / 293)
+
+/* 2's complement temp conversion - this is used when CONFIG5 bit 0 is set */
+
+#define REG2TEMP(val) ((((val) >> 2) * 1000) + (((val) & 3) * 250))
+
+#define TEMP2REG(val) ((val) <= -128000 ? -128 : \
+	(val) >= 127000 ? 127 : \
+	(val) < 0 ? ((val) - 500) / 1000 : \
+	((val) + 500) / 1000)
+
+
+/* Offset 64 temp conversion - this is used when CONFIG5 bit 0 is clear */
+
+#define OFF64_REG2TEMP(val) (((((val) >> 2) - 64) * 1000) + (((val) & 3) * 250))
+#define OFF64_TEMP2REG(val) ((((val) + 64500) / 1000) << 2)
+
+static unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(adt7475);
+
+struct adt7475_data {
+	struct i2c_client client;
+	struct device *hwmon_dev;
+	struct mutex lock;
+
+	int type;
+	char temptype;
+
+	char valid;
+	unsigned long updated;
+
+	u16 alarms;
+	u16 voltage[3][3];
+	s16 temp[6][3];
+	u16 tach[2][4];
+	u8  pwm[4][3];
+	u8  range[3];
+};
+
+static struct i2c_driver adt7475_driver;
+static struct adt7475_data *adt7475_update_device(struct device *dev);
+
+static u16 adt7475_read_word(struct i2c_client *client, int reg)
+{
+	u16 val;
+
+	val = i2c_smbus_read_byte_data(client, reg);
+	val |= (i2c_smbus_read_byte_data(client, reg + 1) << 8);
+
+	return val;
+}
+
+static void adt7475_write_word(struct i2c_client *client, int reg, u16 val)
+{
+	i2c_smbus_write_byte_data(client, reg + 1, val >> 8);
+	i2c_smbus_write_byte_data(client, reg, val & 0xFF);
+}
+
+static ssize_t show_voltage(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val = data->voltage[sattr->nr][sattr->index];
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 1)) & 1);
+	default:
+		return sprintf(buf, "%d\n",
+			sattr->index == 0 ? REG2VCCP(val) : REG2VCC(val));
+	}
+}
+
+static ssize_t set_voltage(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count) {
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg;
+
+	mutex_lock(&data->lock);
+
+	data->voltage[sattr->nr][sattr->index] =
+		sattr->index ? VCC2REG(val) : VCCP2REG(val);
+
+	if (sattr->nr == MIN)
+		reg = VOLTAGE_MIN_REG(sattr->index);
+	else
+		reg = VOLTAGE_MAX_REG(sattr->index);
+
+	i2c_smbus_write_byte_data(client, reg,
+		(u8) (data->voltage[sattr->nr][sattr->index] >> 2));
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val = data->temp[sattr->nr][sattr->index];
+	int ret = 0;
+	u8 out;
+
+	switch (sattr->nr) {
+	case HYSTERSIS:
+		if (sattr->index != 1)
+			out = (val >> 4) & 0xF;
+		else
+			out = (val & 0xF);
+
+		ret = sprintf(buf, "%d\n", out);
+		break;
+
+	case OFFSET:
+		/* Offset is always 2's complement, regardless of the
+		 * setting in CONFIG5 */
+
+		if (data->temptype & CONFIG5_TEMPOFFSET)
+			ret = sprintf(buf, "%d\n", (s8) val);
+		else
+			ret = sprintf(buf, "%d\n", ((s8)val) >> 1);
+		break;
+
+	case ALARM:
+		ret = sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 4))  & 1);
+		break;
+
+	case CRIT_ALARM:
+		ret = sprintf(buf, "%d\n",
+			((data->alarms & 0x200) ? 1 : 0));
+		break;
+
+	case FAULT:
+		/* Note - only for remote1 and remote2 */
+
+		ret = sprintf(buf, "%d\n",
+			(data->alarms & (sattr->index ? 0x4000 : 0x8000)));
+		break;
+
+	default:
+		/* All other temp values are in the configured format */
+
+		ret = sprintf(buf, "%d\n",
+			      (data->temptype & CONFIG5_TWOSCOMP) ?
+			      REG2TEMP(val) : OFF64_REG2TEMP(val));
+	}
+
+	return ret;
+}
+
+static ssize_t set_temp(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg = 0;
+	u8 out;
+	int l;
+
+	mutex_lock(&data->lock);
+
+	switch (sattr->nr) {
+	case OFFSET:
+		l = val;
+
+		if (data->temptype & CONFIG5_TEMPOFFSET) {
+			if (l > 127)
+				l = 127;
+			else if (l < -127)
+				l = -126;
+		} else {
+			if (l > 64)
+				l = 64;
+			else if (l < -63)
+				l = -63;
+
+			l <<= 1;
+		}
+
+		out = data->temp[OFFSET][sattr->index] = (u8) l;
+		break;
+
+	case HYSTERSIS:
+		if (sattr->index != 1) {
+			data->temp[HYSTERSIS][sattr->index] &= 0xF0;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF) << 4;
+		} else {
+			data->temp[HYSTERSIS][sattr->index] &= 0x0F;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF);
+		}
+
+		out = data->temp[HYSTERSIS][sattr->index];
+		break;
+
+	default:
+		data->temp[sattr->nr][sattr->index] =
+			(data->temptype & CONFIG5_TWOSCOMP) ?
+				TEMP2REG(val) : OFF64_TEMP2REG(val);
+
+		/* We maintain an extra 2 digits of precision for simplicity
+		 * - shift those back off before writing the value */
+
+		out = (u8) (data->temp[sattr->nr][sattr->index] >> 2);
+	}
+
+	switch (sattr->nr) {
+	case MIN:
+		reg = TEMP_MIN_REG(sattr->index);
+		break;
+	case MAX:
+		reg = TEMP_MAX_REG(sattr->index);
+		break;
+	case OFFSET:
+		reg = TEMP_OFFSET_REG(sattr->index);
+		break;
+	case AUTOMIN:
+		reg = TEMP_TMIN_REG(sattr->index);
+		break;
+	case THERM:
+		reg = TEMP_THERM_REG(sattr->index);
+		break;
+	case HYSTERSIS:
+		if (sattr->index != 2)
+			reg = REG_REMOTE1_HYSTERSIS;
+		else
+			reg = REG_REMOTE2_HYSTERSIS;
+
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg, out);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_range(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	unsigned short val = data->range[sattr->index];
+	return sprintf(buf, "%d\n", (val >> 4) & 0xF);
+}
+
+static ssize_t set_range(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] &= ~0xF0;
+	data->range[sattr->index] |= (val & 0xF) << 4;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_tach(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+
+	/* 0xFFFF means the period was invalid */
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 10) & 1));
+	default:
+	if (data->tach[sattr->nr][sattr->index] == 0xFFFF)
+		return sprintf(buf, "0\n");
+	else
+		return sprintf(buf, "%d\n",
+		       TACH2RPM(data->tach[sattr->nr][sattr->index]));
+	}
+}
+
+
+
+
+static ssize_t set_tach(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->tach[MIN][sattr->index] = (u16) RPM2TACH(val);
+
+	adt7475_write_word(client, TACH_MIN_REG(sattr->index),
+			   data->tach[MIN][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwm[sattr->nr][sattr->index]);
+}
+
+static ssize_t show_pwmctrl(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n",
+		(data->pwm[CONTROL][sattr->index] >> 5) & 7);
+}
+
+static ssize_t show_pwmfreq(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->range[sattr->index] & 3);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg = 0;
+
+	mutex_lock(&data->lock);
+
+	data->pwm[sattr->nr][sattr->index] = (u8) val;
+
+	switch (sattr->nr) {
+	case INPUT:
+		/* If we are not in manual mode, then we shouldn't allow
+		 * the user to set the pwm speed */
+
+		if (((data->pwm[CONTROL][sattr->index] >> 5) & 7) != 7)
+			return 0;
+
+		reg = PWM_REG(sattr->index);
+		break;
+
+	case MIN:
+		reg = PWM_MIN_REG(sattr->index);
+		break;
+
+	case MAX:
+		reg = PWM_MAX_REG(sattr->index);
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg,
+		data->pwm[sattr->nr][sattr->index]);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t set_pwmctrl(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->pwm[CONTROL][sattr->index] &= ~0xE0;
+	data->pwm[CONTROL][sattr->index] |= (val & 7) << 5;
+
+	i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(sattr->index),
+				  data->pwm[CONTROL][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t set_pwmfreq(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] &= ~3;
+	data->range[sattr->index] |= val & 0x03;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_voltage, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, show_voltage, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_voltage, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_voltage, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, show_temp, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_temp, NULL, FAULT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 0);
+static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_alarm, S_IRUGO, show_temp, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 1);
+static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_alarm, S_IRUGO, show_temp, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_temp, NULL, FAULT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 2);
+static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_tach, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_tach, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_tach, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_tach, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_tach, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_tach, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_tach, NULL, INPUT, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_alarm, S_IRUGO, show_tach, NULL, ALARM, 3);
+static SENSOR_DEVICE_ATTR_2(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 2);
+
+static struct attribute *adt7475_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_max.dev_attr.attr,
+	&sensor_dev_attr_in2_min.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_fault.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_offset.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_offset.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_fault.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_offset.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_min.dev_attr.attr,
+	&sensor_dev_attr_fan4_alarm.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_max.dev_attr.attr,
+	&sensor_dev_attr_pwm1_min.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm2_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_max.dev_attr.attr,
+	&sensor_dev_attr_pwm2_min.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm3_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_max.dev_attr.attr,
+	&sensor_dev_attr_pwm3_min.dev_attr.attr,
+	NULL,
+};
+
+/* The list of chips we support - these index into the following structure */
+
+#define ADT7475 0
+#define ADT7475_MAX_ID 1
+
+static struct adt7475_chip {
+	const char *name;
+	struct attribute_group group;
+} adt7475_chips[ADT7475_MAX_ID] = {
+	{ .name = "adt7475",
+	  .group = {
+		  .attrs = adt7475_attrs,
+	  }
+	},
+};
+
+static int adt7475_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *client;
+	struct adt7475_data *data;
+	unsigned char val;
+	int ret = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return 0;
+
+	/* Figure out what type of sensor is attached */
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+
+	if (data == NULL)
+		return -ENOMEM;
+
+	client = &data->client;
+	i2c_set_clientdata(client, data);
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &adt7475_driver;
+	client->flags = 0;
+
+	/* Check the company version first */
+
+	if (i2c_smbus_read_byte_data(client, REG_VENDID) != 0x41) {
+		dev_err(&adapter->dev, "This is not an adt7475 part\n");
+		goto efree;
+	}
+
+	/* Then check the part number */
+	val = i2c_smbus_read_byte_data(client, REG_DEVID);
+
+	if (val == 0x75)
+		data->type = ADT7475;
+	else {
+		dev_err(&adapter->dev,
+		"Couldn't detect a adt7475 part at 0x%02x\n", address);
+
+		goto efree;
+	}
+
+	/* Record how the temp registers are presented, either 2's complement
+	   or offset 64
+	*/
+
+	data->temptype = i2c_smbus_read_byte_data(client, REG_CONFIG5) & 3;
+
+	/* FIXME: Get the reading type */
+	/* FIXME:  Get the scale of the temprature readings */
+
+	strlcpy(client->name, adt7475_chips[data->type].name, I2C_NAME_SIZE);
+
+	data->valid = 0;
+	mutex_init(&data->lock);
+
+	ret = i2c_attach_client(client);
+
+	if (ret)
+		goto efree;
+
+	ret = sysfs_create_group(&client->dev.kobj,
+		&adt7475_chips[data->type].group);
+
+	if (ret)
+		goto edetach;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	ret = PTR_ERR(data->hwmon_dev);
+
+	sysfs_remove_group(&client->dev.kobj, &adt7475_chips[data->type].group);
+ edetach:
+	i2c_detach_client(client);
+ efree:
+	kfree(data);
+	return ret;
+}
+
+static int adt7475_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+
+	return i2c_probe(adapter, &addr_data, adt7475_detect);
+}
+
+static int adt7475_detach_client(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &adt7475_chips[data->type].group);
+
+	ret = i2c_detach_client(client);
+	if (!ret)
+		kfree(data);
+	return ret;
+}
+
+static struct i2c_driver adt7475_driver = {
+	.driver = {
+		.name   = "adt7475",
+	},
+	.attach_adapter = adt7475_attach_adapter,
+	.detach_client  = adt7475_detach_client,
+};
+
+static struct adt7475_data *adt7475_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	u8 ext;
+	int i;
+
+	mutex_lock(&data->lock);
+
+	if (!time_after(jiffies, data->updated + HZ * 2) && data->valid) {
+		mutex_unlock(&data->lock);
+		return data;
+	}
+
+	data->alarms = adt7475_read(REG_STATUS2) << 8;
+	data->alarms |= adt7475_read(REG_STATUS1);
+
+	ext = adt7475_read(REG_EXTEND1);
+
+	/* Get the voltage readings */
+
+	for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) {
+		data->voltage[INPUT][i] = (adt7475_read(VOLTAGE_REG(i)) << 2) |
+			((ext >> ((i + 1) * 2)) & 3);
+
+		/* Adjust these values so they match the input precision */
+
+		data->voltage[MIN][i] = adt7475_read(VOLTAGE_MIN_REG(i)) << 2;
+		data->voltage[MAX][i] = adt7475_read(VOLTAGE_MAX_REG(i)) << 2;
+	}
+
+	ext = adt7475_read(REG_EXTEND2);
+
+	for (i = 0; i < ADT7475_TEMP_COUNT; i++) {
+		data->temp[INPUT][i] = (adt7475_read(TEMP_REG(i)) << 2) |
+			((ext >> ((i + 1) * 2)) & 3);
+
+		/* Adjust these values so they match the input precision */
+
+		data->temp[MIN][i] = adt7475_read(TEMP_MIN_REG(i)) << 2;
+		data->temp[MAX][i] = adt7475_read(TEMP_MAX_REG(i)) << 2;
+		data->temp[AUTOMIN][i] = adt7475_read(TEMP_TMIN_REG(i)) << 2;
+		data->temp[THERM][i] = adt7475_read(TEMP_THERM_REG(i)) << 2;
+
+		data->temp[OFFSET][i] = adt7475_read(TEMP_OFFSET_REG(i));
+	}
+
+	data->temp[HYSTERSIS][0] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][1] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][2] = (u16) adt7475_read(REG_REMOTE2_HYSTERSIS);
+
+	for (i = 0; i < ADT7475_TACH_COUNT; i++) {
+		data->tach[INPUT][i] = adt7475_read_word(client, TACH_REG(i));
+		data->tach[MIN][i] = adt7475_read_word(client, TACH_MIN_REG(i));
+	}
+
+	for (i = 0; i < ADT7475_PWM_COUNT; i++) {
+		data->pwm[INPUT][i] = adt7475_read(PWM_REG(i));
+		data->pwm[MAX][i] = adt7475_read(PWM_MAX_REG(i));
+		data->pwm[MIN][i] = adt7475_read(PWM_MIN_REG(i));
+		data->pwm[CONTROL][i] = adt7475_read(PWM_CONFIG_REG(i));
+	}
+
+	data->range[0] =  adt7475_read(TEMP_TRANGE_REG(0));
+	data->range[1] =  adt7475_read(TEMP_TRANGE_REG(1));
+	data->range[2] =  adt7475_read(TEMP_TRANGE_REG(2));
+
+	data->updated = jiffies;
+	data->valid = 1;
+
+	mutex_unlock(&data->lock);
+
+	return data;
+}
+
+static int __init sensors_adt7475_init(void)
+{
+	return i2c_add_driver(&adt7475_driver);
+}
+
+static void __exit sensors_adt7475_exit(void)
+{
+	i2c_del_driver(&adt7475_driver);
+}
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("adt7475 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_adt7475_init);
+module_exit(sensors_adt7475_exit);

[-- Attachment #3: 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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
@ 2008-06-13  7:10 ` Matt Roberds
  2008-08-07 20:56 ` Hans de Goede
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Matt Roberds @ 2008-06-13  7:10 UTC (permalink / raw)
  To: lm-sensors

On Tue, 10 Jun 2008, Jordan Crouse wrote:
> Attached is a patch for the ADT7475 that has been bouncing around on
> the list for some time now.  It is suitable for the testing
> GIT tree, and I think it is a good candidate for the 2.6.26 push.

FWIW, I've been running this patch on my desktop for a few months (Asus
M2N-SLI Deluxe motherboard) and it seems to work fine.  Thanks!

Matt Roberds


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

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

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
  2008-06-13  7:10 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
@ 2008-08-07 20:56 ` Hans de Goede
  2008-09-05 20:54 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-08-07 20:56 UTC (permalink / raw)
  To: lm-sensors

Jordan Crouse wrote:
> Hi Mark -
> 
> Attached is a patch for the ADT7475 that has been bouncing around on
> the list for some time now.  It is suitable for the testing
> GIT tree, and I think it is a good candidate for the 2.6.26 push.
> 

Hi,

Very sorry for the long silence, it seems all of us involved in the hwmon tree 
have a bit too much on our plate. As can be witnessed from Mark Hoffman 
stepping down as hwmon subsys maintainer.

Anyways I've started reviewing your driver, but I haven't come around to 
reviewing the actual code. Thanks for writting good documentation :) As I now 
already have found quite a few cases where your driver deviates from the 
standard sysfs interface as documented in:
Documentation/hwmon/sysfs-interface

So in this initial review I'll only comment on your:
Documentation/hwmon/adt7475

And I kindly ask you todo a revised version of your driver fixing the interface 
deviations I believe to be present. When thats done I'll do a full review and I 
promise I'll be a lot quicker this time around (and when I promise something, 
you can expect it to happen!).


+This describes the interface for the ADT7475 driver:
+
+(there are 4 fans, numbered fan1 to fan4):
+
+fanX_input              Read the current speed of the fan (in RPMs)
+fanX_min                Read/write the minimum speed of the fan.  Dropping
+	   		below this sets an alarm.
+
+(there are three PWMs, numbered pwm1 to pwm3):
+
+pwmX                    Read/write the current duty cycle of the PWM.  Writes
+     			only have effect when auto mode is turned off (see
+			below).
+

Good, I assume (not looked at the code yet) this goes from 0 - 255 ? If not it 
should and that should be scaled to whatever the IC wants.

+pwmX_enable             Read/write the PWM configuration based on the following
+                        table:
+
+		        0 - Remote1 temp controls PWMx (auto mode)
+		        1 - local temp controls PWMx (auto mode)
+	 	        2 - remote2 temp controls PWMx (auto mode)
+                        3 - PWMx runs at full speed
+                        4 - PWMx is disabled
+                        5 - Use fastest speed calculated by local and remote2
+                        6 - Use fastest speed calculated by all three channels
+                        7 - Manual mode
+

Erm, this is somewhat non standard, atleast 0 and 1 have a well defined meaning 
in the sysfs-interface standard:

"pwm[1-*]_enable
                 Fan speed control method:
                 0: no fan speed control (i.e. fan at full speed)
                 1: manual fan speed control enabled (using pwm[1-*])
                 2+: automatic fan speed control enabled
                 Check individual chip documentation files for automatic mode
                 details."

Actually reading the datasheet, I think this is not what we want, what we want 
pwmX_enable to be for the adt7475 is:

0: No fan speed control (fan at full speed, iow 3 in your old table)
1: Manual fan speed control (7 in your old table)
2: Automatic fan speed control (0-2, 5-6 in your old table, see below for
    which one should be used when).

And then add a pwmX_auto_channels_temp atrribute to control which temp 
sensor(s) influence the pwm, quoting our sysfs standard doc again:

"pwm[1-*]_auto_channels_temp
                 Select which temperature channels affect this PWM output in
                 auto mode. Bitfield, 1 is temp1, 2 is temp2, 4 is temp3 etc...
                 Which values are possible depend on the chip used.
                 RW"

So that would mean that valid values for the adt7475 are, with corresponding 
number in your old table:
1 -> 0 (I assume remote1 is temp1, local temp2 and remote2 temp3)
2 -> 1
4 -> 2
6 -> 5
7 -> 6

Note that 4 from your old table is not used, we don't want people to be able to 
  turn of the pwm (and thus the fan) through pwm_enable _ever_. If they really 
want todo something that stupid they should configure manual mode and write a 
duty cycle of 0 %.


+pwmX_freq               Read/write the PWM frequency.  The value returned is
+	  	        an index into the following table:
+
+		        0x0 - 11.0 Hz
+		        0x1 - 14.7 Hz
+		        0x2 - 22.1 Hz
+		        0x3 - 29.4 Hz
+		        0x4 - 35.3 Hz
+		        0x5 - 44.1 Hz
+		        0x6 - 58.8 Hz
+		        0x7 - 88.2 Hz
+

That table should be in the driver and userspace should read/write a value in 
Hz, to quote the docs:

"pwm[1-*]_freq   Base PWM frequency in Hz.
                 Only possibly available when pwmN_mode is PWM, but not always
                 present even then.
                 RW"



+pwmX_max                Read/write the maximum PWM duty cycle.  The PWM
+                        duty cycle will never exceed this.
+
+pwmX_min                Read/write the minimum PWM duty cycle in automatic mode
+

Good (but again 0 - 255 scale please).

+(there are three temperature settings numbered temp1 to temp3):
+
+tempX_input             Read the current temperature.  The value is in milli
+			degrees of Celsius.
+
+tempX_max               Read/write the upper temperature limit - exceeding this
+	                will cause an alarm.
+
+tempX_min               Read/write the lower temperature limit - exceeding this
+                        will cause an alarm.
+
+tempX_offset            Read/write the temperature adjustment offset
+

I assume this are all milli degrees too? If not they should be.

+tempX_crit_max        Read/write the THERM limit for remote1.  Exceeding this
+                        causes the chip to force the processor off.
+

The standardized name for this is tempX_crit, and drop the force the processor 
off language, that really depends on how the IC is hooked up on the motherboard 
and thus isn't always true.

+tempX_auto_min   	Read/write the minimum temperature where the fans will
+                        turn on in automatic mode.
+
+tempX_auto_range  	Read/write the range over which the automatic fan
+			control will be executed.  The value returned is a
+			index into the following table:
+
+			0x0 - 2 C
+			0x1 - 2.5 C
+			0x2 - 3.33 C
+			0x3 - 4 C
+			0x4 - 5 C
+			0x5 - 6.67 C
+			0x6 - 8 C
+			0x7 - 10 C
+			0x8 - 13.33 C
+			0x9 - 16 C
+			0xA - 20 C
+			0xB - 26.67 C
+			0xC - 32 C
+			0xD - 40 C
+			0xE - 53.33 C
+			0xF - 80 C
+

Please rename tempX_auto_min to

tempX_auto_point1_temp

And rename tempX_auto_range to tempX_auto_point2_temp and when 
tempX_auto_point2_temp gets read show tempX_auto_point1_temp + the range value 
looked up in the above table (in milli degrees please) and vica versa when 
tempX_auto_point2_temp gets written.

+tempX_crit_hyst 	set the temperature range below crit_max where the
+                        fans will stay on - this helps drive the temperature
+                        low enough so it doesn't stay near the edge and
+                        cause THERM to keep tripping.
+

Good, but note that this should not be an offset, but an absolute value so when
temp1_crit is for example 100000 and the temp must drop more then 10 degrees 
before the crit alarm gets cleared, then reading temp1_crit_hyst should return 
90000.

+tempX_alarm		Read a 1 if the max/min alarm is set
+tempX_crit_alarm	Read a 1 if the critical limit is exceeded
+tempX_fault		Read a 1 if either temp1 or temp3 diode has a fault
+

All good.

+(There are two voltage settings, in1 and in2):
+
+inX_input               Read the current voltage on VCC.  Value is in
+			millivolts.
+
+inX_min                 read/write the minimum voltage limit.
+			Dropping below this causes an alarm.
+
+inX_max   		read/write the maximum voltage limit.
+			Exceeding this causes an alarm.
+
+inX_alarm		Read a 1 if the max/min alarm is set.

Also all good.


Again, apologies for being so slow, esp since all I've done now is take a look 
at the API and not even at the code :(

I'll be leaving tomorrow morning for a week vacation. I hope you will have been 
able todo a new revision fixing all the API remarks I've made and then I'll do 
a full review real soon!

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] 15+ messages in thread

* [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
  2008-06-13  7:10 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
  2008-08-07 20:56 ` Hans de Goede
@ 2008-09-05 20:54 ` Jordan Crouse
  2008-09-06 10:00 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Hans de Goede
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Jordan Crouse @ 2008-09-05 20:54 UTC (permalink / raw)
  To: lm-sensors

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

Resend of the ADT7475 driver.  Jean said he would like to
see a review on this version, even though it had been discussed before.
Therefore I'm CCing Hans for his opinion.. :) I would really like to
see this go into 2.6.28 and get out of my patch stack.

Jordan

-- 
Jordan Crouse
Systems Software Development Engineer 
Advanced Micro Devices, Inc.

[-- Attachment #2: adt7475.patch --]
[-- Type: text/x-diff, Size: 35802 bytes --]

[PATCH] hwmon:  Add a driver for the ADT7475 thermal sensor

From: Jordan Crouse <jordan.crouse@amd.com>

HWMON driver for the ADT7475 thermal sensor.

Signed-off-by: Jordan Crouse <jordan.crouse@amd.com>
---

 Documentation/hwmon/adt7475 |  104 +++++
 drivers/hwmon/Kconfig       |   10 
 drivers/hwmon/Makefile      |    2 
 drivers/hwmon/adt7475.c     |  938 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1054 insertions(+), 0 deletions(-)

diff --git a/Documentation/hwmon/adt7475 b/Documentation/hwmon/adt7475
new file mode 100644
index 0000000..64eb78c
--- /dev/null
+++ b/Documentation/hwmon/adt7475
@@ -0,0 +1,104 @@
+This describes the interface for the ADT7475 driver:
+
+(there are 4 fans, numbered fan1 to fan4):
+
+fanX_input              Read the current speed of the fan (in RPMs)
+fanX_min                Read/write the minimum speed of the fan.  Dropping
+	   		below this sets an alarm.
+
+(there are three PWMs, numbered pwm1 to pwm3):
+
+pwmX                    Read/write the current duty cycle of the PWM.  Writes
+     			only have effect when auto mode is turned off (see
+			below).
+
+pwmX_enable             Read/write the PWM configuration based on the following
+                        table:
+
+		        0 - Remote1 temp controls PWMx (auto mode)
+		        1 - local temp controls PWMx (auto mode)
+	 	        2 - remote2 temp controls PWMx (auto mode)
+                        3 - PWMx runs at full speed
+                        4 - PWMx is disabled
+                        5 - Use fastest speed calculated by local and remote2
+                        6 - Use fastest speed calculated by all three channels
+                        7 - Manual mode
+
+pwmX_freq               Read/write the PWM frequency.  The value returned is
+	  	        an index into the following table:
+
+		        0x0 - 11.0 Hz
+		        0x1 - 14.7 Hz
+		        0x2 - 22.1 Hz
+		        0x3 - 29.4 Hz
+		        0x4 - 35.3 Hz
+		        0x5 - 44.1 Hz
+		        0x6 - 58.8 Hz
+		        0x7 - 88.2 Hz
+
+pwmX_max                Read/write the maximum PWM duty cycle.  The PWM
+                        duty cycle will never exceed this.
+
+pwmX_min                Read/write the minimum PWM duty cycle in automatic mode
+
+(there are three temperature settings numbered temp1 to temp3):
+
+tempX_input             Read the current temperature.  The value is in milli
+			degrees of Celsius.
+
+tempX_max               Read/write the upper temperature limit - exceeding this
+	                will cause an alarm.
+
+tempX_min               Read/write the lower temperature limit - exceeding this
+                        will cause an alarm.
+
+tempX_offset            Read/write the temperature adjustment offset
+
+tempX_crit_max        Read/write the THERM limit for remote1.  Exceeding this
+                        causes the chip to force the processor off.
+
+tempX_auto_min   	Read/write the minimum temperature where the fans will
+                        turn on in automatic mode.
+
+tempX_auto_range  	Read/write the range over which the automatic fan
+			control will be executed.  The value returned is a
+			index into the following table:
+
+			0x0 - 2 C
+			0x1 - 2.5 C
+			0x2 - 3.33 C
+			0x3 - 4 C
+			0x4 - 5 C
+			0x5 - 6.67 C
+			0x6 - 8 C
+			0x7 - 10 C
+			0x8 - 13.33 C
+			0x9 - 16 C
+			0xA - 20 C
+			0xB - 26.67 C
+			0xC - 32 C
+			0xD - 40 C
+			0xE - 53.33 C
+			0xF - 80 C
+
+tempX_crit_hyst 	set the temperature range below crit_max where the
+                        fans will stay on - this helps drive the temperature
+                        low enough so it doesn't stay near the edge and
+                        cause THERM to keep tripping.
+
+tempX_alarm		Read a 1 if the max/min alarm is set
+tempX_crit_alarm	Read a 1 if the critical limit is exceeded
+tempX_fault		Read a 1 if either temp1 or temp3 diode has a fault
+
+(There are two voltage settings, in1 and in2):
+
+inX_input               Read the current voltage on VCC.  Value is in
+			millivolts.
+
+inX_min                 read/write the minimum voltage limit.
+			Dropping below this causes an alarm.
+
+inX_max   		read/write the maximum voltage limit.
+			Exceeding this causes an alarm.
+
+inX_alarm		Read a 1 if the max/min alarm is set.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index d402e8d..8a1baf5 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -179,6 +179,16 @@ config SENSORS_ADT7473
 	  This driver can also be built as a module. If so, the module
 	  will be called adt7473.
 
+config SENSORS_ADT7475
+	tristate "Analog Devices ADT7475"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  ADT7475 temperature monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called adt7475.
+
 config SENSORS_K8TEMP
 	tristate "AMD Athlon64/FX or Opteron temperature sensor"
 	depends on X86 && PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 950134a..424094c 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -27,6 +27,8 @@ obj-$(CONFIG_SENSORS_ADM9240)	+= adm9240.o
 obj-$(CONFIG_SENSORS_ADS7828)	+= ads7828.o
 obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
+obj-$(CONFIG_SENSORS_ADT7475)   += adt7475.o
+
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c
new file mode 100644
index 0000000..891687d
--- /dev/null
+++ b/drivers/hwmon/adt7475.c
@@ -0,0 +1,938 @@
+/*
+ * adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives
+ * Copyright (C) 2007-2008, Advanced Micro Devices, Inc.
+ *
+ * Derived from the lm83 driver by Jean Delvare
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+
+/* Indexes for the sysfs hooks */
+
+#define INPUT     0
+#define MIN       1
+#define MAX       2
+#define CONTROL   3
+#define OFFSET    3
+#define AUTOMIN   4
+#define FREQ      4
+#define THERM     5
+#define HYSTERSIS 6
+#define RANGE     7
+#define ALARM     9
+#define CRIT_ALARM 10
+#define FAULT      11
+
+
+/* 7475 Common Registers */
+
+#define REG_VOLTAGE_BASE        0x21
+#define REG_TEMP_BASE           0x25
+#define REG_TACH_BASE		0x28
+#define REG_PWM_BASE            0x30
+#define REG_PWM_MAX_BASE        0x38
+
+#define REG_DEVID               0x3D
+#define REG_VENDID              0x3E
+
+#define REG_CONFIG1             0x40
+#define REG_STATUS1             0x41
+#define REG_STATUS2             0x42
+
+/* Not all the alarm bits are enabled - mask the ones we don't use */
+#define ALARM_MASK              0xFE76
+
+#define REG_VOLTAGE_MIN_BASE    0x46
+#define REG_VOLTAGE_MAX_BASE    0x47
+
+#define REG_TEMP_MIN_BASE       0x4E
+#define REG_TEMP_MAX_BASE	0x4F
+
+#define REG_TACH_MIN_BASE	0x54
+
+#define REG_PWM_CONFIG_BASE     0x5C
+
+#define REG_TEMP_TRANGE_BASE    0x5F
+
+#define REG_ACOUSTICS1          0x62
+#define REG_ACOUSTICS2          0x63
+
+#define REG_PWM_MIN_BASE        0x64
+
+#define REG_TEMP_TMIN_BASE      0x67
+#define REG_TEMP_THERM_BASE     0x6A
+
+#define REG_REMOTE1_HYSTERSIS   0x6D
+#define REG_REMOTE2_HYSTERSIS   0x6E
+
+#define REG_TEMP_OFFSET_BASE    0x70
+
+#define REG_CONFIG2             0x73
+#define REG_INTERRUPT_MASK1     0x74
+#define REG_INTERRUPT_MASK2     0x75
+#define REG_EXTEND1             0x76
+#define REG_EXTEND2             0x77
+#define REG_CONFIG3             0x78
+#define REG_THERM_TIMER_STATUS  0x79
+#define REG_THERM_TIMER_LIMIT   0x7A
+#define REG_TACH_PULSES         0x7B
+#define REG_CONFIG5             0x7C
+
+#define CONFIG5_TWOSCOMP        0x01
+#define CONFIG5_TEMPOFFSET      0x02
+
+#define REG_CONFIG4             0x7D
+
+/* ADT7475 Settings */
+
+#define ADT7475_VOLTAGE_COUNT   2
+#define ADT7475_TEMP_COUNT      3
+#define ADT7475_TACH_COUNT	4
+#define ADT7475_PWM_COUNT       3
+
+/* 7475 specific registers */
+
+#define REG_CONFIG6             0x10
+#define REG_CONFIG7             0x11
+
+
+/* Macro to read the registers */
+
+#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg))
+
+/* Macros to easily index the registers */
+
+#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2))
+#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2))
+
+#define PWM_REG(idx) (REG_PWM_BASE + (idx))
+#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx))
+#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx))
+#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx))
+
+#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx))
+#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2))
+#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2))
+
+#define TEMP_REG(idx) (REG_TEMP_BASE + (idx))
+#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2))
+#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2))
+#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx))
+#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx))
+#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx))
+#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx))
+
+/* Convert the tach reading into RPMs */
+
+#define TACH2RPM(val) ((90000 * 60) / (val))
+#define RPM2TACH(val) ((90000 * 60) / (val))
+
+/* Convert the voltage registers into mW */
+
+#define REG2VCC(val) ((42 * (int)(val)) / 10)
+#define REG2VCCP(val) ((293 * (int)(val)) / 100)
+
+#define VCC2REG(val)  (((val) * 10) / 42)
+#define VCCP2REG(val) (((val) * 100) / 293)
+
+/* 2's complement temp conversion - this is used when CONFIG5 bit 0 is set */
+
+#define REG2TEMP(val) ((((val) >> 2) * 1000) + (((val) & 3) * 250))
+
+#define TEMP2REG(val) ((val) <= -128000 ? -128 : \
+	(val) >= 127000 ? 127 : \
+	(val) < 0 ? ((val) - 500) / 1000 : \
+	((val) + 500) / 1000)
+
+
+/* Offset 64 temp conversion - this is used when CONFIG5 bit 0 is clear */
+
+#define OFF64_REG2TEMP(val) (((((val) >> 2) - 64) * 1000) + (((val) & 3) * 250))
+#define OFF64_TEMP2REG(val) ((((val) + 64500) / 1000) << 2)
+
+static unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(adt7475);
+
+struct adt7475_data {
+	struct i2c_client client;
+	struct device *hwmon_dev;
+	struct mutex lock;
+
+	int type;
+	char temptype;
+
+	char valid;
+	unsigned long updated;
+
+	u16 alarms;
+	u16 voltage[3][3];
+	s16 temp[6][3];
+	u16 tach[2][4];
+	u8  pwm[4][3];
+	u8  range[3];
+};
+
+static struct i2c_driver adt7475_driver;
+static struct adt7475_data *adt7475_update_device(struct device *dev);
+
+static u16 adt7475_read_word(struct i2c_client *client, int reg)
+{
+	u16 val;
+
+	val = i2c_smbus_read_byte_data(client, reg);
+	val |= (i2c_smbus_read_byte_data(client, reg + 1) << 8);
+
+	return val;
+}
+
+static void adt7475_write_word(struct i2c_client *client, int reg, u16 val)
+{
+	i2c_smbus_write_byte_data(client, reg + 1, val >> 8);
+	i2c_smbus_write_byte_data(client, reg, val & 0xFF);
+}
+
+static ssize_t show_voltage(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val = data->voltage[sattr->nr][sattr->index];
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 1)) & 1);
+	default:
+		return sprintf(buf, "%d\n",
+			sattr->index == 0 ? REG2VCCP(val) : REG2VCC(val));
+	}
+}
+
+static ssize_t set_voltage(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count) {
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg;
+
+	mutex_lock(&data->lock);
+
+	data->voltage[sattr->nr][sattr->index] =
+		sattr->index ? VCC2REG(val) : VCCP2REG(val);
+
+	if (sattr->nr == MIN)
+		reg = VOLTAGE_MIN_REG(sattr->index);
+	else
+		reg = VOLTAGE_MAX_REG(sattr->index);
+
+	i2c_smbus_write_byte_data(client, reg,
+		(u8) (data->voltage[sattr->nr][sattr->index] >> 2));
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val = data->temp[sattr->nr][sattr->index];
+	int ret = 0;
+	u8 out;
+
+	switch (sattr->nr) {
+	case HYSTERSIS:
+		if (sattr->index != 1)
+			out = (val >> 4) & 0xF;
+		else
+			out = (val & 0xF);
+
+		ret = sprintf(buf, "%d\n", out);
+		break;
+
+	case OFFSET:
+		/* Offset is always 2's complement, regardless of the
+		 * setting in CONFIG5 */
+
+		if (data->temptype & CONFIG5_TEMPOFFSET)
+			ret = sprintf(buf, "%d\n", (s8) val);
+		else
+			ret = sprintf(buf, "%d\n", ((s8)val) >> 1);
+		break;
+
+	case ALARM:
+		ret = sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 4))  & 1);
+		break;
+
+	case CRIT_ALARM:
+		ret = sprintf(buf, "%d\n",
+			((data->alarms & 0x200) ? 1 : 0));
+		break;
+
+	case FAULT:
+		/* Note - only for remote1 and remote2 */
+
+		ret = sprintf(buf, "%d\n",
+			(data->alarms & (sattr->index ? 0x4000 : 0x8000)));
+		break;
+
+	default:
+		/* All other temp values are in the configured format */
+
+		ret = sprintf(buf, "%d\n",
+			      (data->temptype & CONFIG5_TWOSCOMP) ?
+			      REG2TEMP(val) : OFF64_REG2TEMP(val));
+	}
+
+	return ret;
+}
+
+static ssize_t set_temp(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg = 0;
+	u8 out;
+	int l;
+
+	mutex_lock(&data->lock);
+
+	switch (sattr->nr) {
+	case OFFSET:
+		l = val;
+
+		if (data->temptype & CONFIG5_TEMPOFFSET) {
+			if (l > 127)
+				l = 127;
+			else if (l < -127)
+				l = -126;
+		} else {
+			if (l > 64)
+				l = 64;
+			else if (l < -63)
+				l = -63;
+
+			l <<= 1;
+		}
+
+		out = data->temp[OFFSET][sattr->index] = (u8) l;
+		break;
+
+	case HYSTERSIS:
+		if (sattr->index != 1) {
+			data->temp[HYSTERSIS][sattr->index] &= 0xF0;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF) << 4;
+		} else {
+			data->temp[HYSTERSIS][sattr->index] &= 0x0F;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF);
+		}
+
+		out = data->temp[HYSTERSIS][sattr->index];
+		break;
+
+	default:
+		data->temp[sattr->nr][sattr->index] =
+			(data->temptype & CONFIG5_TWOSCOMP) ?
+				TEMP2REG(val) : OFF64_TEMP2REG(val);
+
+		/* We maintain an extra 2 digits of precision for simplicity
+		 * - shift those back off before writing the value */
+
+		out = (u8) (data->temp[sattr->nr][sattr->index] >> 2);
+	}
+
+	switch (sattr->nr) {
+	case MIN:
+		reg = TEMP_MIN_REG(sattr->index);
+		break;
+	case MAX:
+		reg = TEMP_MAX_REG(sattr->index);
+		break;
+	case OFFSET:
+		reg = TEMP_OFFSET_REG(sattr->index);
+		break;
+	case AUTOMIN:
+		reg = TEMP_TMIN_REG(sattr->index);
+		break;
+	case THERM:
+		reg = TEMP_THERM_REG(sattr->index);
+		break;
+	case HYSTERSIS:
+		if (sattr->index != 2)
+			reg = REG_REMOTE1_HYSTERSIS;
+		else
+			reg = REG_REMOTE2_HYSTERSIS;
+
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg, out);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_range(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	unsigned short val = data->range[sattr->index];
+	return sprintf(buf, "%d\n", (val >> 4) & 0xF);
+}
+
+static ssize_t set_range(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] &= ~0xF0;
+	data->range[sattr->index] |= (val & 0xF) << 4;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_tach(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+
+	/* 0xFFFF means the period was invalid */
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			(data->alarms >> (sattr->index + 10) & 1));
+	default:
+	if (data->tach[sattr->nr][sattr->index] == 0xFFFF)
+		return sprintf(buf, "0\n");
+	else
+		return sprintf(buf, "%d\n",
+		       TACH2RPM(data->tach[sattr->nr][sattr->index]));
+	}
+}
+
+
+
+
+static ssize_t set_tach(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->tach[MIN][sattr->index] = (u16) RPM2TACH(val);
+
+	adt7475_write_word(client, TACH_MIN_REG(sattr->index),
+			   data->tach[MIN][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwm[sattr->nr][sattr->index]);
+}
+
+static ssize_t show_pwmctrl(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n",
+		(data->pwm[CONTROL][sattr->index] >> 5) & 7);
+}
+
+static ssize_t show_pwmfreq(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->range[sattr->index] & 3);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+	unsigned char reg = 0;
+
+	mutex_lock(&data->lock);
+
+	data->pwm[sattr->nr][sattr->index] = (u8) val;
+
+	switch (sattr->nr) {
+	case INPUT:
+		/* If we are not in manual mode, then we shouldn't allow
+		 * the user to set the pwm speed */
+
+		if (((data->pwm[CONTROL][sattr->index] >> 5) & 7) != 7)
+			return 0;
+
+		reg = PWM_REG(sattr->index);
+		break;
+
+	case MIN:
+		reg = PWM_MIN_REG(sattr->index);
+		break;
+
+	case MAX:
+		reg = PWM_MAX_REG(sattr->index);
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg,
+		data->pwm[sattr->nr][sattr->index]);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t set_pwmctrl(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->pwm[CONTROL][sattr->index] &= ~0xE0;
+	data->pwm[CONTROL][sattr->index] |= (val & 7) << 5;
+
+	i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(sattr->index),
+				  data->pwm[CONTROL][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t set_pwmfreq(struct device *dev, struct device_attribute *attr,
+	const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	long val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] &= ~3;
+	data->range[sattr->index] |= val & 0x03;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_voltage, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, show_voltage, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_voltage, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_voltage, set_voltage, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_voltage, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, show_temp, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_temp, NULL, FAULT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 0);
+static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_alarm, S_IRUGO, show_temp, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 1);
+static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_alarm, S_IRUGO, show_temp, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp, NULL, CRIT_ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_temp, NULL, FAULT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, MAX, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp, set_temp, OFFSET, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_min, S_IRUGO | S_IWUSR, show_temp, set_temp, AUTOMIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_range, S_IRUGO | S_IWUSR, show_range, set_range, 0, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_max, S_IRUGO | S_IWUSR, show_temp, set_temp, THERM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_temp, set_temp, HYSTERSIS, 2);
+static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_tach, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_tach, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_tach, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_tach, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_tach, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_tach, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_tach, NULL, INPUT, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_tach, set_tach, MIN, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_alarm, S_IRUGO, show_tach, NULL, ALARM, 3);
+static SENSOR_DEVICE_ATTR_2(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_freq, S_IRUGO | S_IWUSR, show_pwmfreq, set_pwmfreq, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_enable, S_IRUGO | S_IWUSR, show_pwmctrl, set_pwmctrl, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_max, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MAX, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_min, S_IRUGO | S_IWUSR, show_pwm, set_pwm, MIN, 2);
+
+static struct attribute *adt7475_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_max.dev_attr.attr,
+	&sensor_dev_attr_in2_min.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_fault.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_offset.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_offset.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_fault.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_offset.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_range.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_min.dev_attr.attr,
+	&sensor_dev_attr_fan4_alarm.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_max.dev_attr.attr,
+	&sensor_dev_attr_pwm1_min.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm2_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_max.dev_attr.attr,
+	&sensor_dev_attr_pwm2_min.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm3_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_max.dev_attr.attr,
+	&sensor_dev_attr_pwm3_min.dev_attr.attr,
+	NULL,
+};
+
+/* The list of chips we support - these index into the following structure */
+
+#define ADT7475 0
+#define ADT7475_MAX_ID 1
+
+static struct adt7475_chip {
+	const char *name;
+	struct attribute_group group;
+} adt7475_chips[ADT7475_MAX_ID] = {
+	{ .name = "adt7475",
+	  .group = {
+		  .attrs = adt7475_attrs,
+	  }
+	},
+};
+
+static int adt7475_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *client;
+	struct adt7475_data *data;
+	unsigned char val;
+	int ret = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return 0;
+
+	/* Figure out what type of sensor is attached */
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+
+	if (data == NULL)
+		return -ENOMEM;
+
+	client = &data->client;
+	i2c_set_clientdata(client, data);
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &adt7475_driver;
+	client->flags = 0;
+
+	/* Check the company version first */
+
+	if (i2c_smbus_read_byte_data(client, REG_VENDID) != 0x41) {
+		dev_err(&adapter->dev, "This is not an adt7475 part\n");
+		goto efree;
+	}
+
+	/* Then check the part number */
+	val = i2c_smbus_read_byte_data(client, REG_DEVID);
+
+	if (val == 0x75)
+		data->type = ADT7475;
+	else {
+		dev_err(&adapter->dev,
+		"Couldn't detect a adt7475 part at 0x%02x\n", address);
+
+		goto efree;
+	}
+
+	/* Record how the temp registers are presented, either 2's complement
+	   or offset 64
+	*/
+
+	data->temptype = i2c_smbus_read_byte_data(client, REG_CONFIG5) & 3;
+
+	/* FIXME: Get the reading type */
+	/* FIXME:  Get the scale of the temprature readings */
+
+	strlcpy(client->name, adt7475_chips[data->type].name, I2C_NAME_SIZE);
+
+	data->valid = 0;
+	mutex_init(&data->lock);
+
+	ret = i2c_attach_client(client);
+
+	if (ret)
+		goto efree;
+
+	ret = sysfs_create_group(&client->dev.kobj,
+		&adt7475_chips[data->type].group);
+
+	if (ret)
+		goto edetach;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	ret = PTR_ERR(data->hwmon_dev);
+
+	sysfs_remove_group(&client->dev.kobj, &adt7475_chips[data->type].group);
+ edetach:
+	i2c_detach_client(client);
+ efree:
+	kfree(data);
+	return ret;
+}
+
+static int adt7475_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+
+	return i2c_probe(adapter, &addr_data, adt7475_detect);
+}
+
+static int adt7475_detach_client(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &adt7475_chips[data->type].group);
+
+	ret = i2c_detach_client(client);
+	if (!ret)
+		kfree(data);
+	return ret;
+}
+
+static struct i2c_driver adt7475_driver = {
+	.driver = {
+		.name   = "adt7475",
+	},
+	.attach_adapter = adt7475_attach_adapter,
+	.detach_client  = adt7475_detach_client,
+};
+
+static struct adt7475_data *adt7475_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	u8 ext;
+	int i;
+
+	mutex_lock(&data->lock);
+
+	if (!time_after(jiffies, data->updated + HZ * 2) && data->valid) {
+		mutex_unlock(&data->lock);
+		return data;
+	}
+
+	data->alarms = adt7475_read(REG_STATUS2) << 8;
+	data->alarms |= adt7475_read(REG_STATUS1);
+
+	ext = adt7475_read(REG_EXTEND1);
+
+	/* Get the voltage readings */
+
+	for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) {
+		data->voltage[INPUT][i] = (adt7475_read(VOLTAGE_REG(i)) << 2) |
+			((ext >> ((i + 1) * 2)) & 3);
+
+		/* Adjust these values so they match the input precision */
+
+		data->voltage[MIN][i] = adt7475_read(VOLTAGE_MIN_REG(i)) << 2;
+		data->voltage[MAX][i] = adt7475_read(VOLTAGE_MAX_REG(i)) << 2;
+	}
+
+	ext = adt7475_read(REG_EXTEND2);
+
+	for (i = 0; i < ADT7475_TEMP_COUNT; i++) {
+		data->temp[INPUT][i] = (adt7475_read(TEMP_REG(i)) << 2) |
+			((ext >> ((i + 1) * 2)) & 3);
+
+		/* Adjust these values so they match the input precision */
+
+		data->temp[MIN][i] = adt7475_read(TEMP_MIN_REG(i)) << 2;
+		data->temp[MAX][i] = adt7475_read(TEMP_MAX_REG(i)) << 2;
+		data->temp[AUTOMIN][i] = adt7475_read(TEMP_TMIN_REG(i)) << 2;
+		data->temp[THERM][i] = adt7475_read(TEMP_THERM_REG(i)) << 2;
+
+		data->temp[OFFSET][i] = adt7475_read(TEMP_OFFSET_REG(i));
+	}
+
+	data->temp[HYSTERSIS][0] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][1] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][2] = (u16) adt7475_read(REG_REMOTE2_HYSTERSIS);
+
+	for (i = 0; i < ADT7475_TACH_COUNT; i++) {
+		data->tach[INPUT][i] = adt7475_read_word(client, TACH_REG(i));
+		data->tach[MIN][i] = adt7475_read_word(client, TACH_MIN_REG(i));
+	}
+
+	for (i = 0; i < ADT7475_PWM_COUNT; i++) {
+		data->pwm[INPUT][i] = adt7475_read(PWM_REG(i));
+		data->pwm[MAX][i] = adt7475_read(PWM_MAX_REG(i));
+		data->pwm[MIN][i] = adt7475_read(PWM_MIN_REG(i));
+		data->pwm[CONTROL][i] = adt7475_read(PWM_CONFIG_REG(i));
+	}
+
+	data->range[0] =  adt7475_read(TEMP_TRANGE_REG(0));
+	data->range[1] =  adt7475_read(TEMP_TRANGE_REG(1));
+	data->range[2] =  adt7475_read(TEMP_TRANGE_REG(2));
+
+	data->updated = jiffies;
+	data->valid = 1;
+
+	mutex_unlock(&data->lock);
+
+	return data;
+}
+
+static int __init sensors_adt7475_init(void)
+{
+	return i2c_add_driver(&adt7475_driver);
+}
+
+static void __exit sensors_adt7475_exit(void)
+{
+	i2c_del_driver(&adt7475_driver);
+}
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("adt7475 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_adt7475_init);
+module_exit(sensors_adt7475_exit);

[-- Attachment #3: 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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (2 preceding siblings ...)
  2008-09-05 20:54 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
@ 2008-09-06 10:00 ` Hans de Goede
  2008-12-19 21:31 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-09-06 10:00 UTC (permalink / raw)
  To: lm-sensors

Jordan Crouse wrote:
> Resend of the ADT7475 driver.  Jean said he would like to
> see a review on this version, even though it had been discussed before.
> Therefore I'm CCing Hans for his opinion.. :) I would really like to
> see this go into 2.6.28 and get out of my patch stack.
>

Jordan,

I did do a review although very late (but didn't get very far, as there
are some sysfs API issues that need addressing first).

See:
http://lists.lm-sensors.org/pipermail/lm-sensors/2008-August/023948.html

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] 15+ messages in thread

* [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (3 preceding siblings ...)
  2008-09-06 10:00 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Hans de Goede
@ 2008-12-19 21:31 ` Hans de Goede
  2008-12-19 21:32 ` Hans de Goede
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-12-19 21:31 UTC (permalink / raw)
  To: lm-sensors

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

Hi Jean,

As Jordan did not get around to doing this and the 2.6.29 merge window is near 
I've fixed the last few outstanding issues from my review(s) of his adt7475 
I've done myself. Attached is a new version of the adt7475 with:
* all issues review found fixed
* converted to the new i2c driver style
* a few more registers needed reading in store methods
* corrected register to voltage conversion (more accurate)
* some style fixes
* locking around show functions where the value shown depends on more then
   1 reg.
* read limit registers only once every 60 seconds
* rename pwm_min and pwm_max to pwm_auto_pointX_pwm, as the
   control the scope between which the pwm gets changed in auto mode
* remove tempX_crit_alarm (only one crit alarm flag for all 3 channels)

I hope this is ok now to be queued for 2.6.29

Thanks & Regards,

Hans

[-- Attachment #2: hwmon-add-adt7475-driver.patch --]
[-- Type: text/plain, Size: 41730 bytes --]

[PATCH] hwmon: Add a driver for the ADT7475 thermal sensor

From: Jordan Crouse <jordan.crouse@amd.com>

HWMON driver for the ADT7475 thermal sensor.

Signed-off-by: Jordan Crouse <jordan.crouse@amd.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
diff -up /dev/null ./Documentation/hwmon/adt7475
--- /dev/null	2008-12-19 21:10:25.037006022 +0100
+++ ./Documentation/hwmon/adt7475	2008-12-19 22:09:32.000000000 +0100
@@ -0,0 +1,87 @@
+This describes the interface for the ADT7475 driver:
+
+(there are 4 fans, numbered fan1 to fan4):
+
+fanX_input              Read the current speed of the fan (in RPMs)
+fanX_min                Read/write the minimum speed of the fan.  Dropping
+	   		below this sets an alarm.
+
+(there are three PWMs, numbered pwm1 to pwm3):
+
+pwmX                    Read/write the current duty cycle of the PWM.  Writes
+     			only have effect when auto mode is turned off (see
+			below).  Range is 0 - 255.
+
+pwmX_enable             Fan speed control method:
+
+		        0 - No control (fan at full speed)
+			1 - Manual fan speed control (using pwm[1-*])
+			2 - Automatic fan speed control
+
+pwmX_auto_channels_temp	Select which channels affect this PWM
+
+			1 - TEMP1 controls PWM
+			2 - TEMP2 controls PWM
+			4 - TEMP3 controls PWM
+			6 - TEMP2 and TEMP3 control PWM
+			7 - All three inputs control PWM
+
+pwmX_freq               Read/write the PWM frequency in Hz. The number
+			should be one of the following:
+
+		        11 Hz
+		        14 Hz
+		        22 Hz
+		        29 Hz
+		        35 Hz
+		        44 Hz
+		        58 Hz
+		        88 Hz
+
+pwmX_auto_point1_pwm    Read/write the minimum PWM duty cycle in automatic mode
+
+pwmX_auto_point2_pwm    Read/write the maximum PWM duty cycle in automatic mode
+
+(there are three temperature settings numbered temp1 to temp3):
+
+tempX_input             Read the current temperature.  The value is in milli
+			degrees of Celsius.
+
+tempX_max               Read/write the upper temperature limit - exceeding this
+	                will cause an alarm.
+
+tempX_min               Read/write the lower temperature limit - exceeding this
+                        will cause an alarm.
+
+tempX_offset            Read/write the temperature adjustment offset
+
+tempX_crit              Read/write the THERM limit for remote1.
+
+tempX_crit_hyst 	Set the temperature value below crit where the
+                        fans will stay on - this helps drive the temperature
+                        low enough so it doesn't stay near the edge and
+                        cause THERM to keep tripping.
+
+tempX_auto_point1_temp 	Read/write the minimum temperature where the fans will
+                        turn on in automatic mode.
+
+tempX_auto_point2_temp  Read/write the maximum temperature over which the fans
+			will run in automatic mode.  tempX_auto_point1_temp
+			and tempX_auto_point2_temp together define the
+			range of automatic control.
+
+tempX_alarm		Read a 1 if the max/min alarm is set
+tempX_fault		Read a 1 if either temp1 or temp3 diode has a fault
+
+(There are two voltage settings, in1 and in2):
+
+inX_input               Read the current voltage on VCC.  Value is in
+			millivolts.
+
+inX_min                 read/write the minimum voltage limit.
+			Dropping below this causes an alarm.
+
+inX_max   		read/write the maximum voltage limit.
+			Exceeding this causes an alarm.
+
+inX_alarm		Read a 1 if the max/min alarm is set.
diff -up ./drivers/hwmon/Kconfig.adt7475 ./drivers/hwmon/Kconfig
--- ./drivers/hwmon/Kconfig.adt7475	2008-12-19 21:51:29.000000000 +0100
+++ ./drivers/hwmon/Kconfig	2008-12-19 22:05:34.000000000 +0100
@@ -189,6 +189,16 @@ config SENSORS_ADT7473
 	  This driver can also be built as a module. If so, the module
 	  will be called adt7473.
 
+config SENSORS_ADT7475
+	tristate "Analog Devices ADT7475"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  ADT7475 temperature monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called adt7475.
+
 config SENSORS_K8TEMP
 	tristate "AMD Athlon64/FX or Opteron temperature sensor"
 	depends on X86 && PCI && EXPERIMENTAL
diff -up ./drivers/hwmon/Makefile.adt7475 ./drivers/hwmon/Makefile
--- ./drivers/hwmon/Makefile.adt7475	2008-12-19 21:51:29.000000000 +0100
+++ ./drivers/hwmon/Makefile	2008-12-19 22:05:34.000000000 +0100
@@ -28,6 +28,8 @@ obj-$(CONFIG_SENSORS_ADS7828)	+= ads7828
 obj-$(CONFIG_SENSORS_ADT7462)	+= adt7462.o
 obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
+obj-$(CONFIG_SENSORS_ADT7475)   += adt7475.o
+
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
diff -up /dev/null ./drivers/hwmon/adt7475.c
--- /dev/null	2008-12-19 21:10:25.037006022 +0100
+++ ./drivers/hwmon/adt7475.c	2008-12-19 22:18:12.000000000 +0100
@@ -0,0 +1,1221 @@
+/*
+ * adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives
+ * Copyright (C) 2007-2008, Advanced Micro Devices, Inc.
+ * Copyright (C) 2008 Jordan Crouse <jordan@cosmicpenguin.net>
+ * Copyright (C) 2008 Hans de Goede <hdegoede@redhat.com>
+
+ * Derived from the lm83 driver by Jean Delvare
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+
+/* Indexes for the sysfs hooks */
+
+#define INPUT     0
+#define MIN       1
+#define MAX       2
+#define CONTROL   3
+#define OFFSET    3
+#define AUTOMIN   4
+#define THERM     5
+#define HYSTERSIS 6
+
+/* These are unique identifiers for the sysfs functions - unlike the
+   numbers above, these are not also indexes into an array
+*/
+
+#define ALARM      9
+#define FAULT      10
+
+/* 7475 Common Registers */
+
+#define REG_VOLTAGE_BASE        0x21
+#define REG_TEMP_BASE           0x25
+#define REG_TACH_BASE		0x28
+#define REG_PWM_BASE            0x30
+#define REG_PWM_MAX_BASE        0x38
+
+#define REG_DEVID               0x3D
+#define REG_VENDID              0x3E
+
+#define REG_STATUS1             0x41
+#define REG_STATUS2             0x42
+
+#define REG_VOLTAGE_MIN_BASE    0x46
+#define REG_VOLTAGE_MAX_BASE    0x47
+
+#define REG_TEMP_MIN_BASE       0x4E
+#define REG_TEMP_MAX_BASE	0x4F
+
+#define REG_TACH_MIN_BASE	0x54
+
+#define REG_PWM_CONFIG_BASE     0x5C
+
+#define REG_TEMP_TRANGE_BASE    0x5F
+
+#define REG_PWM_MIN_BASE        0x64
+
+#define REG_TEMP_TMIN_BASE      0x67
+#define REG_TEMP_THERM_BASE     0x6A
+
+#define REG_REMOTE1_HYSTERSIS   0x6D
+#define REG_REMOTE2_HYSTERSIS   0x6E
+
+#define REG_TEMP_OFFSET_BASE    0x70
+
+#define REG_EXTEND1             0x76
+#define REG_EXTEND2             0x77
+#define REG_CONFIG5             0x7C
+
+#define CONFIG5_TWOSCOMP        0x01
+#define CONFIG5_TEMPOFFSET      0x02
+
+/* ADT7475 Settings */
+
+#define ADT7475_VOLTAGE_COUNT   2
+#define ADT7475_TEMP_COUNT      3
+#define ADT7475_TACH_COUNT	4
+#define ADT7475_PWM_COUNT       3
+
+/* Macro to read the registers */
+
+#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg))
+
+/* Macros to easily index the registers */
+
+#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2))
+#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2))
+
+#define PWM_REG(idx) (REG_PWM_BASE + (idx))
+#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx))
+#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx))
+#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx))
+
+#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx))
+#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2))
+#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2))
+
+#define TEMP_REG(idx) (REG_TEMP_BASE + (idx))
+#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2))
+#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2))
+#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx))
+#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx))
+#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx))
+#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx))
+
+static unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(adt7475);
+
+static const struct i2c_device_id adt7475_id[] = {
+	{ "adt7475", adt7475 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adt7475_id);
+
+struct adt7475_data {
+	struct device *hwmon_dev;
+	struct mutex lock;
+
+	unsigned long measure_updated;
+	unsigned long limits_updated;
+	char valid;
+
+	u8 config5;
+	u16 alarms;
+	u16 voltage[3][3];
+	u16 temp[7][3];
+	u16 tach[2][4];
+	u8 pwm[4][3];
+	u8 range[3];
+	u8 pwmctl[3];
+	u8 pwmchan[3];
+};
+
+static struct i2c_driver adt7475_driver;
+static struct adt7475_data *adt7475_update_device(struct device *dev);
+static void adt7475_read_hystersis(struct i2c_client *client);
+static void adt7475_read_pwm(struct i2c_client *client, int index);
+
+/* Given a temp value, convert it to register value */
+
+static inline u16 temp2reg(struct adt7475_data *data, long val)
+{
+	u16 ret;
+
+	if (!(data->config5 & CONFIG5_TWOSCOMP)) {
+		val = SENSORS_LIMIT(val, -64000, 191000);
+		ret = (val + 64500) / 1000;
+	} else {
+		val = SENSORS_LIMIT(val, -128000, 127000);
+		if (val < -500)
+			ret = (256500 + val) / 1000;
+		else
+			ret = (val + 500) / 1000;
+	}
+
+	return ret << 2;
+}
+
+/* Given a register value, convert it to a real temp value */
+
+static inline int reg2temp(struct adt7475_data *data, u16 reg)
+{
+	if (data->config5 & CONFIG5_TWOSCOMP) {
+		if (reg >= 512)
+			return (reg - 1024) * 250;
+		else
+			return reg * 250;
+	} else
+		return (reg - 256) * 250;
+}
+
+static inline int tach2rpm(u16 tach)
+{
+	if (tach == 0 || tach == 0xFFFF)
+		return 0;
+
+	return (90000 * 60) / tach;
+}
+
+static inline u16 rpm2tach(unsigned long rpm)
+{
+	if (rpm == 0)
+		return 0;
+
+	return SENSORS_LIMIT((90000 * 60) / rpm, 1, 0xFFFF);
+}
+
+static inline int reg2vcc(u16 reg)
+{
+	return (4296 * reg) / 1000;
+}
+
+static inline int reg2vccp(u16 reg)
+{
+	return (2929 * reg) / 1000;
+}
+
+static inline u16 vcc2reg(long vcc)
+{
+	vcc = SENSORS_LIMIT(vcc, 0, 4396);
+	return (vcc * 1000) / 4296;
+}
+
+static inline u16 vccp2reg(long vcc)
+{
+	vcc = SENSORS_LIMIT(vcc, 0, 2998);
+	return (vcc * 1000) / 2929;
+}
+
+static u16 adt7475_read_word(struct i2c_client *client, int reg)
+{
+	u16 val;
+
+	val = i2c_smbus_read_byte_data(client, reg);
+	val |= (i2c_smbus_read_byte_data(client, reg + 1) << 8);
+
+	return val;
+}
+
+static void adt7475_write_word(struct i2c_client *client, int reg, u16 val)
+{
+	i2c_smbus_write_byte_data(client, reg + 1, val >> 8);
+	i2c_smbus_write_byte_data(client, reg, val & 0xFF);
+}
+
+/* Find the nearest value in a table - used for pwm frequency and
+   auto temp range */
+static int find_nearest(long val, const int *array, int size)
+{
+	int i;
+
+	if (val < array[0])
+		return 0;
+
+	if (val > array[size - 1])
+		return array[size - 1];
+
+	for (i = 0; i < size - 1; i++) {
+		int a, b;
+
+		if (val > array[i + 1])
+			continue;
+
+		a = val - array[i];
+		b = array[i + 1] - val;
+
+		return (a <= b) ? i : i + 1;
+	}
+
+	return 0;
+}
+
+static ssize_t show_voltage(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val;
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			       (data->alarms >> (sattr->index + 1)) & 1);
+	default:
+		val = data->voltage[sattr->nr][sattr->index];
+		return sprintf(buf, "%d\n",
+			       sattr->index ==
+			       0 ? reg2vccp(val) : reg2vcc(val));
+	}
+}
+
+static ssize_t set_voltage(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	data->voltage[sattr->nr][sattr->index] =
+		sattr->index ? vcc2reg(val) : vccp2reg(val);
+
+	if (sattr->nr == MIN)
+		reg = VOLTAGE_MIN_REG(sattr->index);
+	else
+		reg = VOLTAGE_MAX_REG(sattr->index);
+
+	i2c_smbus_write_byte_data(client, reg,
+				  data->voltage[sattr->nr][sattr->index] >> 2);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out;
+
+	switch (sattr->nr) {
+	case HYSTERSIS:
+		mutex_lock(&data->lock);
+		out = data->temp[sattr->nr][sattr->index];
+		if (sattr->index != 1)
+			out = (out >> 4) & 0xF;
+		else
+			out = (out & 0xF);
+		/* Show the value as an absolute number tied to
+		 * THERM */
+		out = reg2temp(data, data->temp[THERM][sattr->index]) -
+			out * 1000;
+		mutex_unlock(&data->lock);
+		break;
+
+	case OFFSET:
+		/* Offset is always 2's complement, regardless of the
+		 * setting in CONFIG5 */
+		mutex_lock(&data->lock);
+		out = (s8)data->temp[sattr->nr][sattr->index];
+		if (data->config5 & CONFIG5_TEMPOFFSET)
+			out *= 1000;
+		else
+			out *= 500;
+		mutex_unlock(&data->lock);
+		break;
+
+	case ALARM:
+		out = (data->alarms >> (sattr->index + 4)) & 1;
+		break;
+
+	case FAULT:
+		/* Note - only for remote1 and remote2 */
+		out = data->alarms & (sattr->index ? 0x8000 : 0x4000);
+		out = out ? 0 : 1;
+		break;
+
+	default:
+		/* All other temp values are in the configured format */
+		out = reg2temp(data, data->temp[sattr->nr][sattr->index]);
+	}
+
+	return sprintf(buf, "%d\n", out);
+}
+
+static ssize_t set_temp(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg = 0;
+	u8 out;
+	int temp;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	/* We need the config register in all cases for temp <-> reg conv. */
+	data->config5 = adt7475_read(REG_CONFIG5);
+
+	switch (sattr->nr) {
+	case OFFSET:
+		if (data->config5 & CONFIG5_TEMPOFFSET) {
+			val = SENSORS_LIMIT(val, -63000, 127000);
+			out = data->temp[OFFSET][sattr->index] = val / 1000;
+		} else {
+			val = SENSORS_LIMIT(val, -63000, 64000);
+			out = data->temp[OFFSET][sattr->index] = val / 500;
+		}
+		break;
+
+	case HYSTERSIS:
+		/* The value will be given as an absolute value, turn it
+		   into an offset based on THERM */
+
+		/* Read fresh THERM and HYSTERSIS values from the chip */
+		data->temp[THERM][sattr->index] =
+			adt7475_read(TEMP_THERM_REG(sattr->index)) << 2;
+		adt7475_read_hystersis(client);
+
+		temp = reg2temp(data, data->temp[THERM][sattr->index]);
+		val = SENSORS_LIMIT(val, temp - 15000, temp);
+		val = (temp - val) / 1000;
+
+		if (sattr->index != 1) {
+			data->temp[HYSTERSIS][sattr->index] &= 0xF0;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF) << 4;
+		} else {
+			data->temp[HYSTERSIS][sattr->index] &= 0x0F;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF);
+		}
+
+		out = data->temp[HYSTERSIS][sattr->index];
+		break;
+
+	default:
+		data->temp[sattr->nr][sattr->index] = temp2reg(data, val);
+
+		/* We maintain an extra 2 digits of precision for simplicity
+		 * - shift those back off before writing the value */
+		out = (u8) (data->temp[sattr->nr][sattr->index] >> 2);
+	}
+
+	switch (sattr->nr) {
+	case MIN:
+		reg = TEMP_MIN_REG(sattr->index);
+		break;
+	case MAX:
+		reg = TEMP_MAX_REG(sattr->index);
+		break;
+	case OFFSET:
+		reg = TEMP_OFFSET_REG(sattr->index);
+		break;
+	case AUTOMIN:
+		reg = TEMP_TMIN_REG(sattr->index);
+		break;
+	case THERM:
+		reg = TEMP_THERM_REG(sattr->index);
+		break;
+	case HYSTERSIS:
+		if (sattr->index != 2)
+			reg = REG_REMOTE1_HYSTERSIS;
+		else
+			reg = REG_REMOTE2_HYSTERSIS;
+
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg, out);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+/* Table of autorange values - the user will write the value in millidegrees,
+   and we'll convert it */
+static const int autorange_table[] = {
+	2000, 2500, 3330, 4000, 5000, 6670, 8000,
+	10000, 13330, 16000, 20000, 26670, 32000, 40000,
+	53330, 80000
+};
+
+static ssize_t show_point2(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out, val;
+
+	mutex_lock(&data->lock);
+	out = (data->range[sattr->index] >> 4) & 0x0F;
+	val = reg2temp(data, data->temp[AUTOMIN][sattr->index]);
+	mutex_unlock(&data->lock);
+
+	return sprintf(buf, "%d\n", val + autorange_table[out]);
+}
+
+static ssize_t set_point2(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int temp;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	/* Get a fresh copy of the needed registers */
+	data->config5 = adt7475_read(REG_CONFIG5);
+	data->temp[AUTOMIN][sattr->index] =
+		adt7475_read(TEMP_TMIN_REG(sattr->index)) << 2;
+	data->range[sattr->index] =
+		adt7475_read(TEMP_TRANGE_REG(sattr->index));
+
+	/* The user will write an absolute value, so subtract the start point
+	   to figure the range */
+	temp = reg2temp(data, data->temp[AUTOMIN][sattr->index]);
+	val = SENSORS_LIMIT(val, temp + autorange_table[0],
+		temp + autorange_table[ARRAY_SIZE(autorange_table) - 1]);
+	val -= temp;
+
+	/* Find the nearest table entry to what the user wrote */
+	val = find_nearest(val, autorange_table, ARRAY_SIZE(autorange_table));
+
+	data->range[sattr->index] &= ~0xF0;
+	data->range[sattr->index] |= val << 4;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_tach(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out;
+
+	if (sattr->nr == ALARM)
+		out = (data->alarms >> (sattr->index + 10)) & 1;
+	else
+		out = tach2rpm(data->tach[sattr->nr][sattr->index]);
+
+	return sprintf(buf, "%d\n", out);
+}
+
+static ssize_t set_tach(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	data->tach[MIN][sattr->index] = rpm2tach(val);
+
+	adt7475_write_word(client, TACH_MIN_REG(sattr->index),
+			   data->tach[MIN][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwm[sattr->nr][sattr->index]);
+}
+
+static ssize_t show_pwmchan(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwmchan[sattr->index]);
+}
+
+static ssize_t show_pwmctrl(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwmctl[sattr->index]);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg = 0;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	switch (sattr->nr) {
+	case INPUT:
+		/* Get a fresh value for CONTROL */
+		data->pwm[CONTROL][sattr->index] =
+			adt7475_read(PWM_CONFIG_REG(sattr->index));
+
+		/* If we are not in manual mode, then we shouldn't allow
+		 * the user to set the pwm speed */
+		if (((data->pwm[CONTROL][sattr->index] >> 5) & 7) != 7) {
+			mutex_unlock(&data->lock);
+			return count;
+		}
+
+		reg = PWM_REG(sattr->index);
+		break;
+
+	case MIN:
+		reg = PWM_MIN_REG(sattr->index);
+		break;
+
+	case MAX:
+		reg = PWM_MAX_REG(sattr->index);
+		break;
+	}
+
+	data->pwm[sattr->nr][sattr->index] = SENSORS_LIMIT(val, 0, 0xFF);
+	i2c_smbus_write_byte_data(client, reg,
+				  data->pwm[sattr->nr][sattr->index]);
+
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+/* Called by set_pwmctrl and set_pwmchan */
+
+static int hw_set_pwm(struct i2c_client *client, int index,
+		      unsigned int pwmctl, unsigned int pwmchan)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = 0;
+
+	switch (pwmctl) {
+	case 0:
+		val = 0x03;	/* Run at full speed */
+		break;
+	case 1:
+		val = 0x07;	/* Manual mode */
+		break;
+	case 2:
+		switch (pwmchan) {
+		case 1:
+			/* Remote1 controls PWM */
+			val = 0x00;
+			break;
+		case 2:
+			/* local controls PWM */
+			val = 0x01;
+			break;
+		case 4:
+			/* remote2 controls PWM */
+			val = 0x02;
+			break;
+		case 6:
+			/* local/remote2 control PWM */
+			val = 0x05;
+			break;
+		case 7:
+			/* All three control PWM */
+			val = 0x06;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	data->pwmctl[index] = pwmctl;
+	data->pwmchan[index] = pwmchan;
+
+	data->pwm[CONTROL][index] &= ~0xE0;
+	data->pwm[CONTROL][index] |= (val & 7) << 5;
+
+	i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+				  data->pwm[CONTROL][index]);
+
+	return 0;
+}
+
+static ssize_t set_pwmchan(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int r;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+	/* Read Modify Write PWM values */
+	adt7475_read_pwm(client, sattr->index);
+	r = hw_set_pwm(client, sattr->index, data->pwmctl[sattr->index], val);
+	if (r)
+		count = r;
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t set_pwmctrl(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int r;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+	/* Read Modify Write PWM values */
+	adt7475_read_pwm(client, sattr->index);
+	r = hw_set_pwm(client, sattr->index, val, data->pwmchan[sattr->index]);
+	if (r)
+		count = r;
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+/* List of frequencies for the PWM */
+static const int pwmfreq_table[] = {
+	11, 14, 22, 29, 35, 44, 58, 88
+};
+
+static ssize_t show_pwmfreq(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n",
+		       pwmfreq_table[data->range[sattr->index] & 3]);
+}
+
+static ssize_t set_pwmfreq(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int out;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	out = find_nearest(val, pwmfreq_table, ARRAY_SIZE(pwmfreq_table));
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] =
+		adt7475_read(TEMP_TRANGE_REG(sattr->index));
+	data->range[sattr->index] &= ~3;
+	data->range[sattr->index] |= out;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_voltage, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, show_voltage, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_voltage, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_voltage, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, show_temp, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_temp, NULL, FAULT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 0);
+static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_alarm, S_IRUGO, show_temp, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 1);
+static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_alarm, S_IRUGO, show_temp, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_temp, NULL, FAULT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 2);
+static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_tach, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_tach, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_tach, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_tach, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_tach, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_tach, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_tach, NULL, INPUT, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_alarm, S_IRUGO, show_tach, NULL, ALARM, 3);
+static SENSOR_DEVICE_ATTR_2(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    0);
+static SENSOR_DEVICE_ATTR_2(pwm1_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    1);
+static SENSOR_DEVICE_ATTR_2(pwm2_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    2);
+static SENSOR_DEVICE_ATTR_2(pwm3_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 2);
+
+static struct attribute *adt7475_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_max.dev_attr.attr,
+	&sensor_dev_attr_in2_min.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_fault.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_offset.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_offset.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_fault.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_offset.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_min.dev_attr.attr,
+	&sensor_dev_attr_fan4_alarm.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm2_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm3_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
+	NULL,
+};
+
+struct attribute_group adt7475_attr_group = { .attrs = adt7475_attrs };
+
+static int adt7475_detect(struct i2c_client *client, int kind,
+			  struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	if (kind <= 0) {
+		if (adt7475_read(REG_VENDID) != 0x41 ||
+		    adt7475_read(REG_DEVID) != 0x75) {
+			dev_err(&adapter->dev,
+				"Couldn't detect a adt7475 part at 0x%02x\n",
+				(unsigned int)client->addr);
+			return -ENODEV;
+		}
+	}
+
+	strlcpy(info->type, adt7475_id[0].name, I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int adt7475_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct adt7475_data *data;
+	int i, ret = 0;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+	i2c_set_clientdata(client, data);
+
+	/* Call adt7475_read_pwm for all pwm's as this will reprogram any
+	   pwm's which are disabled to manual mode with 0% duty cycle */
+	for (i = 0; i < ADT7475_PWM_COUNT; i++)
+		adt7475_read_pwm(client, i);
+
+	ret = sysfs_create_group(&client->dev.kobj, &adt7475_attr_group);
+	if (ret)
+		goto efree;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		ret = PTR_ERR(data->hwmon_dev);
+		goto eremove;
+	}
+
+	return 0;
+
+eremove:
+	sysfs_remove_group(&client->dev.kobj, &adt7475_attr_group);
+efree:
+	kfree(data);
+	return ret;
+}
+
+static int adt7475_remove(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &adt7475_attr_group);
+	kfree(data);
+
+	return 0;
+}
+
+static struct i2c_driver adt7475_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.driver = {
+		   .name = "adt7475",
+		   },
+	.probe		= adt7475_probe,
+	.remove		= adt7475_remove,
+	.id_table	= adt7475_id,
+	.detect		= adt7475_detect,
+	.address_data	= &addr_data,
+};
+
+static void adt7475_read_hystersis(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	data->temp[HYSTERSIS][0] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][1] = data->temp[HYSTERSIS][0];
+	data->temp[HYSTERSIS][2] = (u16) adt7475_read(REG_REMOTE2_HYSTERSIS);
+}
+
+static void adt7475_read_pwm(struct i2c_client *client, int index)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned int v;
+
+	data->pwm[CONTROL][index] = adt7475_read(PWM_CONFIG_REG(index));
+
+	/* Figure out the internal value for pwmctrl and pwmchan
+	   based on the current settings */
+	v = (data->pwm[CONTROL][index] >> 5) & 7;
+
+	if (v == 3)
+		data->pwmctl[index] = 0;
+	else if (v == 7)
+		data->pwmctl[index] = 1;
+	else if (v == 4) {
+		/* The fan is disabled - we don't want to
+		   support that, so change to manual mode and
+		   set the duty cycle to 0 instead
+		*/
+		data->pwm[INPUT][index] = 0;
+		data->pwm[CONTROL][index] &= ~0xE0;
+		data->pwm[CONTROL][index] |= (7 << 5);
+
+		i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+					  data->pwm[INPUT][index]);
+
+		i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+					  data->pwm[CONTROL][index]);
+
+		data->pwmctl[index] = 1;
+	} else {
+		data->pwmctl[index] = 2;
+
+		switch (v) {
+		case 0:
+			data->pwmchan[index] = 1;
+			break;
+		case 1:
+			data->pwmchan[index] = 2;
+			break;
+		case 2:
+			data->pwmchan[index] = 4;
+			break;
+		case 5:
+			data->pwmchan[index] = 6;
+			break;
+		case 6:
+			data->pwmchan[index] = 7;
+			break;
+		}
+	}
+}
+
+static struct adt7475_data *adt7475_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	u8 ext;
+	int i;
+
+	mutex_lock(&data->lock);
+
+	/* Measurement values update every 2 seconds */
+	if (time_after(jiffies, data->measure_updated + HZ * 2) ||
+	    !data->valid) {
+		data->alarms = adt7475_read(REG_STATUS2) << 8;
+		data->alarms |= adt7475_read(REG_STATUS1);
+
+		ext = adt7475_read(REG_EXTEND1);
+		for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++)
+			data->voltage[INPUT][i] =
+				(adt7475_read(VOLTAGE_REG(i)) << 2) |
+				((ext >> ((i + 1) * 2)) & 3);
+
+		ext = adt7475_read(REG_EXTEND2);
+		for (i = 0; i < ADT7475_TEMP_COUNT; i++)
+			data->temp[INPUT][i] =
+				(adt7475_read(TEMP_REG(i)) << 2) |
+				((ext >> ((i + 1) * 2)) & 3);
+
+		for (i = 0; i < ADT7475_TACH_COUNT; i++)
+			data->tach[INPUT][i] =
+				adt7475_read_word(client, TACH_REG(i));
+
+		/* Updated by hw when in auto mode */
+		for (i = 0; i < ADT7475_PWM_COUNT; i++)
+			data->pwm[INPUT][i] = adt7475_read(PWM_REG(i));
+
+		data->measure_updated = jiffies;
+	}
+
+	/* Limits and settings, should never change update every 60 seconds */
+	if (time_after(jiffies, data->limits_updated + HZ * 2) ||
+	    !data->valid) {
+		data->config5 = adt7475_read(REG_CONFIG5);
+
+		for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) {
+			/* Adjust values so they match the input precision */
+			data->voltage[MIN][i] =
+				adt7475_read(VOLTAGE_MIN_REG(i)) << 2;
+			data->voltage[MAX][i] =
+				adt7475_read(VOLTAGE_MAX_REG(i)) << 2;
+		}
+
+		for (i = 0; i < ADT7475_TEMP_COUNT; i++) {
+			/* Adjust values so they match the input precision */
+			data->temp[MIN][i] =
+				adt7475_read(TEMP_MIN_REG(i)) << 2;
+			data->temp[MAX][i] =
+				adt7475_read(TEMP_MAX_REG(i)) << 2;
+			data->temp[AUTOMIN][i] =
+				adt7475_read(TEMP_TMIN_REG(i)) << 2;
+			data->temp[THERM][i] =
+				adt7475_read(TEMP_THERM_REG(i)) << 2;
+			data->temp[OFFSET][i] =
+				adt7475_read(TEMP_OFFSET_REG(i));
+		}
+		adt7475_read_hystersis(client);
+
+		for (i = 0; i < ADT7475_TACH_COUNT; i++)
+			data->tach[MIN][i] =
+				adt7475_read_word(client, TACH_MIN_REG(i));
+
+		for (i = 0; i < ADT7475_PWM_COUNT; i++) {
+			data->pwm[MAX][i] = adt7475_read(PWM_MAX_REG(i));
+			data->pwm[MIN][i] = adt7475_read(PWM_MIN_REG(i));
+			/* Set the channel and control information */
+			adt7475_read_pwm(client, i);
+		}
+
+		data->range[0] = adt7475_read(TEMP_TRANGE_REG(0));
+		data->range[1] = adt7475_read(TEMP_TRANGE_REG(1));
+		data->range[2] = adt7475_read(TEMP_TRANGE_REG(2));
+
+		data->limits_updated = jiffies;
+		data->valid = 1;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return data;
+}
+
+static int __init sensors_adt7475_init(void)
+{
+	return i2c_add_driver(&adt7475_driver);
+}
+
+static void __exit sensors_adt7475_exit(void)
+{
+	i2c_del_driver(&adt7475_driver);
+}
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("adt7475 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_adt7475_init);
+module_exit(sensors_adt7475_exit);

[-- Attachment #3: 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] 15+ messages in thread

* [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (4 preceding siblings ...)
  2008-12-19 21:31 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
@ 2008-12-19 21:32 ` Hans de Goede
  2008-12-19 23:46 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-12-19 21:32 UTC (permalink / raw)
  To: lm-sensors

p.s.

This has been tested on my Asus M2N SLI deluxe motherboard


Hi Jean,

As Jordan did not get around to doing this and the 2.6.29 merge window is near
I've fixed the last few outstanding issues from my review(s) of his adt7475
I've done myself. Attached is a new version of the adt7475 with:
* all issues review found fixed
* converted to the new i2c driver style
* a few more registers needed reading in store methods
* corrected register to voltage conversion (more accurate)
* some style fixes
* locking around show functions where the value shown depends on more then
    1 reg.
* read limit registers only once every 60 seconds
* rename pwm_min and pwm_max to pwm_auto_pointX_pwm, as the
    control the scope between which the pwm gets changed in auto mode
* remove tempX_crit_alarm (only one crit alarm flag for all 3 channels)

I hope this is ok now to be queued for 2.6.29

Thanks & 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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (5 preceding siblings ...)
  2008-12-19 21:32 ` Hans de Goede
@ 2008-12-19 23:46 ` Matt Roberds
  2008-12-20  8:16 ` Hans de Goede
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Matt Roberds @ 2008-12-19 23:46 UTC (permalink / raw)
  To: lm-sensors

On Fri, 19 Dec 2008, Hans de Goede wrote:
> * converted to the new i2c driver style

I suspect this is what is causing the failure to compile on my old
kernel (2.6.18), and right now I don't have time to try it against a
newer kernel.

I did take a quick look through the patch and I do have a question about
find_nearest().  Sometimes it returns an array index (if the query is
less than the lowest data value in the array, or if the query is in the
middle of the data values in the array), and sometimes it returns a data
value (if the query is higher than the highest data value in the array).
Looking at what happens next after find_nearest() is called, I think it
should always return an array index.

In other words, in find_nearest(), this:

if (val > array[size - 1])
   return array[size - 1];

should be this:

if (val > array[size - 1])
   return size - 1;

Also, when find_nearest() is used against autorange_table, the low 4
bits of the return value are used.  Since autorange_table has 16
entries, this is probably OK.  But when find_nearest is used against
pwmfreq_table, only the low 2 bits of the return value are used.
pwmfreq_table has 8 entries; should the low 3 bits of the return value
be used instead?

In other words, in set_pwmfreq(), should this:

data->range[sattr->index] &= ~3;
data->range[sattr->index] |= out & 0x03;

be something like this:

data->range[sattr->index] &= ~0x07;
data->range[sattr->index] |= out & 0x07;

Maybe this is just how the hardware works, but I figured I would mention
it.

Matt Roberds


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

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

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (6 preceding siblings ...)
  2008-12-19 23:46 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
@ 2008-12-20  8:16 ` Hans de Goede
  2008-12-20  8:22 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-12-20  8:16 UTC (permalink / raw)
  To: lm-sensors



Matt Roberds wrote:
> On Fri, 19 Dec 2008, Hans de Goede wrote:
>> * converted to the new i2c driver style
> 
> I suspect this is what is causing the failure to compile on my old
> kernel (2.6.18), and right now I don't have time to try it against a
> newer kernel.
> 

Yes, you need atleast a 2.6.27 kernel for the new style.

> I did take a quick look through the patch and I do have a question about
> find_nearest().  Sometimes it returns an array index (if the query is
> less than the lowest data value in the array, or if the query is in the
> middle of the data values in the array), and sometimes it returns a data
> value (if the query is higher than the highest data value in the array).
> Looking at what happens next after find_nearest() is called, I think it
> should always return an array index.
> 
> In other words, in find_nearest(), this:
> 
> if (val > array[size - 1])
>   return array[size - 1];
> 
> should be this:
> 
> if (val > array[size - 1])
>   return size - 1;
> 

Correct, good catch!

> Also, when find_nearest() is used against autorange_table, the low 4
> bits of the return value are used.  Since autorange_table has 16
> entries, this is probably OK.  But when find_nearest is used against
> pwmfreq_table, only the low 2 bits of the return value are used.
> pwmfreq_table has 8 entries; should the low 3 bits of the return value
> be used instead?
> 
> In other words, in set_pwmfreq(), should this:
> 
> data->range[sattr->index] &= ~3;
> data->range[sattr->index] |= out & 0x03;
> 
> be something like this:
> 
> data->range[sattr->index] &= ~0x07;
> data->range[sattr->index] |= out & 0x07;
> 
> Maybe this is just how the hardware works, but I figured I would mention
> it.

Hmm, another good catch, lemme check the datasheet ... You are right there are 
3 frequency bits (the 3 least significant bits) in the range register so the 
mask should be 7 not 3. New version coming up!

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] 15+ messages in thread

* [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (7 preceding siblings ...)
  2008-12-20  8:16 ` Hans de Goede
@ 2008-12-20  8:22 ` Hans de Goede
  2008-12-22 23:23 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Jordan Crouse
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2008-12-20  8:22 UTC (permalink / raw)
  To: lm-sensors

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

Hi Jean,

Here is a new version with the comments / review from Matt Roberds taken in to 
account.

Thanks & Regards,

Hans


[-- Attachment #2: hwmon-add-adt7475-driver-v2.patch --]
[-- Type: text/plain, Size: 41723 bytes --]

[PATCH] hwmon: Add a driver for the ADT7475 thermal sensor

From: Jordan Crouse <jordan.crouse@amd.com>

HWMON driver for the ADT7475 thermal sensor.

Signed-off-by: Jordan Crouse <jordan.crouse@amd.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
diff -up /dev/null ./Documentation/hwmon/adt7475
--- /dev/null	2008-12-20 09:09:18.038005902 +0100
+++ ./Documentation/hwmon/adt7475	2008-12-19 22:09:32.000000000 +0100
@@ -0,0 +1,87 @@
+This describes the interface for the ADT7475 driver:
+
+(there are 4 fans, numbered fan1 to fan4):
+
+fanX_input              Read the current speed of the fan (in RPMs)
+fanX_min                Read/write the minimum speed of the fan.  Dropping
+	   		below this sets an alarm.
+
+(there are three PWMs, numbered pwm1 to pwm3):
+
+pwmX                    Read/write the current duty cycle of the PWM.  Writes
+     			only have effect when auto mode is turned off (see
+			below).  Range is 0 - 255.
+
+pwmX_enable             Fan speed control method:
+
+		        0 - No control (fan at full speed)
+			1 - Manual fan speed control (using pwm[1-*])
+			2 - Automatic fan speed control
+
+pwmX_auto_channels_temp	Select which channels affect this PWM
+
+			1 - TEMP1 controls PWM
+			2 - TEMP2 controls PWM
+			4 - TEMP3 controls PWM
+			6 - TEMP2 and TEMP3 control PWM
+			7 - All three inputs control PWM
+
+pwmX_freq               Read/write the PWM frequency in Hz. The number
+			should be one of the following:
+
+		        11 Hz
+		        14 Hz
+		        22 Hz
+		        29 Hz
+		        35 Hz
+		        44 Hz
+		        58 Hz
+		        88 Hz
+
+pwmX_auto_point1_pwm    Read/write the minimum PWM duty cycle in automatic mode
+
+pwmX_auto_point2_pwm    Read/write the maximum PWM duty cycle in automatic mode
+
+(there are three temperature settings numbered temp1 to temp3):
+
+tempX_input             Read the current temperature.  The value is in milli
+			degrees of Celsius.
+
+tempX_max               Read/write the upper temperature limit - exceeding this
+	                will cause an alarm.
+
+tempX_min               Read/write the lower temperature limit - exceeding this
+                        will cause an alarm.
+
+tempX_offset            Read/write the temperature adjustment offset
+
+tempX_crit              Read/write the THERM limit for remote1.
+
+tempX_crit_hyst 	Set the temperature value below crit where the
+                        fans will stay on - this helps drive the temperature
+                        low enough so it doesn't stay near the edge and
+                        cause THERM to keep tripping.
+
+tempX_auto_point1_temp 	Read/write the minimum temperature where the fans will
+                        turn on in automatic mode.
+
+tempX_auto_point2_temp  Read/write the maximum temperature over which the fans
+			will run in automatic mode.  tempX_auto_point1_temp
+			and tempX_auto_point2_temp together define the
+			range of automatic control.
+
+tempX_alarm		Read a 1 if the max/min alarm is set
+tempX_fault		Read a 1 if either temp1 or temp3 diode has a fault
+
+(There are two voltage settings, in1 and in2):
+
+inX_input               Read the current voltage on VCC.  Value is in
+			millivolts.
+
+inX_min                 read/write the minimum voltage limit.
+			Dropping below this causes an alarm.
+
+inX_max   		read/write the maximum voltage limit.
+			Exceeding this causes an alarm.
+
+inX_alarm		Read a 1 if the max/min alarm is set.
diff -up ./drivers/hwmon/Kconfig.adt7475 ./drivers/hwmon/Kconfig
--- ./drivers/hwmon/Kconfig.adt7475	2008-12-19 21:51:29.000000000 +0100
+++ ./drivers/hwmon/Kconfig	2008-12-19 22:05:34.000000000 +0100
@@ -189,6 +189,16 @@ config SENSORS_ADT7473
 	  This driver can also be built as a module. If so, the module
 	  will be called adt7473.
 
+config SENSORS_ADT7475
+	tristate "Analog Devices ADT7475"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  ADT7475 temperature monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called adt7475.
+
 config SENSORS_K8TEMP
 	tristate "AMD Athlon64/FX or Opteron temperature sensor"
 	depends on X86 && PCI && EXPERIMENTAL
diff -up ./drivers/hwmon/Makefile.adt7475 ./drivers/hwmon/Makefile
--- ./drivers/hwmon/Makefile.adt7475	2008-12-19 21:51:29.000000000 +0100
+++ ./drivers/hwmon/Makefile	2008-12-19 22:05:34.000000000 +0100
@@ -28,6 +28,8 @@ obj-$(CONFIG_SENSORS_ADS7828)	+= ads7828
 obj-$(CONFIG_SENSORS_ADT7462)	+= adt7462.o
 obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470.o
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
+obj-$(CONFIG_SENSORS_ADT7475)   += adt7475.o
+
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
diff -up /dev/null ./drivers/hwmon/adt7475.c
--- /dev/null	2008-12-20 09:09:18.038005902 +0100
+++ ./drivers/hwmon/adt7475.c	2008-12-20 09:20:23.000000000 +0100
@@ -0,0 +1,1221 @@
+/*
+ * adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives
+ * Copyright (C) 2007-2008, Advanced Micro Devices, Inc.
+ * Copyright (C) 2008 Jordan Crouse <jordan@cosmicpenguin.net>
+ * Copyright (C) 2008 Hans de Goede <hdegoede@redhat.com>
+
+ * Derived from the lm83 driver by Jean Delvare
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+
+/* Indexes for the sysfs hooks */
+
+#define INPUT     0
+#define MIN       1
+#define MAX       2
+#define CONTROL   3
+#define OFFSET    3
+#define AUTOMIN   4
+#define THERM     5
+#define HYSTERSIS 6
+
+/* These are unique identifiers for the sysfs functions - unlike the
+   numbers above, these are not also indexes into an array
+*/
+
+#define ALARM      9
+#define FAULT      10
+
+/* 7475 Common Registers */
+
+#define REG_VOLTAGE_BASE        0x21
+#define REG_TEMP_BASE           0x25
+#define REG_TACH_BASE		0x28
+#define REG_PWM_BASE            0x30
+#define REG_PWM_MAX_BASE        0x38
+
+#define REG_DEVID               0x3D
+#define REG_VENDID              0x3E
+
+#define REG_STATUS1             0x41
+#define REG_STATUS2             0x42
+
+#define REG_VOLTAGE_MIN_BASE    0x46
+#define REG_VOLTAGE_MAX_BASE    0x47
+
+#define REG_TEMP_MIN_BASE       0x4E
+#define REG_TEMP_MAX_BASE	0x4F
+
+#define REG_TACH_MIN_BASE	0x54
+
+#define REG_PWM_CONFIG_BASE     0x5C
+
+#define REG_TEMP_TRANGE_BASE    0x5F
+
+#define REG_PWM_MIN_BASE        0x64
+
+#define REG_TEMP_TMIN_BASE      0x67
+#define REG_TEMP_THERM_BASE     0x6A
+
+#define REG_REMOTE1_HYSTERSIS   0x6D
+#define REG_REMOTE2_HYSTERSIS   0x6E
+
+#define REG_TEMP_OFFSET_BASE    0x70
+
+#define REG_EXTEND1             0x76
+#define REG_EXTEND2             0x77
+#define REG_CONFIG5             0x7C
+
+#define CONFIG5_TWOSCOMP        0x01
+#define CONFIG5_TEMPOFFSET      0x02
+
+/* ADT7475 Settings */
+
+#define ADT7475_VOLTAGE_COUNT   2
+#define ADT7475_TEMP_COUNT      3
+#define ADT7475_TACH_COUNT	4
+#define ADT7475_PWM_COUNT       3
+
+/* Macro to read the registers */
+
+#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg))
+
+/* Macros to easily index the registers */
+
+#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2))
+#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2))
+
+#define PWM_REG(idx) (REG_PWM_BASE + (idx))
+#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx))
+#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx))
+#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx))
+
+#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx))
+#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2))
+#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2))
+
+#define TEMP_REG(idx) (REG_TEMP_BASE + (idx))
+#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2))
+#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2))
+#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx))
+#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx))
+#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx))
+#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx))
+
+static unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(adt7475);
+
+static const struct i2c_device_id adt7475_id[] = {
+	{ "adt7475", adt7475 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adt7475_id);
+
+struct adt7475_data {
+	struct device *hwmon_dev;
+	struct mutex lock;
+
+	unsigned long measure_updated;
+	unsigned long limits_updated;
+	char valid;
+
+	u8 config5;
+	u16 alarms;
+	u16 voltage[3][3];
+	u16 temp[7][3];
+	u16 tach[2][4];
+	u8 pwm[4][3];
+	u8 range[3];
+	u8 pwmctl[3];
+	u8 pwmchan[3];
+};
+
+static struct i2c_driver adt7475_driver;
+static struct adt7475_data *adt7475_update_device(struct device *dev);
+static void adt7475_read_hystersis(struct i2c_client *client);
+static void adt7475_read_pwm(struct i2c_client *client, int index);
+
+/* Given a temp value, convert it to register value */
+
+static inline u16 temp2reg(struct adt7475_data *data, long val)
+{
+	u16 ret;
+
+	if (!(data->config5 & CONFIG5_TWOSCOMP)) {
+		val = SENSORS_LIMIT(val, -64000, 191000);
+		ret = (val + 64500) / 1000;
+	} else {
+		val = SENSORS_LIMIT(val, -128000, 127000);
+		if (val < -500)
+			ret = (256500 + val) / 1000;
+		else
+			ret = (val + 500) / 1000;
+	}
+
+	return ret << 2;
+}
+
+/* Given a register value, convert it to a real temp value */
+
+static inline int reg2temp(struct adt7475_data *data, u16 reg)
+{
+	if (data->config5 & CONFIG5_TWOSCOMP) {
+		if (reg >= 512)
+			return (reg - 1024) * 250;
+		else
+			return reg * 250;
+	} else
+		return (reg - 256) * 250;
+}
+
+static inline int tach2rpm(u16 tach)
+{
+	if (tach == 0 || tach == 0xFFFF)
+		return 0;
+
+	return (90000 * 60) / tach;
+}
+
+static inline u16 rpm2tach(unsigned long rpm)
+{
+	if (rpm == 0)
+		return 0;
+
+	return SENSORS_LIMIT((90000 * 60) / rpm, 1, 0xFFFF);
+}
+
+static inline int reg2vcc(u16 reg)
+{
+	return (4296 * reg) / 1000;
+}
+
+static inline int reg2vccp(u16 reg)
+{
+	return (2929 * reg) / 1000;
+}
+
+static inline u16 vcc2reg(long vcc)
+{
+	vcc = SENSORS_LIMIT(vcc, 0, 4396);
+	return (vcc * 1000) / 4296;
+}
+
+static inline u16 vccp2reg(long vcc)
+{
+	vcc = SENSORS_LIMIT(vcc, 0, 2998);
+	return (vcc * 1000) / 2929;
+}
+
+static u16 adt7475_read_word(struct i2c_client *client, int reg)
+{
+	u16 val;
+
+	val = i2c_smbus_read_byte_data(client, reg);
+	val |= (i2c_smbus_read_byte_data(client, reg + 1) << 8);
+
+	return val;
+}
+
+static void adt7475_write_word(struct i2c_client *client, int reg, u16 val)
+{
+	i2c_smbus_write_byte_data(client, reg + 1, val >> 8);
+	i2c_smbus_write_byte_data(client, reg, val & 0xFF);
+}
+
+/* Find the nearest value in a table - used for pwm frequency and
+   auto temp range */
+static int find_nearest(long val, const int *array, int size)
+{
+	int i;
+
+	if (val < array[0])
+		return 0;
+
+	if (val > array[size - 1])
+		return size - 1;
+
+	for (i = 0; i < size - 1; i++) {
+		int a, b;
+
+		if (val > array[i + 1])
+			continue;
+
+		a = val - array[i];
+		b = array[i + 1] - val;
+
+		return (a <= b) ? i : i + 1;
+	}
+
+	return 0;
+}
+
+static ssize_t show_voltage(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	unsigned short val;
+
+	switch (sattr->nr) {
+	case ALARM:
+		return sprintf(buf, "%d\n",
+			       (data->alarms >> (sattr->index + 1)) & 1);
+	default:
+		val = data->voltage[sattr->nr][sattr->index];
+		return sprintf(buf, "%d\n",
+			       sattr->index ==
+			       0 ? reg2vccp(val) : reg2vcc(val));
+	}
+}
+
+static ssize_t set_voltage(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	data->voltage[sattr->nr][sattr->index] =
+		sattr->index ? vcc2reg(val) : vccp2reg(val);
+
+	if (sattr->nr == MIN)
+		reg = VOLTAGE_MIN_REG(sattr->index);
+	else
+		reg = VOLTAGE_MAX_REG(sattr->index);
+
+	i2c_smbus_write_byte_data(client, reg,
+				  data->voltage[sattr->nr][sattr->index] >> 2);
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out;
+
+	switch (sattr->nr) {
+	case HYSTERSIS:
+		mutex_lock(&data->lock);
+		out = data->temp[sattr->nr][sattr->index];
+		if (sattr->index != 1)
+			out = (out >> 4) & 0xF;
+		else
+			out = (out & 0xF);
+		/* Show the value as an absolute number tied to
+		 * THERM */
+		out = reg2temp(data, data->temp[THERM][sattr->index]) -
+			out * 1000;
+		mutex_unlock(&data->lock);
+		break;
+
+	case OFFSET:
+		/* Offset is always 2's complement, regardless of the
+		 * setting in CONFIG5 */
+		mutex_lock(&data->lock);
+		out = (s8)data->temp[sattr->nr][sattr->index];
+		if (data->config5 & CONFIG5_TEMPOFFSET)
+			out *= 1000;
+		else
+			out *= 500;
+		mutex_unlock(&data->lock);
+		break;
+
+	case ALARM:
+		out = (data->alarms >> (sattr->index + 4)) & 1;
+		break;
+
+	case FAULT:
+		/* Note - only for remote1 and remote2 */
+		out = data->alarms & (sattr->index ? 0x8000 : 0x4000);
+		out = out ? 0 : 1;
+		break;
+
+	default:
+		/* All other temp values are in the configured format */
+		out = reg2temp(data, data->temp[sattr->nr][sattr->index]);
+	}
+
+	return sprintf(buf, "%d\n", out);
+}
+
+static ssize_t set_temp(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg = 0;
+	u8 out;
+	int temp;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	/* We need the config register in all cases for temp <-> reg conv. */
+	data->config5 = adt7475_read(REG_CONFIG5);
+
+	switch (sattr->nr) {
+	case OFFSET:
+		if (data->config5 & CONFIG5_TEMPOFFSET) {
+			val = SENSORS_LIMIT(val, -63000, 127000);
+			out = data->temp[OFFSET][sattr->index] = val / 1000;
+		} else {
+			val = SENSORS_LIMIT(val, -63000, 64000);
+			out = data->temp[OFFSET][sattr->index] = val / 500;
+		}
+		break;
+
+	case HYSTERSIS:
+		/* The value will be given as an absolute value, turn it
+		   into an offset based on THERM */
+
+		/* Read fresh THERM and HYSTERSIS values from the chip */
+		data->temp[THERM][sattr->index] =
+			adt7475_read(TEMP_THERM_REG(sattr->index)) << 2;
+		adt7475_read_hystersis(client);
+
+		temp = reg2temp(data, data->temp[THERM][sattr->index]);
+		val = SENSORS_LIMIT(val, temp - 15000, temp);
+		val = (temp - val) / 1000;
+
+		if (sattr->index != 1) {
+			data->temp[HYSTERSIS][sattr->index] &= 0xF0;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF) << 4;
+		} else {
+			data->temp[HYSTERSIS][sattr->index] &= 0x0F;
+			data->temp[HYSTERSIS][sattr->index] |= (val & 0xF);
+		}
+
+		out = data->temp[HYSTERSIS][sattr->index];
+		break;
+
+	default:
+		data->temp[sattr->nr][sattr->index] = temp2reg(data, val);
+
+		/* We maintain an extra 2 digits of precision for simplicity
+		 * - shift those back off before writing the value */
+		out = (u8) (data->temp[sattr->nr][sattr->index] >> 2);
+	}
+
+	switch (sattr->nr) {
+	case MIN:
+		reg = TEMP_MIN_REG(sattr->index);
+		break;
+	case MAX:
+		reg = TEMP_MAX_REG(sattr->index);
+		break;
+	case OFFSET:
+		reg = TEMP_OFFSET_REG(sattr->index);
+		break;
+	case AUTOMIN:
+		reg = TEMP_TMIN_REG(sattr->index);
+		break;
+	case THERM:
+		reg = TEMP_THERM_REG(sattr->index);
+		break;
+	case HYSTERSIS:
+		if (sattr->index != 2)
+			reg = REG_REMOTE1_HYSTERSIS;
+		else
+			reg = REG_REMOTE2_HYSTERSIS;
+
+		break;
+	}
+
+	i2c_smbus_write_byte_data(client, reg, out);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+/* Table of autorange values - the user will write the value in millidegrees,
+   and we'll convert it */
+static const int autorange_table[] = {
+	2000, 2500, 3330, 4000, 5000, 6670, 8000,
+	10000, 13330, 16000, 20000, 26670, 32000, 40000,
+	53330, 80000
+};
+
+static ssize_t show_point2(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out, val;
+
+	mutex_lock(&data->lock);
+	out = (data->range[sattr->index] >> 4) & 0x0F;
+	val = reg2temp(data, data->temp[AUTOMIN][sattr->index]);
+	mutex_unlock(&data->lock);
+
+	return sprintf(buf, "%d\n", val + autorange_table[out]);
+}
+
+static ssize_t set_point2(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int temp;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	/* Get a fresh copy of the needed registers */
+	data->config5 = adt7475_read(REG_CONFIG5);
+	data->temp[AUTOMIN][sattr->index] =
+		adt7475_read(TEMP_TMIN_REG(sattr->index)) << 2;
+	data->range[sattr->index] =
+		adt7475_read(TEMP_TRANGE_REG(sattr->index));
+
+	/* The user will write an absolute value, so subtract the start point
+	   to figure the range */
+	temp = reg2temp(data, data->temp[AUTOMIN][sattr->index]);
+	val = SENSORS_LIMIT(val, temp + autorange_table[0],
+		temp + autorange_table[ARRAY_SIZE(autorange_table) - 1]);
+	val -= temp;
+
+	/* Find the nearest table entry to what the user wrote */
+	val = find_nearest(val, autorange_table, ARRAY_SIZE(autorange_table));
+
+	data->range[sattr->index] &= ~0xF0;
+	data->range[sattr->index] |= val << 4;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_tach(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int out;
+
+	if (sattr->nr == ALARM)
+		out = (data->alarms >> (sattr->index + 10)) & 1;
+	else
+		out = tach2rpm(data->tach[sattr->nr][sattr->index]);
+
+	return sprintf(buf, "%d\n", out);
+}
+
+static ssize_t set_tach(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	data->tach[MIN][sattr->index] = rpm2tach(val);
+
+	adt7475_write_word(client, TACH_MIN_REG(sattr->index),
+			   data->tach[MIN][sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwm[sattr->nr][sattr->index]);
+}
+
+static ssize_t show_pwmchan(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwmchan[sattr->index]);
+}
+
+static ssize_t show_pwmctrl(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n", data->pwmctl[sattr->index]);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned char reg = 0;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	switch (sattr->nr) {
+	case INPUT:
+		/* Get a fresh value for CONTROL */
+		data->pwm[CONTROL][sattr->index] =
+			adt7475_read(PWM_CONFIG_REG(sattr->index));
+
+		/* If we are not in manual mode, then we shouldn't allow
+		 * the user to set the pwm speed */
+		if (((data->pwm[CONTROL][sattr->index] >> 5) & 7) != 7) {
+			mutex_unlock(&data->lock);
+			return count;
+		}
+
+		reg = PWM_REG(sattr->index);
+		break;
+
+	case MIN:
+		reg = PWM_MIN_REG(sattr->index);
+		break;
+
+	case MAX:
+		reg = PWM_MAX_REG(sattr->index);
+		break;
+	}
+
+	data->pwm[sattr->nr][sattr->index] = SENSORS_LIMIT(val, 0, 0xFF);
+	i2c_smbus_write_byte_data(client, reg,
+				  data->pwm[sattr->nr][sattr->index]);
+
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+/* Called by set_pwmctrl and set_pwmchan */
+
+static int hw_set_pwm(struct i2c_client *client, int index,
+		      unsigned int pwmctl, unsigned int pwmchan)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	long val = 0;
+
+	switch (pwmctl) {
+	case 0:
+		val = 0x03;	/* Run at full speed */
+		break;
+	case 1:
+		val = 0x07;	/* Manual mode */
+		break;
+	case 2:
+		switch (pwmchan) {
+		case 1:
+			/* Remote1 controls PWM */
+			val = 0x00;
+			break;
+		case 2:
+			/* local controls PWM */
+			val = 0x01;
+			break;
+		case 4:
+			/* remote2 controls PWM */
+			val = 0x02;
+			break;
+		case 6:
+			/* local/remote2 control PWM */
+			val = 0x05;
+			break;
+		case 7:
+			/* All three control PWM */
+			val = 0x06;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	data->pwmctl[index] = pwmctl;
+	data->pwmchan[index] = pwmchan;
+
+	data->pwm[CONTROL][index] &= ~0xE0;
+	data->pwm[CONTROL][index] |= (val & 7) << 5;
+
+	i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+				  data->pwm[CONTROL][index]);
+
+	return 0;
+}
+
+static ssize_t set_pwmchan(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int r;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+	/* Read Modify Write PWM values */
+	adt7475_read_pwm(client, sattr->index);
+	r = hw_set_pwm(client, sattr->index, data->pwmctl[sattr->index], val);
+	if (r)
+		count = r;
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+static ssize_t set_pwmctrl(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int r;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+	/* Read Modify Write PWM values */
+	adt7475_read_pwm(client, sattr->index);
+	r = hw_set_pwm(client, sattr->index, val, data->pwmchan[sattr->index]);
+	if (r)
+		count = r;
+	mutex_unlock(&data->lock);
+
+	return count;
+}
+
+/* List of frequencies for the PWM */
+static const int pwmfreq_table[] = {
+	11, 14, 22, 29, 35, 44, 58, 88
+};
+
+static ssize_t show_pwmfreq(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct adt7475_data *data = adt7475_update_device(dev);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	return sprintf(buf, "%d\n",
+		       pwmfreq_table[data->range[sattr->index] & 7]);
+}
+
+static ssize_t set_pwmfreq(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	int out;
+	long val;
+
+	if (strict_strtol(buf, 10, &val))
+		return -EINVAL;
+
+	out = find_nearest(val, pwmfreq_table, ARRAY_SIZE(pwmfreq_table));
+
+	mutex_lock(&data->lock);
+
+	data->range[sattr->index] =
+		adt7475_read(TEMP_TRANGE_REG(sattr->index));
+	data->range[sattr->index] &= ~7;
+	data->range[sattr->index] |= out;
+
+	i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index),
+				  data->range[sattr->index]);
+
+	mutex_unlock(&data->lock);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_voltage, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, show_voltage, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_voltage, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_voltage,
+			    set_voltage, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_voltage, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, show_temp, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_temp, NULL, FAULT, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 0);
+static SENSOR_DEVICE_ATTR_2(temp1_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 0);
+static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_alarm, S_IRUGO, show_temp, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 1);
+static SENSOR_DEVICE_ATTR_2(temp2_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 1);
+static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_alarm, S_IRUGO, show_temp, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_temp, NULL, FAULT, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MAX, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    MIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, OFFSET, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_point1_temp, S_IRUGO | S_IWUSR,
+			    show_temp, set_temp, AUTOMIN, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_auto_point2_temp, S_IRUGO | S_IWUSR,
+			    show_point2, set_point2, 0, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp,
+			    THERM, 2);
+static SENSOR_DEVICE_ATTR_2(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_temp,
+			    set_temp, HYSTERSIS, 2);
+static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_tach, NULL, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 0);
+static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_tach, NULL, ALARM, 0);
+static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_tach, NULL, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_tach, NULL, ALARM, 1);
+static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_tach, NULL, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_tach, NULL, ALARM, 2);
+static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_tach, NULL, INPUT, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_tach, set_tach,
+			    MIN, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_alarm, S_IRUGO, show_tach, NULL, ALARM, 3);
+static SENSOR_DEVICE_ATTR_2(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    0);
+static SENSOR_DEVICE_ATTR_2(pwm1_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 0);
+static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 0);
+static SENSOR_DEVICE_ATTR_2(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    1);
+static SENSOR_DEVICE_ATTR_2(pwm2_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 1);
+static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 1);
+static SENSOR_DEVICE_ATTR_2(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT,
+			    2);
+static SENSOR_DEVICE_ATTR_2(pwm3_freq, S_IRUGO | S_IWUSR, show_pwmfreq,
+			    set_pwmfreq, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_enable, S_IRUGO | S_IWUSR, show_pwmctrl,
+			    set_pwmctrl, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_channel_temp, S_IRUGO | S_IWUSR,
+			    show_pwmchan, set_pwmchan, INPUT, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MIN, 2);
+static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm,
+			    set_pwm, MAX, 2);
+
+static struct attribute *adt7475_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in2_max.dev_attr.attr,
+	&sensor_dev_attr_in2_min.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_fault.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_offset.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_offset.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_fault.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_offset.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_min.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_min.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_min.dev_attr.attr,
+	&sensor_dev_attr_fan4_alarm.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2.dev_attr.attr,
+	&sensor_dev_attr_pwm2_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3.dev_attr.attr,
+	&sensor_dev_attr_pwm3_freq.dev_attr.attr,
+	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_channel_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
+	NULL,
+};
+
+struct attribute_group adt7475_attr_group = { .attrs = adt7475_attrs };
+
+static int adt7475_detect(struct i2c_client *client, int kind,
+			  struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	if (kind <= 0) {
+		if (adt7475_read(REG_VENDID) != 0x41 ||
+		    adt7475_read(REG_DEVID) != 0x75) {
+			dev_err(&adapter->dev,
+				"Couldn't detect a adt7475 part at 0x%02x\n",
+				(unsigned int)client->addr);
+			return -ENODEV;
+		}
+	}
+
+	strlcpy(info->type, adt7475_id[0].name, I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int adt7475_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct adt7475_data *data;
+	int i, ret = 0;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+	i2c_set_clientdata(client, data);
+
+	/* Call adt7475_read_pwm for all pwm's as this will reprogram any
+	   pwm's which are disabled to manual mode with 0% duty cycle */
+	for (i = 0; i < ADT7475_PWM_COUNT; i++)
+		adt7475_read_pwm(client, i);
+
+	ret = sysfs_create_group(&client->dev.kobj, &adt7475_attr_group);
+	if (ret)
+		goto efree;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		ret = PTR_ERR(data->hwmon_dev);
+		goto eremove;
+	}
+
+	return 0;
+
+eremove:
+	sysfs_remove_group(&client->dev.kobj, &adt7475_attr_group);
+efree:
+	kfree(data);
+	return ret;
+}
+
+static int adt7475_remove(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &adt7475_attr_group);
+	kfree(data);
+
+	return 0;
+}
+
+static struct i2c_driver adt7475_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.driver = {
+		   .name = "adt7475",
+		   },
+	.probe		= adt7475_probe,
+	.remove		= adt7475_remove,
+	.id_table	= adt7475_id,
+	.detect		= adt7475_detect,
+	.address_data	= &addr_data,
+};
+
+static void adt7475_read_hystersis(struct i2c_client *client)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+
+	data->temp[HYSTERSIS][0] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS);
+	data->temp[HYSTERSIS][1] = data->temp[HYSTERSIS][0];
+	data->temp[HYSTERSIS][2] = (u16) adt7475_read(REG_REMOTE2_HYSTERSIS);
+}
+
+static void adt7475_read_pwm(struct i2c_client *client, int index)
+{
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	unsigned int v;
+
+	data->pwm[CONTROL][index] = adt7475_read(PWM_CONFIG_REG(index));
+
+	/* Figure out the internal value for pwmctrl and pwmchan
+	   based on the current settings */
+	v = (data->pwm[CONTROL][index] >> 5) & 7;
+
+	if (v == 3)
+		data->pwmctl[index] = 0;
+	else if (v == 7)
+		data->pwmctl[index] = 1;
+	else if (v == 4) {
+		/* The fan is disabled - we don't want to
+		   support that, so change to manual mode and
+		   set the duty cycle to 0 instead
+		*/
+		data->pwm[INPUT][index] = 0;
+		data->pwm[CONTROL][index] &= ~0xE0;
+		data->pwm[CONTROL][index] |= (7 << 5);
+
+		i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+					  data->pwm[INPUT][index]);
+
+		i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+					  data->pwm[CONTROL][index]);
+
+		data->pwmctl[index] = 1;
+	} else {
+		data->pwmctl[index] = 2;
+
+		switch (v) {
+		case 0:
+			data->pwmchan[index] = 1;
+			break;
+		case 1:
+			data->pwmchan[index] = 2;
+			break;
+		case 2:
+			data->pwmchan[index] = 4;
+			break;
+		case 5:
+			data->pwmchan[index] = 6;
+			break;
+		case 6:
+			data->pwmchan[index] = 7;
+			break;
+		}
+	}
+}
+
+static struct adt7475_data *adt7475_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct adt7475_data *data = i2c_get_clientdata(client);
+	u8 ext;
+	int i;
+
+	mutex_lock(&data->lock);
+
+	/* Measurement values update every 2 seconds */
+	if (time_after(jiffies, data->measure_updated + HZ * 2) ||
+	    !data->valid) {
+		data->alarms = adt7475_read(REG_STATUS2) << 8;
+		data->alarms |= adt7475_read(REG_STATUS1);
+
+		ext = adt7475_read(REG_EXTEND1);
+		for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++)
+			data->voltage[INPUT][i] =
+				(adt7475_read(VOLTAGE_REG(i)) << 2) |
+				((ext >> ((i + 1) * 2)) & 3);
+
+		ext = adt7475_read(REG_EXTEND2);
+		for (i = 0; i < ADT7475_TEMP_COUNT; i++)
+			data->temp[INPUT][i] =
+				(adt7475_read(TEMP_REG(i)) << 2) |
+				((ext >> ((i + 1) * 2)) & 3);
+
+		for (i = 0; i < ADT7475_TACH_COUNT; i++)
+			data->tach[INPUT][i] =
+				adt7475_read_word(client, TACH_REG(i));
+
+		/* Updated by hw when in auto mode */
+		for (i = 0; i < ADT7475_PWM_COUNT; i++)
+			data->pwm[INPUT][i] = adt7475_read(PWM_REG(i));
+
+		data->measure_updated = jiffies;
+	}
+
+	/* Limits and settings, should never change update every 60 seconds */
+	if (time_after(jiffies, data->limits_updated + HZ * 2) ||
+	    !data->valid) {
+		data->config5 = adt7475_read(REG_CONFIG5);
+
+		for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) {
+			/* Adjust values so they match the input precision */
+			data->voltage[MIN][i] =
+				adt7475_read(VOLTAGE_MIN_REG(i)) << 2;
+			data->voltage[MAX][i] =
+				adt7475_read(VOLTAGE_MAX_REG(i)) << 2;
+		}
+
+		for (i = 0; i < ADT7475_TEMP_COUNT; i++) {
+			/* Adjust values so they match the input precision */
+			data->temp[MIN][i] =
+				adt7475_read(TEMP_MIN_REG(i)) << 2;
+			data->temp[MAX][i] =
+				adt7475_read(TEMP_MAX_REG(i)) << 2;
+			data->temp[AUTOMIN][i] =
+				adt7475_read(TEMP_TMIN_REG(i)) << 2;
+			data->temp[THERM][i] =
+				adt7475_read(TEMP_THERM_REG(i)) << 2;
+			data->temp[OFFSET][i] =
+				adt7475_read(TEMP_OFFSET_REG(i));
+		}
+		adt7475_read_hystersis(client);
+
+		for (i = 0; i < ADT7475_TACH_COUNT; i++)
+			data->tach[MIN][i] =
+				adt7475_read_word(client, TACH_MIN_REG(i));
+
+		for (i = 0; i < ADT7475_PWM_COUNT; i++) {
+			data->pwm[MAX][i] = adt7475_read(PWM_MAX_REG(i));
+			data->pwm[MIN][i] = adt7475_read(PWM_MIN_REG(i));
+			/* Set the channel and control information */
+			adt7475_read_pwm(client, i);
+		}
+
+		data->range[0] = adt7475_read(TEMP_TRANGE_REG(0));
+		data->range[1] = adt7475_read(TEMP_TRANGE_REG(1));
+		data->range[2] = adt7475_read(TEMP_TRANGE_REG(2));
+
+		data->limits_updated = jiffies;
+		data->valid = 1;
+	}
+
+	mutex_unlock(&data->lock);
+
+	return data;
+}
+
+static int __init sensors_adt7475_init(void)
+{
+	return i2c_add_driver(&adt7475_driver);
+}
+
+static void __exit sensors_adt7475_exit(void)
+{
+	i2c_del_driver(&adt7475_driver);
+}
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("adt7475 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_adt7475_init);
+module_exit(sensors_adt7475_exit);

[-- Attachment #3: 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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (8 preceding siblings ...)
  2008-12-20  8:22 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
@ 2008-12-22 23:23 ` Jordan Crouse
  2008-12-23 18:38 ` Matt Roberds
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Jordan Crouse @ 2008-12-22 23:23 UTC (permalink / raw)
  To: lm-sensors

Hans de Goede wrote:
> Hi Jean,
> 
> As Jordan did not get around to doing this and the 2.6.29 merge window 
> is near I've fixed the last few outstanding issues from my review(s) of 
> his adt7475 I've done myself. Attached is a new version of the adt7475 

Thanks - I let this slide due to other issues, and I was feeling bad 
about letting this drop through another kernel cycle - I appreciate your 
help.

Jordan

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

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

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (9 preceding siblings ...)
  2008-12-22 23:23 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Jordan Crouse
@ 2008-12-23 18:38 ` Matt Roberds
  2009-01-04 17:57 ` Daimonos Tereutes
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 15+ messages in thread
From: Matt Roberds @ 2008-12-23 18:38 UTC (permalink / raw)
  To: lm-sensors

On Tue, 23 Dec 2008, Daimonos Tereutes wrote:
> I'm using the patch for ADT7475 since the begining on an ASUS M2N32 WS
> Professional without problem.

It looks like you might be using a modified version of this
sensors3.conf :
http://www.lm-sensors.org/wiki/Configurations/Asus/M2N-SLI%20Deluxe

On your board, it seems like it8716 in2 (+3.3 V) works, and it8716 temp3
is chassis temp.  Also, the assignment of fans (CPU/chassis/power
supply) to chip inputs is a little different.  If you have verified that
the labels are correct, can you post your sensors3.conf here on the
list?  This will help it get to the wiki as a reference for others.

Thanks!

Matt Roberds


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

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

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (10 preceding siblings ...)
  2008-12-23 18:38 ` Matt Roberds
@ 2009-01-04 17:57 ` Daimonos Tereutes
  2009-01-07 21:52 ` Jean Delvare
  2009-01-08  7:56 ` Hans de Goede
  13 siblings, 0 replies; 15+ messages in thread
From: Daimonos Tereutes @ 2009-01-04 17:57 UTC (permalink / raw)
  To: lm-sensors

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

Matt Roberds a écrit :
> On Tue, 23 Dec 2008, Daimonos Tereutes wrote:
>> I'm using the patch for ADT7475 since the begining on an ASUS M2N32 WS
>> Professional without problem.
> 
> It looks like you might be using a modified version of this
> sensors3.conf :
> http://www.lm-sensors.org/wiki/Configurations/Asus/M2N-SLI%20Deluxe
> 
> On your board, it seems like it8716 in2 (+3.3 V) works, and it8716 temp3
> is chassis temp.  Also, the assignment of fans (CPU/chassis/power
> supply) to chip inputs is a little different.  If you have verified that
> the labels are correct, can you post your sensors3.conf here on the
> list?  This will help it get to the wiki as a reference for others.
> 
> Thanks!
> 
> Matt Roberds
> 
Hello,
Sorry for late reply, I was away from computer.

My starting point was indeed
http://www.lm-sensors.org/wiki/Configurations/Asus/M2N-SLI%20Deluxe

I added some names based on dissasembly of ACPI following instructions on this
page : http://www.lm-sensors.org/wiki/AsusFormulaHacking

And some other based on different messages on the lm_sensors list.

Hopes it will help someone.

François

[-- Attachment #2: sensors3.conf --]
[-- Type: text/plain, Size: 3343 bytes --]

chip "it8716-*"

# Voltages

    label  in0  "VCore"
    ignore in1  #"VDDR"    # Allways 0V
    label  in2  "+3.3V"    # 3.3 V is monitored by adt7475 too
    label  in3  "+5V"      # VCC
    label  in4  "+12V"
    label  in5  "VDDR"     # VDDR? (~1.83V)
    ignore in6             # Allways 0V
    label  in7  "5VSB"     # Not in BIOS
    label  in8  "VBat"     # Not in BIOS

    # Vcore, VDDR, and Vbat are connected directly, so no compute
    # line is needed for these. For +5V, +12V and 5VSB, the default
    # resistors seem to have been used.
# Those formulae are from http://www.lm-sensors.org/wiki/Configurations/Asus/M2N-SLI%20Deluxe
#    compute in3  ((6.8/10)+1)*@ , @/((6.8/10)+1)
#    compute in4  ((30/10)+1)*@  , @/((30/10)+1)
#    compute in5  (84*@)/50, (@/84)*50
# Those formulae are from ACPI disassembly. The results are similare
    compute in3  (84*@)/50, (@/84)*50
    compute in4  (40*@)/10, (@/40)*10
    compute in5  @/2, @*2
    compute in7  ((6.8/10)+1)*@, @/((6.8/10)+1)

   # The BIOS won't set any limit for voltages.

   # CPU is an Athlon X2 BE-2350, with Cool'n'Quiet activated so the rather wide range of Voltage
   set in0_min 1.05 * 0.95
   set in0_max 1.25 * 1.05

   set in2_min 3.3 * 0.95
   set in2_max 3.3 * 1.05
   set in3_min   5 * 0.95
   set in3_max   5 * 1.05
   set in4_min  12 * 0.95
   set in4_max  12 * 1.05
   set in7_min   5 * 0.95
   set in7_max   5 * 1.05

# Temperatures

# Not sure.  temp1 always reads close to what k8temp reports for Core0,
# and to what the BIOS reports as the CPU temperature.  temp2 is between
# temp1 and temp3; it is close to what the BIOS reports as the motherboard
# temperature.  temp3 always reads approximate ambient temperature.

    label temp1 "CPU Temp"
    label temp2 "MB Temp"
    label temp3 "Chassis Temp"


# Fans

    label  fan1 "CPU Fan"
    label  fan2 "Chassis Fan 1"
    label  fan3 "Chassis Fan 2"

# QFAN is on in BIOS, fan1 is a 4 pin fan, but fan2 and fan3 are 3 pin fans
# and only show when RPM is above 1000.
    set fan1_min 1200
    set fan2_min 300
    set fan3_min 300

chip "adt7475-*"

# Note: this section depends on some further hacks I have made to
# Jordan Crouse's driver

# Voltages

    ignore in1
    label  in2  "+3.3V"

# Temperatures

# temp1 and temp3 don't appear to be hooked up; temp2 is on board the
# adt7475 chip itself.

    ignore temp1
    label  temp2  "ADT7475 Temp"
    ignore temp3

# Fans

    label  fan1 "Chassis Fan 3"
    label  fan2 "Chassis Fan 4"
    label  fan3 "Power Supply Fan" # Only one in BIOS
    label  fan4 "Chassis Fan 5"

# fan3 is take from ACPI dissasembly
# other fan are simply numbered in order, I never saw anything but 0 RPM
# for these. According to MB manual there aren't even connected
    # Ignore fans you don't have
    set fan3_min 600


chip "k8temp-*"

   label temp1 "core0 temp0"
   label temp2 "core0 temp1"
   label temp3 "core1 temp0"
   label temp4 "core1 temp1"

# 21°C offset taken from message on lm_sensors list
# not sure if it is correct or if CPU is boggus, but
# temps are similar to others probes temp.
   compute temp1 (@+21), (@-21) 
   compute temp2 (@+21), (@-21) 
   compute temp3 (@+21), (@-21)
   compute temp4 (@+21), (@-21) 

[-- Attachment #3: 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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (11 preceding siblings ...)
  2009-01-04 17:57 ` Daimonos Tereutes
@ 2009-01-07 21:52 ` Jean Delvare
  2009-01-08  7:56 ` Hans de Goede
  13 siblings, 0 replies; 15+ messages in thread
From: Jean Delvare @ 2009-01-07 21:52 UTC (permalink / raw)
  To: lm-sensors

Hi Hans,

On Sat, 20 Dec 2008 09:22:33 +0100, Hans de Goede wrote:
> Here is a new version with the comments / review from Matt Roberds taken in to 
> account.

Thank you (and to Jordan and Matt as well) for finishing the driver.
I've just added it to my hwmon tree, so that it goes into linux-next.
I'll push it to Linus as part of my next round of hwmon patches (which
should basically be the adt7475 driver + the k8temp fixes which I still
need to review and test.)

-- 
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] 15+ messages in thread

* Re: [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475
  2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
                   ` (12 preceding siblings ...)
  2009-01-07 21:52 ` Jean Delvare
@ 2009-01-08  7:56 ` Hans de Goede
  13 siblings, 0 replies; 15+ messages in thread
From: Hans de Goede @ 2009-01-08  7:56 UTC (permalink / raw)
  To: lm-sensors



Jean Delvare wrote:
> Hi Hans,
> 
> On Sat, 20 Dec 2008 09:22:33 +0100, Hans de Goede wrote:
>> Here is a new version with the comments / review from Matt Roberds taken in to 
>> account.
> 
> Thank you (and to Jordan and Matt as well) for finishing the driver.
> I've just added it to my hwmon tree, so that it goes into linux-next.
> I'll push it to Linus as part of my next round of hwmon patches

Great, thanks!



  (which
> should basically be the adt7475 driver + the k8temp fixes which I still
> need to review and test.)
> 

I can test the k8temp driver too if you want. I actually have access to 3 
different k8 models. But testing on some of them is easier for me then testing 
on others. I can test without much work from my side on atleast 1.

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] 15+ messages in thread

end of thread, other threads:[~2009-01-08  7:56 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-06-10 19:48 [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
2008-06-13  7:10 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
2008-08-07 20:56 ` Hans de Goede
2008-09-05 20:54 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Jordan Crouse
2008-09-06 10:00 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Hans de Goede
2008-12-19 21:31 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
2008-12-19 21:32 ` Hans de Goede
2008-12-19 23:46 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Matt Roberds
2008-12-20  8:16 ` Hans de Goede
2008-12-20  8:22 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 thermal Hans de Goede
2008-12-22 23:23 ` [lm-sensors] [PATCH] hwmon: Add a driver for the ADT7475 Jordan Crouse
2008-12-23 18:38 ` Matt Roberds
2009-01-04 17:57 ` Daimonos Tereutes
2009-01-07 21:52 ` Jean Delvare
2009-01-08  7:56 ` Hans de Goede

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.