All of lore.kernel.org
 help / color / mirror / Atom feed
* [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821
@ 2009-08-31 20:24 ` tomaz.mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: tomaz.mertelj @ 2009-08-31 20:24 UTC (permalink / raw)
  To: lm-sensors; +Cc: linux-kernel

[-- Attachment #1: Mail message body --]
[-- Type: text/plain, Size: 205 bytes --]

I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
channel temperature sensor, pwm controller. This is my first linux project 
so some corrections will be necessary.

T. Mertelj


[-- Attachment #2: amc6821.diff --]
[-- Type: application/octet-stream, Size: 31259 bytes --]

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..d231a28 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..75dee4c
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,851 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+			 monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[ ] = {0x18, 0x19, 0x1a, 0x2c,0x2d,0x2e,
+	0x4c,0x4d,0x4e,I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(struct i2c_client *client,const struct i2c_device_id *id);
+static int amc6821_detect(struct i2c_client *client, int kind,struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+
+static struct i2c_driver amc6821_driver = {
+	.class			= I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data
+{
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	u8 temp1_alarm;
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+	u8 temp2_alarm;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){ \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	 if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min,AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max,AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min,AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max,AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit,AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit,AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if( data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+}\
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if( data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr,const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock); \
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255); \
+	 i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); \
+	mutex_unlock(&data->update_lock); \
+	return count;
+}
+
+static ssize_t get_pwm1_enable(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+	   if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return -EIO;
+	}
+
+	switch (val){
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			count= -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] *1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(struct device *dev, struct device_attribute *devattr,char *buf){
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr] );
+}
+
+
+#define set_temp_auto_point_temp(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch(nr){\
+		case 0:\
+			data->name[0] = SENSORS_LIMIT(val / 1000, 0,data->temp1_auto_point_temp[1]-1);\
+			data->name[0] = SENSORS_LIMIT(data->name[0], 0,data->temp2_auto_point_temp[1]-1);\
+			data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+			data->valid = 0;\
+			if (i2c_smbus_write_byte_data(client, AMC6821_REG_PSV_TEMP, data->name[ 0] )) {\
+					dev_err(&client->dev, "Register write error, aborting.\n");\
+					count = -EIO;\
+			}\
+			goto EXIT;\
+			 break;\
+		case 1:\
+			data->name[1] = SENSORS_LIMIT(val / 1000, data->name[0]+1, data->name[ 2]-1); \
+			data->name[1] &= 0x7C;\
+			break;\
+		case 2:\
+			data->name[2] = SENSORS_LIMIT(val / 1000, data->name[1]+1, 127); \
+			break;\
+	}\
+	val = (16*(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->name[2]-data->name[1]);\
+	for(tmp = 4; tmp >= 0; tmp--){\
+		if( val <= (0x200 >> tmp)) break;\
+	}\
+	tmp |= ((u8) data->name[ 1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	}\
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[ 1] = SENSORS_LIMIT(val, 0, 255);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, data->pwm1_auto_point_pwm[ 1])) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	val =(16 * (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1]);
+	for(tmp = 4; tmp >= 0; tmp--){
+		if( val <= (0x200 >> tmp)) break;
+	}
+	tmp |= ((u8) data->temp1_auto_point_temp[ 1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,  AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+
+	val = (16 * (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1]);
+	for(tmp = 4; tmp >= 0; tmp--){
+		if( val <= (0x200 >> tmp)) break;
+	}
+	tmp |= ((u8) data->temp2_auto_point_temp[ 1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,  AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if ( 0 == data->name) return sprintf(buf,"0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if( data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1>val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8 )) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val){
+		case 2:
+			config &= ~AMC6821_CONF4_PSPR;
+			data->fan1_div=2;
+			break;
+		case 4:
+			config |= AMC6821_CONF4_PSPR;
+			data->fan1_div=4;
+			break;
+		default:
+			mutex_unlock(&data->update_lock);
+			return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+		dev_err(&client->dev, "Configuration register write error, aborting.\n");
+		count= -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min, set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max, set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit, set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR, get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min, set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max, set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit, set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1,0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_pwm1_enable, set_pwm1_enable,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, get_pwm1_auto_point_pwm, NULL,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm,1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, get_pwm1_auto_point_pwm, NULL,2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, get_pwm1_auto_channels_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO, get_temp1_auto_point_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, get_temp1_auto_point_temp, set_temp1_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, get_temp1_auto_point_temp, set_temp1_auto_point_temp,2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(struct i2c_client *client, int kind,
+	struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support byte read mode, skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) & 0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) & 0xB6))){
+			dev_dbg(&adapter->dev,"amc6821: detection failed at 0x%02x.\n", address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	if (!(data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL))) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if( init ){
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv) {
+			config |= AMC6821_CONF1_PWMINV;
+		} else {
+			config &= ~AMC6821_CONF1_PWMINV;
+		}
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) || !data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 :2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client, AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp2_auto_point_temp[0] = data->temp1_auto_point_temp[0]=i2c_smbus_read_byte_data(client, AMC6821_REG_PSV_TEMP);
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg=0x20 >> reg;
+		if(reg>0)
+			data->temp1_auto_point_temp[2] = data->temp1_auto_point_temp[1] + (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg=0x20 >> reg;
+		if(reg>0)
+			data->temp2_auto_point_temp[2] = data->temp2_auto_point_temp[1] + (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+		reg=(reg >> 5) & 0x3;
+		switch(reg) {
+			case 0:
+				data->pwm1_auto_channels_temp=0;
+				data->pwm1_enable=1;
+				break;
+			case 2:
+				data->pwm1_auto_channels_temp=2;
+				data->pwm1_enable=2;
+				break;
+			case 3:
+				data->pwm1_auto_channels_temp=3;
+				data->pwm1_enable=3;
+				break;
+			case 1:
+				data->pwm1_auto_channels_temp=0;
+				data->pwm1_enable=0;
+				break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("TI amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

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

* [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-08-31 20:24 ` tomaz.mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: tomaz.mertelj @ 2009-08-31 20:24 UTC (permalink / raw)
  To: lm-sensors; +Cc: linux-kernel

[-- Attachment #1: Mail message body --]
[-- Type: text/plain, Size: 205 bytes --]

I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
channel temperature sensor, pwm controller. This is my first linux project 
so some corrections will be necessary.

T. Mertelj


[-- Attachment #2: amc6821.diff --]
[-- Type: application/octet-stream, Size: 31259 bytes --]

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..d231a28 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..75dee4c
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,851 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+			 monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[ ] = {0x18, 0x19, 0x1a, 0x2c,0x2d,0x2e,
+	0x4c,0x4d,0x4e,I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(struct i2c_client *client,const struct i2c_device_id *id);
+static int amc6821_detect(struct i2c_client *client, int kind,struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+
+static struct i2c_driver amc6821_driver = {
+	.class			= I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data
+{
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	u8 temp1_alarm;
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+	u8 temp2_alarm;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){ \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	 if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min,AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max,AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min,AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max,AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit,AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit,AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if( data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+}\
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if( data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr,const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock); \
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255); \
+	 i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); \
+	mutex_unlock(&data->update_lock); \
+	return count;
+}
+
+static ssize_t get_pwm1_enable(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+	   if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return -EIO;
+	}
+
+	switch (val){
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			count= -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] *1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(struct device *dev, struct device_attribute *devattr,char *buf){
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr] );
+}
+
+
+#define set_temp_auto_point_temp(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch(nr){\
+		case 0:\
+			data->name[0] = SENSORS_LIMIT(val / 1000, 0,data->temp1_auto_point_temp[1]-1);\
+			data->name[0] = SENSORS_LIMIT(data->name[0], 0,data->temp2_auto_point_temp[1]-1);\
+			data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+			data->valid = 0;\
+			if (i2c_smbus_write_byte_data(client, AMC6821_REG_PSV_TEMP, data->name[ 0] )) {\
+					dev_err(&client->dev, "Register write error, aborting.\n");\
+					count = -EIO;\
+			}\
+			goto EXIT;\
+			 break;\
+		case 1:\
+			data->name[1] = SENSORS_LIMIT(val / 1000, data->name[0]+1, data->name[ 2]-1); \
+			data->name[1] &= 0x7C;\
+			break;\
+		case 2:\
+			data->name[2] = SENSORS_LIMIT(val / 1000, data->name[1]+1, 127); \
+			break;\
+	}\
+	val = (16*(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->name[2]-data->name[1]);\
+	for(tmp = 4; tmp >= 0; tmp--){\
+		if( val <= (0x200 >> tmp)) break;\
+	}\
+	tmp |= ((u8) data->name[ 1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	}\
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[ 1] = SENSORS_LIMIT(val, 0, 255);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, data->pwm1_auto_point_pwm[ 1])) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	val =(16 * (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1]);
+	for(tmp = 4; tmp >= 0; tmp--){
+		if( val <= (0x200 >> tmp)) break;
+	}
+	tmp |= ((u8) data->temp1_auto_point_temp[ 1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,  AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+
+	val = (16 * (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1]);
+	for(tmp = 4; tmp >= 0; tmp--){
+		if( val <= (0x200 >> tmp)) break;
+	}
+	tmp |= ((u8) data->temp2_auto_point_temp[ 1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,  AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(struct device *dev, struct device_attribute *devattr,char *buf){ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if ( 0 == data->name) return sprintf(buf,"0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if( data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1>val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8 )) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count= -EIO;\
+	}\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(struct device *dev, struct device_attribute *devattr,char *buf){
+	struct amc6821_data *data = amc6821_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val){
+		case 2:
+			config &= ~AMC6821_CONF4_PSPR;
+			data->fan1_div=2;
+			break;
+		case 4:
+			config |= AMC6821_CONF4_PSPR;
+			data->fan1_div=4;
+			break;
+		default:
+			mutex_unlock(&data->update_lock);
+			return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+		dev_err(&client->dev, "Configuration register write error, aborting.\n");
+		count= -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min, set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max, set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit, set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR, get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min, set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max, set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit, set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1,0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_pwm1_enable, set_pwm1_enable,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, get_pwm1_auto_point_pwm, NULL,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm,1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, get_pwm1_auto_point_pwm, NULL,2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, get_pwm1_auto_channels_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO, get_temp1_auto_point_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, get_temp1_auto_point_temp, set_temp1_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, get_temp1_auto_point_temp, set_temp1_auto_point_temp,2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, get_temp2_auto_point_temp, set_temp2_auto_point_temp,2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(struct i2c_client *client, int kind,
+	struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support byte read mode, skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) & 0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) & 0xB6))){
+			dev_dbg(&adapter->dev,"amc6821: detection failed at 0x%02x.\n", address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	if (!(data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL))) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if( init ){
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev, "Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv) {
+			config |= AMC6821_CONF1_PWMINV;
+		} else {
+			config &= ~AMC6821_CONF1_PWMINV;
+		}
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev, "Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) || !data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 :2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client, AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp2_auto_point_temp[0] = data->temp1_auto_point_temp[0]=i2c_smbus_read_byte_data(client, AMC6821_REG_PSV_TEMP);
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg=0x20 >> reg;
+		if(reg>0)
+			data->temp1_auto_point_temp[2] = data->temp1_auto_point_temp[1] + (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg=0x20 >> reg;
+		if(reg>0)
+			data->temp2_auto_point_temp[2] = data->temp2_auto_point_temp[1] + (data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+		reg=(reg >> 5) & 0x3;
+		switch(reg) {
+			case 0:
+				data->pwm1_auto_channels_temp=0;
+				data->pwm1_enable=1;
+				break;
+			case 2:
+				data->pwm1_auto_channels_temp=2;
+				data->pwm1_enable=2;
+				break;
+			case 3:
+				data->pwm1_auto_channels_temp=3;
+				data->pwm1_enable=3;
+				break;
+			case 1:
+				data->pwm1_auto_channels_temp=0;
+				data->pwm1_enable=0;
+				break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("TI amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
@ 2009-09-01 17:56   ` Andre Prendel
  -1 siblings, 0 replies; 37+ messages in thread
From: Andre Prendel @ 2009-09-01 17:56 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Mon, Aug 31, 2009 at 10:24:50PM +0200, tomaz.mertelj@guest.arnes.si wrote:

Hello Tomaz,

> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.

First thing, please send the patch inline attached that one can
comment the source code.

> T. Mertelj
> 

Regards,
Andre

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

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-01 17:56   ` Andre Prendel
  0 siblings, 0 replies; 37+ messages in thread
From: Andre Prendel @ 2009-09-01 17:56 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Mon, Aug 31, 2009 at 10:24:50PM +0200, tomaz.mertelj@guest.arnes.si wrote:

Hello Tomaz,

> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.

First thing, please send the patch inline attached that one can
comment the source code.

> T. Mertelj
> 

Regards,
Andre

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-01 17:56   ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andre Prendel
@ 2009-09-02  8:20     ` Tomaz Mertelj
  -1 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-02  8:20 UTC (permalink / raw)
  To: Andre Prendel; +Cc: lm-sensors, linux-kernel

At 19:56 1. 9. 2009 +0200, you wrote:

>First thing, please send the patch inline attached that one can
>comment the source code.


Sorry, it was filtered out due to a wrong attchment format.

Tomaz


diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..d231a28 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
           This driver can also be built as a module.  If so, the module
           will be called ads7828.

+config SENSORS_AMC6821
+       tristate "Texas Instruments AMC6821"
+       depends on I2C
+       help
+         If you say yes here you get support for the Texas Instruments
+         AMC6821 hardware monitoring chips.
+
+         This driver can also be build as a module.  If so, the module
+         will be called amc6821.
+
  config SENSORS_THMC50
         tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
         depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
  obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
  obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
  obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)  += amc6821.o
  obj-$(CONFIG_SENSORS_THMC50)   += thmc50.o
  obj-$(CONFIG_SENSORS_TMP401)   += tmp401.o
  obj-$(CONFIG_SENSORS_VIA686A)  += via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..75dee4c
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,851 @@
+/*
+       amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+                        monitoring
+       Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>      /* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[ ] = {0x18, 0x19, 0x1a, 0x2c,0x2d,0x2e,
+       0x4c,0x4d,0x4e,I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0; /*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(struct i2c_client *client,const struct 
i2c_device_id *id);
+static int amc6821_detect(struct i2c_client *client, int kind,struct 
i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+       { "amc6821", amc6821 },
+       { }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+
+static struct i2c_driver amc6821_driver = {
+       .class                  = I2C_CLASS_HWMON,
+       .driver = {
+               .name   = "amc6821",
+       },
+       .probe = amc6821_probe,
+       .remove = amc6821_remove,
+       .id_table = amc6821_id,
+       .detect = amc6821_detect,
+       .address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data
+{
+       struct device *hwmon_dev;
+       struct mutex update_lock;
+       char valid; /* zero until following fields are valid */
+       unsigned long last_updated; /* in jiffies */
+
+       /* register values */
+       int temp1_input;
+       int temp1_min;
+       int temp1_max;
+       int temp1_crit;
+
+       u8 temp1_alarm;
+       int temp2_input;
+       int temp2_min;
+       int temp2_max;
+       int temp2_crit;
+       u8 temp2_alarm;
+
+       u16 fan1_input;
+       u16 fan1_min;
+       u16 fan1_max;
+       u8 fan1_div;
+
+       u8 pwm1;
+       u8 temp1_auto_point_temp[3];
+       u8 temp2_auto_point_temp[3];
+       u8 pwm1_auto_point_pwm[3];
+       u8 pwm1_enable;
+       u8 pwm1_auto_channels_temp;
+
+       u8 stat1;
+       u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){ \
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count) \
+{ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = i2c_get_clientdata(client); \
+       int val = simple_strtol(buf, NULL, 10); \
+       \
+       mutex_lock(&data->update_lock); \
+       data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+        if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+set_temp_para(temp1_min,AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max,AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min,AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max,AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit,AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit,AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){\
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       if( data->reg &  mask)\
+               return sprintf(buf, "1");\
+       else    \
+               return sprintf(buf, "0");\
+}\
+
+get_temp_alarm(temp1_min_alarm, stat1, AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       if( data->stat1 & AMC6821_STAT1_RTF)
+               return sprintf(buf, "1");
+       else
+               return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(struct device *dev, struct device_attribute 
*devattr,const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+
+       mutex_lock(&data->update_lock); \
+       data->pwm1 = SENSORS_LIMIT(val , 0, 255); \
+        i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); \
+       mutex_unlock(&data->update_lock); \
+       return count;
+}
+
+static ssize_t get_pwm1_enable(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+
+       int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+          if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return -EIO;
+       }
+
+       switch (val){
+       case 1:
+               config &= ~AMC6821_CONF1_FDRC0;
+               config &= ~AMC6821_CONF1_FDRC1;
+               break;
+       case 2:
+               config &= ~AMC6821_CONF1_FDRC0;
+               config |= AMC6821_CONF1_FDRC1;
+               break;
+       case 3:
+               config |= AMC6821_CONF1_FDRC0;
+               config |= AMC6821_CONF1_FDRC1;
+               break;
+       default:
+               return -EINVAL;
+       }
+       mutex_lock(&data->update_lock);
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       count= -EIO;
+       }
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(struct device *dev, struct 
device_attribute *devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){\
+       int nr = to_sensor_dev_attr(devattr)->index;\
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       return sprintf(buf, "%d\n", data->name[nr] *1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(struct device *dev, struct 
device_attribute *devattr,char *buf){
+       int nr = to_sensor_dev_attr(devattr)->index;
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr] );
+}
+
+
+#define set_temp_auto_point_temp(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count) \
+{ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       int nr = to_sensor_dev_attr(attr)->index;\
+       int val = simple_strtol(buf, NULL, 10); \
+       u8 tmp;\
+       \
+       mutex_lock(&data->update_lock); \
+       switch(nr){\
+               case 0:\
+                       data->name[0] = SENSORS_LIMIT(val / 1000, 
0,data->temp1_auto_point_temp[1]-1);\
+                       data->name[0] = SENSORS_LIMIT(data->name[0], 
0,data->temp2_auto_point_temp[1]-1);\
+                       data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+                       data->valid = 0;\
+                       if (i2c_smbus_write_byte_data(client, 
AMC6821_REG_PSV_TEMP, data->name[ 0] )) {\
+                                       dev_err(&client->dev, "Register 
write error, aborting.\n");\
+                                       count = -EIO;\
+                       }\
+                       goto EXIT;\
+                        break;\
+               case 1:\
+                       data->name[1] = SENSORS_LIMIT(val / 1000, 
data->name[0]+1, data->name[ 2]-1); \
+                       data->name[1] &= 0x7C;\
+                       break;\
+               case 2:\
+                       data->name[2] = SENSORS_LIMIT(val / 1000, 
data->name[1]+1, 127); \
+                       break;\
+       }\
+       val = 
(16*(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->name[2]-data->name[1]);\
+       for(tmp = 4; tmp >= 0; tmp--){\
+               if( val <= (0x200 >> tmp)) break;\
+       }\
+       tmp |= ((u8) data->name[ 1] & 0x7C) << 1;\
+       if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count = -EIO;\
+       }\
+       data->valid = 0;\
+EXIT:\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(struct device *dev, struct 
device_attribute *attr, const char *buf, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+       u8 tmp;
+
+       mutex_lock(&data->update_lock);
+       data->pwm1_auto_point_pwm[ 1] = SENSORS_LIMIT(val, 0, 255);
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, 
data->pwm1_auto_point_pwm[ 1])) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+
+       val =(16 * 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1]);
+       for(tmp = 4; tmp >= 0; tmp--){
+               if( val <= (0x200 >> tmp)) break;
+       }
+       tmp |= ((u8) data->temp1_auto_point_temp[ 1] & 0x7C) << 1;
+       if (i2c_smbus_write_byte_data(client,  AMC6821_REG_LTEMP_FAN_CTRL, 
tmp)) {
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+       data->valid = 0;
+
+       val = (16 * 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1]);
+       for(tmp = 4; tmp >= 0; tmp--){
+               if( val <= (0x200 >> tmp)) break;
+       }
+       tmp |= ((u8) data->temp2_auto_point_temp[ 1] & 0x7C) << 1;
+       if (i2c_smbus_write_byte_data(client,  AMC6821_REG_RTEMP_FAN_CTRL, 
tmp)) {
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+       data->valid = 0;
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(struct device *dev, 
struct device_attribute *devattr,char *buf){ \
+       struct amc6821_data *data = amc6821_update_device(dev); \
+       if ( 0 = data->name) return sprintf(buf,"0");\
+       return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       if( data->stat1 & AMC6821_STAT1_FANS)
+               return sprintf(buf, "1");
+       else
+               return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = i2c_get_clientdata(client); \
+       int val = simple_strtol(buf, NULL, 10); \
+       val = 1>val ? 0xFFFF : 6000000/val; \
+\
+       mutex_lock(&data->update_lock); \
+       data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+       if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8 )) { \
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+
+       return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+       int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+       if (config < 0) {
+               dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+               return -EIO;
+       }
+       mutex_lock(&data->update_lock);
+       switch (val){
+               case 2:
+                       config &= ~AMC6821_CONF4_PSPR;
+                       data->fan1_div=2;
+                       break;
+               case 4:
+                       config |= AMC6821_CONF4_PSPR;
+                       data->fan1_div=4;
+                       break;
+               default:
+                       mutex_unlock(&data->update_lock);
+                       return -EINVAL;
+       }
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+               dev_err(&client->dev, "Configuration register write error, 
aborting.\n");
+               count= -EIO;
+       }
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min, 
set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max, 
set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit, 
set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, get_temp1_min_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, get_temp1_max_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, get_temp1_crit_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR, get_temp2_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min, 
set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max, 
set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit, 
set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, get_temp2_min_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, get_temp2_max_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, get_temp2_crit_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, get_fan1_min, 
set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, get_fan1_max, 
set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, get_fan1_div, 
set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1,0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_pwm1_enable, 
set_pwm1_enable,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, 
get_pwm1_auto_point_pwm, NULL,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, 
get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm,1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, 
get_pwm1_auto_point_pwm, NULL,2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, 
get_pwm1_auto_channels_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO, 
get_temp1_auto_point_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, 
get_temp1_auto_point_temp, set_temp1_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, 
get_temp1_auto_point_temp, set_temp1_auto_point_temp,2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_min.dev_attr.attr,
+       &sensor_dev_attr_temp1_max.dev_attr.attr,
+       &sensor_dev_attr_temp1_crit.dev_attr.attr,
+       &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_input.dev_attr.attr,
+       &sensor_dev_attr_temp2_min.dev_attr.attr,
+       &sensor_dev_attr_temp2_max.dev_attr.attr,
+       &sensor_dev_attr_temp2_crit.dev_attr.attr,
+       &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_fault.dev_attr.attr,
+       &sensor_dev_attr_fan1_input.dev_attr.attr,
+       &sensor_dev_attr_fan1_min.dev_attr.attr,
+       &sensor_dev_attr_fan1_max.dev_attr.attr,
+       &sensor_dev_attr_fan1_fault.dev_attr.attr,
+       &sensor_dev_attr_fan1_div.dev_attr.attr,
+       &sensor_dev_attr_pwm1.dev_attr.attr,
+       &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+       NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+       .attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(struct i2c_client *client, int kind,
+       struct i2c_board_info *info)
+{
+       struct i2c_adapter *adapter = client->adapter;
+       int address = client->addr;
+
+       dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support 
byte read mode, skipping.\n");
+               return -ENODEV;
+       }
+       if ((kind < 0) &&
+               ((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) & 
0xDE) ||
+               (i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) & 
0xB6))){
+                       dev_dbg(&adapter->dev,"amc6821: detection failed at 
0x%02x.\n", address);
+               return -ENODEV;
+       }
+
+       dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+       strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+       return 0;
+}
+
+static int amc6821_probe(struct i2c_client *client,
+       const struct i2c_device_id *id)
+{
+       struct amc6821_data *data;
+       int err;
+
+       if (!(data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL))) {
+               dev_err(&client->dev, "out of memory.\n");
+               return -ENOMEM;
+       }
+
+
+       i2c_set_clientdata(client, data);
+       mutex_init(&data->update_lock);
+
+       /*
+        * Initialize the amc6821 chip
+        */
+       err = amc6821_init_client(client);
+       if (err)
+               goto err_free;
+
+       err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+       if (err)
+               goto err_free;
+
+       data->hwmon_dev = hwmon_device_register(&client->dev);
+       if (!IS_ERR(data->hwmon_dev))
+               return 0;
+
+       err = PTR_ERR(data->hwmon_dev);
+       dev_err(&client->dev, "error registering hwmon device.\n");
+       sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+       kfree(data);
+       return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+       struct amc6821_data *data = i2c_get_clientdata(client);
+
+       hwmon_device_unregister(data->hwmon_dev);
+       sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+       kfree(data);
+
+       return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+       int config;
+       int err = -EIO;
+
+       if( init ){
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+               if (config < 0) {
+                               dev_err(&client->dev, "Error reading 
configuration register, aborting.\n");
+                               return err;
+               }
+
+               config |= AMC6821_CONF4_MODE;
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF2_RTFIE;
+               config &= ~AMC6821_CONF2_LTOIE;
+               config &= ~AMC6821_CONF2_RTOIE;
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF2, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF1_THERMOVIE;
+               config &= ~AMC6821_CONF1_FANIE;
+               config |= AMC6821_CONF1_START;
+               if (pwminv) {
+                       config |= AMC6821_CONF1_PWMINV;
+               } else {
+                       config &= ~AMC6821_CONF1_PWMINV;
+               }
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+       }
+       return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int timeout = HZ;
+       u8 reg;
+
+       mutex_lock(&data->update_lock);
+
+       if (time_after(jiffies, data->last_updated + timeout) || 
!data->valid) {
+               data->temp1_input = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_HI);
+               data->temp1_min = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_LIMIT_MIN);
+               data->temp1_max = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_LIMIT_MAX);
+               data->temp1_crit = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_CRIT);
+
+               data->temp2_input = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_HI);
+               data->temp2_min = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_LIMIT_MIN);
+               data->temp2_max = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_LIMIT_MAX);
+               data->temp2_crit = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_CRIT);
+
+               data->stat1 = 
i2c_smbus_read_byte_data(client,AMC6821_REG_STAT1);
+               data->stat2 = 
i2c_smbus_read_byte_data(client,AMC6821_REG_STAT2);
+
+               data->pwm1 = i2c_smbus_read_byte_data(client,AMC6821_REG_DCY);
+               data->fan1_input = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_LOW);
+               data->fan1_input += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_HI) << 8;
+               data->fan1_min = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL);
+               data->fan1_min += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL+1) << 8;
+               data->fan1_max = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL);
+               data->fan1_max += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL+1) << 8;
+               data->fan1_div = i2c_smbus_read_byte_data(client, 
AMC6821_REG_CONF4);
+               data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 :2;
+
+               data->pwm1_auto_point_pwm[0] = 0;
+               data->pwm1_auto_point_pwm[2] = 255;
+               data->pwm1_auto_point_pwm[1] = 
i2c_smbus_read_byte_data(client, AMC6821_REG_DCY_LOW_TEMP);
+
+               data->temp2_auto_point_temp[0] = 
data->temp1_auto_point_temp[0]=i2c_smbus_read_byte_data(client, 
AMC6821_REG_PSV_TEMP);
+               reg = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_FAN_CTRL);
+               data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+               reg &= 0x07;
+               reg=0x20 >> reg;
+               if(reg>0)
+                       data->temp1_auto_point_temp[2] = 
data->temp1_auto_point_temp[1] + 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+               else
+                       data->temp1_auto_point_temp[2] = 255;
+
+               reg = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_FAN_CTRL);
+               data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+               reg &= 0x07;
+               reg=0x20 >> reg;
+               if(reg>0)
+                       data->temp2_auto_point_temp[2] = 
data->temp2_auto_point_temp[1] + 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+               else
+                       data->temp1_auto_point_temp[2] = 255;
+
+               reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+               reg=(reg >> 5) & 0x3;
+               switch(reg) {
+                       case 0:
+                               data->pwm1_auto_channels_temp=0;
+                               data->pwm1_enable=1;
+                               break;
+                       case 2:
+                               data->pwm1_auto_channels_temp=2;
+                               data->pwm1_enable=2;
+                               break;
+                       case 3:
+                               data->pwm1_auto_channels_temp=3;
+                               data->pwm1_enable=3;
+                               break;
+                       case 1:
+                               data->pwm1_auto_channels_temp=0;
+                               data->pwm1_enable=0;
+                               break;
+               }
+
+               data->last_updated = jiffies;
+               data->valid = 1;
+       }
+       mutex_unlock(&data->update_lock);
+       return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+       return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+       i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("TI amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");




***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-02  8:20     ` Tomaz Mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-02  8:20 UTC (permalink / raw)
  To: Andre Prendel; +Cc: lm-sensors, linux-kernel

At 19:56 1. 9. 2009 +0200, you wrote:

>First thing, please send the patch inline attached that one can
>comment the source code.


Sorry, it was filtered out due to a wrong attchment format.

Tomaz


diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..d231a28 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
           This driver can also be built as a module.  If so, the module
           will be called ads7828.

+config SENSORS_AMC6821
+       tristate "Texas Instruments AMC6821"
+       depends on I2C
+       help
+         If you say yes here you get support for the Texas Instruments
+         AMC6821 hardware monitoring chips.
+
+         This driver can also be build as a module.  If so, the module
+         will be called amc6821.
+
  config SENSORS_THMC50
         tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
         depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
  obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
  obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
  obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)  += amc6821.o
  obj-$(CONFIG_SENSORS_THMC50)   += thmc50.o
  obj-$(CONFIG_SENSORS_TMP401)   += tmp401.o
  obj-$(CONFIG_SENSORS_VIA686A)  += via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..75dee4c
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,851 @@
+/*
+       amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+                        monitoring
+       Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>      /* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[ ] = {0x18, 0x19, 0x1a, 0x2c,0x2d,0x2e,
+       0x4c,0x4d,0x4e,I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0; /*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(struct i2c_client *client,const struct 
i2c_device_id *id);
+static int amc6821_detect(struct i2c_client *client, int kind,struct 
i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+       { "amc6821", amc6821 },
+       { }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+
+static struct i2c_driver amc6821_driver = {
+       .class                  = I2C_CLASS_HWMON,
+       .driver = {
+               .name   = "amc6821",
+       },
+       .probe = amc6821_probe,
+       .remove = amc6821_remove,
+       .id_table = amc6821_id,
+       .detect = amc6821_detect,
+       .address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data
+{
+       struct device *hwmon_dev;
+       struct mutex update_lock;
+       char valid; /* zero until following fields are valid */
+       unsigned long last_updated; /* in jiffies */
+
+       /* register values */
+       int temp1_input;
+       int temp1_min;
+       int temp1_max;
+       int temp1_crit;
+
+       u8 temp1_alarm;
+       int temp2_input;
+       int temp2_min;
+       int temp2_max;
+       int temp2_crit;
+       u8 temp2_alarm;
+
+       u16 fan1_input;
+       u16 fan1_min;
+       u16 fan1_max;
+       u8 fan1_div;
+
+       u8 pwm1;
+       u8 temp1_auto_point_temp[3];
+       u8 temp2_auto_point_temp[3];
+       u8 pwm1_auto_point_pwm[3];
+       u8 pwm1_enable;
+       u8 pwm1_auto_channels_temp;
+
+       u8 stat1;
+       u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){ \
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count) \
+{ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = i2c_get_clientdata(client); \
+       int val = simple_strtol(buf, NULL, 10); \
+       \
+       mutex_lock(&data->update_lock); \
+       data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+        if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+set_temp_para(temp1_min,AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max,AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min,AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max,AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit,AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit,AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){\
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       if( data->reg &  mask)\
+               return sprintf(buf, "1");\
+       else    \
+               return sprintf(buf, "0");\
+}\
+
+get_temp_alarm(temp1_min_alarm, stat1, AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       if( data->stat1 & AMC6821_STAT1_RTF)
+               return sprintf(buf, "1");
+       else
+               return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(struct device *dev, struct device_attribute 
*devattr,const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+
+       mutex_lock(&data->update_lock); \
+       data->pwm1 = SENSORS_LIMIT(val , 0, 255); \
+        i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); \
+       mutex_unlock(&data->update_lock); \
+       return count;
+}
+
+static ssize_t get_pwm1_enable(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+
+       int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+          if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return -EIO;
+       }
+
+       switch (val){
+       case 1:
+               config &= ~AMC6821_CONF1_FDRC0;
+               config &= ~AMC6821_CONF1_FDRC1;
+               break;
+       case 2:
+               config &= ~AMC6821_CONF1_FDRC0;
+               config |= AMC6821_CONF1_FDRC1;
+               break;
+       case 3:
+               config |= AMC6821_CONF1_FDRC0;
+               config |= AMC6821_CONF1_FDRC1;
+               break;
+       default:
+               return -EINVAL;
+       }
+       mutex_lock(&data->update_lock);
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       count= -EIO;
+       }
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(struct device *dev, struct 
device_attribute *devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(struct device *dev, struct device_attribute 
*devattr,char *buf){\
+       int nr = to_sensor_dev_attr(devattr)->index;\
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       return sprintf(buf, "%d\n", data->name[nr] *1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(struct device *dev, struct 
device_attribute *devattr,char *buf){
+       int nr = to_sensor_dev_attr(devattr)->index;
+       struct amc6821_data *data = amc6821_update_device(dev);
+       return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr] );
+}
+
+
+#define set_temp_auto_point_temp(name,reg)\
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count) \
+{ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = amc6821_update_device(dev);\
+       int nr = to_sensor_dev_attr(attr)->index;\
+       int val = simple_strtol(buf, NULL, 10); \
+       u8 tmp;\
+       \
+       mutex_lock(&data->update_lock); \
+       switch(nr){\
+               case 0:\
+                       data->name[0] = SENSORS_LIMIT(val / 1000, 
0,data->temp1_auto_point_temp[1]-1);\
+                       data->name[0] = SENSORS_LIMIT(data->name[0], 
0,data->temp2_auto_point_temp[1]-1);\
+                       data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+                       data->valid = 0;\
+                       if (i2c_smbus_write_byte_data(client, 
AMC6821_REG_PSV_TEMP, data->name[ 0] )) {\
+                                       dev_err(&client->dev, "Register 
write error, aborting.\n");\
+                                       count = -EIO;\
+                       }\
+                       goto EXIT;\
+                        break;\
+               case 1:\
+                       data->name[1] = SENSORS_LIMIT(val / 1000, 
data->name[0]+1, data->name[ 2]-1); \
+                       data->name[1] &= 0x7C;\
+                       break;\
+               case 2:\
+                       data->name[2] = SENSORS_LIMIT(val / 1000, 
data->name[1]+1, 127); \
+                       break;\
+       }\
+       val = 
(16*(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->name[2]-data->name[1]);\
+       for(tmp = 4; tmp >= 0; tmp--){\
+               if( val <= (0x200 >> tmp)) break;\
+       }\
+       tmp |= ((u8) data->name[ 1] & 0x7C) << 1;\
+       if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count = -EIO;\
+       }\
+       data->valid = 0;\
+EXIT:\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(struct device *dev, struct 
device_attribute *attr, const char *buf, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+       u8 tmp;
+
+       mutex_lock(&data->update_lock);
+       data->pwm1_auto_point_pwm[ 1] = SENSORS_LIMIT(val, 0, 255);
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, 
data->pwm1_auto_point_pwm[ 1])) {\
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+
+       val =(16 * 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1]);
+       for(tmp = 4; tmp >= 0; tmp--){
+               if( val <= (0x200 >> tmp)) break;
+       }
+       tmp |= ((u8) data->temp1_auto_point_temp[ 1] & 0x7C) << 1;
+       if (i2c_smbus_write_byte_data(client,  AMC6821_REG_LTEMP_FAN_CTRL, 
tmp)) {
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+       data->valid = 0;
+
+       val = (16 * 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1]))/(data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1]);
+       for(tmp = 4; tmp >= 0; tmp--){
+               if( val <= (0x200 >> tmp)) break;
+       }
+       tmp |= ((u8) data->temp2_auto_point_temp[ 1] & 0x7C) << 1;
+       if (i2c_smbus_write_byte_data(client,  AMC6821_REG_RTEMP_FAN_CTRL, 
tmp)) {
+               dev_err(&client->dev, "Register write error, aborting.\n");
+               count = -EIO;
+       }
+       data->valid = 0;
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(struct device *dev, 
struct device_attribute *devattr,char *buf){ \
+       struct amc6821_data *data = amc6821_update_device(dev); \
+       if ( 0 == data->name) return sprintf(buf,"0");\
+       return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+       if( data->stat1 & AMC6821_STAT1_FANS)
+               return sprintf(buf, "1");
+       else
+               return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){ \
+       struct i2c_client *client = to_i2c_client(dev); \
+       struct amc6821_data *data = i2c_get_clientdata(client); \
+       int val = simple_strtol(buf, NULL, 10); \
+       val = 1>val ? 0xFFFF : 6000000/val; \
+\
+       mutex_lock(&data->update_lock); \
+       data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+       if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8 )) { \
+               dev_err(&client->dev, "Register write error, aborting.\n");\
+               count= -EIO;\
+       }\
+       mutex_unlock(&data->update_lock); \
+       return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(struct device *dev, struct device_attribute 
*devattr,char *buf){
+       struct amc6821_data *data = amc6821_update_device(dev);
+
+       return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(struct device *dev, struct device_attribute 
*attr, const char *buf, size_t count){
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+       int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+       if (config < 0) {
+               dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+               return -EIO;
+       }
+       mutex_lock(&data->update_lock);
+       switch (val){
+               case 2:
+                       config &= ~AMC6821_CONF4_PSPR;
+                       data->fan1_div=2;
+                       break;
+               case 4:
+                       config |= AMC6821_CONF4_PSPR;
+                       data->fan1_div=4;
+                       break;
+               default:
+                       mutex_unlock(&data->update_lock);
+                       return -EINVAL;
+       }
+       if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+               dev_err(&client->dev, "Configuration register write error, 
aborting.\n");
+               count= -EIO;
+       }
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min, 
set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max, 
set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit, 
set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, get_temp1_min_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, get_temp1_max_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, get_temp1_crit_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR, get_temp2_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min, 
set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max, 
set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit, 
set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, get_temp2_min_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, get_temp2_max_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, get_temp2_crit_alarm, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, get_fan1_min, 
set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, get_fan1_max, 
set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, get_fan1_div, 
set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1,0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_pwm1_enable, 
set_pwm1_enable,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, 
get_pwm1_auto_point_pwm, NULL,0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, 
get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm,1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, 
get_pwm1_auto_point_pwm, NULL,2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, 
get_pwm1_auto_channels_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO, 
get_temp1_auto_point_temp, NULL,0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, 
get_temp1_auto_point_temp, set_temp1_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, 
get_temp1_auto_point_temp, set_temp1_auto_point_temp,2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, 
get_temp2_auto_point_temp, set_temp2_auto_point_temp,2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_min.dev_attr.attr,
+       &sensor_dev_attr_temp1_max.dev_attr.attr,
+       &sensor_dev_attr_temp1_crit.dev_attr.attr,
+       &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_input.dev_attr.attr,
+       &sensor_dev_attr_temp2_min.dev_attr.attr,
+       &sensor_dev_attr_temp2_max.dev_attr.attr,
+       &sensor_dev_attr_temp2_crit.dev_attr.attr,
+       &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_fault.dev_attr.attr,
+       &sensor_dev_attr_fan1_input.dev_attr.attr,
+       &sensor_dev_attr_fan1_min.dev_attr.attr,
+       &sensor_dev_attr_fan1_max.dev_attr.attr,
+       &sensor_dev_attr_fan1_fault.dev_attr.attr,
+       &sensor_dev_attr_fan1_div.dev_attr.attr,
+       &sensor_dev_attr_pwm1.dev_attr.attr,
+       &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+       NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+       .attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(struct i2c_client *client, int kind,
+       struct i2c_board_info *info)
+{
+       struct i2c_adapter *adapter = client->adapter;
+       int address = client->addr;
+
+       dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               dev_dbg(&adapter->dev, "max6650: I2C bus doesn't support 
byte read mode, skipping.\n");
+               return -ENODEV;
+       }
+       if ((kind < 0) &&
+               ((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) & 
0xDE) ||
+               (i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) & 
0xB6))){
+                       dev_dbg(&adapter->dev,"amc6821: detection failed at 
0x%02x.\n", address);
+               return -ENODEV;
+       }
+
+       dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+       strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+       return 0;
+}
+
+static int amc6821_probe(struct i2c_client *client,
+       const struct i2c_device_id *id)
+{
+       struct amc6821_data *data;
+       int err;
+
+       if (!(data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL))) {
+               dev_err(&client->dev, "out of memory.\n");
+               return -ENOMEM;
+       }
+
+
+       i2c_set_clientdata(client, data);
+       mutex_init(&data->update_lock);
+
+       /*
+        * Initialize the amc6821 chip
+        */
+       err = amc6821_init_client(client);
+       if (err)
+               goto err_free;
+
+       err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+       if (err)
+               goto err_free;
+
+       data->hwmon_dev = hwmon_device_register(&client->dev);
+       if (!IS_ERR(data->hwmon_dev))
+               return 0;
+
+       err = PTR_ERR(data->hwmon_dev);
+       dev_err(&client->dev, "error registering hwmon device.\n");
+       sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+       kfree(data);
+       return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+       struct amc6821_data *data = i2c_get_clientdata(client);
+
+       hwmon_device_unregister(data->hwmon_dev);
+       sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+       kfree(data);
+
+       return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+       int config;
+       int err = -EIO;
+
+       if( init ){
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+               if (config < 0) {
+                               dev_err(&client->dev, "Error reading 
configuration register, aborting.\n");
+                               return err;
+               }
+
+               config |= AMC6821_CONF4_MODE;
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF2_RTFIE;
+               config &= ~AMC6821_CONF2_LTOIE;
+               config &= ~AMC6821_CONF2_RTOIE;
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF2, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+
+               config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+               if (config < 0) {
+                       dev_err(&client->dev, "Error reading configuration 
register, aborting.\n");
+                       return err;
+               }
+
+               config &= ~AMC6821_CONF1_THERMOVIE;
+               config &= ~AMC6821_CONF1_FANIE;
+               config |= AMC6821_CONF1_START;
+               if (pwminv) {
+                       config |= AMC6821_CONF1_PWMINV;
+               } else {
+                       config &= ~AMC6821_CONF1_PWMINV;
+               }
+
+               if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, 
config)) {
+                       dev_err(&client->dev, "Configuration register write 
error, aborting.\n");
+                       return err;
+               }
+       }
+       return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct amc6821_data *data = i2c_get_clientdata(client);
+       int timeout = HZ;
+       u8 reg;
+
+       mutex_lock(&data->update_lock);
+
+       if (time_after(jiffies, data->last_updated + timeout) || 
!data->valid) {
+               data->temp1_input = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_HI);
+               data->temp1_min = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_LIMIT_MIN);
+               data->temp1_max = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_LIMIT_MAX);
+               data->temp1_crit = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_CRIT);
+
+               data->temp2_input = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_HI);
+               data->temp2_min = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_LIMIT_MIN);
+               data->temp2_max = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_LIMIT_MAX);
+               data->temp2_crit = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_CRIT);
+
+               data->stat1 = 
i2c_smbus_read_byte_data(client,AMC6821_REG_STAT1);
+               data->stat2 = 
i2c_smbus_read_byte_data(client,AMC6821_REG_STAT2);
+
+               data->pwm1 = i2c_smbus_read_byte_data(client,AMC6821_REG_DCY);
+               data->fan1_input = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_LOW);
+               data->fan1_input += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TDATA_HI) << 8;
+               data->fan1_min = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL);
+               data->fan1_min += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_LLIMITL+1) << 8;
+               data->fan1_max = 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL);
+               data->fan1_max += 
i2c_smbus_read_byte_data(client,AMC6821_REG_TACH_HLIMITL+1) << 8;
+               data->fan1_div = i2c_smbus_read_byte_data(client, 
AMC6821_REG_CONF4);
+               data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 :2;
+
+               data->pwm1_auto_point_pwm[0] = 0;
+               data->pwm1_auto_point_pwm[2] = 255;
+               data->pwm1_auto_point_pwm[1] = 
i2c_smbus_read_byte_data(client, AMC6821_REG_DCY_LOW_TEMP);
+
+               data->temp2_auto_point_temp[0] = 
data->temp1_auto_point_temp[0]=i2c_smbus_read_byte_data(client, 
AMC6821_REG_PSV_TEMP);
+               reg = i2c_smbus_read_byte_data(client, 
AMC6821_REG_LTEMP_FAN_CTRL);
+               data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+               reg &= 0x07;
+               reg=0x20 >> reg;
+               if(reg>0)
+                       data->temp1_auto_point_temp[2] = 
data->temp1_auto_point_temp[1] + 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+               else
+                       data->temp1_auto_point_temp[2] = 255;
+
+               reg = i2c_smbus_read_byte_data(client, 
AMC6821_REG_RTEMP_FAN_CTRL);
+               data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+               reg &= 0x07;
+               reg=0x20 >> reg;
+               if(reg>0)
+                       data->temp2_auto_point_temp[2] = 
data->temp2_auto_point_temp[1] + 
(data->pwm1_auto_point_pwm[2]-data->pwm1_auto_point_pwm[1])/reg;
+               else
+                       data->temp1_auto_point_temp[2] = 255;
+
+               reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+               reg=(reg >> 5) & 0x3;
+               switch(reg) {
+                       case 0:
+                               data->pwm1_auto_channels_temp=0;
+                               data->pwm1_enable=1;
+                               break;
+                       case 2:
+                               data->pwm1_auto_channels_temp=2;
+                               data->pwm1_enable=2;
+                               break;
+                       case 3:
+                               data->pwm1_auto_channels_temp=3;
+                               data->pwm1_enable=3;
+                               break;
+                       case 1:
+                               data->pwm1_auto_channels_temp=0;
+                               data->pwm1_enable=0;
+                               break;
+               }
+
+               data->last_updated = jiffies;
+               data->valid = 1;
+       }
+       mutex_unlock(&data->update_lock);
+       return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+       return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+       i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("TI amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");




***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
@ 2009-09-02  8:45   ` corentin.labbe
  -1 siblings, 0 replies; 37+ messages in thread
From: corentin.labbe @ 2009-09-02  8:45 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

tomaz.mertelj@guest.arnes.si a écrit :
> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.
> 
> T. Mertelj

Hello

I have some warnings

  CC      drivers/hwmon/amc6821.o
drivers/hwmon/amc6821.c: In function `set_temp1_auto_point_temp':
drivers/hwmon/amc6821.c:390: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c: In function `set_temp2_auto_point_temp':
drivers/hwmon/amc6821.c:391: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c: In function `set_pwm1_auto_point_pwm':
drivers/hwmon/amc6821.c:408: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c:419: warning: comparison is always true due to limited range of data type

you use tmp as a unsigned 8 bit so it is always >= 0 (it can't be below 0)
Prefer use:
s8 tmp;


Cordially

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-02  8:45   ` corentin.labbe
  0 siblings, 0 replies; 37+ messages in thread
From: corentin.labbe @ 2009-09-02  8:45 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

tomaz.mertelj@guest.arnes.si a écrit :
> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.
> 
> T. Mertelj

Hello

I have some warnings

  CC      drivers/hwmon/amc6821.o
drivers/hwmon/amc6821.c: In function `set_temp1_auto_point_temp':
drivers/hwmon/amc6821.c:390: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c: In function `set_temp2_auto_point_temp':
drivers/hwmon/amc6821.c:391: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c: In function `set_pwm1_auto_point_pwm':
drivers/hwmon/amc6821.c:408: warning: comparison is always true due to limited range of data type
drivers/hwmon/amc6821.c:419: warning: comparison is always true due to limited range of data type

you use tmp as a unsigned 8 bit so it is always >= 0 (it can't be below 0)
Prefer use:
s8 tmp;


Cordially

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
@ 2009-09-02 23:55   ` Andrew Morton
  -1 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-02 23:55 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Mon, 31 Aug 2009 22:24:50 +0200
tomaz.mertelj@guest.arnes.si wrote:

> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.
> 

The driver looks saneish to me, but...

- The attachment uses \r\n line termination and caused me quite a bit
  of trouble before I worked out what was going on.

- The changelog is missing your Signed-off-by:.  Please read up on
  this in Documentation/SubmittingPatches.

- The driver needs lots and lots of trivial coding-style fixes. 
  Please take a look at some similar drivers and also use the
  scripts/checkpatch.pl tool.

  checkpatch says:

	total: 153 errors, 110 warnings, 874 lines checked

  which must be some sort of record ;)

  Also, some C functions have needless `\' line continuations in them.


All pretty simple stuff.  Please take a look at all that and then resend?

Thanks.


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

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

* Re: [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-02 23:55   ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-02 23:55 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Mon, 31 Aug 2009 22:24:50 +0200
tomaz.mertelj@guest.arnes.si wrote:

> I have crafted a hwmon driver for Texas Instruments amc6821 SMB-bus 2-
> channel temperature sensor, pwm controller. This is my first linux project 
> so some corrections will be necessary.
> 

The driver looks saneish to me, but...

- The attachment uses \r\n line termination and caused me quite a bit
  of trouble before I worked out what was going on.

- The changelog is missing your Signed-off-by:.  Please read up on
  this in Documentation/SubmittingPatches.

- The driver needs lots and lots of trivial coding-style fixes. 
  Please take a look at some similar drivers and also use the
  scripts/checkpatch.pl tool.

  checkpatch says:

	total: 153 errors, 110 warnings, 874 lines checked

  which must be some sort of record ;)

  Also, some C functions have needless `\' line continuations in them.


All pretty simple stuff.  Please take a look at all that and then resend?

Thanks.


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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
@ 2009-09-05 12:08   ` tomaz.mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: tomaz.mertelj @ 2009-09-05 12:08 UTC (permalink / raw)
  To: Andrew Morton; +Cc: lm-sensors, linux-kernel

[-- Attachment #1: Mail message body --]
[-- Type: text/plain, Size: 36956 bytes --]


>- The attachment uses \r\n line termination and caused me quite a bit
>  of trouble before I worked out what was going on.

I am sorry. All my e-mail access is from MS-windows and I can not figure 
how to get rid of \r\n. For convenience I attach also the raw patch with \n 
only in encoded format. 

>- The changelog is missing your Signed-off-by:.  Please read up on
>  this in Documentation/SubmittingPatches.

Fixed.

>- The driver needs lots and lots of trivial coding-style fixes. 
>  Please take a look at some similar drivers and also use the
>  scripts/checkpatch.pl tool.

Fixed.


I also added documentation file and fixed bugs related to setting the trip 
points.

Best Regards,

Tomaz

--
Signed-off-by: tomaz.mertelj@guest.arnes.si

diff --git a/Documentation/hwmon/amc6821 b/Documentation/hwmon/
amc6821
new file mode 100644
index 0000000..b7cba86
--- /dev/null
+++ b/Documentation/hwmon/amc6821
@@ -0,0 +1,101 @@
+Kernel driver amc6821
+=====================
+
+Supported chips:
+	Prefix: 'amc6821'
+	Addresses scanned: 0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, 0x4c, 
0x4d, 0x4e
+	Datasheet: http://focus.ti.com/docs/prod/folders/print/amc6821.html
+
+Authors:
+	Tomaz Mertelj <tomaz.mertelj@guest.arnes.si>
+
+
+Description
+-----------
+
+This driver implements support for the Texas Instruments amc6821 chip.
+The chip has one on-chip and one remote temperature sensor and one 
pwm fan
+regulator.
+The pwm can be controlled either from software or automatically.
+
+The driver provides the following sensor accesses in sysfs:
+
+temp1_input		ro	on-chip temperature
+temp1_min		rw	"
+temp1_max		rw	"
+temp1_crit	 	rw	"
+temp1_min_alarm		ro	"
+temp1_max_alarm		ro	"
+temp1_crit_alarm	ro	"
+
+temp2_input		ro	remote temperature
+temp2_min		rw	"
+temp2_max		rw	"
+temp2_crit	 	rw	"
+temp2_min_alarm		ro	"
+temp2_max_alarm		ro	"
+temp2_crit_alarm	ro	"
+temp2_fault		ro	"
+
+fan1_input	 	ro	tachometer speed
+fan1_min		rw	"
+fan1_max		rw	"
+fan1_fault	 	ro	"
+fan1_div		rw	Fan divisor can be either 2 or 4.
+
+pwm1			rw	pwm1
+pwm1_enable		rw	regulator mode, 1=open loop, 2=fan 
controlled
+				by remote temperature, 3=fan controlled 
by
+				combination of on-chip temperature and
+				remote-sensor temperature,
+pwm1_auto_channels_temp ro	1 if pwm_enable==2, 3 if pwm_
enable==3
+pwm1_auto_point1_pwm	ro	Hardwired to 0, shared for both
+				temperature channels.
+pwm1_auto_point2_pwm	rw	This value, shared for both 
temperature
+				channels.
+pwm1_auto_point3_pwm	rw	Hardwired to 255, shared for 
both
+				temperature channels.
+
+temp1_auto_point1_temp	ro	Hardwired to temp2_auto_
point1_temp
+				which is rw. Below this temperature fan 
stops.
+temp1_auto_point2_temp	rw	The low-temperature limit of the 
proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can 
go from
+				0 degree C and 124 degree C in steps of
+				4 degree C. Read it out after writing to 
get
+				actual value.
+temp1_auto_point3_temp	rw	Above this temperature fan 
runs at maximum
+				speed. It can go from temp1_auto_
point2_temp.
+				It can only have certain discrete values
+				which depend on temp1_auto_point2_
temp and
+				pwm1_auto_point2_pwm. Read it out 
after
+				writing to get the actual value.
+
+temp2_auto_point1_temp	rw	Must be between 0 degree C 
and 63 degree C and
+				it defines passive cooling temperature.
+				Below this temperature the fan stops in
+				the closed loop mode.
+temp2_auto_point2_temp	rw	The low-temperature limit of the 
proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can 
go from
+				0 degree C and 124 degree C in steps
+				of 4 degree C.
+
+temp2_auto_point3_temp	rw	Above this temperature fan 
runs at maximum
+				speed. It can only have certain discrete
+				values which depend on temp2_auto_
point2_temp
+				and pwm1_auto_point2_pwm. Read it 
out after
+				writing to get actual value.
+
+
+Module parameters
+-----------------
+
+If your board has a BIOS that initializes the amc6821 correctly, you should
+load the module with: init=0.
+
+If your board BIOS doesn't initialize the chip, or you want
+different settings, you can set the following parameters:
+init=1,
+pwminv: 0 default pwm output, 1 inverts pwm output.
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..dbe180f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C  && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..6905c5e
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,1037 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for 
hardware
+	monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published 
by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty 
of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public 
License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 
0x2e,
+	0x4c, 0x4d, 0x4e, I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(
+		struct i2c_client *client,
+		const struct i2c_device_id *id);
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+static struct i2c_driver amc6821_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data {
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min, AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max, AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min, AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max, AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit, AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit, AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(\
+	struct device *dev, \
+	struct device_attribute *devattr,\
+	char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if (data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+} \
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255);
+	i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data-
>pwm1);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t get_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF1);
+	if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return -EIO;
+	}
+
+	switch (val) {
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, 
config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] * 1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr]);
+}
+
+
+#define set_temp_auto_point_temp(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	int dt;\
+	int dpwm;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch (nr) {\
+	case 0:\
+		data->name[0] = SENSORS_LIMIT(val / 1000, 0,\
+				data->temp1_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0,\
+				data->temp2_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+		data->valid = 0;\
+		if (i2c_smbus_write_byte_data(\
+					client,\
+					AMC6821_REG_PSV_TEMP,\
+					data->name[0])) {\
+				dev_err(&client->dev,\
+					"Register write error, aborting.
\n");\
+				count = -EIO;\
+		} \
+		goto EXIT;\
+		break;\
+	case 1:\
+		data->name[1] = SENSORS_LIMIT(\
+					val / 1000,\
+					(data->name[0] & 0x7C) + 4,\
+					124);\
+		data->name[1] &= 0x7C;\
+		data->name[2] = SENSORS_LIMIT(\
+					data->name[2], data->name[1] + 
1,\
+					255);\
+		break;\
+	case 2:\
+		data->name[2] = SENSORS_LIMIT(\
+					val / 1000,\
+					data->name[1]+1,\
+					255); \
+		break;\
+	} \
+	dt = data->name[2]-data->name[1];\
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_
point_pwm[1];\
+	for (tmp = 4; tmp > 0; tmp--) {\
+		if (dt * (0x20 >> tmp) >= dpwm)\
+			break;\
+	} \
+	tmp |= (data->name[1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_
LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_
RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+	int dt;
+	int dpwm;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[1] = SENSORS_LIMIT(val, 0, 254);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_
TEMP,
+			data->pwm1_auto_point_pwm[1])) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_
point_pwm[1];
+	dt = data->temp1_auto_point_temp[2]-data->temp1_auto_point_
temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp1_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dt = data->temp2_auto_point_temp[2]-data->temp2_auto_point_
temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp2_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if (0 == data->name)\
+		return sprintf(buf, "0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf, size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1 > val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 2:
+		config &= ~AMC6821_CONF4_PSPR;
+		data->fan1_div = 2;
+		break;
+	case 4:
+		config |= AMC6821_CONF4_PSPR;
+		data->fan1_div = 4;
+		break;
+	default:
+		mutex_unlock(&data->update_lock);
+		return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, 
config)) {
+		dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+		count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_
temp1_min,
+	set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_
temp1_max,
+	set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_
temp1_crit,
+	set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO,
+	get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
+	get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
+	get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR,
+	get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_
temp2_min,
+	set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_
temp2_max,
+	set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_
temp2_crit,
+	set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO,
+	get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO,
+	get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO,
+	get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO,
+	get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
+	get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR,
+	get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
+	get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, 
set_pwm1, 0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+	get_pwm1_enable, set_pwm1_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_
IRUGO,
+	get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO,
+	get_pwm1_auto_channels_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO,
+	get_temp1_auto_point_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_
IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_
IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", 
kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_
DATA)) {
+		dev_dbg(&adapter->dev,
+		"max6650: I2C bus doesn't support byte read mode, 
skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_
ID) &
+			0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_
COMP_ID) &
+			0xB6))) {
+			dev_dbg(&adapter->dev,
+				"amc6821: detection failed at 0x%02x.
\n",
+				address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", 
address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(
+	struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if (init) {
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_
CONF4,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_
CONF3,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client,
+				AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv)
+			config |= AMC6821_CONF1_PWMINV;
+		else
+			config &= ~AMC6821_CONF1_PWMINV;
+
+		if (i2c_smbus_write_byte_data(
+				client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) ||
+			!data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_
PSPR ? 4 : 2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_
data(client,
+			AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp1_auto_point_temp[0] =
+			i2c_smbus_read_byte_data(client,
+					AMC6821_REG_PSV_TEMP);
+		data->temp2_auto_point_temp[0] =
+				data->temp1_auto_point_temp[0];
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp1_auto_point_temp[2] =
+				data->temp1_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp2_auto_point_temp[2] =
+				data->temp2_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp2_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF1);
+		reg = (reg >> 5) & 0x3;
+		switch (reg) {
+		case 0: /*open loop: software sets pwm1*/
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 1;
+			break;
+		case 2: /*closed loop: remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 2;
+			data->pwm1_enable = 2;
+			break;
+		case 3: /*closed loop: local and remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 3;
+			data->pwm1_enable = 3;
+			break;
+		case 1: /*semi-open loop: software sets rpm, chip 
controls pwm1,
+			  *currently not implemented
+			  */
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 0;
+			break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

[-- Attachment #2: amc6821.diff --]
[-- Type: application/octet-stream, Size: 34830 bytes --]

diff --git a/Documentation/hwmon/amc6821 b/Documentation/hwmon/amc6821
new file mode 100644
index 0000000..b7cba86
--- /dev/null
+++ b/Documentation/hwmon/amc6821
@@ -0,0 +1,101 @@
+Kernel driver amc6821
+=====================
+
+Supported chips:
+	Prefix: 'amc6821'
+	Addresses scanned: 0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, 0x4c, 0x4d, 0x4e
+	Datasheet: http://focus.ti.com/docs/prod/folders/print/amc6821.html
+
+Authors:
+	Tomaz Mertelj <tomaz.mertelj@guest.arnes.si>
+
+
+Description
+-----------
+
+This driver implements support for the Texas Instruments amc6821 chip.
+The chip has one on-chip and one remote temperature sensor and one pwm fan
+regulator.
+The pwm can be controlled either from software or automatically.
+
+The driver provides the following sensor accesses in sysfs:
+
+temp1_input		ro	on-chip temperature
+temp1_min		rw	"
+temp1_max		rw	"
+temp1_crit	 	rw	"
+temp1_min_alarm		ro	"
+temp1_max_alarm		ro	"
+temp1_crit_alarm	ro	"
+
+temp2_input		ro	remote temperature
+temp2_min		rw	"
+temp2_max		rw	"
+temp2_crit	 	rw	"
+temp2_min_alarm		ro	"
+temp2_max_alarm		ro	"
+temp2_crit_alarm	ro	"
+temp2_fault		ro	"
+
+fan1_input	 	ro	tachometer speed
+fan1_min		rw	"
+fan1_max		rw	"
+fan1_fault	 	ro	"
+fan1_div		rw	Fan divisor can be either 2 or 4.
+
+pwm1			rw	pwm1
+pwm1_enable		rw	regulator mode, 1=open loop, 2=fan controlled
+				by remote temperature, 3=fan controlled by
+				combination of on-chip temperature and
+				remote-sensor temperature,
+pwm1_auto_channels_temp ro	1 if pwm_enable==2, 3 if pwm_enable==3
+pwm1_auto_point1_pwm	ro	Hardwired to 0, shared for both
+				temperature channels.
+pwm1_auto_point2_pwm	rw	This value, shared for both temperature
+				channels.
+pwm1_auto_point3_pwm	rw	Hardwired to 255, shared for both
+				temperature channels.
+
+temp1_auto_point1_temp	ro	Hardwired to temp2_auto_point1_temp
+				which is rw. Below this temperature fan stops.
+temp1_auto_point2_temp	rw	The low-temperature limit of the proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can go from
+				0 degree C and 124 degree C in steps of
+				4 degree C. Read it out after writing to get
+				actual value.
+temp1_auto_point3_temp	rw	Above this temperature fan runs at maximum
+				speed. It can go from temp1_auto_point2_temp.
+				It can only have certain discrete values
+				which depend on temp1_auto_point2_temp and
+				pwm1_auto_point2_pwm. Read it out after
+				writing to get the actual value.
+
+temp2_auto_point1_temp	rw	Must be between 0 degree C and 63 degree C and
+				it defines passive cooling temperature.
+				Below this temperature the fan stops in
+				the closed loop mode.
+temp2_auto_point2_temp	rw	The low-temperature limit of the proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can go from
+				0 degree C and 124 degree C in steps
+				of 4 degree C.
+
+temp2_auto_point3_temp	rw	Above this temperature fan runs at maximum
+				speed. It can only have certain discrete
+				values which depend on temp2_auto_point2_temp
+				and pwm1_auto_point2_pwm. Read it out after
+				writing to get actual value.
+
+
+Module parameters
+-----------------
+
+If your board has a BIOS that initializes the amc6821 correctly, you should
+load the module with: init=0.
+
+If your board BIOS doesn't initialize the chip, or you want
+different settings, you can set the following parameters:
+init=1,
+pwminv: 0 default pwm output, 1 inverts pwm output.
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..dbe180f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C  && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..6905c5e
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,1037 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+	monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e,
+	0x4c, 0x4d, 0x4e, I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(
+		struct i2c_client *client,
+		const struct i2c_device_id *id);
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+static struct i2c_driver amc6821_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data {
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min, AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max, AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min, AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max, AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit, AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit, AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(\
+	struct device *dev, \
+	struct device_attribute *devattr,\
+	char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if (data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+} \
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255);
+	i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t get_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+	if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return -EIO;
+	}
+
+	switch (val) {
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] * 1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr]);
+}
+
+
+#define set_temp_auto_point_temp(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	int dt;\
+	int dpwm;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch (nr) {\
+	case 0:\
+		data->name[0] = SENSORS_LIMIT(val / 1000, 0,\
+				data->temp1_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0,\
+				data->temp2_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+		data->valid = 0;\
+		if (i2c_smbus_write_byte_data(\
+					client,\
+					AMC6821_REG_PSV_TEMP,\
+					data->name[0])) {\
+				dev_err(&client->dev,\
+					"Register write error, aborting.\n");\
+				count = -EIO;\
+		} \
+		goto EXIT;\
+		break;\
+	case 1:\
+		data->name[1] = SENSORS_LIMIT(\
+					val / 1000,\
+					(data->name[0] & 0x7C) + 4,\
+					124);\
+		data->name[1] &= 0x7C;\
+		data->name[2] = SENSORS_LIMIT(\
+					data->name[2], data->name[1] + 1,\
+					255);\
+		break;\
+	case 2:\
+		data->name[2] = SENSORS_LIMIT(\
+					val / 1000,\
+					data->name[1]+1,\
+					255); \
+		break;\
+	} \
+	dt = data->name[2]-data->name[1];\
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];\
+	for (tmp = 4; tmp > 0; tmp--) {\
+		if (dt * (0x20 >> tmp) >= dpwm)\
+			break;\
+	} \
+	tmp |= (data->name[1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+	int dt;
+	int dpwm;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[1] = SENSORS_LIMIT(val, 0, 254);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP,
+			data->pwm1_auto_point_pwm[1])) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];
+	dt = data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp1_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dt = data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp2_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if (0 == data->name)\
+		return sprintf(buf, "0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf, size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1 > val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 2:
+		config &= ~AMC6821_CONF4_PSPR;
+		data->fan1_div = 2;
+		break;
+	case 4:
+		config |= AMC6821_CONF4_PSPR;
+		data->fan1_div = 4;
+		break;
+	default:
+		mutex_unlock(&data->update_lock);
+		return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+		dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+		count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min,
+	set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max,
+	set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit,
+	set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO,
+	get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
+	get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
+	get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR,
+	get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min,
+	set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max,
+	set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit,
+	set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO,
+	get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO,
+	get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO,
+	get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO,
+	get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
+	get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR,
+	get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
+	get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1, 0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+	get_pwm1_enable, set_pwm1_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO,
+	get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO,
+	get_pwm1_auto_channels_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO,
+	get_temp1_auto_point_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_dbg(&adapter->dev,
+		"max6650: I2C bus doesn't support byte read mode, skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) &
+			0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) &
+			0xB6))) {
+			dev_dbg(&adapter->dev,
+				"amc6821: detection failed at 0x%02x.\n",
+				address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(
+	struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if (init) {
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client,
+				AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv)
+			config |= AMC6821_CONF1_PWMINV;
+		else
+			config &= ~AMC6821_CONF1_PWMINV;
+
+		if (i2c_smbus_write_byte_data(
+				client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) ||
+			!data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 : 2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp1_auto_point_temp[0] =
+			i2c_smbus_read_byte_data(client,
+					AMC6821_REG_PSV_TEMP);
+		data->temp2_auto_point_temp[0] =
+				data->temp1_auto_point_temp[0];
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp1_auto_point_temp[2] =
+				data->temp1_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp2_auto_point_temp[2] =
+				data->temp2_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp2_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+		reg = (reg >> 5) & 0x3;
+		switch (reg) {
+		case 0: /*open loop: software sets pwm1*/
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 1;
+			break;
+		case 2: /*closed loop: remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 2;
+			data->pwm1_enable = 2;
+			break;
+		case 3: /*closed loop: local and remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 3;
+			data->pwm1_enable = 3;
+			break;
+		case 1: /*semi-open loop: software sets rpm, chip controls pwm1,
+			  *currently not implemented
+			  */
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 0;
+			break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

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

* Re: [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-05 12:08   ` tomaz.mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: tomaz.mertelj @ 2009-09-05 12:08 UTC (permalink / raw)
  To: Andrew Morton; +Cc: lm-sensors, linux-kernel

[-- Attachment #1: Mail message body --]
[-- Type: text/plain, Size: 36956 bytes --]


>- The attachment uses \r\n line termination and caused me quite a bit
>  of trouble before I worked out what was going on.

I am sorry. All my e-mail access is from MS-windows and I can not figure 
how to get rid of \r\n. For convenience I attach also the raw patch with \n 
only in encoded format. 

>- The changelog is missing your Signed-off-by:.  Please read up on
>  this in Documentation/SubmittingPatches.

Fixed.

>- The driver needs lots and lots of trivial coding-style fixes. 
>  Please take a look at some similar drivers and also use the
>  scripts/checkpatch.pl tool.

Fixed.


I also added documentation file and fixed bugs related to setting the trip 
points.

Best Regards,

Tomaz

--
Signed-off-by: tomaz.mertelj@guest.arnes.si

diff --git a/Documentation/hwmon/amc6821 b/Documentation/hwmon/
amc6821
new file mode 100644
index 0000000..b7cba86
--- /dev/null
+++ b/Documentation/hwmon/amc6821
@@ -0,0 +1,101 @@
+Kernel driver amc6821
+=====================
+
+Supported chips:
+	Prefix: 'amc6821'
+	Addresses scanned: 0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, 0x4c, 
0x4d, 0x4e
+	Datasheet: http://focus.ti.com/docs/prod/folders/print/amc6821.html
+
+Authors:
+	Tomaz Mertelj <tomaz.mertelj@guest.arnes.si>
+
+
+Description
+-----------
+
+This driver implements support for the Texas Instruments amc6821 chip.
+The chip has one on-chip and one remote temperature sensor and one 
pwm fan
+regulator.
+The pwm can be controlled either from software or automatically.
+
+The driver provides the following sensor accesses in sysfs:
+
+temp1_input		ro	on-chip temperature
+temp1_min		rw	"
+temp1_max		rw	"
+temp1_crit	 	rw	"
+temp1_min_alarm		ro	"
+temp1_max_alarm		ro	"
+temp1_crit_alarm	ro	"
+
+temp2_input		ro	remote temperature
+temp2_min		rw	"
+temp2_max		rw	"
+temp2_crit	 	rw	"
+temp2_min_alarm		ro	"
+temp2_max_alarm		ro	"
+temp2_crit_alarm	ro	"
+temp2_fault		ro	"
+
+fan1_input	 	ro	tachometer speed
+fan1_min		rw	"
+fan1_max		rw	"
+fan1_fault	 	ro	"
+fan1_div		rw	Fan divisor can be either 2 or 4.
+
+pwm1			rw	pwm1
+pwm1_enable		rw	regulator mode, 1=open loop, 2=fan 
controlled
+				by remote temperature, 3=fan controlled 
by
+				combination of on-chip temperature and
+				remote-sensor temperature,
+pwm1_auto_channels_temp ro	1 if pwm_enable==2, 3 if pwm_
enable==3
+pwm1_auto_point1_pwm	ro	Hardwired to 0, shared for both
+				temperature channels.
+pwm1_auto_point2_pwm	rw	This value, shared for both 
temperature
+				channels.
+pwm1_auto_point3_pwm	rw	Hardwired to 255, shared for 
both
+				temperature channels.
+
+temp1_auto_point1_temp	ro	Hardwired to temp2_auto_
point1_temp
+				which is rw. Below this temperature fan 
stops.
+temp1_auto_point2_temp	rw	The low-temperature limit of the 
proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can 
go from
+				0 degree C and 124 degree C in steps of
+				4 degree C. Read it out after writing to 
get
+				actual value.
+temp1_auto_point3_temp	rw	Above this temperature fan 
runs at maximum
+				speed. It can go from temp1_auto_
point2_temp.
+				It can only have certain discrete values
+				which depend on temp1_auto_point2_
temp and
+				pwm1_auto_point2_pwm. Read it out 
after
+				writing to get the actual value.
+
+temp2_auto_point1_temp	rw	Must be between 0 degree C 
and 63 degree C and
+				it defines passive cooling temperature.
+				Below this temperature the fan stops in
+				the closed loop mode.
+temp2_auto_point2_temp	rw	The low-temperature limit of the 
proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can 
go from
+				0 degree C and 124 degree C in steps
+				of 4 degree C.
+
+temp2_auto_point3_temp	rw	Above this temperature fan 
runs at maximum
+				speed. It can only have certain discrete
+				values which depend on temp2_auto_
point2_temp
+				and pwm1_auto_point2_pwm. Read it 
out after
+				writing to get actual value.
+
+
+Module parameters
+-----------------
+
+If your board has a BIOS that initializes the amc6821 correctly, you should
+load the module with: init=0.
+
+If your board BIOS doesn't initialize the chip, or you want
+different settings, you can set the following parameters:
+init=1,
+pwminv: 0 default pwm output, 1 inverts pwm output.
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..dbe180f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C  && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..6905c5e
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,1037 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for 
hardware
+	monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published 
by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty 
of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public 
License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 
0x2e,
+	0x4c, 0x4d, 0x4e, I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(
+		struct i2c_client *client,
+		const struct i2c_device_id *id);
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+static struct i2c_driver amc6821_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data {
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min, AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max, AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min, AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max, AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit, AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit, AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(\
+	struct device *dev, \
+	struct device_attribute *devattr,\
+	char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if (data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+} \
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255);
+	i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data-
>pwm1);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t get_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF1);
+	if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return -EIO;
+	}
+
+	switch (val) {
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, 
config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] * 1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr]);
+}
+
+
+#define set_temp_auto_point_temp(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	int dt;\
+	int dpwm;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch (nr) {\
+	case 0:\
+		data->name[0] = SENSORS_LIMIT(val / 1000, 0,\
+				data->temp1_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0,\
+				data->temp2_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+		data->valid = 0;\
+		if (i2c_smbus_write_byte_data(\
+					client,\
+					AMC6821_REG_PSV_TEMP,\
+					data->name[0])) {\
+				dev_err(&client->dev,\
+					"Register write error, aborting.
\n");\
+				count = -EIO;\
+		} \
+		goto EXIT;\
+		break;\
+	case 1:\
+		data->name[1] = SENSORS_LIMIT(\
+					val / 1000,\
+					(data->name[0] & 0x7C) + 4,\
+					124);\
+		data->name[1] &= 0x7C;\
+		data->name[2] = SENSORS_LIMIT(\
+					data->name[2], data->name[1] + 
1,\
+					255);\
+		break;\
+	case 2:\
+		data->name[2] = SENSORS_LIMIT(\
+					val / 1000,\
+					data->name[1]+1,\
+					255); \
+		break;\
+	} \
+	dt = data->name[2]-data->name[1];\
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_
point_pwm[1];\
+	for (tmp = 4; tmp > 0; tmp--) {\
+		if (dt * (0x20 >> tmp) >= dpwm)\
+			break;\
+	} \
+	tmp |= (data->name[1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_
LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_
RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+	int dt;
+	int dpwm;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[1] = SENSORS_LIMIT(val, 0, 254);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_
TEMP,
+			data->pwm1_auto_point_pwm[1])) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_
point_pwm[1];
+	dt = data->temp1_auto_point_temp[2]-data->temp1_auto_point_
temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp1_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dt = data->temp2_auto_point_temp[2]-data->temp2_auto_point_
temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp2_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if (0 == data->name)\
+		return sprintf(buf, "0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf, size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1 > val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 2:
+		config &= ~AMC6821_CONF4_PSPR;
+		data->fan1_div = 2;
+		break;
+	case 4:
+		config |= AMC6821_CONF4_PSPR;
+		data->fan1_div = 4;
+		break;
+	default:
+		mutex_unlock(&data->update_lock);
+		return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, 
config)) {
+		dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+		count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_
temp1_min,
+	set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_
temp1_max,
+	set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_
temp1_crit,
+	set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO,
+	get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
+	get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
+	get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR,
+	get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_
temp2_min,
+	set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_
temp2_max,
+	set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_
temp2_crit,
+	set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO,
+	get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO,
+	get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO,
+	get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO,
+	get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
+	get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR,
+	get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, 
NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
+	get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, 
set_pwm1, 0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+	get_pwm1_enable, set_pwm1_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_
IRUGO,
+	get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO,
+	get_pwm1_auto_channels_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO,
+	get_temp1_auto_point_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_
IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_
IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_
IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", 
kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_
DATA)) {
+		dev_dbg(&adapter->dev,
+		"max6650: I2C bus doesn't support byte read mode, 
skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_
ID) &
+			0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_
COMP_ID) &
+			0xB6))) {
+			dev_dbg(&adapter->dev,
+				"amc6821: detection failed at 0x%02x.
\n",
+				address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", 
address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(
+	struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if (init) {
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_
CONF4,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_
CONF3,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client,
+				AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_
REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n")
;
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv)
+			config |= AMC6821_CONF1_PWMINV;
+		else
+			config &= ~AMC6821_CONF1_PWMINV;
+
+		if (i2c_smbus_write_byte_data(
+				client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) ||
+			!data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_
PSPR ? 4 : 2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_
data(client,
+			AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp1_auto_point_temp[0] =
+			i2c_smbus_read_byte_data(client,
+					AMC6821_REG_PSV_TEMP);
+		data->temp2_auto_point_temp[0] =
+				data->temp1_auto_point_temp[0];
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp1_auto_point_temp[2] =
+				data->temp1_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp2_auto_point_temp[2] =
+				data->temp2_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp2_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_
CONF1);
+		reg = (reg >> 5) & 0x3;
+		switch (reg) {
+		case 0: /*open loop: software sets pwm1*/
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 1;
+			break;
+		case 2: /*closed loop: remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 2;
+			data->pwm1_enable = 2;
+			break;
+		case 3: /*closed loop: local and remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 3;
+			data->pwm1_enable = 3;
+			break;
+		case 1: /*semi-open loop: software sets rpm, chip 
controls pwm1,
+			  *currently not implemented
+			  */
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 0;
+			break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

[-- Attachment #2: amc6821.diff --]
[-- Type: application/octet-stream, Size: 34830 bytes --]

diff --git a/Documentation/hwmon/amc6821 b/Documentation/hwmon/amc6821
new file mode 100644
index 0000000..b7cba86
--- /dev/null
+++ b/Documentation/hwmon/amc6821
@@ -0,0 +1,101 @@
+Kernel driver amc6821
+=====================
+
+Supported chips:
+	Prefix: 'amc6821'
+	Addresses scanned: 0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, 0x4c, 0x4d, 0x4e
+	Datasheet: http://focus.ti.com/docs/prod/folders/print/amc6821.html
+
+Authors:
+	Tomaz Mertelj <tomaz.mertelj@guest.arnes.si>
+
+
+Description
+-----------
+
+This driver implements support for the Texas Instruments amc6821 chip.
+The chip has one on-chip and one remote temperature sensor and one pwm fan
+regulator.
+The pwm can be controlled either from software or automatically.
+
+The driver provides the following sensor accesses in sysfs:
+
+temp1_input		ro	on-chip temperature
+temp1_min		rw	"
+temp1_max		rw	"
+temp1_crit	 	rw	"
+temp1_min_alarm		ro	"
+temp1_max_alarm		ro	"
+temp1_crit_alarm	ro	"
+
+temp2_input		ro	remote temperature
+temp2_min		rw	"
+temp2_max		rw	"
+temp2_crit	 	rw	"
+temp2_min_alarm		ro	"
+temp2_max_alarm		ro	"
+temp2_crit_alarm	ro	"
+temp2_fault		ro	"
+
+fan1_input	 	ro	tachometer speed
+fan1_min		rw	"
+fan1_max		rw	"
+fan1_fault	 	ro	"
+fan1_div		rw	Fan divisor can be either 2 or 4.
+
+pwm1			rw	pwm1
+pwm1_enable		rw	regulator mode, 1=open loop, 2=fan controlled
+				by remote temperature, 3=fan controlled by
+				combination of on-chip temperature and
+				remote-sensor temperature,
+pwm1_auto_channels_temp ro	1 if pwm_enable==2, 3 if pwm_enable==3
+pwm1_auto_point1_pwm	ro	Hardwired to 0, shared for both
+				temperature channels.
+pwm1_auto_point2_pwm	rw	This value, shared for both temperature
+				channels.
+pwm1_auto_point3_pwm	rw	Hardwired to 255, shared for both
+				temperature channels.
+
+temp1_auto_point1_temp	ro	Hardwired to temp2_auto_point1_temp
+				which is rw. Below this temperature fan stops.
+temp1_auto_point2_temp	rw	The low-temperature limit of the proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can go from
+				0 degree C and 124 degree C in steps of
+				4 degree C. Read it out after writing to get
+				actual value.
+temp1_auto_point3_temp	rw	Above this temperature fan runs at maximum
+				speed. It can go from temp1_auto_point2_temp.
+				It can only have certain discrete values
+				which depend on temp1_auto_point2_temp and
+				pwm1_auto_point2_pwm. Read it out after
+				writing to get the actual value.
+
+temp2_auto_point1_temp	rw	Must be between 0 degree C and 63 degree C and
+				it defines passive cooling temperature.
+				Below this temperature the fan stops in
+				the closed loop mode.
+temp2_auto_point2_temp	rw	The low-temperature limit of the proportional
+				range. Below this temperature
+				pwm1 = pwm1_auto_point2_pwm. It can go from
+				0 degree C and 124 degree C in steps
+				of 4 degree C.
+
+temp2_auto_point3_temp	rw	Above this temperature fan runs at maximum
+				speed. It can only have certain discrete
+				values which depend on temp2_auto_point2_temp
+				and pwm1_auto_point2_pwm. Read it out after
+				writing to get actual value.
+
+
+Module parameters
+-----------------
+
+If your board has a BIOS that initializes the amc6821 correctly, you should
+load the module with: init=0.
+
+If your board BIOS doesn't initialize the chip, or you want
+different settings, you can set the following parameters:
+init=1,
+pwminv: 0 default pwm output, 1 inverts pwm output.
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2d50166..dbe180f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -777,6 +777,16 @@ config SENSORS_ADS7828
 	  This driver can also be built as a module.  If so, the module
 	  will be called ads7828.
 
+config SENSORS_AMC6821
+	tristate "Texas Instruments AMC6821"
+	depends on I2C  && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Texas Instruments
+	  AMC6821 hardware monitoring chips.
+
+	  This driver can also be build as a module.  If so, the module
+	  will be called amc6821.
+
 config SENSORS_THMC50
 	tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b793dce..431de76 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
 obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
+obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
new file mode 100644
index 0000000..6905c5e
--- /dev/null
+++ b/drivers/hwmon/amc6821.c
@@ -0,0 +1,1037 @@
+/*
+	amc6821.c - Part of lm_sensors, Linux kernel modules for hardware
+	monitoring
+	Copyright (C) 2009 T. Mertelj <tomaz.mertelj@guest.arnes.si>
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+#include <linux/kernel.h>	/* Needed for KERN_INFO */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+
+/*
+ * Addresses to scan.
+ */
+
+static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e,
+	0x4c, 0x4d, 0x4e, I2C_CLIENT_END};
+
+
+
+/*
+ * Insmod parameters
+ */
+
+static int pwminv = 0;	/*Inverted PWM output. */
+module_param(pwminv, int, S_IRUGO);
+
+static int init = 1; /*Power-on initialization.*/
+module_param(init, int, S_IRUGO);
+
+
+I2C_CLIENT_INSMOD_1(amc6821);
+
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_LLIMITH 0x11
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_HLIMITH 0x13
+
+#define AMC6821_CONF1_START 0x01
+#define AMC6821_CONF1_FAN_INT_EN 0x02
+#define AMC6821_CONF1_FANIE 0x04
+#define AMC6821_CONF1_PWMINV 0x08
+#define AMC6821_CONF1_FAN_FAULT_EN 0x10
+#define AMC6821_CONF1_FDRC0 0x20
+#define AMC6821_CONF1_FDRC1 0x40
+#define AMC6821_CONF1_THERMOVIE 0x80
+
+#define AMC6821_CONF2_PWM_EN 0x01
+#define AMC6821_CONF2_TACH_MODE 0x02
+#define AMC6821_CONF2_TACH_EN 0x04
+#define AMC6821_CONF2_RTFIE 0x08
+#define AMC6821_CONF2_LTOIE 0x10
+#define AMC6821_CONF2_RTOIE 0x20
+#define AMC6821_CONF2_PSVIE 0x40
+#define AMC6821_CONF2_RST 0x80
+
+#define AMC6821_CONF3_THERM_FAN_EN 0x80
+#define AMC6821_CONF3_REV_MASK 0x0F
+
+#define AMC6821_CONF4_OVREN 0x10
+#define AMC6821_CONF4_TACH_FAST 0x20
+#define AMC6821_CONF4_PSPR 0x40
+#define AMC6821_CONF4_MODE 0x80
+
+#define AMC6821_STAT1_RPM_ALARM 0x01
+#define AMC6821_STAT1_FANS 0x02
+#define AMC6821_STAT1_RTH 0x04
+#define AMC6821_STAT1_RTL 0x08
+#define AMC6821_STAT1_R_THERM 0x10
+#define AMC6821_STAT1_RTF 0x20
+#define AMC6821_STAT1_LTH 0x40
+#define AMC6821_STAT1_LTL 0x80
+
+#define AMC6821_STAT2_RTC 0x08
+#define AMC6821_STAT2_LTC 0x10
+#define AMC6821_STAT2_LPSV 0x20
+#define AMC6821_STAT2_L_THERM 0x40
+#define AMC6821_STAT2_THERM_IN 0x80
+
+
+static int amc6821_probe(
+		struct i2c_client *client,
+		const struct i2c_device_id *id);
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info);
+static int amc6821_init_client(struct i2c_client *client);
+static int amc6821_remove(struct i2c_client *client);
+static struct amc6821_data *amc6821_update_device(struct device *dev);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+static const struct i2c_device_id amc6821_id[] = {
+	{ "amc6821", amc6821 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, amc6821_id);
+
+static struct i2c_driver amc6821_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "amc6821",
+	},
+	.probe = amc6821_probe,
+	.remove = amc6821_remove,
+	.id_table = amc6821_id,
+	.detect = amc6821_detect,
+	.address_data = &addr_data,
+};
+
+
+/*
+ * Client data (each client gets its own)
+  */
+
+struct amc6821_data {
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	char valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* register values */
+	int temp1_input;
+	int temp1_min;
+	int temp1_max;
+	int temp1_crit;
+
+	int temp2_input;
+	int temp2_min;
+	int temp2_max;
+	int temp2_crit;
+
+	u16 fan1_input;
+	u16 fan1_min;
+	u16 fan1_max;
+	u8 fan1_div;
+
+	u8 pwm1;
+	u8 temp1_auto_point_temp[3];
+	u8 temp2_auto_point_temp[3];
+	u8 pwm1_auto_point_pwm[3];
+	u8 pwm1_enable;
+	u8 pwm1_auto_channels_temp;
+
+	u8 stat1;
+	u8 stat2;
+};
+
+
+#define get_temp_para(name) \
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name * 1000);\
+}
+
+get_temp_para(temp1_input);
+get_temp_para(temp1_min);
+get_temp_para(temp1_max);
+get_temp_para(temp2_input);
+get_temp_para(temp2_min);
+get_temp_para(temp2_max);
+get_temp_para(temp1_crit);
+get_temp_para(temp2_crit);
+
+#define set_temp_para(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	\
+	mutex_lock(&data->update_lock); \
+	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_para(temp1_min, AMC6821_REG_LTEMP_LIMIT_MIN);
+set_temp_para(temp1_max, AMC6821_REG_LTEMP_LIMIT_MAX);
+set_temp_para(temp2_min, AMC6821_REG_RTEMP_LIMIT_MIN);
+set_temp_para(temp2_max, AMC6821_REG_RTEMP_LIMIT_MAX);
+set_temp_para(temp1_crit, AMC6821_REG_LTEMP_CRIT);
+set_temp_para(temp2_crit, AMC6821_REG_RTEMP_CRIT);
+
+#define get_temp_alarm(name, reg, mask)\
+static ssize_t get_##name(\
+	struct device *dev, \
+	struct device_attribute *devattr,\
+	char *buf)\
+{\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	if (data->reg &  mask)\
+		return sprintf(buf, "1");\
+	else	\
+		return sprintf(buf, "0");\
+} \
+
+get_temp_alarm(temp1_min_alarm, stat1,	AMC6821_STAT1_LTL)
+get_temp_alarm(temp1_max_alarm, stat1, AMC6821_STAT1_LTH)
+get_temp_alarm(temp2_min_alarm, stat1, AMC6821_STAT1_RTL)
+get_temp_alarm(temp2_max_alarm, stat1, AMC6821_STAT1_RTH)
+get_temp_alarm(temp1_crit_alarm, stat2, AMC6821_STAT2_LTC)
+get_temp_alarm(temp2_crit_alarm, stat2, AMC6821_STAT2_RTC)
+
+
+static ssize_t get_temp2_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_RTF)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+static ssize_t get_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1);
+}
+
+static ssize_t set_pwm1(
+		struct device *dev,
+		struct device_attribute *devattr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->pwm1 = SENSORS_LIMIT(val , 0, 255);
+	i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t get_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_enable);
+}
+
+static ssize_t set_pwm1_enable(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+	if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return -EIO;
+	}
+
+	switch (val) {
+	case 1:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config &= ~AMC6821_CONF1_FDRC1;
+		break;
+	case 2:
+		config &= ~AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	case 3:
+		config |= AMC6821_CONF1_FDRC0;
+		config |= AMC6821_CONF1_FDRC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mutex_lock(&data->update_lock);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+static ssize_t get_pwm1_auto_channels_temp(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+}
+
+
+#define get_auto_point(name)\
+static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{\
+	int nr = to_sensor_dev_attr(devattr)->index;\
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	return sprintf(buf, "%d\n", data->name[nr] * 1000);\
+}
+
+get_auto_point(temp1_auto_point_temp);
+get_auto_point(temp2_auto_point_temp);
+
+static ssize_t get_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	int nr = to_sensor_dev_attr(devattr)->index;
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[nr]);
+}
+
+
+#define set_temp_auto_point_temp(name, reg)\
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf,\
+		size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = amc6821_update_device(dev);\
+	int nr = to_sensor_dev_attr(attr)->index;\
+	int val = simple_strtol(buf, NULL, 10); \
+	u8 tmp;\
+	int dt;\
+	int dpwm;\
+	\
+	mutex_lock(&data->update_lock); \
+	switch (nr) {\
+	case 0:\
+		data->name[0] = SENSORS_LIMIT(val / 1000, 0,\
+				data->temp1_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0,\
+				data->temp2_auto_point_temp[1]);\
+		data->name[0] = SENSORS_LIMIT(data->name[0], 0, 63); \
+		data->valid = 0;\
+		if (i2c_smbus_write_byte_data(\
+					client,\
+					AMC6821_REG_PSV_TEMP,\
+					data->name[0])) {\
+				dev_err(&client->dev,\
+					"Register write error, aborting.\n");\
+				count = -EIO;\
+		} \
+		goto EXIT;\
+		break;\
+	case 1:\
+		data->name[1] = SENSORS_LIMIT(\
+					val / 1000,\
+					(data->name[0] & 0x7C) + 4,\
+					124);\
+		data->name[1] &= 0x7C;\
+		data->name[2] = SENSORS_LIMIT(\
+					data->name[2], data->name[1] + 1,\
+					255);\
+		break;\
+	case 2:\
+		data->name[2] = SENSORS_LIMIT(\
+					val / 1000,\
+					data->name[1]+1,\
+					255); \
+		break;\
+	} \
+	dt = data->name[2]-data->name[1];\
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];\
+	for (tmp = 4; tmp > 0; tmp--) {\
+		if (dt * (0x20 >> tmp) >= dpwm)\
+			break;\
+	} \
+	tmp |= (data->name[1] & 0x7C) << 1;\
+	if (i2c_smbus_write_byte_data(client, reg, tmp)) {\
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	data->valid = 0;\
+EXIT:\
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+set_temp_auto_point_temp(temp1_auto_point_temp, AMC6821_REG_LTEMP_FAN_CTRL);
+set_temp_auto_point_temp(temp2_auto_point_temp, AMC6821_REG_RTEMP_FAN_CTRL);
+
+static ssize_t set_pwm1_auto_point_pwm(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	u8 tmp;
+	int dt;
+	int dpwm;
+
+	mutex_lock(&data->update_lock);
+	data->pwm1_auto_point_pwm[1] = SENSORS_LIMIT(val, 0, 254);
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP,
+			data->pwm1_auto_point_pwm[1])) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];
+	dt = data->temp1_auto_point_temp[2]-data->temp1_auto_point_temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp1_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+
+	dt = data->temp2_auto_point_temp[2]-data->temp2_auto_point_temp[1];
+	for (tmp = 4; tmp > 0; tmp--) {
+		if (dt * (0x20 >> tmp) >= dpwm)
+			break;
+	}
+	tmp |= (data->temp2_auto_point_temp[1] & 0x7C) << 1;
+	if (i2c_smbus_write_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL, tmp)) {
+		dev_err(&client->dev, "Register write error, aborting.\n");
+		count = -EIO;
+	}
+	data->valid = 0;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+#define get_fan_para(name) static ssize_t get_##name(\
+		struct device *dev,\
+		struct device_attribute *devattr,\
+		char *buf)\
+{ \
+	struct amc6821_data *data = amc6821_update_device(dev); \
+	if (0 == data->name)\
+		return sprintf(buf, "0");\
+	return sprintf(buf, "%d\n", (int)(6000000 / data->name)); \
+}
+
+get_fan_para(fan1_input);
+get_fan_para(fan1_min);
+get_fan_para(fan1_max);
+
+static ssize_t get_fan1_fault(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	if (data->stat1 & AMC6821_STAT1_FANS)
+		return sprintf(buf, "1");
+	else
+		return sprintf(buf, "0");
+}
+
+
+#define set_fan_para(name, reg) \
+static ssize_t set_##name(\
+		struct device *dev,\
+		struct device_attribute *attr,\
+		const char *buf, size_t count)\
+{ \
+	struct i2c_client *client = to_i2c_client(dev); \
+	struct amc6821_data *data = i2c_get_clientdata(client); \
+	int val = simple_strtol(buf, NULL, 10); \
+	val = 1 > val ? 0xFFFF : 6000000/val; \
+\
+	mutex_lock(&data->update_lock); \
+	data->name = (u16) SENSORS_LIMIT(val, 1, 0xFFFF); \
+	if (i2c_smbus_write_byte_data(client, reg, data->name & 0xFF)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	if (i2c_smbus_write_byte_data(client, reg+1, data->name >> 8)) { \
+		dev_err(&client->dev, "Register write error, aborting.\n");\
+		count = -EIO;\
+	} \
+	mutex_unlock(&data->update_lock); \
+	return count; \
+}
+
+
+set_fan_para(fan1_min, AMC6821_REG_TACH_LLIMITL);
+set_fan_para(fan1_max, AMC6821_REG_TACH_HLIMITL);
+
+
+static ssize_t get_fan1_div(
+		struct device *dev,
+		struct device_attribute *devattr,
+		char *buf)
+{
+	struct amc6821_data *data = amc6821_update_device(dev);
+	return sprintf(buf, "%d\n", data->fan1_div);
+}
+
+static ssize_t set_fan1_div(
+		struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int val = simple_strtol(buf, NULL, 10);
+	int config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+	if (config < 0) {
+		dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+		return -EIO;
+	}
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 2:
+		config &= ~AMC6821_CONF4_PSPR;
+		data->fan1_div = 2;
+		break;
+	case 4:
+		config |= AMC6821_CONF4_PSPR;
+		data->fan1_div = 4;
+		break;
+	default:
+		mutex_unlock(&data->update_lock);
+		return -EINVAL;
+	}
+	if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
+		dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+		count = -EIO;
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp1_min,
+	set_temp1_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp1_max,
+	set_temp1_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp1_crit,
+	set_temp1_crit, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO,
+	get_temp1_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
+	get_temp1_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
+	get_temp1_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO | S_IWUSR,
+	get_temp2_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp2_min,
+	set_temp2_min, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp2_max,
+	set_temp2_max, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp2_crit,
+	set_temp2_crit, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO,
+	get_temp2_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO,
+	get_temp2_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO,
+	get_temp2_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO,
+	get_temp2_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan1_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
+	get_fan1_min, set_fan1_min, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR,
+	get_fan1_max, set_fan1_max, 0);
+static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR,
+	get_fan1_div, set_fan1_div, 0);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1, 0);
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+	get_pwm1_enable, set_pwm1_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 0);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO,
+	get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO,
+	get_pwm1_auto_point_pwm, NULL, 2);
+static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO,
+	get_pwm1_auto_channels_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO,
+	get_temp1_auto_point_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp1_auto_point3_temp, S_IWUSR | S_IRUGO,
+	get_temp1_auto_point_temp, set_temp1_auto_point_temp, 2);
+
+static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 0);
+static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 1);
+static SENSOR_DEVICE_ATTR(temp2_auto_point3_temp, S_IWUSR | S_IRUGO,
+	get_temp2_auto_point_temp, set_temp2_auto_point_temp, 2);
+
+
+
+static struct attribute *amc6821_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_min.dev_attr.attr,
+	&sensor_dev_attr_fan1_max.dev_attr.attr,
+	&sensor_dev_attr_fan1_fault.dev_attr.attr,
+	&sensor_dev_attr_fan1_div.dev_attr.attr,
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_channels_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_pwm1_auto_point3_pwm.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_auto_point3_temp.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_auto_point3_temp.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group amc6821_attr_grp = {
+	.attrs = amc6821_attrs,
+};
+
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int amc6821_detect(
+		struct i2c_client *client,
+		int kind,
+		struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int address = client->addr;
+
+	dev_dbg(&adapter->dev, "amc6821_detect called, kind = %d\n", kind);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_dbg(&adapter->dev,
+		"max6650: I2C bus doesn't support byte read mode, skipping.\n");
+		return -ENODEV;
+	}
+	if ((kind < 0) &&
+		((i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID) &
+			0xDE) ||
+		(i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID) &
+			0xB6))) {
+			dev_dbg(&adapter->dev,
+				"amc6821: detection failed at 0x%02x.\n",
+				address);
+		return -ENODEV;
+	}
+
+	dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
+	strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int amc6821_probe(
+	struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct amc6821_data *data;
+	int err;
+
+	data = kzalloc(sizeof(struct amc6821_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&client->dev, "out of memory.\n");
+		return -ENOMEM;
+	}
+
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/*
+	 * Initialize the amc6821 chip
+	 */
+	err = amc6821_init_client(client);
+	if (err)
+		goto err_free;
+
+	err = sysfs_create_group(&client->dev.kobj, &amc6821_attr_grp);
+	if (err)
+		goto err_free;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (!IS_ERR(data->hwmon_dev))
+		return 0;
+
+	err = PTR_ERR(data->hwmon_dev);
+	dev_err(&client->dev, "error registering hwmon device.\n");
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+err_free:
+	kfree(data);
+	return err;
+}
+
+static int amc6821_remove(struct i2c_client *client)
+{
+	struct amc6821_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &amc6821_attr_grp);
+
+	kfree(data);
+
+	return 0;
+}
+
+
+static int amc6821_init_client(struct i2c_client *client)
+{
+	int config;
+	int err = -EIO;
+
+	if (init) {
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
+
+		if (config < 0) {
+				dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+				return err;
+		}
+
+		config |= AMC6821_CONF4_MODE;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF3_THERM_FAN_EN;
+
+		if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3,
+				config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF2_RTFIE;
+		config &= ~AMC6821_CONF2_LTOIE;
+		config &= ~AMC6821_CONF2_RTOIE;
+		if (i2c_smbus_write_byte_data(client,
+				AMC6821_REG_CONF2, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+
+		config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+
+		if (config < 0) {
+			dev_err(&client->dev,
+			"Error reading configuration register, aborting.\n");
+			return err;
+		}
+
+		config &= ~AMC6821_CONF1_THERMOVIE;
+		config &= ~AMC6821_CONF1_FANIE;
+		config |= AMC6821_CONF1_START;
+		if (pwminv)
+			config |= AMC6821_CONF1_PWMINV;
+		else
+			config &= ~AMC6821_CONF1_PWMINV;
+
+		if (i2c_smbus_write_byte_data(
+				client, AMC6821_REG_CONF1, config)) {
+			dev_err(&client->dev,
+			"Configuration register write error, aborting.\n");
+			return err;
+		}
+	}
+	return 0;
+}
+
+
+static struct amc6821_data *amc6821_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct amc6821_data *data = i2c_get_clientdata(client);
+	int timeout = HZ;
+	u8 reg;
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + timeout) ||
+			!data->valid) {
+		data->temp1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_HI);
+		data->temp1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MIN);
+		data->temp1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_LIMIT_MAX);
+		data->temp1_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_CRIT);
+
+		data->temp2_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_HI);
+		data->temp2_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MIN);
+		data->temp2_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_LIMIT_MAX);
+		data->temp2_crit = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_CRIT);
+
+		data->stat1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT1);
+		data->stat2 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_STAT2);
+
+		data->pwm1 = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY);
+		data->fan1_input = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_LOW);
+		data->fan1_input += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TDATA_HI) << 8;
+		data->fan1_min = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL);
+		data->fan1_min += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_LLIMITL+1) << 8;
+		data->fan1_max = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL);
+		data->fan1_max += i2c_smbus_read_byte_data(client,
+			AMC6821_REG_TACH_HLIMITL+1) << 8;
+		data->fan1_div = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_CONF4);
+		data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 : 2;
+
+		data->pwm1_auto_point_pwm[0] = 0;
+		data->pwm1_auto_point_pwm[2] = 255;
+		data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_DCY_LOW_TEMP);
+
+		data->temp1_auto_point_temp[0] =
+			i2c_smbus_read_byte_data(client,
+					AMC6821_REG_PSV_TEMP);
+		data->temp2_auto_point_temp[0] =
+				data->temp1_auto_point_temp[0];
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_LTEMP_FAN_CTRL);
+		data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp1_auto_point_temp[2] =
+				data->temp1_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp1_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client,
+			AMC6821_REG_RTEMP_FAN_CTRL);
+		data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
+		reg &= 0x07;
+		reg = 0x20 >> reg;
+		if (reg > 0)
+			data->temp2_auto_point_temp[2] =
+				data->temp2_auto_point_temp[1] +
+				(data->pwm1_auto_point_pwm[2] -
+				data->pwm1_auto_point_pwm[1]) / reg;
+		else
+			data->temp2_auto_point_temp[2] = 255;
+
+		reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+		reg = (reg >> 5) & 0x3;
+		switch (reg) {
+		case 0: /*open loop: software sets pwm1*/
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 1;
+			break;
+		case 2: /*closed loop: remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 2;
+			data->pwm1_enable = 2;
+			break;
+		case 3: /*closed loop: local and remote T (temp2)*/
+			data->pwm1_auto_channels_temp = 3;
+			data->pwm1_enable = 3;
+			break;
+		case 1: /*semi-open loop: software sets rpm, chip controls pwm1,
+			  *currently not implemented
+			  */
+			data->pwm1_auto_channels_temp = 0;
+			data->pwm1_enable = 0;
+			break;
+		}
+
+		data->last_updated = jiffies;
+		data->valid = 1;
+	}
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+
+static int __init amc6821_init(void)
+{
+	return i2c_add_driver(&amc6821_driver);
+}
+
+static void __exit amc6821_exit(void)
+{
+	i2c_del_driver(&amc6821_driver);
+}
+
+module_init(amc6821_init);
+module_exit(amc6821_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("T. Mertelj <tomaz.mertelj@guest.arnes.si>");
+MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver");
+MODULE_SUPPORTED_DEVICE("amc6821");

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-05 12:08   ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
@ 2009-09-09  0:06     ` Andrew Morton
  -1 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-09  0:06 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Sat, 5 Sep 2009 14:08:34 +0200
tomaz.mertelj@guest.arnes.si wrote:

> +	int temp1_input;
> +	int temp1_min;
> +	int temp1_max;
> +	int temp1_crit;
> +
> +	int temp2_input;
> +	int temp2_min;
> +	int temp2_max;
> +	int temp2_crit;
> +
> +	u16 fan1_input;
> +	u16 fan1_min;
> +	u16 fan1_max;
> +	u8 fan1_div;
> +
> +	u8 pwm1;
> +	u8 temp1_auto_point_temp[3];
> +	u8 temp2_auto_point_temp[3];
> +	u8 pwm1_auto_point_pwm[3];
> +	u8 pwm1_enable;
> +	u8 pwm1_auto_channels_temp;
> +
> +	u8 stat1;
> +	u8 stat2;
> +};
> +
> +
> +#define get_temp_para(name) \
> +static ssize_t get_##name(\
> +		struct device *dev,\
> +		struct device_attribute *devattr,\
> +		char *buf)\
> +{\
> +	struct amc6821_data *data = amc6821_update_device(dev);\
> +	return sprintf(buf, "%d\n", data->name * 1000);\
> +}
> +
> +get_temp_para(temp1_input);
> +get_temp_para(temp1_min);
> +get_temp_para(temp1_max);
> +get_temp_para(temp2_input);
> +get_temp_para(temp2_min);
> +get_temp_para(temp2_max);
> +get_temp_para(temp1_crit);
> +get_temp_para(temp2_crit);
> +
> +#define set_temp_para(name, reg)\
> +static ssize_t set_##name(\
> +		struct device *dev,\
> +		struct device_attribute *attr,\
> +		const char *buf,\
> +		size_t count)\
> +{ \
> +	struct i2c_client *client = to_i2c_client(dev); \
> +	struct amc6821_data *data = i2c_get_clientdata(client); \
> +	int val = simple_strtol(buf, NULL, 10); \
> +	\
> +	mutex_lock(&data->update_lock); \
> +	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
> +	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
> +		dev_err(&client->dev, "Register write error, aborting.\n");\
> +		count = -EIO;\
> +	} \
> +	mutex_unlock(&data->update_lock); \
> +	return count; \
> +}

I'm wondering if these functions need to be so huge.  Couldn't you do

#define set_temp_para(name, reg)\
static ssize_t set_##name(\
		struct device *dev,\
		struct device_attribute *attr,\
		const char *buf,\
		size_t count)\
{\
	return set_helper(dev, attr, buf, count, &dev->name);\
}

And then do all the real work in a common function?  Rather than
expanding tens of copies of the same thing?

Also, the checkpatch warning

WARNING: consider using strict_strtol in preference to simple_strtol
#381: FILE: drivers/hwmon/amc6821.c:228:
+       int val = simple_strtol(buf, NULL, 10); \

is valid.  The problem with simple_strtol() is that it will treat input
of the form "43foo" as "43".  Even though the input was invalid.  A
minor thing, but easily fixed too.



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

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

* Re: [PATCH] hwmon: Driver for Texas Instruments amc6821 chip
@ 2009-09-09  0:06     ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-09  0:06 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: lm-sensors, linux-kernel

On Sat, 5 Sep 2009 14:08:34 +0200
tomaz.mertelj@guest.arnes.si wrote:

> +	int temp1_input;
> +	int temp1_min;
> +	int temp1_max;
> +	int temp1_crit;
> +
> +	int temp2_input;
> +	int temp2_min;
> +	int temp2_max;
> +	int temp2_crit;
> +
> +	u16 fan1_input;
> +	u16 fan1_min;
> +	u16 fan1_max;
> +	u8 fan1_div;
> +
> +	u8 pwm1;
> +	u8 temp1_auto_point_temp[3];
> +	u8 temp2_auto_point_temp[3];
> +	u8 pwm1_auto_point_pwm[3];
> +	u8 pwm1_enable;
> +	u8 pwm1_auto_channels_temp;
> +
> +	u8 stat1;
> +	u8 stat2;
> +};
> +
> +
> +#define get_temp_para(name) \
> +static ssize_t get_##name(\
> +		struct device *dev,\
> +		struct device_attribute *devattr,\
> +		char *buf)\
> +{\
> +	struct amc6821_data *data = amc6821_update_device(dev);\
> +	return sprintf(buf, "%d\n", data->name * 1000);\
> +}
> +
> +get_temp_para(temp1_input);
> +get_temp_para(temp1_min);
> +get_temp_para(temp1_max);
> +get_temp_para(temp2_input);
> +get_temp_para(temp2_min);
> +get_temp_para(temp2_max);
> +get_temp_para(temp1_crit);
> +get_temp_para(temp2_crit);
> +
> +#define set_temp_para(name, reg)\
> +static ssize_t set_##name(\
> +		struct device *dev,\
> +		struct device_attribute *attr,\
> +		const char *buf,\
> +		size_t count)\
> +{ \
> +	struct i2c_client *client = to_i2c_client(dev); \
> +	struct amc6821_data *data = i2c_get_clientdata(client); \
> +	int val = simple_strtol(buf, NULL, 10); \
> +	\
> +	mutex_lock(&data->update_lock); \
> +	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
> +	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
> +		dev_err(&client->dev, "Register write error, aborting.\n");\
> +		count = -EIO;\
> +	} \
> +	mutex_unlock(&data->update_lock); \
> +	return count; \
> +}

I'm wondering if these functions need to be so huge.  Couldn't you do

#define set_temp_para(name, reg)\
static ssize_t set_##name(\
		struct device *dev,\
		struct device_attribute *attr,\
		const char *buf,\
		size_t count)\
{\
	return set_helper(dev, attr, buf, count, &dev->name);\
}

And then do all the real work in a common function?  Rather than
expanding tens of copies of the same thing?

Also, the checkpatch warning

WARNING: consider using strict_strtol in preference to simple_strtol
#381: FILE: drivers/hwmon/amc6821.c:228:
+       int val = simple_strtol(buf, NULL, 10); \

is valid.  The problem with simple_strtol() is that it will treat input
of the form "43foo" as "43".  Even though the input was invalid.  A
minor thing, but easily fixed too.



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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-09  0:06     ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
@ 2009-09-09  7:34       ` Jean Delvare
  -1 siblings, 0 replies; 37+ messages in thread
From: Jean Delvare @ 2009-09-09  7:34 UTC (permalink / raw)
  To: Andrew Morton; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Tue, 8 Sep 2009 17:06:49 -0700, Andrew Morton wrote:
> On Sat, 5 Sep 2009 14:08:34 +0200
> tomaz.mertelj@guest.arnes.si wrote:
> 
> > +	int temp1_input;
> > +	int temp1_min;
> > +	int temp1_max;
> > +	int temp1_crit;
> > +
> > +	int temp2_input;
> > +	int temp2_min;
> > +	int temp2_max;
> > +	int temp2_crit;
> > +
> > +	u16 fan1_input;
> > +	u16 fan1_min;
> > +	u16 fan1_max;
> > +	u8 fan1_div;
> > +
> > +	u8 pwm1;
> > +	u8 temp1_auto_point_temp[3];
> > +	u8 temp2_auto_point_temp[3];
> > +	u8 pwm1_auto_point_pwm[3];
> > +	u8 pwm1_enable;
> > +	u8 pwm1_auto_channels_temp;
> > +
> > +	u8 stat1;
> > +	u8 stat2;
> > +};
> > +
> > +
> > +#define get_temp_para(name) \
> > +static ssize_t get_##name(\
> > +		struct device *dev,\
> > +		struct device_attribute *devattr,\
> > +		char *buf)\
> > +{\
> > +	struct amc6821_data *data = amc6821_update_device(dev);\
> > +	return sprintf(buf, "%d\n", data->name * 1000);\
> > +}
> > +
> > +get_temp_para(temp1_input);
> > +get_temp_para(temp1_min);
> > +get_temp_para(temp1_max);
> > +get_temp_para(temp2_input);
> > +get_temp_para(temp2_min);
> > +get_temp_para(temp2_max);
> > +get_temp_para(temp1_crit);
> > +get_temp_para(temp2_crit);
> > +
> > +#define set_temp_para(name, reg)\
> > +static ssize_t set_##name(\
> > +		struct device *dev,\
> > +		struct device_attribute *attr,\
> > +		const char *buf,\
> > +		size_t count)\
> > +{ \
> > +	struct i2c_client *client = to_i2c_client(dev); \
> > +	struct amc6821_data *data = i2c_get_clientdata(client); \
> > +	int val = simple_strtol(buf, NULL, 10); \
> > +	\
> > +	mutex_lock(&data->update_lock); \
> > +	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
> > +	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
> > +		dev_err(&client->dev, "Register write error, aborting.\n");\
> > +		count = -EIO;\
> > +	} \
> > +	mutex_unlock(&data->update_lock); \
> > +	return count; \
> > +}
> 
> I'm wondering if these functions need to be so huge.  Couldn't you do
> 
> #define set_temp_para(name, reg)\
> static ssize_t set_##name(\
> 		struct device *dev,\
> 		struct device_attribute *attr,\
> 		const char *buf,\
> 		size_t count)\
> {\
> 	return set_helper(dev, attr, buf, count, &dev->name);\
> }
> 
> And then do all the real work in a common function?  Rather than
> expanding tens of copies of the same thing?

Yes please. We got rid of macro-generated callbacks in most hwmon
drivers a couple years ago already.

> 
> Also, the checkpatch warning
> 
> WARNING: consider using strict_strtol in preference to simple_strtol
> #381: FILE: drivers/hwmon/amc6821.c:228:
> +       int val = simple_strtol(buf, NULL, 10); \
> 
> is valid.  The problem with simple_strtol() is that it will treat input
> of the form "43foo" as "43".  Even though the input was invalid.  A
> minor thing, but easily fixed too.

Is there any legitimate use of simple_strtol then? I'm wondering why we
don't just get rid of it and rename strict_strtol to just strtol.

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-09  7:34       ` Jean Delvare
  0 siblings, 0 replies; 37+ messages in thread
From: Jean Delvare @ 2009-09-09  7:34 UTC (permalink / raw)
  To: Andrew Morton; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Tue, 8 Sep 2009 17:06:49 -0700, Andrew Morton wrote:
> On Sat, 5 Sep 2009 14:08:34 +0200
> tomaz.mertelj@guest.arnes.si wrote:
> 
> > +	int temp1_input;
> > +	int temp1_min;
> > +	int temp1_max;
> > +	int temp1_crit;
> > +
> > +	int temp2_input;
> > +	int temp2_min;
> > +	int temp2_max;
> > +	int temp2_crit;
> > +
> > +	u16 fan1_input;
> > +	u16 fan1_min;
> > +	u16 fan1_max;
> > +	u8 fan1_div;
> > +
> > +	u8 pwm1;
> > +	u8 temp1_auto_point_temp[3];
> > +	u8 temp2_auto_point_temp[3];
> > +	u8 pwm1_auto_point_pwm[3];
> > +	u8 pwm1_enable;
> > +	u8 pwm1_auto_channels_temp;
> > +
> > +	u8 stat1;
> > +	u8 stat2;
> > +};
> > +
> > +
> > +#define get_temp_para(name) \
> > +static ssize_t get_##name(\
> > +		struct device *dev,\
> > +		struct device_attribute *devattr,\
> > +		char *buf)\
> > +{\
> > +	struct amc6821_data *data = amc6821_update_device(dev);\
> > +	return sprintf(buf, "%d\n", data->name * 1000);\
> > +}
> > +
> > +get_temp_para(temp1_input);
> > +get_temp_para(temp1_min);
> > +get_temp_para(temp1_max);
> > +get_temp_para(temp2_input);
> > +get_temp_para(temp2_min);
> > +get_temp_para(temp2_max);
> > +get_temp_para(temp1_crit);
> > +get_temp_para(temp2_crit);
> > +
> > +#define set_temp_para(name, reg)\
> > +static ssize_t set_##name(\
> > +		struct device *dev,\
> > +		struct device_attribute *attr,\
> > +		const char *buf,\
> > +		size_t count)\
> > +{ \
> > +	struct i2c_client *client = to_i2c_client(dev); \
> > +	struct amc6821_data *data = i2c_get_clientdata(client); \
> > +	int val = simple_strtol(buf, NULL, 10); \
> > +	\
> > +	mutex_lock(&data->update_lock); \
> > +	data->name = SENSORS_LIMIT(val / 1000, -128, 127); \
> > +	if (i2c_smbus_write_byte_data(client, reg, data->name)) {\
> > +		dev_err(&client->dev, "Register write error, aborting.\n");\
> > +		count = -EIO;\
> > +	} \
> > +	mutex_unlock(&data->update_lock); \
> > +	return count; \
> > +}
> 
> I'm wondering if these functions need to be so huge.  Couldn't you do
> 
> #define set_temp_para(name, reg)\
> static ssize_t set_##name(\
> 		struct device *dev,\
> 		struct device_attribute *attr,\
> 		const char *buf,\
> 		size_t count)\
> {\
> 	return set_helper(dev, attr, buf, count, &dev->name);\
> }
> 
> And then do all the real work in a common function?  Rather than
> expanding tens of copies of the same thing?

Yes please. We got rid of macro-generated callbacks in most hwmon
drivers a couple years ago already.

> 
> Also, the checkpatch warning
> 
> WARNING: consider using strict_strtol in preference to simple_strtol
> #381: FILE: drivers/hwmon/amc6821.c:228:
> +       int val = simple_strtol(buf, NULL, 10); \
> 
> is valid.  The problem with simple_strtol() is that it will treat input
> of the form "43foo" as "43".  Even though the input was invalid.  A
> minor thing, but easily fixed too.

Is there any legitimate use of simple_strtol then? I'm wondering why we
don't just get rid of it and rename strict_strtol to just strtol.

-- 
Jean Delvare

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-09  7:34       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Jean Delvare
@ 2009-09-09  8:06         ` Andrew Morton
  -1 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-09  8:06 UTC (permalink / raw)
  To: Jean Delvare; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Wed, 9 Sep 2009 09:34:35 +0200 Jean Delvare <khali@linux-fr.org> wrote:

> > Also, the checkpatch warning
> > 
> > WARNING: consider using strict_strtol in preference to simple_strtol
> > #381: FILE: drivers/hwmon/amc6821.c:228:
> > +       int val = simple_strtol(buf, NULL, 10); \
> > 
> > is valid.  The problem with simple_strtol() is that it will treat input
> > of the form "43foo" as "43".  Even though the input was invalid.  A
> > minor thing, but easily fixed too.
> 
> Is there any legitimate use of simple_strtol then?

Probably not, unless it's known that the input is a legit decimal
string.

> I'm wondering why we
> don't just get rid of it and rename strict_strtol to just strtol.

Well.  The calling convention is pretty different, the callers need to
be changed to handle errors.  But the main problem is that changing
existing interfaces to use strict_strtol() could break existing
userspace.



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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-09  8:06         ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-09  8:06 UTC (permalink / raw)
  To: Jean Delvare; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Wed, 9 Sep 2009 09:34:35 +0200 Jean Delvare <khali@linux-fr.org> wrote:

> > Also, the checkpatch warning
> > 
> > WARNING: consider using strict_strtol in preference to simple_strtol
> > #381: FILE: drivers/hwmon/amc6821.c:228:
> > +       int val = simple_strtol(buf, NULL, 10); \
> > 
> > is valid.  The problem with simple_strtol() is that it will treat input
> > of the form "43foo" as "43".  Even though the input was invalid.  A
> > minor thing, but easily fixed too.
> 
> Is there any legitimate use of simple_strtol then?

Probably not, unless it's known that the input is a legit decimal
string.

> I'm wondering why we
> don't just get rid of it and rename strict_strtol to just strtol.

Well.  The calling convention is pretty different, the callers need to
be changed to handle errors.  But the main problem is that changing
existing interfaces to use strict_strtol() could break existing
userspace.



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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-09  7:34       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Jean Delvare
@ 2009-09-09 12:24         ` Tomaz Mertelj
  -1 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-09 12:24 UTC (permalink / raw)
  To: Jean Delvare, Andrew Morton; +Cc: linux-kernel, lm-sensors

At 09:34 9. 9. 2009 +0200, Jean Delvare wrote:
> > I'm wondering if these functions need to be so huge.  Couldn't you do
> >
> > #define set_temp_para(name, reg)\
> > static ssize_t set_##name(\
> >               struct device *dev,\
> >               struct device_attribute *attr,\
> >               const char *buf,\
> >               size_t count)\
> > {\
> >       return set_helper(dev, attr, buf, count, &dev->name);\
> > }
> >
> > And then do all the real work in a common function?  Rather than
> > expanding tens of copies of the same thing?
>
>Yes please. We got rid of macro-generated callbacks in most hwmon
>drivers a couple years ago already.

I do not like macro-generated callbacks myself as well. However, I was 
impatient to get the
driver working and since I have seen similar things in a few other drivers ...

I would prefer a single callback (would require some more work):

static ssize_t set_temp(
         struct device *dev,
         struct device_attribute *attr,
         const char *buf,
         size_t count)
{
         struct i2c_client *client = to_i2c_client(dev);
         struct amc6821_data *data = i2c_get_clientdata(client);
         int nr = to_sensor_dev_attr(attr)->index;
         int val = simple_strtol(buf, NULL, 10);
         val = SENSORS_LIMIT(val / 1000, -128, 127);
         int *pvar;
         u8 reg;

         switch (nr) {
         case GET_SET_TEMP1_MIN:
                 pvar=&data->temp1_min;
                 reg=AMC6821_REG_LTEMP_LIMIT_MIN;
                 break;
         case ...

         ...

         default:
                 dev_dbg(dev, "Unknown attr->index (%d)\n", nr);
                 return SOME_ERROR;
         }
         mutex_lock(&data->update_lock);
         *pvar=val;
         if (i2c_smbus_write_byte_data(client, reg, *pvar)) {
                 dev_err(&client->dev, "Register write error, aborting.\n");
                 count = -EIO;
         }
         mutex_unlock(&data->update_lock);
         return count;
}


static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp,
         set_temp, GET_SET_TEMP1_MIN);
...



> >
> > Also, the checkpatch warning
> >
> > WARNING: consider using strict_strtol in preference to simple_strtol
> > #381: FILE: drivers/hwmon/amc6821.c:228:
> > +       int val = simple_strtol(buf, NULL, 10); \
> >
> > is valid.  The problem with simple_strtol() is that it will treat input
> > of the form "43foo" as "43".  Even though the input was invalid.  A
> > minor thing, but easily fixed too.
>
>Is there any legitimate use of simple_strtol then? I'm wondering why we
>don't just get rid of it and rename strict_strtol to just strtol.

I have seen simple_strtol in many hwmon drivers so I thought there might be 
a reason to do it this way?


***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-09 12:24         ` Tomaz Mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-09 12:24 UTC (permalink / raw)
  To: Jean Delvare, Andrew Morton; +Cc: linux-kernel, lm-sensors

At 09:34 9. 9. 2009 +0200, Jean Delvare wrote:
> > I'm wondering if these functions need to be so huge.  Couldn't you do
> >
> > #define set_temp_para(name, reg)\
> > static ssize_t set_##name(\
> >               struct device *dev,\
> >               struct device_attribute *attr,\
> >               const char *buf,\
> >               size_t count)\
> > {\
> >       return set_helper(dev, attr, buf, count, &dev->name);\
> > }
> >
> > And then do all the real work in a common function?  Rather than
> > expanding tens of copies of the same thing?
>
>Yes please. We got rid of macro-generated callbacks in most hwmon
>drivers a couple years ago already.

I do not like macro-generated callbacks myself as well. However, I was 
impatient to get the
driver working and since I have seen similar things in a few other drivers ...

I would prefer a single callback (would require some more work):

static ssize_t set_temp(
         struct device *dev,
         struct device_attribute *attr,
         const char *buf,
         size_t count)
{
         struct i2c_client *client = to_i2c_client(dev);
         struct amc6821_data *data = i2c_get_clientdata(client);
         int nr = to_sensor_dev_attr(attr)->index;
         int val = simple_strtol(buf, NULL, 10);
         val = SENSORS_LIMIT(val / 1000, -128, 127);
         int *pvar;
         u8 reg;

         switch (nr) {
         case GET_SET_TEMP1_MIN:
                 pvar=&data->temp1_min;
                 reg=AMC6821_REG_LTEMP_LIMIT_MIN;
                 break;
         case ...

         ...

         default:
                 dev_dbg(dev, "Unknown attr->index (%d)\n", nr);
                 return SOME_ERROR;
         }
         mutex_lock(&data->update_lock);
         *pvar=val;
         if (i2c_smbus_write_byte_data(client, reg, *pvar)) {
                 dev_err(&client->dev, "Register write error, aborting.\n");
                 count = -EIO;
         }
         mutex_unlock(&data->update_lock);
         return count;
}


static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp,
         set_temp, GET_SET_TEMP1_MIN);
...



> >
> > Also, the checkpatch warning
> >
> > WARNING: consider using strict_strtol in preference to simple_strtol
> > #381: FILE: drivers/hwmon/amc6821.c:228:
> > +       int val = simple_strtol(buf, NULL, 10); \
> >
> > is valid.  The problem with simple_strtol() is that it will treat input
> > of the form "43foo" as "43".  Even though the input was invalid.  A
> > minor thing, but easily fixed too.
>
>Is there any legitimate use of simple_strtol then? I'm wondering why we
>don't just get rid of it and rename strict_strtol to just strtol.

I have seen simple_strtol in many hwmon drivers so I thought there might be 
a reason to do it this way?


***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-09 12:24         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Tomaz Mertelj
@ 2009-09-09 12:45           ` Jean Delvare
  -1 siblings, 0 replies; 37+ messages in thread
From: Jean Delvare @ 2009-09-09 12:45 UTC (permalink / raw)
  To: Tomaz Mertelj; +Cc: Andrew Morton, linux-kernel, lm-sensors

On Wed, 09 Sep 2009 14:24:05 +0200, Tomaz Mertelj wrote:
> At 09:34 9. 9. 2009 +0200, Jean Delvare wrote:
> >Yes please. We got rid of macro-generated callbacks in most hwmon
> >drivers a couple years ago already.
> 
> I do not like macro-generated callbacks myself as well. However, I was 
> impatient to get the
> driver working and since I have seen similar things in a few other drivers ...
> 
> I would prefer a single callback (would require some more work):
> 
> static ssize_t set_temp(
>          struct device *dev,
>          struct device_attribute *attr,
>          const char *buf,
>          size_t count)
> {
>          struct i2c_client *client = to_i2c_client(dev);
>          struct amc6821_data *data = i2c_get_clientdata(client);
>          int nr = to_sensor_dev_attr(attr)->index;
>          int val = simple_strtol(buf, NULL, 10);
>          val = SENSORS_LIMIT(val / 1000, -128, 127);
>          int *pvar;
>          u8 reg;
> 
>          switch (nr) {
>          case GET_SET_TEMP1_MIN:
>                  pvar=&data->temp1_min;
>                  reg=AMC6821_REG_LTEMP_LIMIT_MIN;
>                  break;
>          case ...
> 
>          ...
> 
>          default:
>                  dev_dbg(dev, "Unknown attr->index (%d)\n", nr);
>                  return SOME_ERROR;
>          }
>          mutex_lock(&data->update_lock);
>          *pvar=val;
>          if (i2c_smbus_write_byte_data(client, reg, *pvar)) {
>                  dev_err(&client->dev, "Register write error, aborting.\n");
>                  count = -EIO;
>          }
>          mutex_unlock(&data->update_lock);
>          return count;
> }
> 
> 
> static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp,
>          set_temp, GET_SET_TEMP1_MIN);
> ...

Yes, would be much better. Or you can make things even better by
defining arrays of variables in your data structure, and arrays for
register numbers too. And if you need to pass 2 identifiers per entry,
you can take a look at struct sensor_device_attribute_2. So you have a
lot of possibilities to make the code more compact. To which degree you
want that, is up to you.

> > > Also, the checkpatch warning
> > >
> > > WARNING: consider using strict_strtol in preference to simple_strtol
> > > #381: FILE: drivers/hwmon/amc6821.c:228:
> > > +       int val = simple_strtol(buf, NULL, 10); \
> > >
> > > is valid.  The problem with simple_strtol() is that it will treat input
> > > of the form "43foo" as "43".  Even though the input was invalid.  A
> > > minor thing, but easily fixed too.
> >
> >Is there any legitimate use of simple_strtol then? I'm wondering why we
> >don't just get rid of it and rename strict_strtol to just strtol.
> 
> I have seen simple_strtol in many hwmon drivers so I thought there might be 
> a reason to do it this way?

Historical, as I recall, the strict variant did not exist when we
converted the first driver. And then copy-and-paste from driver to
driver, and here we are.

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments    amc6821 chip
@ 2009-09-09 12:45           ` Jean Delvare
  0 siblings, 0 replies; 37+ messages in thread
From: Jean Delvare @ 2009-09-09 12:45 UTC (permalink / raw)
  To: Tomaz Mertelj; +Cc: Andrew Morton, linux-kernel, lm-sensors

On Wed, 09 Sep 2009 14:24:05 +0200, Tomaz Mertelj wrote:
> At 09:34 9. 9. 2009 +0200, Jean Delvare wrote:
> >Yes please. We got rid of macro-generated callbacks in most hwmon
> >drivers a couple years ago already.
> 
> I do not like macro-generated callbacks myself as well. However, I was 
> impatient to get the
> driver working and since I have seen similar things in a few other drivers ...
> 
> I would prefer a single callback (would require some more work):
> 
> static ssize_t set_temp(
>          struct device *dev,
>          struct device_attribute *attr,
>          const char *buf,
>          size_t count)
> {
>          struct i2c_client *client = to_i2c_client(dev);
>          struct amc6821_data *data = i2c_get_clientdata(client);
>          int nr = to_sensor_dev_attr(attr)->index;
>          int val = simple_strtol(buf, NULL, 10);
>          val = SENSORS_LIMIT(val / 1000, -128, 127);
>          int *pvar;
>          u8 reg;
> 
>          switch (nr) {
>          case GET_SET_TEMP1_MIN:
>                  pvar=&data->temp1_min;
>                  reg=AMC6821_REG_LTEMP_LIMIT_MIN;
>                  break;
>          case ...
> 
>          ...
> 
>          default:
>                  dev_dbg(dev, "Unknown attr->index (%d)\n", nr);
>                  return SOME_ERROR;
>          }
>          mutex_lock(&data->update_lock);
>          *pvar=val;
>          if (i2c_smbus_write_byte_data(client, reg, *pvar)) {
>                  dev_err(&client->dev, "Register write error, aborting.\n");
>                  count = -EIO;
>          }
>          mutex_unlock(&data->update_lock);
>          return count;
> }
> 
> 
> static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp,
>          set_temp, GET_SET_TEMP1_MIN);
> ...

Yes, would be much better. Or you can make things even better by
defining arrays of variables in your data structure, and arrays for
register numbers too. And if you need to pass 2 identifiers per entry,
you can take a look at struct sensor_device_attribute_2. So you have a
lot of possibilities to make the code more compact. To which degree you
want that, is up to you.

> > > Also, the checkpatch warning
> > >
> > > WARNING: consider using strict_strtol in preference to simple_strtol
> > > #381: FILE: drivers/hwmon/amc6821.c:228:
> > > +       int val = simple_strtol(buf, NULL, 10); \
> > >
> > > is valid.  The problem with simple_strtol() is that it will treat input
> > > of the form "43foo" as "43".  Even though the input was invalid.  A
> > > minor thing, but easily fixed too.
> >
> >Is there any legitimate use of simple_strtol then? I'm wondering why we
> >don't just get rid of it and rename strict_strtol to just strtol.
> 
> I have seen simple_strtol in many hwmon drivers so I thought there might be 
> a reason to do it this way?

Historical, as I recall, the strict variant did not exist when we
converted the first driver. And then copy-and-paste from driver to
driver, and here we are.

-- 
Jean Delvare

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-09  7:34       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Jean Delvare
@ 2009-09-21 21:44         ` Andrew Morton
  -1 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-21 21:44 UTC (permalink / raw)
  To: Jean Delvare; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Wed, 9 Sep 2009 09:34:35 +0200
Jean Delvare <khali@linux-fr.org> wrote:

> > #define set_temp_para(name, reg)\
> > static ssize_t set_##name(\
> > 		struct device *dev,\
> > 		struct device_attribute *attr,\
> > 		const char *buf,\
> > 		size_t count)\
> > {\
> > 	return set_helper(dev, attr, buf, count, &dev->name);\
> > }
> > 
> > And then do all the real work in a common function?  Rather than
> > expanding tens of copies of the same thing?
> 
> Yes please. We got rid of macro-generated callbacks in most hwmon
> drivers a couple years ago already.

I never received an update to this patch so I'm retaining it in my tree
for now.


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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-21 21:44         ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-21 21:44 UTC (permalink / raw)
  To: Jean Delvare; +Cc: tomaz.mertelj, linux-kernel, lm-sensors

On Wed, 9 Sep 2009 09:34:35 +0200
Jean Delvare <khali@linux-fr.org> wrote:

> > #define set_temp_para(name, reg)\
> > static ssize_t set_##name(\
> > 		struct device *dev,\
> > 		struct device_attribute *attr,\
> > 		const char *buf,\
> > 		size_t count)\
> > {\
> > 	return set_helper(dev, attr, buf, count, &dev->name);\
> > }
> > 
> > And then do all the real work in a common function?  Rather than
> > expanding tens of copies of the same thing?
> 
> Yes please. We got rid of macro-generated callbacks in most hwmon
> drivers a couple years ago already.

I never received an update to this patch so I'm retaining it in my tree
for now.


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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-21 21:44         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
@ 2009-09-22  5:59           ` Tomaz Mertelj
  -1 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-22  5:59 UTC (permalink / raw)
  To: Andrew Morton, Jean Delvare; +Cc: linux-kernel, lm-sensors

At 14:44 21. 9. 2009 -0700, Andrew Morton wrote:
>On Wed, 9 Sep 2009 09:34:35 +0200
>Jean Delvare <khali@linux-fr.org> wrote:
>
> > > #define set_temp_para(name, reg)\
> > > static ssize_t set_##name(\
> > >             struct device *dev,\
> > >             struct device_attribute *attr,\
> > >             const char *buf,\
> > >             size_t count)\
> > > {\
> > >     return set_helper(dev, attr, buf, count, &dev->name);\
> > > }
> > >
> > > And then do all the real work in a common function?  Rather than
> > > expanding tens of copies of the same thing?
> >
> > Yes please. We got rid of macro-generated callbacks in most hwmon
> > drivers a couple years ago already.
>
>I never received an update to this patch so I'm retaining it in my tree
>for now.


OK. I have an update almost ready. I only need some time to test it.

Should I post a patch on top of the original patch or a full patch to the 
latest kernel tree?

Tomaz Mertelj


***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-22  5:59           ` Tomaz Mertelj
  0 siblings, 0 replies; 37+ messages in thread
From: Tomaz Mertelj @ 2009-09-22  5:59 UTC (permalink / raw)
  To: Andrew Morton, Jean Delvare; +Cc: linux-kernel, lm-sensors

At 14:44 21. 9. 2009 -0700, Andrew Morton wrote:
>On Wed, 9 Sep 2009 09:34:35 +0200
>Jean Delvare <khali@linux-fr.org> wrote:
>
> > > #define set_temp_para(name, reg)\
> > > static ssize_t set_##name(\
> > >             struct device *dev,\
> > >             struct device_attribute *attr,\
> > >             const char *buf,\
> > >             size_t count)\
> > > {\
> > >     return set_helper(dev, attr, buf, count, &dev->name);\
> > > }
> > >
> > > And then do all the real work in a common function?  Rather than
> > > expanding tens of copies of the same thing?
> >
> > Yes please. We got rid of macro-generated callbacks in most hwmon
> > drivers a couple years ago already.
>
>I never received an update to this patch so I'm retaining it in my tree
>for now.


OK. I have an update almost ready. I only need some time to test it.

Should I post a patch on top of the original patch or a full patch to the 
latest kernel tree?

Tomaz Mertelj


***********************************************************************************
Tomaz Mertelj
E-mail: tomaz.mertelj@guest.arnes.si    Home page: 
http://optlab.ijs.si/tmertelj


Staniceva 14
1000 Ljubljana
Slovenia
***********************************************************************************




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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-22  5:59           ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Tomaz Mertelj
@ 2009-09-22  6:02             ` Andrew Morton
  -1 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-22  6:02 UTC (permalink / raw)
  To: Tomaz Mertelj; +Cc: Jean Delvare, linux-kernel, lm-sensors

On Tue, 22 Sep 2009 07:59:14 +0200 Tomaz Mertelj <tomaz.mertelj@guest.arnes.si> wrote:

> Should I post a patch on top of the original patch or a full patch to the 
> latest kernel tree?

Either is OK for me.

I'll turn it into an incremental patch so I can see what you changed.

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments  amc6821 chip
@ 2009-09-22  6:02             ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-22  6:02 UTC (permalink / raw)
  To: Tomaz Mertelj; +Cc: Jean Delvare, linux-kernel, lm-sensors

On Tue, 22 Sep 2009 07:59:14 +0200 Tomaz Mertelj <tomaz.mertelj@guest.arnes.si> wrote:

> Should I post a patch on top of the original patch or a full patch to the 
> latest kernel tree?

Either is OK for me.

I'll turn it into an incremental patch so I can see what you changed.

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments
  2009-09-23  9:32 [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
@ 2009-09-30 19:44 ` Andrew Morton
  0 siblings, 0 replies; 37+ messages in thread
From: Andrew Morton @ 2009-09-30 19:44 UTC (permalink / raw)
  To: tomaz.mertelj; +Cc: khali, linux-kernel, lm-sensors

On Wed, 23 Sep 2009 11:32:47 +0200
tomaz.mertelj@guest.arnes.si wrote:

> On Wed, 9 Sep 2009 09:34:35 +0200
> Jean Delvare <khali@linux-fr.org> wrote:
> 
> > > And then do all the real work in a common function?  Rather than
> > > expanding tens of copies of the same thing?
> >
> > Yes please. We got rid of macro-generated callbacks in most hwmon
> > drivers a couple years ago already.
> 
> Here is an incremental patch to TI amc6821 chip hwmon driver:

The patch is hopelessly wordwrapped.  Please fix and resend?

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

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

* [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (4 preceding siblings ...)
  (?)
@ 2013-01-13  0:16 ` Guenter Roeck
  -1 siblings, 0 replies; 37+ messages in thread
From: Guenter Roeck @ 2013-01-13  0:16 UTC (permalink / raw)
  To: lm-sensors

Add support for the TI / Burr-Brown INA209 voltage / current / power
monitor.

Cc: Paul Hays <haysp@magma.net>
Cc: Ira W. Snyder <iws@ovro.caltech.edu>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
---
 Documentation/hwmon/ina209 |   93 +++++++
 drivers/hwmon/Kconfig      |   10 +
 drivers/hwmon/Makefile     |    1 +
 drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 739 insertions(+)
 create mode 100644 Documentation/hwmon/ina209
 create mode 100644 drivers/hwmon/ina209.c

diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
new file mode 100644
index 0000000..2e11bbc
--- /dev/null
+++ b/Documentation/hwmon/ina209
@@ -0,0 +1,93 @@
+Kernel driver ina209
+==========+
+Supported chips:
+  * Burr-Brown / Texas Instruments INA209
+    Prefix: 'ina209'
+    Addresses scanned: -
+    Datasheet:
+        http://www.ti.com/lit/gpn/ina209
+
+Author: Paul Hays <haysp@magma.net>
+Author: Ira W. Snyder <iws@ovro.caltech.edu>
+Author: Guenter Roeck <linux@roeck-us.net>
+
+
+Description
+-----------
+
+The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
+of a D.C. power supply. It can perform measurements and calculations in the
+background to supply readings at any time. It includes a programmable
+calibration multiplier to scale the displayed current and power values.
+
+
+Sysfs entries
+-------------
+
+The INA209 chip is highly configurable both via hardwiring and via
+the I2C bus. See the datasheet for details.
+
+This tries to expose most monitoring features of the hardware via
+sysfs. It does not support every feature of this chip.
+
+
+in0_input		shunt voltage (mV)
+in0_input_highest	shunt voltage historical maximum reading (mV)
+in0_input_lowest	shunt voltage historical minimum reading (mV)
+in0_reset_history	reset shunt voltage history
+in0_max			shunt voltage max alarm limit (mV)
+in0_min			shunt voltage min alarm limit (mV)
+in0_crit_max		shunt voltage crit max alarm limit (mV)
+in0_crit_min		shunt voltage crit min alarm limit (mV)
+in0_max_alarm		shunt voltage max alarm limit exceeded
+in0_min_alarm		shunt voltage min alarm limit exceeded
+in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
+in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
+
+in1_input		bus voltage (mV)
+in1_input_highest	bus voltage historical maximum reading (mV)
+in1_input_lowest	bus voltage historical minimum reading (mV)
+in1_reset_history	reset bus voltage history
+in1_max			bus voltage max alarm limit (mV)
+in1_min			bus voltage min alarm limit (mV)
+in1_crit_max		bus voltage crit max alarm limit (mV)
+in1_crit_min		bus voltage crit min alarm limit (mV)
+in1_max_alarm		bus voltage max alarm limit exceeded
+in1_min_alarm		bus voltage min alarm limit exceeded
+in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
+in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
+
+power1_input		power measurement (uW)
+power1_input_highest	power historical maximum reading (uW)
+power1_reset_history	reset power history
+power1_max		power max alarm limit (uW)
+power1_crit		power crit alarm limit (uW)
+power1_max_alarm	power max alarm limit exceeded
+power1_crit_alarm	power crit alarm limit exceeded
+
+curr1_input		current measurement (mA)
+
+update_interval		data conversion time; affects number of samples used
+			to average results for shunt and bus voltages.
+
+General Remarks
+---------------
+
+The power and current registers in this chip require that the calibration
+register is programmed correctly before they are used. Normally this is expected
+to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
+voltage can be provided using platform data. The driver uses platform data from
+the ina2xx driver for this purpose. If calibration register data is not provided
+via platform data, the driver checks if the calibration register has been
+programmed (ie has a value not equal to zero). If so, this value is retained.
+Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
+programmed into the calibration register.
+
+
+Output Pins
+-----------
+
+Output pin programming is a board feature which depends on the BIOS. It is
+outside the scope of a hardware monitoring driver to enable or disable output
+pins.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ef57572..5dacd58 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
 	  This driver can also be build as a module.  If so, the module
 	  will be called amc6821.
 
+config SENSORS_INA209
+	tristate "TI / Burr Brown INA209"
+	depends on I2C
+	help
+	  If you say yes here you get support for the TI / Burr Brown INA209
+	  voltage / current / power monitor I2C interface.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called ina209.
+
 config SENSORS_INA2XX
 	tristate "Texas Instruments INA219 and compatibles"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a37a82c..8d6d97e 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
 obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
 obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
+obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
 obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
 obj-$(CONFIG_SENSORS_IT87)	+= it87.o
 obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
new file mode 100644
index 0000000..1070611
--- /dev/null
+++ b/drivers/hwmon/ina209.c
@@ -0,0 +1,635 @@
+/*
+ * Driver for the Texas Instruments / Burr Brown INA209
+ * Bidirectional Current/Power Monitor
+ *
+ * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
+ *
+ * Derived from Ira W. Snyder's original driver submission
+ *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
+ *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * Aligned with ina2xx driver
+ *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
+ *	Thanks to Jan Volkering
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * Datasheet:
+ * http://www.ti.com/lit/gpn/ina209
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/bug.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+#include <linux/platform_data/ina2xx.h>
+
+/* register definitions */
+#define INA209_CONFIGURATION		0x00
+#define INA209_STATUS			0x01
+#define INA209_STATUS_MASK		0x02
+#define INA209_SHUNT_VOLTAGE		0x03
+#define INA209_BUS_VOLTAGE		0x04
+#define INA209_POWER			0x05
+#define INA209_CURRENT			0x06
+#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
+#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
+#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
+#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
+#define INA209_POWER_PEAK		0x0b
+#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
+#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
+#define INA209_POWER_WARN		0x0e
+#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
+#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
+#define INA209_POWER_OVER_LIMIT		0x11
+#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
+#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
+#define INA209_CRITICAL_DAC_POS		0x14
+#define INA209_CRITICAL_DAC_NEG		0x15
+#define INA209_CALIBRATION		0x16
+
+#define INA209_REGISTERS		0x17
+
+#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
+#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
+
+struct ina209_data {
+	struct device *hwmon_dev;
+
+	struct mutex update_lock;
+	bool valid;
+	unsigned long last_updated;	/* in jiffies */
+
+	u16 regs[INA209_REGISTERS];	/* All chip registers */
+
+	u16 config_orig;		/* Original configuration */
+	u16 calibration_orig;		/* Original calibration */
+	u16 update_interval;
+};
+
+static struct ina209_data *ina209_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ina209_data *data = i2c_get_clientdata(client);
+	struct ina209_data *ret = data;
+	s32 val;
+	int i;
+
+	mutex_lock(&data->update_lock);
+
+	if (!data->valid ||
+	    time_after(jiffies, data->last_updated + data->update_interval)) {
+		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
+			val = i2c_smbus_read_word_swapped(client, i);
+			if (val < 0) {
+				ret = ERR_PTR(val);
+				goto abort;
+			}
+			data->regs[i] = val;
+		}
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+abort:
+	mutex_unlock(&data->update_lock);
+	return ret;
+}
+
+/*
+ * Read a value from a device register and convert it to the
+ * appropriate sysfs units
+ */
+static long ina209_from_reg(const u8 reg, const u16 val)
+{
+	switch (reg) {
+	case INA209_SHUNT_VOLTAGE:
+	case INA209_SHUNT_VOLTAGE_POS_PEAK:
+	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
+	case INA209_SHUNT_VOLTAGE_POS_WARN:
+	case INA209_SHUNT_VOLTAGE_NEG_WARN:
+		/* LSB\x10 uV. Convert to mV. */
+		return DIV_ROUND_CLOSEST(val, 100);
+
+	case INA209_BUS_VOLTAGE:
+	case INA209_BUS_VOLTAGE_MAX_PEAK:
+	case INA209_BUS_VOLTAGE_MIN_PEAK:
+	case INA209_BUS_VOLTAGE_OVER_WARN:
+	case INA209_BUS_VOLTAGE_UNDER_WARN:
+	case INA209_BUS_VOLTAGE_OVER_LIMIT:
+	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
+		/* LSB=4 mV, last 3 bits unused */
+		return (val >> 3) * 4;
+
+	case INA209_CRITICAL_DAC_POS:
+		/* LSB=1 mV, in the upper 8 bits */
+		return val >> 8;
+
+	case INA209_CRITICAL_DAC_NEG:
+		/* LSB=1 mV, in the upper 8 bits */
+		return -1 * (val >> 8);
+
+	case INA209_POWER:
+	case INA209_POWER_PEAK:
+	case INA209_POWER_WARN:
+	case INA209_POWER_OVER_LIMIT:
+		/* LSB  mW. Convert to uW */
+		return val * 20 * 1000L;
+
+	case INA209_CURRENT:
+		/* LSB=1 mA (selected). Is in mA */
+		return val;
+	}
+
+	/* programmer goofed */
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+/*
+ * Take a value and convert it to register format, clamping the value
+ * to the appropriate range.
+ */
+static int ina209_to_reg(u8 reg, u16 old, long val)
+{
+	switch (reg) {
+	case INA209_SHUNT_VOLTAGE_POS_WARN:
+	case INA209_SHUNT_VOLTAGE_NEG_WARN:
+		/* Limit to +- 320 mV, 10 uV LSB */
+		return clamp_val(val, -320, 320) * 100;
+
+	case INA209_BUS_VOLTAGE_OVER_WARN:
+	case INA209_BUS_VOLTAGE_UNDER_WARN:
+	case INA209_BUS_VOLTAGE_OVER_LIMIT:
+	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
+		/*
+		 * Limit to 0-32000 mV, 4 mV LSB
+		 *
+		 * The last three bits aren't part of the value, but we'll
+		 * preserve them in their original state.
+		 */
+		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
+		  | (old & 0x7);
+
+	case INA209_CRITICAL_DAC_NEG:
+		/*
+		 * Limit to -255-0 mV, 1 mV LSB
+		 * Convert the value to a positive value for the register
+		 *
+		 * The value lives in the top 8 bits only, be careful
+		 * and keep original value of other bits.
+		 */
+		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
+
+	case INA209_CRITICAL_DAC_POS:
+		/*
+		 * Limit to 0-255 mV, 1 mV LSB
+		 *
+		 * The value lives in the top 8 bits only, be careful
+		 * and keep original value of other bits.
+		 */
+		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
+
+	case INA209_POWER_WARN:
+	case INA209_POWER_OVER_LIMIT:
+		/* 20 mW LSB */
+		return DIV_ROUND_CLOSEST(val, 20 * 1000);
+	}
+
+	/* Other registers are read-only, return access error */
+	return -EACCES;
+}
+
+static int ina209_interval_from_reg(u16 reg)
+{
+	return 68 >> (15 - ((reg >> 3) & 0x0f));
+}
+
+static u16 ina209_reg_from_interval(u16 config, long interval)
+{
+	int i, adc;
+
+	if (interval <= 0) {
+		adc = 8;
+	} else {
+		adc = 15;
+		for (i = 34 + 34 / 2; i; i >>= 1) {
+			if (i < interval)
+				break;
+			adc--;
+		}
+	}
+	return (config & 0xf807) | (adc << 3) | (adc << 7);
+}
+
+static ssize_t ina209_set_interval(struct device *dev,
+				   struct device_attribute *da,
+				   const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ina209_data *data = ina209_update_device(dev);
+	long val;
+	u16 regval;
+	int ret;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	ret = kstrtol(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&data->update_lock);
+	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
+					  val);
+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
+	data->regs[INA209_CONFIGURATION] = regval;
+	data->update_interval = ina209_interval_from_reg(regval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t ina209_show_interval(struct device *dev,
+				    struct device_attribute *da, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ina209_data *data = i2c_get_clientdata(client);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
+}
+
+/*
+ * History is reset by writing 1 into bit 0 of the respective peak register.
+ * Since more than one peak register may be affected by the scope of a
+ * reset_history attribute write, use a bit mask in attr->index to identify
+ * which registers are affected.
+ */
+static u16 ina209_reset_history_regs[] = {
+	INA209_SHUNT_VOLTAGE_POS_PEAK,
+	INA209_SHUNT_VOLTAGE_NEG_PEAK,
+	INA209_BUS_VOLTAGE_MAX_PEAK,
+	INA209_BUS_VOLTAGE_MIN_PEAK,
+	INA209_POWER_PEAK
+};
+
+static ssize_t ina209_reset_history(struct device *dev,
+				    struct device_attribute *da,
+				    const char *buf,
+				    size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ina209_data *data = i2c_get_clientdata(client);
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+	u32 mask = attr->index;
+	long val;
+	int i, ret;
+
+	ret = kstrtol(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&data->update_lock);
+	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
+		if (mask & (1 << i))
+			i2c_smbus_write_word_swapped(client,
+					ina209_reset_history_regs[i], 1);
+	}
+	data->valid = false;
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t ina209_set_value(struct device *dev,
+				struct device_attribute *da,
+				const char *buf,
+				size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ina209_data *data = ina209_update_device(dev);
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+	int reg = attr->index;
+	long val;
+	int ret;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	ret = kstrtol(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&data->update_lock);
+	ret = ina209_to_reg(reg, data->regs[reg], val);
+	if (ret < 0) {
+		count = ret;
+		goto abort;
+	}
+	i2c_smbus_write_word_swapped(client, reg, ret);
+	data->regs[reg] = ret;
+abort:
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t ina209_show_value(struct device *dev,
+				 struct device_attribute *da,
+				 char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+	struct ina209_data *data = ina209_update_device(dev);
+	long val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	val = ina209_from_reg(attr->index, data->regs[attr->index]);
+	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
+}
+
+static ssize_t ina209_show_alarm(struct device *dev,
+				 struct device_attribute *da,
+				 char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+	struct ina209_data *data = ina209_update_device(dev);
+	const unsigned int mask = attr->index;
+	u16 status;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	status = data->regs[INA209_STATUS];
+
+	/*
+	 * All alarms are in the INA209_STATUS register. To avoid a long
+	 * switch statement, the mask is passed in attr->index
+	 */
+	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
+}
+
+/* Shunt voltage, history, limits, alarms */
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
+			  INA209_SHUNT_VOLTAGE);
+static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
+			  INA209_SHUNT_VOLTAGE_POS_PEAK);
+static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
+			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
+static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
+			  ina209_reset_history, (1 << 0) | (1 << 1));
+static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
+static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
+static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_CRITICAL_DAC_POS);
+static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
+
+static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 11);
+static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 12);
+static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 6);
+static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 7);
+
+/* Bus voltage, history, limits, alarms */
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
+			  INA209_BUS_VOLTAGE);
+static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
+			  INA209_BUS_VOLTAGE_MAX_PEAK);
+static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
+			  INA209_BUS_VOLTAGE_MIN_PEAK);
+static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
+			  ina209_reset_history, (1 << 2) | (1 << 3));
+static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
+static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
+static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
+static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
+
+static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 14);
+static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 15);
+static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 9);
+static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 10);
+
+/* Power */
+static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
+			  INA209_POWER);
+static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
+			  NULL, INA209_POWER_PEAK);
+static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
+			  ina209_reset_history, 1 << 4);
+static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_POWER_WARN);
+static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
+			  ina209_set_value, INA209_POWER_OVER_LIMIT);
+
+static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 13);
+static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
+			  1 << 8);
+
+/* Current */
+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
+			  INA209_CURRENT);
+
+static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
+			  ina209_show_interval, ina209_set_interval, 0);
+
+/*
+ * Finally, construct an array of pointers to members of the above objects,
+ * as required for sysfs_create_group()
+ */
+static struct attribute *ina209_attributes[] = {
+	&sensor_dev_attr_in0_input.dev_attr.attr,
+	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
+	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
+	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
+	&sensor_dev_attr_in0_max.dev_attr.attr,
+	&sensor_dev_attr_in0_min.dev_attr.attr,
+	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
+	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
+	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
+	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
+	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
+	&sensor_dev_attr_in1_max.dev_attr.attr,
+	&sensor_dev_attr_in1_min.dev_attr.attr,
+	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
+	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
+	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
+	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_power1_input.dev_attr.attr,
+	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
+	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
+	&sensor_dev_attr_power1_max.dev_attr.attr,
+	&sensor_dev_attr_power1_crit.dev_attr.attr,
+	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
+	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
+
+	&sensor_dev_attr_curr1_input.dev_attr.attr,
+
+	&sensor_dev_attr_update_interval.dev_attr.attr,
+
+	NULL,
+};
+
+static const struct attribute_group ina209_group = {
+	.attrs = ina209_attributes,
+};
+
+static void ina209_restore_conf(struct i2c_client *client,
+				struct ina209_data *data)
+{
+	/* Restore initial configuration */
+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
+				     data->config_orig);
+	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
+				     data->calibration_orig);
+}
+
+static int ina290_init_client(struct i2c_client *client,
+			      struct ina209_data *data)
+{
+	struct ina2xx_platform_data *pdata;
+	long shunt;
+	int reg;
+
+	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
+	if (reg < 0)
+		return reg;
+	data->calibration_orig = reg;
+
+	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
+	if (reg < 0)
+		return reg;
+	data->config_orig = reg;
+
+	if (client->dev.platform_data) {
+		pdata +		  (struct ina2xx_platform_data *)client->dev.platform_data;
+		shunt = pdata->shunt_uohms;
+		if (shunt <= 0)
+			return -ENODEV;
+
+	} else {
+		shunt = data->calibration_orig ?
+		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
+	}
+
+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
+				     INA209_CONFIG_DEFAULT);
+	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
+
+	/*
+	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
+	 * See equation 13 in datasheet.
+	 */
+	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
+				     clamp_val(40960000 / shunt, 1, 65535));
+
+	/* Clear status register */
+	i2c_smbus_read_word_swapped(client, INA209_STATUS);
+
+	return 0;
+}
+
+static int ina209_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	struct ina209_data *data;
+	int ret;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+		return -ENODEV;
+
+	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	ret = ina290_init_client(client, data);
+	if (ret)
+		return ret;
+
+	/* Register sysfs hooks */
+	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
+	if (ret)
+		goto out_restore_conf;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		ret = PTR_ERR(data->hwmon_dev);
+		goto out_hwmon_device_register;
+	}
+
+	return 0;
+
+out_hwmon_device_register:
+	sysfs_remove_group(&client->dev.kobj, &ina209_group);
+out_restore_conf:
+	ina209_restore_conf(client, data);
+	return ret;
+}
+
+static int ina209_remove(struct i2c_client *client)
+{
+	struct ina209_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &ina209_group);
+	ina209_restore_conf(client, data);
+
+	return 0;
+}
+
+static const struct i2c_device_id ina209_id[] = {
+	{ "ina209", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ina209_id);
+
+/* This is the driver that will be inserted */
+static struct i2c_driver ina209_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.driver = {
+		.name	= "ina209",
+	},
+	.probe		= ina209_probe,
+	.remove		= ina209_remove,
+	.id_table	= ina209_id,
+};
+
+module_i2c_driver(ina209_driver);
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
+MODULE_DESCRIPTION("INA209 driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.7


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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (5 preceding siblings ...)
  (?)
@ 2013-01-14 21:17 ` Ira W. Snyder
  -1 siblings, 0 replies; 37+ messages in thread
From: Ira W. Snyder @ 2013-01-14 21:17 UTC (permalink / raw)
  To: lm-sensors

On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> Add support for the TI / Burr-Brown INA209 voltage / current / power
> monitor.
> 
> Cc: Paul Hays <haysp@magma.net>
> Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>

Hi Guenter,

I didn't notice any issues with a quick review. Thanks for pushing the
driver to mainline.

Ira

> ---
>  Documentation/hwmon/ina209 |   93 +++++++
>  drivers/hwmon/Kconfig      |   10 +
>  drivers/hwmon/Makefile     |    1 +
>  drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 739 insertions(+)
>  create mode 100644 Documentation/hwmon/ina209
>  create mode 100644 drivers/hwmon/ina209.c
> 
> diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
> new file mode 100644
> index 0000000..2e11bbc
> --- /dev/null
> +++ b/Documentation/hwmon/ina209
> @@ -0,0 +1,93 @@
> +Kernel driver ina209
> +==========> +
> +Supported chips:
> +  * Burr-Brown / Texas Instruments INA209
> +    Prefix: 'ina209'
> +    Addresses scanned: -
> +    Datasheet:
> +        http://www.ti.com/lit/gpn/ina209
> +
> +Author: Paul Hays <haysp@magma.net>
> +Author: Ira W. Snyder <iws@ovro.caltech.edu>
> +Author: Guenter Roeck <linux@roeck-us.net>
> +
> +
> +Description
> +-----------
> +
> +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
> +of a D.C. power supply. It can perform measurements and calculations in the
> +background to supply readings at any time. It includes a programmable
> +calibration multiplier to scale the displayed current and power values.
> +
> +
> +Sysfs entries
> +-------------
> +
> +The INA209 chip is highly configurable both via hardwiring and via
> +the I2C bus. See the datasheet for details.
> +
> +This tries to expose most monitoring features of the hardware via
> +sysfs. It does not support every feature of this chip.
> +
> +
> +in0_input		shunt voltage (mV)
> +in0_input_highest	shunt voltage historical maximum reading (mV)
> +in0_input_lowest	shunt voltage historical minimum reading (mV)
> +in0_reset_history	reset shunt voltage history
> +in0_max			shunt voltage max alarm limit (mV)
> +in0_min			shunt voltage min alarm limit (mV)
> +in0_crit_max		shunt voltage crit max alarm limit (mV)
> +in0_crit_min		shunt voltage crit min alarm limit (mV)
> +in0_max_alarm		shunt voltage max alarm limit exceeded
> +in0_min_alarm		shunt voltage min alarm limit exceeded
> +in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
> +in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
> +
> +in1_input		bus voltage (mV)
> +in1_input_highest	bus voltage historical maximum reading (mV)
> +in1_input_lowest	bus voltage historical minimum reading (mV)
> +in1_reset_history	reset bus voltage history
> +in1_max			bus voltage max alarm limit (mV)
> +in1_min			bus voltage min alarm limit (mV)
> +in1_crit_max		bus voltage crit max alarm limit (mV)
> +in1_crit_min		bus voltage crit min alarm limit (mV)
> +in1_max_alarm		bus voltage max alarm limit exceeded
> +in1_min_alarm		bus voltage min alarm limit exceeded
> +in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
> +in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
> +
> +power1_input		power measurement (uW)
> +power1_input_highest	power historical maximum reading (uW)
> +power1_reset_history	reset power history
> +power1_max		power max alarm limit (uW)
> +power1_crit		power crit alarm limit (uW)
> +power1_max_alarm	power max alarm limit exceeded
> +power1_crit_alarm	power crit alarm limit exceeded
> +
> +curr1_input		current measurement (mA)
> +
> +update_interval		data conversion time; affects number of samples used
> +			to average results for shunt and bus voltages.
> +
> +General Remarks
> +---------------
> +
> +The power and current registers in this chip require that the calibration
> +register is programmed correctly before they are used. Normally this is expected
> +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
> +voltage can be provided using platform data. The driver uses platform data from
> +the ina2xx driver for this purpose. If calibration register data is not provided
> +via platform data, the driver checks if the calibration register has been
> +programmed (ie has a value not equal to zero). If so, this value is retained.
> +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
> +programmed into the calibration register.
> +
> +
> +Output Pins
> +-----------
> +
> +Output pin programming is a board feature which depends on the BIOS. It is
> +outside the scope of a hardware monitoring driver to enable or disable output
> +pins.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index ef57572..5dacd58 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
>  	  This driver can also be build as a module.  If so, the module
>  	  will be called amc6821.
>  
> +config SENSORS_INA209
> +	tristate "TI / Burr Brown INA209"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for the TI / Burr Brown INA209
> +	  voltage / current / power monitor I2C interface.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called ina209.
> +
>  config SENSORS_INA2XX
>  	tristate "Texas Instruments INA219 and compatibles"
>  	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index a37a82c..8d6d97e 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
>  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
>  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
>  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
> +obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
>  obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
>  obj-$(CONFIG_SENSORS_IT87)	+= it87.o
>  obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
> diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
> new file mode 100644
> index 0000000..1070611
> --- /dev/null
> +++ b/drivers/hwmon/ina209.c
> @@ -0,0 +1,635 @@
> +/*
> + * Driver for the Texas Instruments / Burr Brown INA209
> + * Bidirectional Current/Power Monitor
> + *
> + * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
> + *
> + * Derived from Ira W. Snyder's original driver submission
> + *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
> + *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
> + *
> + * Aligned with ina2xx driver
> + *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
> + *	Thanks to Jan Volkering
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * Datasheet:
> + * http://www.ti.com/lit/gpn/ina209
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/bug.h>
> +#include <linux/i2c.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +
> +#include <linux/platform_data/ina2xx.h>
> +
> +/* register definitions */
> +#define INA209_CONFIGURATION		0x00
> +#define INA209_STATUS			0x01
> +#define INA209_STATUS_MASK		0x02
> +#define INA209_SHUNT_VOLTAGE		0x03
> +#define INA209_BUS_VOLTAGE		0x04
> +#define INA209_POWER			0x05
> +#define INA209_CURRENT			0x06
> +#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
> +#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
> +#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
> +#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
> +#define INA209_POWER_PEAK		0x0b
> +#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
> +#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
> +#define INA209_POWER_WARN		0x0e
> +#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
> +#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
> +#define INA209_POWER_OVER_LIMIT		0x11
> +#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
> +#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
> +#define INA209_CRITICAL_DAC_POS		0x14
> +#define INA209_CRITICAL_DAC_NEG		0x15
> +#define INA209_CALIBRATION		0x16
> +
> +#define INA209_REGISTERS		0x17
> +
> +#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
> +#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
> +
> +struct ina209_data {
> +	struct device *hwmon_dev;
> +
> +	struct mutex update_lock;
> +	bool valid;
> +	unsigned long last_updated;	/* in jiffies */
> +
> +	u16 regs[INA209_REGISTERS];	/* All chip registers */
> +
> +	u16 config_orig;		/* Original configuration */
> +	u16 calibration_orig;		/* Original calibration */
> +	u16 update_interval;
> +};
> +
> +static struct ina209_data *ina209_update_device(struct device *dev)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct ina209_data *data = i2c_get_clientdata(client);
> +	struct ina209_data *ret = data;
> +	s32 val;
> +	int i;
> +
> +	mutex_lock(&data->update_lock);
> +
> +	if (!data->valid ||
> +	    time_after(jiffies, data->last_updated + data->update_interval)) {
> +		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
> +			val = i2c_smbus_read_word_swapped(client, i);
> +			if (val < 0) {
> +				ret = ERR_PTR(val);
> +				goto abort;
> +			}
> +			data->regs[i] = val;
> +		}
> +		data->last_updated = jiffies;
> +		data->valid = true;
> +	}
> +abort:
> +	mutex_unlock(&data->update_lock);
> +	return ret;
> +}
> +
> +/*
> + * Read a value from a device register and convert it to the
> + * appropriate sysfs units
> + */
> +static long ina209_from_reg(const u8 reg, const u16 val)
> +{
> +	switch (reg) {
> +	case INA209_SHUNT_VOLTAGE:
> +	case INA209_SHUNT_VOLTAGE_POS_PEAK:
> +	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
> +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> +		/* LSB\x10 uV. Convert to mV. */
> +		return DIV_ROUND_CLOSEST(val, 100);
> +
> +	case INA209_BUS_VOLTAGE:
> +	case INA209_BUS_VOLTAGE_MAX_PEAK:
> +	case INA209_BUS_VOLTAGE_MIN_PEAK:
> +	case INA209_BUS_VOLTAGE_OVER_WARN:
> +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> +		/* LSB=4 mV, last 3 bits unused */
> +		return (val >> 3) * 4;
> +
> +	case INA209_CRITICAL_DAC_POS:
> +		/* LSB=1 mV, in the upper 8 bits */
> +		return val >> 8;
> +
> +	case INA209_CRITICAL_DAC_NEG:
> +		/* LSB=1 mV, in the upper 8 bits */
> +		return -1 * (val >> 8);
> +
> +	case INA209_POWER:
> +	case INA209_POWER_PEAK:
> +	case INA209_POWER_WARN:
> +	case INA209_POWER_OVER_LIMIT:
> +		/* LSB  mW. Convert to uW */
> +		return val * 20 * 1000L;
> +
> +	case INA209_CURRENT:
> +		/* LSB=1 mA (selected). Is in mA */
> +		return val;
> +	}
> +
> +	/* programmer goofed */
> +	WARN_ON_ONCE(1);
> +	return 0;
> +}
> +
> +/*
> + * Take a value and convert it to register format, clamping the value
> + * to the appropriate range.
> + */
> +static int ina209_to_reg(u8 reg, u16 old, long val)
> +{
> +	switch (reg) {
> +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> +		/* Limit to +- 320 mV, 10 uV LSB */
> +		return clamp_val(val, -320, 320) * 100;
> +
> +	case INA209_BUS_VOLTAGE_OVER_WARN:
> +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> +		/*
> +		 * Limit to 0-32000 mV, 4 mV LSB
> +		 *
> +		 * The last three bits aren't part of the value, but we'll
> +		 * preserve them in their original state.
> +		 */
> +		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
> +		  | (old & 0x7);
> +
> +	case INA209_CRITICAL_DAC_NEG:
> +		/*
> +		 * Limit to -255-0 mV, 1 mV LSB
> +		 * Convert the value to a positive value for the register
> +		 *
> +		 * The value lives in the top 8 bits only, be careful
> +		 * and keep original value of other bits.
> +		 */
> +		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
> +
> +	case INA209_CRITICAL_DAC_POS:
> +		/*
> +		 * Limit to 0-255 mV, 1 mV LSB
> +		 *
> +		 * The value lives in the top 8 bits only, be careful
> +		 * and keep original value of other bits.
> +		 */
> +		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
> +
> +	case INA209_POWER_WARN:
> +	case INA209_POWER_OVER_LIMIT:
> +		/* 20 mW LSB */
> +		return DIV_ROUND_CLOSEST(val, 20 * 1000);
> +	}
> +
> +	/* Other registers are read-only, return access error */
> +	return -EACCES;
> +}
> +
> +static int ina209_interval_from_reg(u16 reg)
> +{
> +	return 68 >> (15 - ((reg >> 3) & 0x0f));
> +}
> +
> +static u16 ina209_reg_from_interval(u16 config, long interval)
> +{
> +	int i, adc;
> +
> +	if (interval <= 0) {
> +		adc = 8;
> +	} else {
> +		adc = 15;
> +		for (i = 34 + 34 / 2; i; i >>= 1) {
> +			if (i < interval)
> +				break;
> +			adc--;
> +		}
> +	}
> +	return (config & 0xf807) | (adc << 3) | (adc << 7);
> +}
> +
> +static ssize_t ina209_set_interval(struct device *dev,
> +				   struct device_attribute *da,
> +				   const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct ina209_data *data = ina209_update_device(dev);
> +	long val;
> +	u16 regval;
> +	int ret;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	ret = kstrtol(buf, 10, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	mutex_lock(&data->update_lock);
> +	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
> +					  val);
> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
> +	data->regs[INA209_CONFIGURATION] = regval;
> +	data->update_interval = ina209_interval_from_reg(regval);
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static ssize_t ina209_show_interval(struct device *dev,
> +				    struct device_attribute *da, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct ina209_data *data = i2c_get_clientdata(client);
> +
> +	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
> +}
> +
> +/*
> + * History is reset by writing 1 into bit 0 of the respective peak register.
> + * Since more than one peak register may be affected by the scope of a
> + * reset_history attribute write, use a bit mask in attr->index to identify
> + * which registers are affected.
> + */
> +static u16 ina209_reset_history_regs[] = {
> +	INA209_SHUNT_VOLTAGE_POS_PEAK,
> +	INA209_SHUNT_VOLTAGE_NEG_PEAK,
> +	INA209_BUS_VOLTAGE_MAX_PEAK,
> +	INA209_BUS_VOLTAGE_MIN_PEAK,
> +	INA209_POWER_PEAK
> +};
> +
> +static ssize_t ina209_reset_history(struct device *dev,
> +				    struct device_attribute *da,
> +				    const char *buf,
> +				    size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct ina209_data *data = i2c_get_clientdata(client);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> +	u32 mask = attr->index;
> +	long val;
> +	int i, ret;
> +
> +	ret = kstrtol(buf, 10, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	mutex_lock(&data->update_lock);
> +	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
> +		if (mask & (1 << i))
> +			i2c_smbus_write_word_swapped(client,
> +					ina209_reset_history_regs[i], 1);
> +	}
> +	data->valid = false;
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static ssize_t ina209_set_value(struct device *dev,
> +				struct device_attribute *da,
> +				const char *buf,
> +				size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct ina209_data *data = ina209_update_device(dev);
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> +	int reg = attr->index;
> +	long val;
> +	int ret;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	ret = kstrtol(buf, 10, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	mutex_lock(&data->update_lock);
> +	ret = ina209_to_reg(reg, data->regs[reg], val);
> +	if (ret < 0) {
> +		count = ret;
> +		goto abort;
> +	}
> +	i2c_smbus_write_word_swapped(client, reg, ret);
> +	data->regs[reg] = ret;
> +abort:
> +	mutex_unlock(&data->update_lock);
> +	return count;
> +}
> +
> +static ssize_t ina209_show_value(struct device *dev,
> +				 struct device_attribute *da,
> +				 char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> +	struct ina209_data *data = ina209_update_device(dev);
> +	long val;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	val = ina209_from_reg(attr->index, data->regs[attr->index]);
> +	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
> +}
> +
> +static ssize_t ina209_show_alarm(struct device *dev,
> +				 struct device_attribute *da,
> +				 char *buf)
> +{
> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> +	struct ina209_data *data = ina209_update_device(dev);
> +	const unsigned int mask = attr->index;
> +	u16 status;
> +
> +	if (IS_ERR(data))
> +		return PTR_ERR(data);
> +
> +	status = data->regs[INA209_STATUS];
> +
> +	/*
> +	 * All alarms are in the INA209_STATUS register. To avoid a long
> +	 * switch statement, the mask is passed in attr->index
> +	 */
> +	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
> +}
> +
> +/* Shunt voltage, history, limits, alarms */
> +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_SHUNT_VOLTAGE);
> +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_SHUNT_VOLTAGE_POS_PEAK);
> +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
> +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
> +			  ina209_reset_history, (1 << 0) | (1 << 1));
> +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
> +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
> +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_CRITICAL_DAC_POS);
> +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
> +
> +static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 11);
> +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 12);
> +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 6);
> +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 7);
> +
> +/* Bus voltage, history, limits, alarms */
> +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_BUS_VOLTAGE);
> +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_BUS_VOLTAGE_MAX_PEAK);
> +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_BUS_VOLTAGE_MIN_PEAK);
> +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
> +			  ina209_reset_history, (1 << 2) | (1 << 3));
> +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
> +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
> +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
> +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
> +
> +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 14);
> +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 15);
> +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 9);
> +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 10);
> +
> +/* Power */
> +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_POWER);
> +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
> +			  NULL, INA209_POWER_PEAK);
> +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
> +			  ina209_reset_history, 1 << 4);
> +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_POWER_WARN);
> +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
> +			  ina209_set_value, INA209_POWER_OVER_LIMIT);
> +
> +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 13);
> +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
> +			  1 << 8);
> +
> +/* Current */
> +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
> +			  INA209_CURRENT);
> +
> +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
> +			  ina209_show_interval, ina209_set_interval, 0);
> +
> +/*
> + * Finally, construct an array of pointers to members of the above objects,
> + * as required for sysfs_create_group()
> + */
> +static struct attribute *ina209_attributes[] = {
> +	&sensor_dev_attr_in0_input.dev_attr.attr,
> +	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
> +	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
> +	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
> +	&sensor_dev_attr_in0_max.dev_attr.attr,
> +	&sensor_dev_attr_in0_min.dev_attr.attr,
> +	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
> +	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
> +	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_in1_input.dev_attr.attr,
> +	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
> +	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
> +	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
> +	&sensor_dev_attr_in1_max.dev_attr.attr,
> +	&sensor_dev_attr_in1_min.dev_attr.attr,
> +	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
> +	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
> +	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
> +	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_power1_input.dev_attr.attr,
> +	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
> +	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
> +	&sensor_dev_attr_power1_max.dev_attr.attr,
> +	&sensor_dev_attr_power1_crit.dev_attr.attr,
> +	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
> +	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
> +
> +	&sensor_dev_attr_curr1_input.dev_attr.attr,
> +
> +	&sensor_dev_attr_update_interval.dev_attr.attr,
> +
> +	NULL,
> +};
> +
> +static const struct attribute_group ina209_group = {
> +	.attrs = ina209_attributes,
> +};
> +
> +static void ina209_restore_conf(struct i2c_client *client,
> +				struct ina209_data *data)
> +{
> +	/* Restore initial configuration */
> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> +				     data->config_orig);
> +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> +				     data->calibration_orig);
> +}
> +
> +static int ina290_init_client(struct i2c_client *client,
> +			      struct ina209_data *data)
> +{
> +	struct ina2xx_platform_data *pdata;
> +	long shunt;
> +	int reg;
> +
> +	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
> +	if (reg < 0)
> +		return reg;
> +	data->calibration_orig = reg;
> +
> +	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
> +	if (reg < 0)
> +		return reg;
> +	data->config_orig = reg;
> +
> +	if (client->dev.platform_data) {
> +		pdata > +		  (struct ina2xx_platform_data *)client->dev.platform_data;
> +		shunt = pdata->shunt_uohms;
> +		if (shunt <= 0)
> +			return -ENODEV;
> +
> +	} else {
> +		shunt = data->calibration_orig ?
> +		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
> +	}
> +
> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> +				     INA209_CONFIG_DEFAULT);
> +	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
> +
> +	/*
> +	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
> +	 * See equation 13 in datasheet.
> +	 */
> +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> +				     clamp_val(40960000 / shunt, 1, 65535));
> +
> +	/* Clear status register */
> +	i2c_smbus_read_word_swapped(client, INA209_STATUS);
> +
> +	return 0;
> +}
> +
> +static int ina209_probe(struct i2c_client *client,
> +			const struct i2c_device_id *id)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	struct ina209_data *data;
> +	int ret;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> +		return -ENODEV;
> +
> +	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, data);
> +	mutex_init(&data->update_lock);
> +
> +	ret = ina290_init_client(client, data);
> +	if (ret)
> +		return ret;
> +
> +	/* Register sysfs hooks */
> +	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
> +	if (ret)
> +		goto out_restore_conf;
> +
> +	data->hwmon_dev = hwmon_device_register(&client->dev);
> +	if (IS_ERR(data->hwmon_dev)) {
> +		ret = PTR_ERR(data->hwmon_dev);
> +		goto out_hwmon_device_register;
> +	}
> +
> +	return 0;
> +
> +out_hwmon_device_register:
> +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> +out_restore_conf:
> +	ina209_restore_conf(client, data);
> +	return ret;
> +}
> +
> +static int ina209_remove(struct i2c_client *client)
> +{
> +	struct ina209_data *data = i2c_get_clientdata(client);
> +
> +	hwmon_device_unregister(data->hwmon_dev);
> +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> +	ina209_restore_conf(client, data);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ina209_id[] = {
> +	{ "ina209", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ina209_id);
> +
> +/* This is the driver that will be inserted */
> +static struct i2c_driver ina209_driver = {
> +	.class		= I2C_CLASS_HWMON,
> +	.driver = {
> +		.name	= "ina209",
> +	},
> +	.probe		= ina209_probe,
> +	.remove		= ina209_remove,
> +	.id_table	= ina209_id,
> +};
> +
> +module_i2c_driver(ina209_driver);
> +
> +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
> +MODULE_DESCRIPTION("INA209 driver");
> +MODULE_LICENSE("GPL");
> -- 
> 1.7.9.7
> 

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (6 preceding siblings ...)
  (?)
@ 2013-01-15  6:04 ` Guenter Roeck
  -1 siblings, 0 replies; 37+ messages in thread
From: Guenter Roeck @ 2013-01-15  6:04 UTC (permalink / raw)
  To: lm-sensors

On Mon, Jan 14, 2013 at 01:17:04PM -0800, Ira W. Snyder wrote:
> On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> > Add support for the TI / Burr-Brown INA209 voltage / current / power
> > monitor.
> > 
> > Cc: Paul Hays <haysp@magma.net>
> > Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> 
> Hi Guenter,
> 
> I didn't notice any issues with a quick review. Thanks for pushing the
> driver to mainline.
> 
Hi Ira,

you are welcome. I actually had the code in my queue for a long time;
I just found some time last weekend to finally test it.

It would be great if you can do some testing and send me a Tested-by and/or
Acked-by feedback.

Thanks,
Guenter

> Ira
> 
> > ---
> >  Documentation/hwmon/ina209 |   93 +++++++
> >  drivers/hwmon/Kconfig      |   10 +
> >  drivers/hwmon/Makefile     |    1 +
> >  drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 739 insertions(+)
> >  create mode 100644 Documentation/hwmon/ina209
> >  create mode 100644 drivers/hwmon/ina209.c
> > 
> > diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
> > new file mode 100644
> > index 0000000..2e11bbc
> > --- /dev/null
> > +++ b/Documentation/hwmon/ina209
> > @@ -0,0 +1,93 @@
> > +Kernel driver ina209
> > +==========> > +
> > +Supported chips:
> > +  * Burr-Brown / Texas Instruments INA209
> > +    Prefix: 'ina209'
> > +    Addresses scanned: -
> > +    Datasheet:
> > +        http://www.ti.com/lit/gpn/ina209
> > +
> > +Author: Paul Hays <haysp@magma.net>
> > +Author: Ira W. Snyder <iws@ovro.caltech.edu>
> > +Author: Guenter Roeck <linux@roeck-us.net>
> > +
> > +
> > +Description
> > +-----------
> > +
> > +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
> > +of a D.C. power supply. It can perform measurements and calculations in the
> > +background to supply readings at any time. It includes a programmable
> > +calibration multiplier to scale the displayed current and power values.
> > +
> > +
> > +Sysfs entries
> > +-------------
> > +
> > +The INA209 chip is highly configurable both via hardwiring and via
> > +the I2C bus. See the datasheet for details.
> > +
> > +This tries to expose most monitoring features of the hardware via
> > +sysfs. It does not support every feature of this chip.
> > +
> > +
> > +in0_input		shunt voltage (mV)
> > +in0_input_highest	shunt voltage historical maximum reading (mV)
> > +in0_input_lowest	shunt voltage historical minimum reading (mV)
> > +in0_reset_history	reset shunt voltage history
> > +in0_max			shunt voltage max alarm limit (mV)
> > +in0_min			shunt voltage min alarm limit (mV)
> > +in0_crit_max		shunt voltage crit max alarm limit (mV)
> > +in0_crit_min		shunt voltage crit min alarm limit (mV)
> > +in0_max_alarm		shunt voltage max alarm limit exceeded
> > +in0_min_alarm		shunt voltage min alarm limit exceeded
> > +in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
> > +in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
> > +
> > +in1_input		bus voltage (mV)
> > +in1_input_highest	bus voltage historical maximum reading (mV)
> > +in1_input_lowest	bus voltage historical minimum reading (mV)
> > +in1_reset_history	reset bus voltage history
> > +in1_max			bus voltage max alarm limit (mV)
> > +in1_min			bus voltage min alarm limit (mV)
> > +in1_crit_max		bus voltage crit max alarm limit (mV)
> > +in1_crit_min		bus voltage crit min alarm limit (mV)
> > +in1_max_alarm		bus voltage max alarm limit exceeded
> > +in1_min_alarm		bus voltage min alarm limit exceeded
> > +in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
> > +in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
> > +
> > +power1_input		power measurement (uW)
> > +power1_input_highest	power historical maximum reading (uW)
> > +power1_reset_history	reset power history
> > +power1_max		power max alarm limit (uW)
> > +power1_crit		power crit alarm limit (uW)
> > +power1_max_alarm	power max alarm limit exceeded
> > +power1_crit_alarm	power crit alarm limit exceeded
> > +
> > +curr1_input		current measurement (mA)
> > +
> > +update_interval		data conversion time; affects number of samples used
> > +			to average results for shunt and bus voltages.
> > +
> > +General Remarks
> > +---------------
> > +
> > +The power and current registers in this chip require that the calibration
> > +register is programmed correctly before they are used. Normally this is expected
> > +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
> > +voltage can be provided using platform data. The driver uses platform data from
> > +the ina2xx driver for this purpose. If calibration register data is not provided
> > +via platform data, the driver checks if the calibration register has been
> > +programmed (ie has a value not equal to zero). If so, this value is retained.
> > +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
> > +programmed into the calibration register.
> > +
> > +
> > +Output Pins
> > +-----------
> > +
> > +Output pin programming is a board feature which depends on the BIOS. It is
> > +outside the scope of a hardware monitoring driver to enable or disable output
> > +pins.
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index ef57572..5dacd58 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
> >  	  This driver can also be build as a module.  If so, the module
> >  	  will be called amc6821.
> >  
> > +config SENSORS_INA209
> > +	tristate "TI / Burr Brown INA209"
> > +	depends on I2C
> > +	help
> > +	  If you say yes here you get support for the TI / Burr Brown INA209
> > +	  voltage / current / power monitor I2C interface.
> > +
> > +	  This driver can also be built as a module. If so, the module will
> > +	  be called ina209.
> > +
> >  config SENSORS_INA2XX
> >  	tristate "Texas Instruments INA219 and compatibles"
> >  	depends on I2C
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index a37a82c..8d6d97e 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> >  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
> >  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
> >  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
> > +obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
> >  obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
> >  obj-$(CONFIG_SENSORS_IT87)	+= it87.o
> >  obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
> > diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
> > new file mode 100644
> > index 0000000..1070611
> > --- /dev/null
> > +++ b/drivers/hwmon/ina209.c
> > @@ -0,0 +1,635 @@
> > +/*
> > + * Driver for the Texas Instruments / Burr Brown INA209
> > + * Bidirectional Current/Power Monitor
> > + *
> > + * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
> > + *
> > + * Derived from Ira W. Snyder's original driver submission
> > + *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
> > + *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
> > + *
> > + * Aligned with ina2xx driver
> > + *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
> > + *	Thanks to Jan Volkering
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; version 2 of the License.
> > + *
> > + * Datasheet:
> > + * http://www.ti.com/lit/gpn/ina209
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/init.h>
> > +#include <linux/err.h>
> > +#include <linux/slab.h>
> > +#include <linux/bug.h>
> > +#include <linux/i2c.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
> > +
> > +#include <linux/platform_data/ina2xx.h>
> > +
> > +/* register definitions */
> > +#define INA209_CONFIGURATION		0x00
> > +#define INA209_STATUS			0x01
> > +#define INA209_STATUS_MASK		0x02
> > +#define INA209_SHUNT_VOLTAGE		0x03
> > +#define INA209_BUS_VOLTAGE		0x04
> > +#define INA209_POWER			0x05
> > +#define INA209_CURRENT			0x06
> > +#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
> > +#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
> > +#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
> > +#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
> > +#define INA209_POWER_PEAK		0x0b
> > +#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
> > +#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
> > +#define INA209_POWER_WARN		0x0e
> > +#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
> > +#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
> > +#define INA209_POWER_OVER_LIMIT		0x11
> > +#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
> > +#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
> > +#define INA209_CRITICAL_DAC_POS		0x14
> > +#define INA209_CRITICAL_DAC_NEG		0x15
> > +#define INA209_CALIBRATION		0x16
> > +
> > +#define INA209_REGISTERS		0x17
> > +
> > +#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
> > +#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
> > +
> > +struct ina209_data {
> > +	struct device *hwmon_dev;
> > +
> > +	struct mutex update_lock;
> > +	bool valid;
> > +	unsigned long last_updated;	/* in jiffies */
> > +
> > +	u16 regs[INA209_REGISTERS];	/* All chip registers */
> > +
> > +	u16 config_orig;		/* Original configuration */
> > +	u16 calibration_orig;		/* Original calibration */
> > +	u16 update_interval;
> > +};
> > +
> > +static struct ina209_data *ina209_update_device(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ina209_data *data = i2c_get_clientdata(client);
> > +	struct ina209_data *ret = data;
> > +	s32 val;
> > +	int i;
> > +
> > +	mutex_lock(&data->update_lock);
> > +
> > +	if (!data->valid ||
> > +	    time_after(jiffies, data->last_updated + data->update_interval)) {
> > +		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
> > +			val = i2c_smbus_read_word_swapped(client, i);
> > +			if (val < 0) {
> > +				ret = ERR_PTR(val);
> > +				goto abort;
> > +			}
> > +			data->regs[i] = val;
> > +		}
> > +		data->last_updated = jiffies;
> > +		data->valid = true;
> > +	}
> > +abort:
> > +	mutex_unlock(&data->update_lock);
> > +	return ret;
> > +}
> > +
> > +/*
> > + * Read a value from a device register and convert it to the
> > + * appropriate sysfs units
> > + */
> > +static long ina209_from_reg(const u8 reg, const u16 val)
> > +{
> > +	switch (reg) {
> > +	case INA209_SHUNT_VOLTAGE:
> > +	case INA209_SHUNT_VOLTAGE_POS_PEAK:
> > +	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
> > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > +		/* LSB\x10 uV. Convert to mV. */
> > +		return DIV_ROUND_CLOSEST(val, 100);
> > +
> > +	case INA209_BUS_VOLTAGE:
> > +	case INA209_BUS_VOLTAGE_MAX_PEAK:
> > +	case INA209_BUS_VOLTAGE_MIN_PEAK:
> > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > +		/* LSB=4 mV, last 3 bits unused */
> > +		return (val >> 3) * 4;
> > +
> > +	case INA209_CRITICAL_DAC_POS:
> > +		/* LSB=1 mV, in the upper 8 bits */
> > +		return val >> 8;
> > +
> > +	case INA209_CRITICAL_DAC_NEG:
> > +		/* LSB=1 mV, in the upper 8 bits */
> > +		return -1 * (val >> 8);
> > +
> > +	case INA209_POWER:
> > +	case INA209_POWER_PEAK:
> > +	case INA209_POWER_WARN:
> > +	case INA209_POWER_OVER_LIMIT:
> > +		/* LSB  mW. Convert to uW */
> > +		return val * 20 * 1000L;
> > +
> > +	case INA209_CURRENT:
> > +		/* LSB=1 mA (selected). Is in mA */
> > +		return val;
> > +	}
> > +
> > +	/* programmer goofed */
> > +	WARN_ON_ONCE(1);
> > +	return 0;
> > +}
> > +
> > +/*
> > + * Take a value and convert it to register format, clamping the value
> > + * to the appropriate range.
> > + */
> > +static int ina209_to_reg(u8 reg, u16 old, long val)
> > +{
> > +	switch (reg) {
> > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > +		/* Limit to +- 320 mV, 10 uV LSB */
> > +		return clamp_val(val, -320, 320) * 100;
> > +
> > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > +		/*
> > +		 * Limit to 0-32000 mV, 4 mV LSB
> > +		 *
> > +		 * The last three bits aren't part of the value, but we'll
> > +		 * preserve them in their original state.
> > +		 */
> > +		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
> > +		  | (old & 0x7);
> > +
> > +	case INA209_CRITICAL_DAC_NEG:
> > +		/*
> > +		 * Limit to -255-0 mV, 1 mV LSB
> > +		 * Convert the value to a positive value for the register
> > +		 *
> > +		 * The value lives in the top 8 bits only, be careful
> > +		 * and keep original value of other bits.
> > +		 */
> > +		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
> > +
> > +	case INA209_CRITICAL_DAC_POS:
> > +		/*
> > +		 * Limit to 0-255 mV, 1 mV LSB
> > +		 *
> > +		 * The value lives in the top 8 bits only, be careful
> > +		 * and keep original value of other bits.
> > +		 */
> > +		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
> > +
> > +	case INA209_POWER_WARN:
> > +	case INA209_POWER_OVER_LIMIT:
> > +		/* 20 mW LSB */
> > +		return DIV_ROUND_CLOSEST(val, 20 * 1000);
> > +	}
> > +
> > +	/* Other registers are read-only, return access error */
> > +	return -EACCES;
> > +}
> > +
> > +static int ina209_interval_from_reg(u16 reg)
> > +{
> > +	return 68 >> (15 - ((reg >> 3) & 0x0f));
> > +}
> > +
> > +static u16 ina209_reg_from_interval(u16 config, long interval)
> > +{
> > +	int i, adc;
> > +
> > +	if (interval <= 0) {
> > +		adc = 8;
> > +	} else {
> > +		adc = 15;
> > +		for (i = 34 + 34 / 2; i; i >>= 1) {
> > +			if (i < interval)
> > +				break;
> > +			adc--;
> > +		}
> > +	}
> > +	return (config & 0xf807) | (adc << 3) | (adc << 7);
> > +}
> > +
> > +static ssize_t ina209_set_interval(struct device *dev,
> > +				   struct device_attribute *da,
> > +				   const char *buf, size_t count)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ina209_data *data = ina209_update_device(dev);
> > +	long val;
> > +	u16 regval;
> > +	int ret;
> > +
> > +	if (IS_ERR(data))
> > +		return PTR_ERR(data);
> > +
> > +	ret = kstrtol(buf, 10, &val);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	mutex_lock(&data->update_lock);
> > +	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
> > +					  val);
> > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
> > +	data->regs[INA209_CONFIGURATION] = regval;
> > +	data->update_interval = ina209_interval_from_reg(regval);
> > +	mutex_unlock(&data->update_lock);
> > +	return count;
> > +}
> > +
> > +static ssize_t ina209_show_interval(struct device *dev,
> > +				    struct device_attribute *da, char *buf)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ina209_data *data = i2c_get_clientdata(client);
> > +
> > +	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
> > +}
> > +
> > +/*
> > + * History is reset by writing 1 into bit 0 of the respective peak register.
> > + * Since more than one peak register may be affected by the scope of a
> > + * reset_history attribute write, use a bit mask in attr->index to identify
> > + * which registers are affected.
> > + */
> > +static u16 ina209_reset_history_regs[] = {
> > +	INA209_SHUNT_VOLTAGE_POS_PEAK,
> > +	INA209_SHUNT_VOLTAGE_NEG_PEAK,
> > +	INA209_BUS_VOLTAGE_MAX_PEAK,
> > +	INA209_BUS_VOLTAGE_MIN_PEAK,
> > +	INA209_POWER_PEAK
> > +};
> > +
> > +static ssize_t ina209_reset_history(struct device *dev,
> > +				    struct device_attribute *da,
> > +				    const char *buf,
> > +				    size_t count)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ina209_data *data = i2c_get_clientdata(client);
> > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > +	u32 mask = attr->index;
> > +	long val;
> > +	int i, ret;
> > +
> > +	ret = kstrtol(buf, 10, &val);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	mutex_lock(&data->update_lock);
> > +	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
> > +		if (mask & (1 << i))
> > +			i2c_smbus_write_word_swapped(client,
> > +					ina209_reset_history_regs[i], 1);
> > +	}
> > +	data->valid = false;
> > +	mutex_unlock(&data->update_lock);
> > +	return count;
> > +}
> > +
> > +static ssize_t ina209_set_value(struct device *dev,
> > +				struct device_attribute *da,
> > +				const char *buf,
> > +				size_t count)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ina209_data *data = ina209_update_device(dev);
> > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > +	int reg = attr->index;
> > +	long val;
> > +	int ret;
> > +
> > +	if (IS_ERR(data))
> > +		return PTR_ERR(data);
> > +
> > +	ret = kstrtol(buf, 10, &val);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	mutex_lock(&data->update_lock);
> > +	ret = ina209_to_reg(reg, data->regs[reg], val);
> > +	if (ret < 0) {
> > +		count = ret;
> > +		goto abort;
> > +	}
> > +	i2c_smbus_write_word_swapped(client, reg, ret);
> > +	data->regs[reg] = ret;
> > +abort:
> > +	mutex_unlock(&data->update_lock);
> > +	return count;
> > +}
> > +
> > +static ssize_t ina209_show_value(struct device *dev,
> > +				 struct device_attribute *da,
> > +				 char *buf)
> > +{
> > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > +	struct ina209_data *data = ina209_update_device(dev);
> > +	long val;
> > +
> > +	if (IS_ERR(data))
> > +		return PTR_ERR(data);
> > +
> > +	val = ina209_from_reg(attr->index, data->regs[attr->index]);
> > +	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
> > +}
> > +
> > +static ssize_t ina209_show_alarm(struct device *dev,
> > +				 struct device_attribute *da,
> > +				 char *buf)
> > +{
> > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > +	struct ina209_data *data = ina209_update_device(dev);
> > +	const unsigned int mask = attr->index;
> > +	u16 status;
> > +
> > +	if (IS_ERR(data))
> > +		return PTR_ERR(data);
> > +
> > +	status = data->regs[INA209_STATUS];
> > +
> > +	/*
> > +	 * All alarms are in the INA209_STATUS register. To avoid a long
> > +	 * switch statement, the mask is passed in attr->index
> > +	 */
> > +	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
> > +}
> > +
> > +/* Shunt voltage, history, limits, alarms */
> > +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_SHUNT_VOLTAGE);
> > +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_SHUNT_VOLTAGE_POS_PEAK);
> > +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
> > +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
> > +			  ina209_reset_history, (1 << 0) | (1 << 1));
> > +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
> > +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
> > +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_CRITICAL_DAC_POS);
> > +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
> > +
> > +static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 11);
> > +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 12);
> > +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 6);
> > +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 7);
> > +
> > +/* Bus voltage, history, limits, alarms */
> > +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_BUS_VOLTAGE);
> > +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_BUS_VOLTAGE_MAX_PEAK);
> > +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_BUS_VOLTAGE_MIN_PEAK);
> > +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
> > +			  ina209_reset_history, (1 << 2) | (1 << 3));
> > +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
> > +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
> > +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
> > +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
> > +
> > +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 14);
> > +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 15);
> > +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 9);
> > +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 10);
> > +
> > +/* Power */
> > +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_POWER);
> > +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
> > +			  NULL, INA209_POWER_PEAK);
> > +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
> > +			  ina209_reset_history, 1 << 4);
> > +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_POWER_WARN);
> > +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
> > +			  ina209_set_value, INA209_POWER_OVER_LIMIT);
> > +
> > +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 13);
> > +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > +			  1 << 8);
> > +
> > +/* Current */
> > +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
> > +			  INA209_CURRENT);
> > +
> > +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
> > +			  ina209_show_interval, ina209_set_interval, 0);
> > +
> > +/*
> > + * Finally, construct an array of pointers to members of the above objects,
> > + * as required for sysfs_create_group()
> > + */
> > +static struct attribute *ina209_attributes[] = {
> > +	&sensor_dev_attr_in0_input.dev_attr.attr,
> > +	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
> > +	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
> > +	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
> > +	&sensor_dev_attr_in0_max.dev_attr.attr,
> > +	&sensor_dev_attr_in0_min.dev_attr.attr,
> > +	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
> > +	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
> > +	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
> > +
> > +	&sensor_dev_attr_in1_input.dev_attr.attr,
> > +	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
> > +	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
> > +	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
> > +	&sensor_dev_attr_in1_max.dev_attr.attr,
> > +	&sensor_dev_attr_in1_min.dev_attr.attr,
> > +	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
> > +	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
> > +	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
> > +
> > +	&sensor_dev_attr_power1_input.dev_attr.attr,
> > +	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
> > +	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
> > +	&sensor_dev_attr_power1_max.dev_attr.attr,
> > +	&sensor_dev_attr_power1_crit.dev_attr.attr,
> > +	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
> > +	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
> > +
> > +	&sensor_dev_attr_curr1_input.dev_attr.attr,
> > +
> > +	&sensor_dev_attr_update_interval.dev_attr.attr,
> > +
> > +	NULL,
> > +};
> > +
> > +static const struct attribute_group ina209_group = {
> > +	.attrs = ina209_attributes,
> > +};
> > +
> > +static void ina209_restore_conf(struct i2c_client *client,
> > +				struct ina209_data *data)
> > +{
> > +	/* Restore initial configuration */
> > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > +				     data->config_orig);
> > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > +				     data->calibration_orig);
> > +}
> > +
> > +static int ina290_init_client(struct i2c_client *client,
> > +			      struct ina209_data *data)
> > +{
> > +	struct ina2xx_platform_data *pdata;
> > +	long shunt;
> > +	int reg;
> > +
> > +	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
> > +	if (reg < 0)
> > +		return reg;
> > +	data->calibration_orig = reg;
> > +
> > +	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
> > +	if (reg < 0)
> > +		return reg;
> > +	data->config_orig = reg;
> > +
> > +	if (client->dev.platform_data) {
> > +		pdata > > +		  (struct ina2xx_platform_data *)client->dev.platform_data;
> > +		shunt = pdata->shunt_uohms;
> > +		if (shunt <= 0)
> > +			return -ENODEV;
> > +
> > +	} else {
> > +		shunt = data->calibration_orig ?
> > +		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
> > +	}
> > +
> > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > +				     INA209_CONFIG_DEFAULT);
> > +	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
> > +
> > +	/*
> > +	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
> > +	 * See equation 13 in datasheet.
> > +	 */
> > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > +				     clamp_val(40960000 / shunt, 1, 65535));
> > +
> > +	/* Clear status register */
> > +	i2c_smbus_read_word_swapped(client, INA209_STATUS);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ina209_probe(struct i2c_client *client,
> > +			const struct i2c_device_id *id)
> > +{
> > +	struct i2c_adapter *adapter = client->adapter;
> > +	struct ina209_data *data;
> > +	int ret;
> > +
> > +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> > +		return -ENODEV;
> > +
> > +	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
> > +	if (!data)
> > +		return -ENOMEM;
> > +
> > +	i2c_set_clientdata(client, data);
> > +	mutex_init(&data->update_lock);
> > +
> > +	ret = ina290_init_client(client, data);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* Register sysfs hooks */
> > +	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
> > +	if (ret)
> > +		goto out_restore_conf;
> > +
> > +	data->hwmon_dev = hwmon_device_register(&client->dev);
> > +	if (IS_ERR(data->hwmon_dev)) {
> > +		ret = PTR_ERR(data->hwmon_dev);
> > +		goto out_hwmon_device_register;
> > +	}
> > +
> > +	return 0;
> > +
> > +out_hwmon_device_register:
> > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > +out_restore_conf:
> > +	ina209_restore_conf(client, data);
> > +	return ret;
> > +}
> > +
> > +static int ina209_remove(struct i2c_client *client)
> > +{
> > +	struct ina209_data *data = i2c_get_clientdata(client);
> > +
> > +	hwmon_device_unregister(data->hwmon_dev);
> > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > +	ina209_restore_conf(client, data);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct i2c_device_id ina209_id[] = {
> > +	{ "ina209", 0 },
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, ina209_id);
> > +
> > +/* This is the driver that will be inserted */
> > +static struct i2c_driver ina209_driver = {
> > +	.class		= I2C_CLASS_HWMON,
> > +	.driver = {
> > +		.name	= "ina209",
> > +	},
> > +	.probe		= ina209_probe,
> > +	.remove		= ina209_remove,
> > +	.id_table	= ina209_id,
> > +};
> > +
> > +module_i2c_driver(ina209_driver);
> > +
> > +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
> > +MODULE_DESCRIPTION("INA209 driver");
> > +MODULE_LICENSE("GPL");
> > -- 
> > 1.7.9.7
> > 
> 

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (7 preceding siblings ...)
  (?)
@ 2013-01-15 22:10 ` Paul Hays
  -1 siblings, 0 replies; 37+ messages in thread
From: Paul Hays @ 2013-01-15 22:10 UTC (permalink / raw)
  To: lm-sensors

I didn't know what to expect after posting this little driver back in 
2008.  Guenter, it's nice to see the code moving toward the mainline. 
Thank you Ira for your improvements (though you seem to have discarded 
many of my comments!)

Here are two small things I noticed:
  - change all ina290_init_client to ina209_init_client
  - My email address has changed from haysp@magma.net; please include
Paul Hays <Paul.Hays@cattail.ca> in MODULE_AUTHOR


On 14/01/2013 4:17 PM, Ira W. Snyder wrote:
> On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
>> Add support for the TI / Burr-Brown INA209 voltage / current / power
>> monitor.
>>
>> Cc: Paul Hays <haysp@magma.net>
>> Cc: Ira W. Snyder <iws@ovro.caltech.edu>
>> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
>
> Hi Guenter,
>
> I didn't notice any issues with a quick review. Thanks for pushing the
> driver to mainline.
>
> Ira
>
>> ---
>>   Documentation/hwmon/ina209 |   93 +++++++
>>   drivers/hwmon/Kconfig      |   10 +
>>   drivers/hwmon/Makefile     |    1 +
>>   drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
>>   4 files changed, 739 insertions(+)
>>   create mode 100644 Documentation/hwmon/ina209
>>   create mode 100644 drivers/hwmon/ina209.c
>>
>> diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
>> new file mode 100644
>> index 0000000..2e11bbc
>> --- /dev/null
>> +++ b/Documentation/hwmon/ina209
>> @@ -0,0 +1,93 @@
>> +Kernel driver ina209
>> +==========>> +
>> +Supported chips:
>> +  * Burr-Brown / Texas Instruments INA209
>> +    Prefix: 'ina209'
>> +    Addresses scanned: -
>> +    Datasheet:
>> +        http://www.ti.com/lit/gpn/ina209
>> +
>> +Author: Paul Hays <haysp@magma.net>
>> +Author: Ira W. Snyder <iws@ovro.caltech.edu>
>> +Author: Guenter Roeck <linux@roeck-us.net>
>> +
>> +
>> +Description
>> +-----------
>> +
>> +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
>> +of a D.C. power supply. It can perform measurements and calculations in the
>> +background to supply readings at any time. It includes a programmable
>> +calibration multiplier to scale the displayed current and power values.
>> +
>> +
>> +Sysfs entries
>> +-------------
>> +
>> +The INA209 chip is highly configurable both via hardwiring and via
>> +the I2C bus. See the datasheet for details.
>> +
>> +This tries to expose most monitoring features of the hardware via
>> +sysfs. It does not support every feature of this chip.
>> +
>> +
>> +in0_input		shunt voltage (mV)
>> +in0_input_highest	shunt voltage historical maximum reading (mV)
>> +in0_input_lowest	shunt voltage historical minimum reading (mV)
>> +in0_reset_history	reset shunt voltage history
>> +in0_max			shunt voltage max alarm limit (mV)
>> +in0_min			shunt voltage min alarm limit (mV)
>> +in0_crit_max		shunt voltage crit max alarm limit (mV)
>> +in0_crit_min		shunt voltage crit min alarm limit (mV)
>> +in0_max_alarm		shunt voltage max alarm limit exceeded
>> +in0_min_alarm		shunt voltage min alarm limit exceeded
>> +in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
>> +in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
>> +
>> +in1_input		bus voltage (mV)
>> +in1_input_highest	bus voltage historical maximum reading (mV)
>> +in1_input_lowest	bus voltage historical minimum reading (mV)
>> +in1_reset_history	reset bus voltage history
>> +in1_max			bus voltage max alarm limit (mV)
>> +in1_min			bus voltage min alarm limit (mV)
>> +in1_crit_max		bus voltage crit max alarm limit (mV)
>> +in1_crit_min		bus voltage crit min alarm limit (mV)
>> +in1_max_alarm		bus voltage max alarm limit exceeded
>> +in1_min_alarm		bus voltage min alarm limit exceeded
>> +in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
>> +in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
>> +
>> +power1_input		power measurement (uW)
>> +power1_input_highest	power historical maximum reading (uW)
>> +power1_reset_history	reset power history
>> +power1_max		power max alarm limit (uW)
>> +power1_crit		power crit alarm limit (uW)
>> +power1_max_alarm	power max alarm limit exceeded
>> +power1_crit_alarm	power crit alarm limit exceeded
>> +
>> +curr1_input		current measurement (mA)
>> +
>> +update_interval		data conversion time; affects number of samples used
>> +			to average results for shunt and bus voltages.
>> +
>> +General Remarks
>> +---------------
>> +
>> +The power and current registers in this chip require that the calibration
>> +register is programmed correctly before they are used. Normally this is expected
>> +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
>> +voltage can be provided using platform data. The driver uses platform data from
>> +the ina2xx driver for this purpose. If calibration register data is not provided
>> +via platform data, the driver checks if the calibration register has been
>> +programmed (ie has a value not equal to zero). If so, this value is retained.
>> +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
>> +programmed into the calibration register.
>> +
>> +
>> +Output Pins
>> +-----------
>> +
>> +Output pin programming is a board feature which depends on the BIOS. It is
>> +outside the scope of a hardware monitoring driver to enable or disable output
>> +pins.
>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>> index ef57572..5dacd58 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
>>   	  This driver can also be build as a module.  If so, the module
>>   	  will be called amc6821.
>>
>> +config SENSORS_INA209
>> +	tristate "TI / Burr Brown INA209"
>> +	depends on I2C
>> +	help
>> +	  If you say yes here you get support for the TI / Burr Brown INA209
>> +	  voltage / current / power monitor I2C interface.
>> +
>> +	  This driver can also be built as a module. If so, the module will
>> +	  be called ina209.
>> +
>>   config SENSORS_INA2XX
>>   	tristate "Texas Instruments INA219 and compatibles"
>>   	depends on I2C
>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>> index a37a82c..8d6d97e 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
>>   obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
>>   obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
>>   obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
>> +obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
>>   obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
>>   obj-$(CONFIG_SENSORS_IT87)	+= it87.o
>>   obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
>> diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
>> new file mode 100644
>> index 0000000..1070611
>> --- /dev/null
>> +++ b/drivers/hwmon/ina209.c
>> @@ -0,0 +1,635 @@
>> +/*
>> + * Driver for the Texas Instruments / Burr Brown INA209
>> + * Bidirectional Current/Power Monitor
>> + *
>> + * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
>> + *
>> + * Derived from Ira W. Snyder's original driver submission
>> + *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
>> + *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
>> + *
>> + * Aligned with ina2xx driver
>> + *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
>> + *	Thanks to Jan Volkering
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; version 2 of the License.
>> + *
>> + * Datasheet:
>> + * http://www.ti.com/lit/gpn/ina209
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/err.h>
>> +#include <linux/slab.h>
>> +#include <linux/bug.h>
>> +#include <linux/i2c.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/hwmon-sysfs.h>
>> +
>> +#include <linux/platform_data/ina2xx.h>
>> +
>> +/* register definitions */
>> +#define INA209_CONFIGURATION		0x00
>> +#define INA209_STATUS			0x01
>> +#define INA209_STATUS_MASK		0x02
>> +#define INA209_SHUNT_VOLTAGE		0x03
>> +#define INA209_BUS_VOLTAGE		0x04
>> +#define INA209_POWER			0x05
>> +#define INA209_CURRENT			0x06
>> +#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
>> +#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
>> +#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
>> +#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
>> +#define INA209_POWER_PEAK		0x0b
>> +#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
>> +#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
>> +#define INA209_POWER_WARN		0x0e
>> +#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
>> +#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
>> +#define INA209_POWER_OVER_LIMIT		0x11
>> +#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
>> +#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
>> +#define INA209_CRITICAL_DAC_POS		0x14
>> +#define INA209_CRITICAL_DAC_NEG		0x15
>> +#define INA209_CALIBRATION		0x16
>> +
>> +#define INA209_REGISTERS		0x17
>> +
>> +#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
>> +#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
>> +
>> +struct ina209_data {
>> +	struct device *hwmon_dev;
>> +
>> +	struct mutex update_lock;
>> +	bool valid;
>> +	unsigned long last_updated;	/* in jiffies */
>> +
>> +	u16 regs[INA209_REGISTERS];	/* All chip registers */
>> +
>> +	u16 config_orig;		/* Original configuration */
>> +	u16 calibration_orig;		/* Original calibration */
>> +	u16 update_interval;
>> +};
>> +
>> +static struct ina209_data *ina209_update_device(struct device *dev)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct ina209_data *data = i2c_get_clientdata(client);
>> +	struct ina209_data *ret = data;
>> +	s32 val;
>> +	int i;
>> +
>> +	mutex_lock(&data->update_lock);
>> +
>> +	if (!data->valid ||
>> +	    time_after(jiffies, data->last_updated + data->update_interval)) {
>> +		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
>> +			val = i2c_smbus_read_word_swapped(client, i);
>> +			if (val < 0) {
>> +				ret = ERR_PTR(val);
>> +				goto abort;
>> +			}
>> +			data->regs[i] = val;
>> +		}
>> +		data->last_updated = jiffies;
>> +		data->valid = true;
>> +	}
>> +abort:
>> +	mutex_unlock(&data->update_lock);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * Read a value from a device register and convert it to the
>> + * appropriate sysfs units
>> + */
>> +static long ina209_from_reg(const u8 reg, const u16 val)
>> +{
>> +	switch (reg) {
>> +	case INA209_SHUNT_VOLTAGE:
>> +	case INA209_SHUNT_VOLTAGE_POS_PEAK:
>> +	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
>> +	case INA209_SHUNT_VOLTAGE_POS_WARN:
>> +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
>> +		/* LSB\x10 uV. Convert to mV. */
>> +		return DIV_ROUND_CLOSEST(val, 100);
>> +
>> +	case INA209_BUS_VOLTAGE:
>> +	case INA209_BUS_VOLTAGE_MAX_PEAK:
>> +	case INA209_BUS_VOLTAGE_MIN_PEAK:
>> +	case INA209_BUS_VOLTAGE_OVER_WARN:
>> +	case INA209_BUS_VOLTAGE_UNDER_WARN:
>> +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
>> +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
>> +		/* LSB=4 mV, last 3 bits unused */
>> +		return (val >> 3) * 4;
>> +
>> +	case INA209_CRITICAL_DAC_POS:
>> +		/* LSB=1 mV, in the upper 8 bits */
>> +		return val >> 8;
>> +
>> +	case INA209_CRITICAL_DAC_NEG:
>> +		/* LSB=1 mV, in the upper 8 bits */
>> +		return -1 * (val >> 8);
>> +
>> +	case INA209_POWER:
>> +	case INA209_POWER_PEAK:
>> +	case INA209_POWER_WARN:
>> +	case INA209_POWER_OVER_LIMIT:
>> +		/* LSB  mW. Convert to uW */
>> +		return val * 20 * 1000L;
>> +
>> +	case INA209_CURRENT:
>> +		/* LSB=1 mA (selected). Is in mA */
>> +		return val;
>> +	}
>> +
>> +	/* programmer goofed */
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +/*
>> + * Take a value and convert it to register format, clamping the value
>> + * to the appropriate range.
>> + */
>> +static int ina209_to_reg(u8 reg, u16 old, long val)
>> +{
>> +	switch (reg) {
>> +	case INA209_SHUNT_VOLTAGE_POS_WARN:
>> +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
>> +		/* Limit to +- 320 mV, 10 uV LSB */
>> +		return clamp_val(val, -320, 320) * 100;
>> +
>> +	case INA209_BUS_VOLTAGE_OVER_WARN:
>> +	case INA209_BUS_VOLTAGE_UNDER_WARN:
>> +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
>> +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
>> +		/*
>> +		 * Limit to 0-32000 mV, 4 mV LSB
>> +		 *
>> +		 * The last three bits aren't part of the value, but we'll
>> +		 * preserve them in their original state.
>> +		 */
>> +		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
>> +		  | (old & 0x7);
>> +
>> +	case INA209_CRITICAL_DAC_NEG:
>> +		/*
>> +		 * Limit to -255-0 mV, 1 mV LSB
>> +		 * Convert the value to a positive value for the register
>> +		 *
>> +		 * The value lives in the top 8 bits only, be careful
>> +		 * and keep original value of other bits.
>> +		 */
>> +		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
>> +
>> +	case INA209_CRITICAL_DAC_POS:
>> +		/*
>> +		 * Limit to 0-255 mV, 1 mV LSB
>> +		 *
>> +		 * The value lives in the top 8 bits only, be careful
>> +		 * and keep original value of other bits.
>> +		 */
>> +		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
>> +
>> +	case INA209_POWER_WARN:
>> +	case INA209_POWER_OVER_LIMIT:
>> +		/* 20 mW LSB */
>> +		return DIV_ROUND_CLOSEST(val, 20 * 1000);
>> +	}
>> +
>> +	/* Other registers are read-only, return access error */
>> +	return -EACCES;
>> +}
>> +
>> +static int ina209_interval_from_reg(u16 reg)
>> +{
>> +	return 68 >> (15 - ((reg >> 3) & 0x0f));
>> +}
>> +
>> +static u16 ina209_reg_from_interval(u16 config, long interval)
>> +{
>> +	int i, adc;
>> +
>> +	if (interval <= 0) {
>> +		adc = 8;
>> +	} else {
>> +		adc = 15;
>> +		for (i = 34 + 34 / 2; i; i >>= 1) {
>> +			if (i < interval)
>> +				break;
>> +			adc--;
>> +		}
>> +	}
>> +	return (config & 0xf807) | (adc << 3) | (adc << 7);
>> +}
>> +
>> +static ssize_t ina209_set_interval(struct device *dev,
>> +				   struct device_attribute *da,
>> +				   const char *buf, size_t count)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct ina209_data *data = ina209_update_device(dev);
>> +	long val;
>> +	u16 regval;
>> +	int ret;
>> +
>> +	if (IS_ERR(data))
>> +		return PTR_ERR(data);
>> +
>> +	ret = kstrtol(buf, 10, &val);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	mutex_lock(&data->update_lock);
>> +	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
>> +					  val);
>> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
>> +	data->regs[INA209_CONFIGURATION] = regval;
>> +	data->update_interval = ina209_interval_from_reg(regval);
>> +	mutex_unlock(&data->update_lock);
>> +	return count;
>> +}
>> +
>> +static ssize_t ina209_show_interval(struct device *dev,
>> +				    struct device_attribute *da, char *buf)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct ina209_data *data = i2c_get_clientdata(client);
>> +
>> +	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
>> +}
>> +
>> +/*
>> + * History is reset by writing 1 into bit 0 of the respective peak register.
>> + * Since more than one peak register may be affected by the scope of a
>> + * reset_history attribute write, use a bit mask in attr->index to identify
>> + * which registers are affected.
>> + */
>> +static u16 ina209_reset_history_regs[] = {
>> +	INA209_SHUNT_VOLTAGE_POS_PEAK,
>> +	INA209_SHUNT_VOLTAGE_NEG_PEAK,
>> +	INA209_BUS_VOLTAGE_MAX_PEAK,
>> +	INA209_BUS_VOLTAGE_MIN_PEAK,
>> +	INA209_POWER_PEAK
>> +};
>> +
>> +static ssize_t ina209_reset_history(struct device *dev,
>> +				    struct device_attribute *da,
>> +				    const char *buf,
>> +				    size_t count)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct ina209_data *data = i2c_get_clientdata(client);
>> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
>> +	u32 mask = attr->index;
>> +	long val;
>> +	int i, ret;
>> +
>> +	ret = kstrtol(buf, 10, &val);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	mutex_lock(&data->update_lock);
>> +	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
>> +		if (mask & (1 << i))
>> +			i2c_smbus_write_word_swapped(client,
>> +					ina209_reset_history_regs[i], 1);
>> +	}
>> +	data->valid = false;
>> +	mutex_unlock(&data->update_lock);
>> +	return count;
>> +}
>> +
>> +static ssize_t ina209_set_value(struct device *dev,
>> +				struct device_attribute *da,
>> +				const char *buf,
>> +				size_t count)
>> +{
>> +	struct i2c_client *client = to_i2c_client(dev);
>> +	struct ina209_data *data = ina209_update_device(dev);
>> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
>> +	int reg = attr->index;
>> +	long val;
>> +	int ret;
>> +
>> +	if (IS_ERR(data))
>> +		return PTR_ERR(data);
>> +
>> +	ret = kstrtol(buf, 10, &val);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	mutex_lock(&data->update_lock);
>> +	ret = ina209_to_reg(reg, data->regs[reg], val);
>> +	if (ret < 0) {
>> +		count = ret;
>> +		goto abort;
>> +	}
>> +	i2c_smbus_write_word_swapped(client, reg, ret);
>> +	data->regs[reg] = ret;
>> +abort:
>> +	mutex_unlock(&data->update_lock);
>> +	return count;
>> +}
>> +
>> +static ssize_t ina209_show_value(struct device *dev,
>> +				 struct device_attribute *da,
>> +				 char *buf)
>> +{
>> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
>> +	struct ina209_data *data = ina209_update_device(dev);
>> +	long val;
>> +
>> +	if (IS_ERR(data))
>> +		return PTR_ERR(data);
>> +
>> +	val = ina209_from_reg(attr->index, data->regs[attr->index]);
>> +	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
>> +}
>> +
>> +static ssize_t ina209_show_alarm(struct device *dev,
>> +				 struct device_attribute *da,
>> +				 char *buf)
>> +{
>> +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
>> +	struct ina209_data *data = ina209_update_device(dev);
>> +	const unsigned int mask = attr->index;
>> +	u16 status;
>> +
>> +	if (IS_ERR(data))
>> +		return PTR_ERR(data);
>> +
>> +	status = data->regs[INA209_STATUS];
>> +
>> +	/*
>> +	 * All alarms are in the INA209_STATUS register. To avoid a long
>> +	 * switch statement, the mask is passed in attr->index
>> +	 */
>> +	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
>> +}
>> +
>> +/* Shunt voltage, history, limits, alarms */
>> +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_SHUNT_VOLTAGE);
>> +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_SHUNT_VOLTAGE_POS_PEAK);
>> +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
>> +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
>> +			  ina209_reset_history, (1 << 0) | (1 << 1));
>> +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
>> +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
>> +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_CRITICAL_DAC_POS);
>> +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
>> +
>> +static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 11);
>> +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 12);
>> +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 6);
>> +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 7);
>> +
>> +/* Bus voltage, history, limits, alarms */
>> +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_BUS_VOLTAGE);
>> +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_BUS_VOLTAGE_MAX_PEAK);
>> +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_BUS_VOLTAGE_MIN_PEAK);
>> +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
>> +			  ina209_reset_history, (1 << 2) | (1 << 3));
>> +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
>> +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
>> +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
>> +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
>> +
>> +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 14);
>> +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 15);
>> +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 9);
>> +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 10);
>> +
>> +/* Power */
>> +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_POWER);
>> +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
>> +			  NULL, INA209_POWER_PEAK);
>> +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
>> +			  ina209_reset_history, 1 << 4);
>> +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_POWER_WARN);
>> +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
>> +			  ina209_set_value, INA209_POWER_OVER_LIMIT);
>> +
>> +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 13);
>> +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
>> +			  1 << 8);
>> +
>> +/* Current */
>> +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
>> +			  INA209_CURRENT);
>> +
>> +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
>> +			  ina209_show_interval, ina209_set_interval, 0);
>> +
>> +/*
>> + * Finally, construct an array of pointers to members of the above objects,
>> + * as required for sysfs_create_group()
>> + */
>> +static struct attribute *ina209_attributes[] = {
>> +	&sensor_dev_attr_in0_input.dev_attr.attr,
>> +	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
>> +	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
>> +	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
>> +	&sensor_dev_attr_in0_max.dev_attr.attr,
>> +	&sensor_dev_attr_in0_min.dev_attr.attr,
>> +	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
>> +	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
>> +	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
>> +
>> +	&sensor_dev_attr_in1_input.dev_attr.attr,
>> +	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
>> +	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
>> +	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
>> +	&sensor_dev_attr_in1_max.dev_attr.attr,
>> +	&sensor_dev_attr_in1_min.dev_attr.attr,
>> +	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
>> +	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
>> +	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
>> +
>> +	&sensor_dev_attr_power1_input.dev_attr.attr,
>> +	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
>> +	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
>> +	&sensor_dev_attr_power1_max.dev_attr.attr,
>> +	&sensor_dev_attr_power1_crit.dev_attr.attr,
>> +	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
>> +	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
>> +
>> +	&sensor_dev_attr_curr1_input.dev_attr.attr,
>> +
>> +	&sensor_dev_attr_update_interval.dev_attr.attr,
>> +
>> +	NULL,
>> +};
>> +
>> +static const struct attribute_group ina209_group = {
>> +	.attrs = ina209_attributes,
>> +};
>> +
>> +static void ina209_restore_conf(struct i2c_client *client,
>> +				struct ina209_data *data)
>> +{
>> +	/* Restore initial configuration */
>> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
>> +				     data->config_orig);
>> +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
>> +				     data->calibration_orig);
>> +}
>> +
>> +static int ina290_init_client(struct i2c_client *client,
>> +			      struct ina209_data *data)
>> +{
>> +	struct ina2xx_platform_data *pdata;
>> +	long shunt;
>> +	int reg;
>> +
>> +	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
>> +	if (reg < 0)
>> +		return reg;
>> +	data->calibration_orig = reg;
>> +
>> +	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
>> +	if (reg < 0)
>> +		return reg;
>> +	data->config_orig = reg;
>> +
>> +	if (client->dev.platform_data) {
>> +		pdata >> +		  (struct ina2xx_platform_data *)client->dev.platform_data;
>> +		shunt = pdata->shunt_uohms;
>> +		if (shunt <= 0)
>> +			return -ENODEV;
>> +
>> +	} else {
>> +		shunt = data->calibration_orig ?
>> +		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
>> +	}
>> +
>> +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
>> +				     INA209_CONFIG_DEFAULT);
>> +	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
>> +
>> +	/*
>> +	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
>> +	 * See equation 13 in datasheet.
>> +	 */
>> +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
>> +				     clamp_val(40960000 / shunt, 1, 65535));
>> +
>> +	/* Clear status register */
>> +	i2c_smbus_read_word_swapped(client, INA209_STATUS);
>> +
>> +	return 0;
>> +}
>> +
>> +static int ina209_probe(struct i2c_client *client,
>> +			const struct i2c_device_id *id)
>> +{
>> +	struct i2c_adapter *adapter = client->adapter;
>> +	struct ina209_data *data;
>> +	int ret;
>> +
>> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
>> +		return -ENODEV;
>> +
>> +	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
>> +	if (!data)
>> +		return -ENOMEM;
>> +
>> +	i2c_set_clientdata(client, data);
>> +	mutex_init(&data->update_lock);
>> +
>> +	ret = ina290_init_client(client, data);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Register sysfs hooks */
>> +	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
>> +	if (ret)
>> +		goto out_restore_conf;
>> +
>> +	data->hwmon_dev = hwmon_device_register(&client->dev);
>> +	if (IS_ERR(data->hwmon_dev)) {
>> +		ret = PTR_ERR(data->hwmon_dev);
>> +		goto out_hwmon_device_register;
>> +	}
>> +
>> +	return 0;
>> +
>> +out_hwmon_device_register:
>> +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
>> +out_restore_conf:
>> +	ina209_restore_conf(client, data);
>> +	return ret;
>> +}
>> +
>> +static int ina209_remove(struct i2c_client *client)
>> +{
>> +	struct ina209_data *data = i2c_get_clientdata(client);
>> +
>> +	hwmon_device_unregister(data->hwmon_dev);
>> +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
>> +	ina209_restore_conf(client, data);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct i2c_device_id ina209_id[] = {
>> +	{ "ina209", 0 },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ina209_id);
>> +
>> +/* This is the driver that will be inserted */
>> +static struct i2c_driver ina209_driver = {
>> +	.class		= I2C_CLASS_HWMON,
>> +	.driver = {
>> +		.name	= "ina209",
>> +	},
>> +	.probe		= ina209_probe,
>> +	.remove		= ina209_remove,
>> +	.id_table	= ina209_id,
>> +};
>> +
>> +module_i2c_driver(ina209_driver);
>> +
>> +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
>> +MODULE_DESCRIPTION("INA209 driver");
>> +MODULE_LICENSE("GPL");
>> --
>> 1.7.9.7
>>
>
>

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (8 preceding siblings ...)
  (?)
@ 2013-01-15 22:56 ` Guenter Roeck
  -1 siblings, 0 replies; 37+ messages in thread
From: Guenter Roeck @ 2013-01-15 22:56 UTC (permalink / raw)
  To: lm-sensors

On Tue, Jan 15, 2013 at 05:10:01PM -0500, Paul Hays wrote:
> I didn't know what to expect after posting this little driver back
> in 2008.  Guenter, it's nice to see the code moving toward the
> mainline. Thank you Ira for your improvements (though you seem to
> have discarded many of my comments!)
> 
Hi Paul,

the key functional changes I made (comparing to v2 of Ira's driver) are

- Use standard ABI. That means there are no enable atributes,
  and the shunt voltage is in reported in mV, not uV.
  I prefer loss of accuracy over a non-standard ABI.
- Dropped detect function as too risky.

Is there anything I missed, and/or anything you think should/could be improved ?

> Here are two small things I noticed:
>  - change all ina290_init_client to ina209_init_client
>  - My email address has changed from haysp@magma.net; please include
> Paul Hays <Paul.Hays@cattail.ca> in MODULE_AUTHOR
> 
Both updated.

Thanks,
Guenter

> 
> On 14/01/2013 4:17 PM, Ira W. Snyder wrote:
> >On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> >>Add support for the TI / Burr-Brown INA209 voltage / current / power
> >>monitor.
> >>
> >>Cc: Paul Hays <haysp@magma.net>
> >>Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> >>Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> >
> >Hi Guenter,
> >
> >I didn't notice any issues with a quick review. Thanks for pushing the
> >driver to mainline.
> >
> >Ira
> >
> >>---
> >>  Documentation/hwmon/ina209 |   93 +++++++
> >>  drivers/hwmon/Kconfig      |   10 +
> >>  drivers/hwmon/Makefile     |    1 +
> >>  drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
> >>  4 files changed, 739 insertions(+)
> >>  create mode 100644 Documentation/hwmon/ina209
> >>  create mode 100644 drivers/hwmon/ina209.c
> >>
> >>diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
> >>new file mode 100644
> >>index 0000000..2e11bbc
> >>--- /dev/null
> >>+++ b/Documentation/hwmon/ina209
> >>@@ -0,0 +1,93 @@
> >>+Kernel driver ina209
> >>+==========> >>+
> >>+Supported chips:
> >>+  * Burr-Brown / Texas Instruments INA209
> >>+    Prefix: 'ina209'
> >>+    Addresses scanned: -
> >>+    Datasheet:
> >>+        http://www.ti.com/lit/gpn/ina209
> >>+
> >>+Author: Paul Hays <haysp@magma.net>
> >>+Author: Ira W. Snyder <iws@ovro.caltech.edu>
> >>+Author: Guenter Roeck <linux@roeck-us.net>
> >>+
> >>+
> >>+Description
> >>+-----------
> >>+
> >>+The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
> >>+of a D.C. power supply. It can perform measurements and calculations in the
> >>+background to supply readings at any time. It includes a programmable
> >>+calibration multiplier to scale the displayed current and power values.
> >>+
> >>+
> >>+Sysfs entries
> >>+-------------
> >>+
> >>+The INA209 chip is highly configurable both via hardwiring and via
> >>+the I2C bus. See the datasheet for details.
> >>+
> >>+This tries to expose most monitoring features of the hardware via
> >>+sysfs. It does not support every feature of this chip.
> >>+
> >>+
> >>+in0_input		shunt voltage (mV)
> >>+in0_input_highest	shunt voltage historical maximum reading (mV)
> >>+in0_input_lowest	shunt voltage historical minimum reading (mV)
> >>+in0_reset_history	reset shunt voltage history
> >>+in0_max			shunt voltage max alarm limit (mV)
> >>+in0_min			shunt voltage min alarm limit (mV)
> >>+in0_crit_max		shunt voltage crit max alarm limit (mV)
> >>+in0_crit_min		shunt voltage crit min alarm limit (mV)
> >>+in0_max_alarm		shunt voltage max alarm limit exceeded
> >>+in0_min_alarm		shunt voltage min alarm limit exceeded
> >>+in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
> >>+in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
> >>+
> >>+in1_input		bus voltage (mV)
> >>+in1_input_highest	bus voltage historical maximum reading (mV)
> >>+in1_input_lowest	bus voltage historical minimum reading (mV)
> >>+in1_reset_history	reset bus voltage history
> >>+in1_max			bus voltage max alarm limit (mV)
> >>+in1_min			bus voltage min alarm limit (mV)
> >>+in1_crit_max		bus voltage crit max alarm limit (mV)
> >>+in1_crit_min		bus voltage crit min alarm limit (mV)
> >>+in1_max_alarm		bus voltage max alarm limit exceeded
> >>+in1_min_alarm		bus voltage min alarm limit exceeded
> >>+in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
> >>+in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
> >>+
> >>+power1_input		power measurement (uW)
> >>+power1_input_highest	power historical maximum reading (uW)
> >>+power1_reset_history	reset power history
> >>+power1_max		power max alarm limit (uW)
> >>+power1_crit		power crit alarm limit (uW)
> >>+power1_max_alarm	power max alarm limit exceeded
> >>+power1_crit_alarm	power crit alarm limit exceeded
> >>+
> >>+curr1_input		current measurement (mA)
> >>+
> >>+update_interval		data conversion time; affects number of samples used
> >>+			to average results for shunt and bus voltages.
> >>+
> >>+General Remarks
> >>+---------------
> >>+
> >>+The power and current registers in this chip require that the calibration
> >>+register is programmed correctly before they are used. Normally this is expected
> >>+to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
> >>+voltage can be provided using platform data. The driver uses platform data from
> >>+the ina2xx driver for this purpose. If calibration register data is not provided
> >>+via platform data, the driver checks if the calibration register has been
> >>+programmed (ie has a value not equal to zero). If so, this value is retained.
> >>+Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
> >>+programmed into the calibration register.
> >>+
> >>+
> >>+Output Pins
> >>+-----------
> >>+
> >>+Output pin programming is a board feature which depends on the BIOS. It is
> >>+outside the scope of a hardware monitoring driver to enable or disable output
> >>+pins.
> >>diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> >>index ef57572..5dacd58 100644
> >>--- a/drivers/hwmon/Kconfig
> >>+++ b/drivers/hwmon/Kconfig
> >>@@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
> >>  	  This driver can also be build as a module.  If so, the module
> >>  	  will be called amc6821.
> >>
> >>+config SENSORS_INA209
> >>+	tristate "TI / Burr Brown INA209"
> >>+	depends on I2C
> >>+	help
> >>+	  If you say yes here you get support for the TI / Burr Brown INA209
> >>+	  voltage / current / power monitor I2C interface.
> >>+
> >>+	  This driver can also be built as a module. If so, the module will
> >>+	  be called ina209.
> >>+
> >>  config SENSORS_INA2XX
> >>  	tristate "Texas Instruments INA219 and compatibles"
> >>  	depends on I2C
> >>diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> >>index a37a82c..8d6d97e 100644
> >>--- a/drivers/hwmon/Makefile
> >>+++ b/drivers/hwmon/Makefile
> >>@@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> >>  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
> >>  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
> >>  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
> >>+obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
> >>  obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
> >>  obj-$(CONFIG_SENSORS_IT87)	+= it87.o
> >>  obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
> >>diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
> >>new file mode 100644
> >>index 0000000..1070611
> >>--- /dev/null
> >>+++ b/drivers/hwmon/ina209.c
> >>@@ -0,0 +1,635 @@
> >>+/*
> >>+ * Driver for the Texas Instruments / Burr Brown INA209
> >>+ * Bidirectional Current/Power Monitor
> >>+ *
> >>+ * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
> >>+ *
> >>+ * Derived from Ira W. Snyder's original driver submission
> >>+ *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
> >>+ *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
> >>+ *
> >>+ * Aligned with ina2xx driver
> >>+ *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
> >>+ *	Thanks to Jan Volkering
> >>+ *
> >>+ * This program is free software; you can redistribute it and/or modify
> >>+ * it under the terms of the GNU General Public License as published by
> >>+ * the Free Software Foundation; version 2 of the License.
> >>+ *
> >>+ * Datasheet:
> >>+ * http://www.ti.com/lit/gpn/ina209
> >>+ */
> >>+
> >>+#include <linux/kernel.h>
> >>+#include <linux/module.h>
> >>+#include <linux/init.h>
> >>+#include <linux/err.h>
> >>+#include <linux/slab.h>
> >>+#include <linux/bug.h>
> >>+#include <linux/i2c.h>
> >>+#include <linux/hwmon.h>
> >>+#include <linux/hwmon-sysfs.h>
> >>+
> >>+#include <linux/platform_data/ina2xx.h>
> >>+
> >>+/* register definitions */
> >>+#define INA209_CONFIGURATION		0x00
> >>+#define INA209_STATUS			0x01
> >>+#define INA209_STATUS_MASK		0x02
> >>+#define INA209_SHUNT_VOLTAGE		0x03
> >>+#define INA209_BUS_VOLTAGE		0x04
> >>+#define INA209_POWER			0x05
> >>+#define INA209_CURRENT			0x06
> >>+#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
> >>+#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
> >>+#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
> >>+#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
> >>+#define INA209_POWER_PEAK		0x0b
> >>+#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
> >>+#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
> >>+#define INA209_POWER_WARN		0x0e
> >>+#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
> >>+#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
> >>+#define INA209_POWER_OVER_LIMIT		0x11
> >>+#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
> >>+#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
> >>+#define INA209_CRITICAL_DAC_POS		0x14
> >>+#define INA209_CRITICAL_DAC_NEG		0x15
> >>+#define INA209_CALIBRATION		0x16
> >>+
> >>+#define INA209_REGISTERS		0x17
> >>+
> >>+#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
> >>+#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
> >>+
> >>+struct ina209_data {
> >>+	struct device *hwmon_dev;
> >>+
> >>+	struct mutex update_lock;
> >>+	bool valid;
> >>+	unsigned long last_updated;	/* in jiffies */
> >>+
> >>+	u16 regs[INA209_REGISTERS];	/* All chip registers */
> >>+
> >>+	u16 config_orig;		/* Original configuration */
> >>+	u16 calibration_orig;		/* Original calibration */
> >>+	u16 update_interval;
> >>+};
> >>+
> >>+static struct ina209_data *ina209_update_device(struct device *dev)
> >>+{
> >>+	struct i2c_client *client = to_i2c_client(dev);
> >>+	struct ina209_data *data = i2c_get_clientdata(client);
> >>+	struct ina209_data *ret = data;
> >>+	s32 val;
> >>+	int i;
> >>+
> >>+	mutex_lock(&data->update_lock);
> >>+
> >>+	if (!data->valid ||
> >>+	    time_after(jiffies, data->last_updated + data->update_interval)) {
> >>+		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
> >>+			val = i2c_smbus_read_word_swapped(client, i);
> >>+			if (val < 0) {
> >>+				ret = ERR_PTR(val);
> >>+				goto abort;
> >>+			}
> >>+			data->regs[i] = val;
> >>+		}
> >>+		data->last_updated = jiffies;
> >>+		data->valid = true;
> >>+	}
> >>+abort:
> >>+	mutex_unlock(&data->update_lock);
> >>+	return ret;
> >>+}
> >>+
> >>+/*
> >>+ * Read a value from a device register and convert it to the
> >>+ * appropriate sysfs units
> >>+ */
> >>+static long ina209_from_reg(const u8 reg, const u16 val)
> >>+{
> >>+	switch (reg) {
> >>+	case INA209_SHUNT_VOLTAGE:
> >>+	case INA209_SHUNT_VOLTAGE_POS_PEAK:
> >>+	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
> >>+	case INA209_SHUNT_VOLTAGE_POS_WARN:
> >>+	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> >>+		/* LSB\x10 uV. Convert to mV. */
> >>+		return DIV_ROUND_CLOSEST(val, 100);
> >>+
> >>+	case INA209_BUS_VOLTAGE:
> >>+	case INA209_BUS_VOLTAGE_MAX_PEAK:
> >>+	case INA209_BUS_VOLTAGE_MIN_PEAK:
> >>+	case INA209_BUS_VOLTAGE_OVER_WARN:
> >>+	case INA209_BUS_VOLTAGE_UNDER_WARN:
> >>+	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> >>+	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> >>+		/* LSB=4 mV, last 3 bits unused */
> >>+		return (val >> 3) * 4;
> >>+
> >>+	case INA209_CRITICAL_DAC_POS:
> >>+		/* LSB=1 mV, in the upper 8 bits */
> >>+		return val >> 8;
> >>+
> >>+	case INA209_CRITICAL_DAC_NEG:
> >>+		/* LSB=1 mV, in the upper 8 bits */
> >>+		return -1 * (val >> 8);
> >>+
> >>+	case INA209_POWER:
> >>+	case INA209_POWER_PEAK:
> >>+	case INA209_POWER_WARN:
> >>+	case INA209_POWER_OVER_LIMIT:
> >>+		/* LSB  mW. Convert to uW */
> >>+		return val * 20 * 1000L;
> >>+
> >>+	case INA209_CURRENT:
> >>+		/* LSB=1 mA (selected). Is in mA */
> >>+		return val;
> >>+	}
> >>+
> >>+	/* programmer goofed */
> >>+	WARN_ON_ONCE(1);
> >>+	return 0;
> >>+}
> >>+
> >>+/*
> >>+ * Take a value and convert it to register format, clamping the value
> >>+ * to the appropriate range.
> >>+ */
> >>+static int ina209_to_reg(u8 reg, u16 old, long val)
> >>+{
> >>+	switch (reg) {
> >>+	case INA209_SHUNT_VOLTAGE_POS_WARN:
> >>+	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> >>+		/* Limit to +- 320 mV, 10 uV LSB */
> >>+		return clamp_val(val, -320, 320) * 100;
> >>+
> >>+	case INA209_BUS_VOLTAGE_OVER_WARN:
> >>+	case INA209_BUS_VOLTAGE_UNDER_WARN:
> >>+	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> >>+	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> >>+		/*
> >>+		 * Limit to 0-32000 mV, 4 mV LSB
> >>+		 *
> >>+		 * The last three bits aren't part of the value, but we'll
> >>+		 * preserve them in their original state.
> >>+		 */
> >>+		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
> >>+		  | (old & 0x7);
> >>+
> >>+	case INA209_CRITICAL_DAC_NEG:
> >>+		/*
> >>+		 * Limit to -255-0 mV, 1 mV LSB
> >>+		 * Convert the value to a positive value for the register
> >>+		 *
> >>+		 * The value lives in the top 8 bits only, be careful
> >>+		 * and keep original value of other bits.
> >>+		 */
> >>+		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
> >>+
> >>+	case INA209_CRITICAL_DAC_POS:
> >>+		/*
> >>+		 * Limit to 0-255 mV, 1 mV LSB
> >>+		 *
> >>+		 * The value lives in the top 8 bits only, be careful
> >>+		 * and keep original value of other bits.
> >>+		 */
> >>+		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
> >>+
> >>+	case INA209_POWER_WARN:
> >>+	case INA209_POWER_OVER_LIMIT:
> >>+		/* 20 mW LSB */
> >>+		return DIV_ROUND_CLOSEST(val, 20 * 1000);
> >>+	}
> >>+
> >>+	/* Other registers are read-only, return access error */
> >>+	return -EACCES;
> >>+}
> >>+
> >>+static int ina209_interval_from_reg(u16 reg)
> >>+{
> >>+	return 68 >> (15 - ((reg >> 3) & 0x0f));
> >>+}
> >>+
> >>+static u16 ina209_reg_from_interval(u16 config, long interval)
> >>+{
> >>+	int i, adc;
> >>+
> >>+	if (interval <= 0) {
> >>+		adc = 8;
> >>+	} else {
> >>+		adc = 15;
> >>+		for (i = 34 + 34 / 2; i; i >>= 1) {
> >>+			if (i < interval)
> >>+				break;
> >>+			adc--;
> >>+		}
> >>+	}
> >>+	return (config & 0xf807) | (adc << 3) | (adc << 7);
> >>+}
> >>+
> >>+static ssize_t ina209_set_interval(struct device *dev,
> >>+				   struct device_attribute *da,
> >>+				   const char *buf, size_t count)
> >>+{
> >>+	struct i2c_client *client = to_i2c_client(dev);
> >>+	struct ina209_data *data = ina209_update_device(dev);
> >>+	long val;
> >>+	u16 regval;
> >>+	int ret;
> >>+
> >>+	if (IS_ERR(data))
> >>+		return PTR_ERR(data);
> >>+
> >>+	ret = kstrtol(buf, 10, &val);
> >>+	if (ret < 0)
> >>+		return ret;
> >>+
> >>+	mutex_lock(&data->update_lock);
> >>+	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
> >>+					  val);
> >>+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
> >>+	data->regs[INA209_CONFIGURATION] = regval;
> >>+	data->update_interval = ina209_interval_from_reg(regval);
> >>+	mutex_unlock(&data->update_lock);
> >>+	return count;
> >>+}
> >>+
> >>+static ssize_t ina209_show_interval(struct device *dev,
> >>+				    struct device_attribute *da, char *buf)
> >>+{
> >>+	struct i2c_client *client = to_i2c_client(dev);
> >>+	struct ina209_data *data = i2c_get_clientdata(client);
> >>+
> >>+	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
> >>+}
> >>+
> >>+/*
> >>+ * History is reset by writing 1 into bit 0 of the respective peak register.
> >>+ * Since more than one peak register may be affected by the scope of a
> >>+ * reset_history attribute write, use a bit mask in attr->index to identify
> >>+ * which registers are affected.
> >>+ */
> >>+static u16 ina209_reset_history_regs[] = {
> >>+	INA209_SHUNT_VOLTAGE_POS_PEAK,
> >>+	INA209_SHUNT_VOLTAGE_NEG_PEAK,
> >>+	INA209_BUS_VOLTAGE_MAX_PEAK,
> >>+	INA209_BUS_VOLTAGE_MIN_PEAK,
> >>+	INA209_POWER_PEAK
> >>+};
> >>+
> >>+static ssize_t ina209_reset_history(struct device *dev,
> >>+				    struct device_attribute *da,
> >>+				    const char *buf,
> >>+				    size_t count)
> >>+{
> >>+	struct i2c_client *client = to_i2c_client(dev);
> >>+	struct ina209_data *data = i2c_get_clientdata(client);
> >>+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> >>+	u32 mask = attr->index;
> >>+	long val;
> >>+	int i, ret;
> >>+
> >>+	ret = kstrtol(buf, 10, &val);
> >>+	if (ret < 0)
> >>+		return ret;
> >>+
> >>+	mutex_lock(&data->update_lock);
> >>+	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
> >>+		if (mask & (1 << i))
> >>+			i2c_smbus_write_word_swapped(client,
> >>+					ina209_reset_history_regs[i], 1);
> >>+	}
> >>+	data->valid = false;
> >>+	mutex_unlock(&data->update_lock);
> >>+	return count;
> >>+}
> >>+
> >>+static ssize_t ina209_set_value(struct device *dev,
> >>+				struct device_attribute *da,
> >>+				const char *buf,
> >>+				size_t count)
> >>+{
> >>+	struct i2c_client *client = to_i2c_client(dev);
> >>+	struct ina209_data *data = ina209_update_device(dev);
> >>+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> >>+	int reg = attr->index;
> >>+	long val;
> >>+	int ret;
> >>+
> >>+	if (IS_ERR(data))
> >>+		return PTR_ERR(data);
> >>+
> >>+	ret = kstrtol(buf, 10, &val);
> >>+	if (ret < 0)
> >>+		return ret;
> >>+
> >>+	mutex_lock(&data->update_lock);
> >>+	ret = ina209_to_reg(reg, data->regs[reg], val);
> >>+	if (ret < 0) {
> >>+		count = ret;
> >>+		goto abort;
> >>+	}
> >>+	i2c_smbus_write_word_swapped(client, reg, ret);
> >>+	data->regs[reg] = ret;
> >>+abort:
> >>+	mutex_unlock(&data->update_lock);
> >>+	return count;
> >>+}
> >>+
> >>+static ssize_t ina209_show_value(struct device *dev,
> >>+				 struct device_attribute *da,
> >>+				 char *buf)
> >>+{
> >>+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> >>+	struct ina209_data *data = ina209_update_device(dev);
> >>+	long val;
> >>+
> >>+	if (IS_ERR(data))
> >>+		return PTR_ERR(data);
> >>+
> >>+	val = ina209_from_reg(attr->index, data->regs[attr->index]);
> >>+	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
> >>+}
> >>+
> >>+static ssize_t ina209_show_alarm(struct device *dev,
> >>+				 struct device_attribute *da,
> >>+				 char *buf)
> >>+{
> >>+	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> >>+	struct ina209_data *data = ina209_update_device(dev);
> >>+	const unsigned int mask = attr->index;
> >>+	u16 status;
> >>+
> >>+	if (IS_ERR(data))
> >>+		return PTR_ERR(data);
> >>+
> >>+	status = data->regs[INA209_STATUS];
> >>+
> >>+	/*
> >>+	 * All alarms are in the INA209_STATUS register. To avoid a long
> >>+	 * switch statement, the mask is passed in attr->index
> >>+	 */
> >>+	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
> >>+}
> >>+
> >>+/* Shunt voltage, history, limits, alarms */
> >>+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_SHUNT_VOLTAGE);
> >>+static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_SHUNT_VOLTAGE_POS_PEAK);
> >>+static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
> >>+static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
> >>+			  ina209_reset_history, (1 << 0) | (1 << 1));
> >>+static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
> >>+static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
> >>+static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_CRITICAL_DAC_POS);
> >>+static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
> >>+
> >>+static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 11);
> >>+static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 12);
> >>+static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 6);
> >>+static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 7);
> >>+
> >>+/* Bus voltage, history, limits, alarms */
> >>+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_BUS_VOLTAGE);
> >>+static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_BUS_VOLTAGE_MAX_PEAK);
> >>+static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_BUS_VOLTAGE_MIN_PEAK);
> >>+static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
> >>+			  ina209_reset_history, (1 << 2) | (1 << 3));
> >>+static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
> >>+static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
> >>+static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
> >>+static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
> >>+
> >>+static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 14);
> >>+static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 15);
> >>+static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 9);
> >>+static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 10);
> >>+
> >>+/* Power */
> >>+static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_POWER);
> >>+static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
> >>+			  NULL, INA209_POWER_PEAK);
> >>+static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
> >>+			  ina209_reset_history, 1 << 4);
> >>+static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_POWER_WARN);
> >>+static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
> >>+			  ina209_set_value, INA209_POWER_OVER_LIMIT);
> >>+
> >>+static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 13);
> >>+static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
> >>+			  1 << 8);
> >>+
> >>+/* Current */
> >>+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
> >>+			  INA209_CURRENT);
> >>+
> >>+static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
> >>+			  ina209_show_interval, ina209_set_interval, 0);
> >>+
> >>+/*
> >>+ * Finally, construct an array of pointers to members of the above objects,
> >>+ * as required for sysfs_create_group()
> >>+ */
> >>+static struct attribute *ina209_attributes[] = {
> >>+	&sensor_dev_attr_in0_input.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_max.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_min.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
> >>+
> >>+	&sensor_dev_attr_in1_input.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_max.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_min.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
> >>+
> >>+	&sensor_dev_attr_power1_input.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_max.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_crit.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
> >>+	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
> >>+
> >>+	&sensor_dev_attr_curr1_input.dev_attr.attr,
> >>+
> >>+	&sensor_dev_attr_update_interval.dev_attr.attr,
> >>+
> >>+	NULL,
> >>+};
> >>+
> >>+static const struct attribute_group ina209_group = {
> >>+	.attrs = ina209_attributes,
> >>+};
> >>+
> >>+static void ina209_restore_conf(struct i2c_client *client,
> >>+				struct ina209_data *data)
> >>+{
> >>+	/* Restore initial configuration */
> >>+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> >>+				     data->config_orig);
> >>+	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> >>+				     data->calibration_orig);
> >>+}
> >>+
> >>+static int ina290_init_client(struct i2c_client *client,
> >>+			      struct ina209_data *data)
> >>+{
> >>+	struct ina2xx_platform_data *pdata;
> >>+	long shunt;
> >>+	int reg;
> >>+
> >>+	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
> >>+	if (reg < 0)
> >>+		return reg;
> >>+	data->calibration_orig = reg;
> >>+
> >>+	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
> >>+	if (reg < 0)
> >>+		return reg;
> >>+	data->config_orig = reg;
> >>+
> >>+	if (client->dev.platform_data) {
> >>+		pdata > >>+		  (struct ina2xx_platform_data *)client->dev.platform_data;
> >>+		shunt = pdata->shunt_uohms;
> >>+		if (shunt <= 0)
> >>+			return -ENODEV;
> >>+
> >>+	} else {
> >>+		shunt = data->calibration_orig ?
> >>+		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
> >>+	}
> >>+
> >>+	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> >>+				     INA209_CONFIG_DEFAULT);
> >>+	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
> >>+
> >>+	/*
> >>+	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
> >>+	 * See equation 13 in datasheet.
> >>+	 */
> >>+	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> >>+				     clamp_val(40960000 / shunt, 1, 65535));
> >>+
> >>+	/* Clear status register */
> >>+	i2c_smbus_read_word_swapped(client, INA209_STATUS);
> >>+
> >>+	return 0;
> >>+}
> >>+
> >>+static int ina209_probe(struct i2c_client *client,
> >>+			const struct i2c_device_id *id)
> >>+{
> >>+	struct i2c_adapter *adapter = client->adapter;
> >>+	struct ina209_data *data;
> >>+	int ret;
> >>+
> >>+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> >>+		return -ENODEV;
> >>+
> >>+	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
> >>+	if (!data)
> >>+		return -ENOMEM;
> >>+
> >>+	i2c_set_clientdata(client, data);
> >>+	mutex_init(&data->update_lock);
> >>+
> >>+	ret = ina290_init_client(client, data);
> >>+	if (ret)
> >>+		return ret;
> >>+
> >>+	/* Register sysfs hooks */
> >>+	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
> >>+	if (ret)
> >>+		goto out_restore_conf;
> >>+
> >>+	data->hwmon_dev = hwmon_device_register(&client->dev);
> >>+	if (IS_ERR(data->hwmon_dev)) {
> >>+		ret = PTR_ERR(data->hwmon_dev);
> >>+		goto out_hwmon_device_register;
> >>+	}
> >>+
> >>+	return 0;
> >>+
> >>+out_hwmon_device_register:
> >>+	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> >>+out_restore_conf:
> >>+	ina209_restore_conf(client, data);
> >>+	return ret;
> >>+}
> >>+
> >>+static int ina209_remove(struct i2c_client *client)
> >>+{
> >>+	struct ina209_data *data = i2c_get_clientdata(client);
> >>+
> >>+	hwmon_device_unregister(data->hwmon_dev);
> >>+	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> >>+	ina209_restore_conf(client, data);
> >>+
> >>+	return 0;
> >>+}
> >>+
> >>+static const struct i2c_device_id ina209_id[] = {
> >>+	{ "ina209", 0 },
> >>+	{ }
> >>+};
> >>+MODULE_DEVICE_TABLE(i2c, ina209_id);
> >>+
> >>+/* This is the driver that will be inserted */
> >>+static struct i2c_driver ina209_driver = {
> >>+	.class		= I2C_CLASS_HWMON,
> >>+	.driver = {
> >>+		.name	= "ina209",
> >>+	},
> >>+	.probe		= ina209_probe,
> >>+	.remove		= ina209_remove,
> >>+	.id_table	= ina209_id,
> >>+};
> >>+
> >>+module_i2c_driver(ina209_driver);
> >>+
> >>+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
> >>+MODULE_DESCRIPTION("INA209 driver");
> >>+MODULE_LICENSE("GPL");
> >>--
> >>1.7.9.7
> >>
> >
> >
> 

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (9 preceding siblings ...)
  (?)
@ 2013-01-22 21:43 ` Ira W. Snyder
  -1 siblings, 0 replies; 37+ messages in thread
From: Ira W. Snyder @ 2013-01-22 21:43 UTC (permalink / raw)
  To: lm-sensors

On Mon, Jan 14, 2013 at 10:04:28PM -0800, Guenter Roeck wrote:
> On Mon, Jan 14, 2013 at 01:17:04PM -0800, Ira W. Snyder wrote:
> > On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> > > Add support for the TI / Burr-Brown INA209 voltage / current / power
> > > monitor.
> > > 
> > > Cc: Paul Hays <haysp@magma.net>
> > > Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> > > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> > 
> > Hi Guenter,
> > 
> > I didn't notice any issues with a quick review. Thanks for pushing the
> > driver to mainline.
> > 
> Hi Ira,
> 
> you are welcome. I actually had the code in my queue for a long time;
> I just found some time last weekend to finally test it.
> 
> It would be great if you can do some testing and send me a Tested-by and/or
> Acked-by feedback.
> 

Hi Guenter,

I finally got around to testing this today. The driver works great.
Please add my:

Tested-by: Ira W. Snyder <iws@ovro.caltech.edu>

> Thanks,
> Guenter
> 
> > Ira
> > 
> > > ---
> > >  Documentation/hwmon/ina209 |   93 +++++++
> > >  drivers/hwmon/Kconfig      |   10 +
> > >  drivers/hwmon/Makefile     |    1 +
> > >  drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 739 insertions(+)
> > >  create mode 100644 Documentation/hwmon/ina209
> > >  create mode 100644 drivers/hwmon/ina209.c
> > > 
> > > diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
> > > new file mode 100644
> > > index 0000000..2e11bbc
> > > --- /dev/null
> > > +++ b/Documentation/hwmon/ina209
> > > @@ -0,0 +1,93 @@
> > > +Kernel driver ina209
> > > +==========> > > +
> > > +Supported chips:
> > > +  * Burr-Brown / Texas Instruments INA209
> > > +    Prefix: 'ina209'
> > > +    Addresses scanned: -
> > > +    Datasheet:
> > > +        http://www.ti.com/lit/gpn/ina209
> > > +
> > > +Author: Paul Hays <haysp@magma.net>
> > > +Author: Ira W. Snyder <iws@ovro.caltech.edu>
> > > +Author: Guenter Roeck <linux@roeck-us.net>
> > > +
> > > +
> > > +Description
> > > +-----------
> > > +
> > > +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
> > > +of a D.C. power supply. It can perform measurements and calculations in the
> > > +background to supply readings at any time. It includes a programmable
> > > +calibration multiplier to scale the displayed current and power values.
> > > +
> > > +
> > > +Sysfs entries
> > > +-------------
> > > +
> > > +The INA209 chip is highly configurable both via hardwiring and via
> > > +the I2C bus. See the datasheet for details.
> > > +
> > > +This tries to expose most monitoring features of the hardware via
> > > +sysfs. It does not support every feature of this chip.
> > > +
> > > +
> > > +in0_input		shunt voltage (mV)
> > > +in0_input_highest	shunt voltage historical maximum reading (mV)
> > > +in0_input_lowest	shunt voltage historical minimum reading (mV)
> > > +in0_reset_history	reset shunt voltage history
> > > +in0_max			shunt voltage max alarm limit (mV)
> > > +in0_min			shunt voltage min alarm limit (mV)
> > > +in0_crit_max		shunt voltage crit max alarm limit (mV)
> > > +in0_crit_min		shunt voltage crit min alarm limit (mV)
> > > +in0_max_alarm		shunt voltage max alarm limit exceeded
> > > +in0_min_alarm		shunt voltage min alarm limit exceeded
> > > +in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
> > > +in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
> > > +
> > > +in1_input		bus voltage (mV)
> > > +in1_input_highest	bus voltage historical maximum reading (mV)
> > > +in1_input_lowest	bus voltage historical minimum reading (mV)
> > > +in1_reset_history	reset bus voltage history
> > > +in1_max			bus voltage max alarm limit (mV)
> > > +in1_min			bus voltage min alarm limit (mV)
> > > +in1_crit_max		bus voltage crit max alarm limit (mV)
> > > +in1_crit_min		bus voltage crit min alarm limit (mV)
> > > +in1_max_alarm		bus voltage max alarm limit exceeded
> > > +in1_min_alarm		bus voltage min alarm limit exceeded
> > > +in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
> > > +in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
> > > +
> > > +power1_input		power measurement (uW)
> > > +power1_input_highest	power historical maximum reading (uW)
> > > +power1_reset_history	reset power history
> > > +power1_max		power max alarm limit (uW)
> > > +power1_crit		power crit alarm limit (uW)
> > > +power1_max_alarm	power max alarm limit exceeded
> > > +power1_crit_alarm	power crit alarm limit exceeded
> > > +
> > > +curr1_input		current measurement (mA)
> > > +
> > > +update_interval		data conversion time; affects number of samples used
> > > +			to average results for shunt and bus voltages.
> > > +
> > > +General Remarks
> > > +---------------
> > > +
> > > +The power and current registers in this chip require that the calibration
> > > +register is programmed correctly before they are used. Normally this is expected
> > > +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
> > > +voltage can be provided using platform data. The driver uses platform data from
> > > +the ina2xx driver for this purpose. If calibration register data is not provided
> > > +via platform data, the driver checks if the calibration register has been
> > > +programmed (ie has a value not equal to zero). If so, this value is retained.
> > > +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
> > > +programmed into the calibration register.
> > > +
> > > +
> > > +Output Pins
> > > +-----------
> > > +
> > > +Output pin programming is a board feature which depends on the BIOS. It is
> > > +outside the scope of a hardware monitoring driver to enable or disable output
> > > +pins.
> > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > > index ef57572..5dacd58 100644
> > > --- a/drivers/hwmon/Kconfig
> > > +++ b/drivers/hwmon/Kconfig
> > > @@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
> > >  	  This driver can also be build as a module.  If so, the module
> > >  	  will be called amc6821.
> > >  
> > > +config SENSORS_INA209
> > > +	tristate "TI / Burr Brown INA209"
> > > +	depends on I2C
> > > +	help
> > > +	  If you say yes here you get support for the TI / Burr Brown INA209
> > > +	  voltage / current / power monitor I2C interface.
> > > +
> > > +	  This driver can also be built as a module. If so, the module will
> > > +	  be called ina209.
> > > +
> > >  config SENSORS_INA2XX
> > >  	tristate "Texas Instruments INA219 and compatibles"
> > >  	depends on I2C
> > > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > > index a37a82c..8d6d97e 100644
> > > --- a/drivers/hwmon/Makefile
> > > +++ b/drivers/hwmon/Makefile
> > > @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> > >  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
> > >  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
> > >  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
> > > +obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
> > >  obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
> > >  obj-$(CONFIG_SENSORS_IT87)	+= it87.o
> > >  obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
> > > diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
> > > new file mode 100644
> > > index 0000000..1070611
> > > --- /dev/null
> > > +++ b/drivers/hwmon/ina209.c
> > > @@ -0,0 +1,635 @@
> > > +/*
> > > + * Driver for the Texas Instruments / Burr Brown INA209
> > > + * Bidirectional Current/Power Monitor
> > > + *
> > > + * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
> > > + *
> > > + * Derived from Ira W. Snyder's original driver submission
> > > + *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
> > > + *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
> > > + *
> > > + * Aligned with ina2xx driver
> > > + *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
> > > + *	Thanks to Jan Volkering
> > > + *
> > > + * This program is free software; you can redistribute it and/or modify
> > > + * it under the terms of the GNU General Public License as published by
> > > + * the Free Software Foundation; version 2 of the License.
> > > + *
> > > + * Datasheet:
> > > + * http://www.ti.com/lit/gpn/ina209
> > > + */
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/module.h>
> > > +#include <linux/init.h>
> > > +#include <linux/err.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/bug.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/hwmon.h>
> > > +#include <linux/hwmon-sysfs.h>
> > > +
> > > +#include <linux/platform_data/ina2xx.h>
> > > +
> > > +/* register definitions */
> > > +#define INA209_CONFIGURATION		0x00
> > > +#define INA209_STATUS			0x01
> > > +#define INA209_STATUS_MASK		0x02
> > > +#define INA209_SHUNT_VOLTAGE		0x03
> > > +#define INA209_BUS_VOLTAGE		0x04
> > > +#define INA209_POWER			0x05
> > > +#define INA209_CURRENT			0x06
> > > +#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
> > > +#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
> > > +#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
> > > +#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
> > > +#define INA209_POWER_PEAK		0x0b
> > > +#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
> > > +#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
> > > +#define INA209_POWER_WARN		0x0e
> > > +#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
> > > +#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
> > > +#define INA209_POWER_OVER_LIMIT		0x11
> > > +#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
> > > +#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
> > > +#define INA209_CRITICAL_DAC_POS		0x14
> > > +#define INA209_CRITICAL_DAC_NEG		0x15
> > > +#define INA209_CALIBRATION		0x16
> > > +
> > > +#define INA209_REGISTERS		0x17
> > > +
> > > +#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
> > > +#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
> > > +
> > > +struct ina209_data {
> > > +	struct device *hwmon_dev;
> > > +
> > > +	struct mutex update_lock;
> > > +	bool valid;
> > > +	unsigned long last_updated;	/* in jiffies */
> > > +
> > > +	u16 regs[INA209_REGISTERS];	/* All chip registers */
> > > +
> > > +	u16 config_orig;		/* Original configuration */
> > > +	u16 calibration_orig;		/* Original calibration */
> > > +	u16 update_interval;
> > > +};
> > > +
> > > +static struct ina209_data *ina209_update_device(struct device *dev)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > +	struct ina209_data *ret = data;
> > > +	s32 val;
> > > +	int i;
> > > +
> > > +	mutex_lock(&data->update_lock);
> > > +
> > > +	if (!data->valid ||
> > > +	    time_after(jiffies, data->last_updated + data->update_interval)) {
> > > +		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
> > > +			val = i2c_smbus_read_word_swapped(client, i);
> > > +			if (val < 0) {
> > > +				ret = ERR_PTR(val);
> > > +				goto abort;
> > > +			}
> > > +			data->regs[i] = val;
> > > +		}
> > > +		data->last_updated = jiffies;
> > > +		data->valid = true;
> > > +	}
> > > +abort:
> > > +	mutex_unlock(&data->update_lock);
> > > +	return ret;
> > > +}
> > > +
> > > +/*
> > > + * Read a value from a device register and convert it to the
> > > + * appropriate sysfs units
> > > + */
> > > +static long ina209_from_reg(const u8 reg, const u16 val)
> > > +{
> > > +	switch (reg) {
> > > +	case INA209_SHUNT_VOLTAGE:
> > > +	case INA209_SHUNT_VOLTAGE_POS_PEAK:
> > > +	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
> > > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > > +		/* LSB\x10 uV. Convert to mV. */
> > > +		return DIV_ROUND_CLOSEST(val, 100);
> > > +
> > > +	case INA209_BUS_VOLTAGE:
> > > +	case INA209_BUS_VOLTAGE_MAX_PEAK:
> > > +	case INA209_BUS_VOLTAGE_MIN_PEAK:
> > > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > > +		/* LSB=4 mV, last 3 bits unused */
> > > +		return (val >> 3) * 4;
> > > +
> > > +	case INA209_CRITICAL_DAC_POS:
> > > +		/* LSB=1 mV, in the upper 8 bits */
> > > +		return val >> 8;
> > > +
> > > +	case INA209_CRITICAL_DAC_NEG:
> > > +		/* LSB=1 mV, in the upper 8 bits */
> > > +		return -1 * (val >> 8);
> > > +
> > > +	case INA209_POWER:
> > > +	case INA209_POWER_PEAK:
> > > +	case INA209_POWER_WARN:
> > > +	case INA209_POWER_OVER_LIMIT:
> > > +		/* LSB  mW. Convert to uW */
> > > +		return val * 20 * 1000L;
> > > +
> > > +	case INA209_CURRENT:
> > > +		/* LSB=1 mA (selected). Is in mA */
> > > +		return val;
> > > +	}
> > > +
> > > +	/* programmer goofed */
> > > +	WARN_ON_ONCE(1);
> > > +	return 0;
> > > +}
> > > +
> > > +/*
> > > + * Take a value and convert it to register format, clamping the value
> > > + * to the appropriate range.
> > > + */
> > > +static int ina209_to_reg(u8 reg, u16 old, long val)
> > > +{
> > > +	switch (reg) {
> > > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > > +		/* Limit to +- 320 mV, 10 uV LSB */
> > > +		return clamp_val(val, -320, 320) * 100;
> > > +
> > > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > > +		/*
> > > +		 * Limit to 0-32000 mV, 4 mV LSB
> > > +		 *
> > > +		 * The last three bits aren't part of the value, but we'll
> > > +		 * preserve them in their original state.
> > > +		 */
> > > +		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
> > > +		  | (old & 0x7);
> > > +
> > > +	case INA209_CRITICAL_DAC_NEG:
> > > +		/*
> > > +		 * Limit to -255-0 mV, 1 mV LSB
> > > +		 * Convert the value to a positive value for the register
> > > +		 *
> > > +		 * The value lives in the top 8 bits only, be careful
> > > +		 * and keep original value of other bits.
> > > +		 */
> > > +		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
> > > +
> > > +	case INA209_CRITICAL_DAC_POS:
> > > +		/*
> > > +		 * Limit to 0-255 mV, 1 mV LSB
> > > +		 *
> > > +		 * The value lives in the top 8 bits only, be careful
> > > +		 * and keep original value of other bits.
> > > +		 */
> > > +		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
> > > +
> > > +	case INA209_POWER_WARN:
> > > +	case INA209_POWER_OVER_LIMIT:
> > > +		/* 20 mW LSB */
> > > +		return DIV_ROUND_CLOSEST(val, 20 * 1000);
> > > +	}
> > > +
> > > +	/* Other registers are read-only, return access error */
> > > +	return -EACCES;
> > > +}
> > > +
> > > +static int ina209_interval_from_reg(u16 reg)
> > > +{
> > > +	return 68 >> (15 - ((reg >> 3) & 0x0f));
> > > +}
> > > +
> > > +static u16 ina209_reg_from_interval(u16 config, long interval)
> > > +{
> > > +	int i, adc;
> > > +
> > > +	if (interval <= 0) {
> > > +		adc = 8;
> > > +	} else {
> > > +		adc = 15;
> > > +		for (i = 34 + 34 / 2; i; i >>= 1) {
> > > +			if (i < interval)
> > > +				break;
> > > +			adc--;
> > > +		}
> > > +	}
> > > +	return (config & 0xf807) | (adc << 3) | (adc << 7);
> > > +}
> > > +
> > > +static ssize_t ina209_set_interval(struct device *dev,
> > > +				   struct device_attribute *da,
> > > +				   const char *buf, size_t count)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct ina209_data *data = ina209_update_device(dev);
> > > +	long val;
> > > +	u16 regval;
> > > +	int ret;
> > > +
> > > +	if (IS_ERR(data))
> > > +		return PTR_ERR(data);
> > > +
> > > +	ret = kstrtol(buf, 10, &val);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	mutex_lock(&data->update_lock);
> > > +	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
> > > +					  val);
> > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
> > > +	data->regs[INA209_CONFIGURATION] = regval;
> > > +	data->update_interval = ina209_interval_from_reg(regval);
> > > +	mutex_unlock(&data->update_lock);
> > > +	return count;
> > > +}
> > > +
> > > +static ssize_t ina209_show_interval(struct device *dev,
> > > +				    struct device_attribute *da, char *buf)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > +
> > > +	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
> > > +}
> > > +
> > > +/*
> > > + * History is reset by writing 1 into bit 0 of the respective peak register.
> > > + * Since more than one peak register may be affected by the scope of a
> > > + * reset_history attribute write, use a bit mask in attr->index to identify
> > > + * which registers are affected.
> > > + */
> > > +static u16 ina209_reset_history_regs[] = {
> > > +	INA209_SHUNT_VOLTAGE_POS_PEAK,
> > > +	INA209_SHUNT_VOLTAGE_NEG_PEAK,
> > > +	INA209_BUS_VOLTAGE_MAX_PEAK,
> > > +	INA209_BUS_VOLTAGE_MIN_PEAK,
> > > +	INA209_POWER_PEAK
> > > +};
> > > +
> > > +static ssize_t ina209_reset_history(struct device *dev,
> > > +				    struct device_attribute *da,
> > > +				    const char *buf,
> > > +				    size_t count)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > +	u32 mask = attr->index;
> > > +	long val;
> > > +	int i, ret;
> > > +
> > > +	ret = kstrtol(buf, 10, &val);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	mutex_lock(&data->update_lock);
> > > +	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
> > > +		if (mask & (1 << i))
> > > +			i2c_smbus_write_word_swapped(client,
> > > +					ina209_reset_history_regs[i], 1);
> > > +	}
> > > +	data->valid = false;
> > > +	mutex_unlock(&data->update_lock);
> > > +	return count;
> > > +}
> > > +
> > > +static ssize_t ina209_set_value(struct device *dev,
> > > +				struct device_attribute *da,
> > > +				const char *buf,
> > > +				size_t count)
> > > +{
> > > +	struct i2c_client *client = to_i2c_client(dev);
> > > +	struct ina209_data *data = ina209_update_device(dev);
> > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > +	int reg = attr->index;
> > > +	long val;
> > > +	int ret;
> > > +
> > > +	if (IS_ERR(data))
> > > +		return PTR_ERR(data);
> > > +
> > > +	ret = kstrtol(buf, 10, &val);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	mutex_lock(&data->update_lock);
> > > +	ret = ina209_to_reg(reg, data->regs[reg], val);
> > > +	if (ret < 0) {
> > > +		count = ret;
> > > +		goto abort;
> > > +	}
> > > +	i2c_smbus_write_word_swapped(client, reg, ret);
> > > +	data->regs[reg] = ret;
> > > +abort:
> > > +	mutex_unlock(&data->update_lock);
> > > +	return count;
> > > +}
> > > +
> > > +static ssize_t ina209_show_value(struct device *dev,
> > > +				 struct device_attribute *da,
> > > +				 char *buf)
> > > +{
> > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > +	struct ina209_data *data = ina209_update_device(dev);
> > > +	long val;
> > > +
> > > +	if (IS_ERR(data))
> > > +		return PTR_ERR(data);
> > > +
> > > +	val = ina209_from_reg(attr->index, data->regs[attr->index]);
> > > +	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
> > > +}
> > > +
> > > +static ssize_t ina209_show_alarm(struct device *dev,
> > > +				 struct device_attribute *da,
> > > +				 char *buf)
> > > +{
> > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > +	struct ina209_data *data = ina209_update_device(dev);
> > > +	const unsigned int mask = attr->index;
> > > +	u16 status;
> > > +
> > > +	if (IS_ERR(data))
> > > +		return PTR_ERR(data);
> > > +
> > > +	status = data->regs[INA209_STATUS];
> > > +
> > > +	/*
> > > +	 * All alarms are in the INA209_STATUS register. To avoid a long
> > > +	 * switch statement, the mask is passed in attr->index
> > > +	 */
> > > +	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
> > > +}
> > > +
> > > +/* Shunt voltage, history, limits, alarms */
> > > +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_SHUNT_VOLTAGE);
> > > +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_SHUNT_VOLTAGE_POS_PEAK);
> > > +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
> > > +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
> > > +			  ina209_reset_history, (1 << 0) | (1 << 1));
> > > +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
> > > +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
> > > +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_CRITICAL_DAC_POS);
> > > +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
> > > +
> > > +static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 11);
> > > +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 12);
> > > +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 6);
> > > +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 7);
> > > +
> > > +/* Bus voltage, history, limits, alarms */
> > > +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_BUS_VOLTAGE);
> > > +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_BUS_VOLTAGE_MAX_PEAK);
> > > +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_BUS_VOLTAGE_MIN_PEAK);
> > > +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
> > > +			  ina209_reset_history, (1 << 2) | (1 << 3));
> > > +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
> > > +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
> > > +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
> > > +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
> > > +
> > > +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 14);
> > > +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 15);
> > > +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 9);
> > > +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 10);
> > > +
> > > +/* Power */
> > > +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_POWER);
> > > +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
> > > +			  NULL, INA209_POWER_PEAK);
> > > +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
> > > +			  ina209_reset_history, 1 << 4);
> > > +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_POWER_WARN);
> > > +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
> > > +			  ina209_set_value, INA209_POWER_OVER_LIMIT);
> > > +
> > > +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 13);
> > > +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > +			  1 << 8);
> > > +
> > > +/* Current */
> > > +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
> > > +			  INA209_CURRENT);
> > > +
> > > +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
> > > +			  ina209_show_interval, ina209_set_interval, 0);
> > > +
> > > +/*
> > > + * Finally, construct an array of pointers to members of the above objects,
> > > + * as required for sysfs_create_group()
> > > + */
> > > +static struct attribute *ina209_attributes[] = {
> > > +	&sensor_dev_attr_in0_input.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_max.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_min.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
> > > +
> > > +	&sensor_dev_attr_in1_input.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_max.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_min.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
> > > +
> > > +	&sensor_dev_attr_power1_input.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_max.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_crit.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
> > > +	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
> > > +
> > > +	&sensor_dev_attr_curr1_input.dev_attr.attr,
> > > +
> > > +	&sensor_dev_attr_update_interval.dev_attr.attr,
> > > +
> > > +	NULL,
> > > +};
> > > +
> > > +static const struct attribute_group ina209_group = {
> > > +	.attrs = ina209_attributes,
> > > +};
> > > +
> > > +static void ina209_restore_conf(struct i2c_client *client,
> > > +				struct ina209_data *data)
> > > +{
> > > +	/* Restore initial configuration */
> > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > > +				     data->config_orig);
> > > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > > +				     data->calibration_orig);
> > > +}
> > > +
> > > +static int ina290_init_client(struct i2c_client *client,
> > > +			      struct ina209_data *data)
> > > +{
> > > +	struct ina2xx_platform_data *pdata;
> > > +	long shunt;
> > > +	int reg;
> > > +
> > > +	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
> > > +	if (reg < 0)
> > > +		return reg;
> > > +	data->calibration_orig = reg;
> > > +
> > > +	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
> > > +	if (reg < 0)
> > > +		return reg;
> > > +	data->config_orig = reg;
> > > +
> > > +	if (client->dev.platform_data) {
> > > +		pdata > > > +		  (struct ina2xx_platform_data *)client->dev.platform_data;
> > > +		shunt = pdata->shunt_uohms;
> > > +		if (shunt <= 0)
> > > +			return -ENODEV;
> > > +
> > > +	} else {
> > > +		shunt = data->calibration_orig ?
> > > +		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
> > > +	}
> > > +
> > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > > +				     INA209_CONFIG_DEFAULT);
> > > +	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
> > > +
> > > +	/*
> > > +	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
> > > +	 * See equation 13 in datasheet.
> > > +	 */
> > > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > > +				     clamp_val(40960000 / shunt, 1, 65535));
> > > +
> > > +	/* Clear status register */
> > > +	i2c_smbus_read_word_swapped(client, INA209_STATUS);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int ina209_probe(struct i2c_client *client,
> > > +			const struct i2c_device_id *id)
> > > +{
> > > +	struct i2c_adapter *adapter = client->adapter;
> > > +	struct ina209_data *data;
> > > +	int ret;
> > > +
> > > +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> > > +		return -ENODEV;
> > > +
> > > +	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
> > > +	if (!data)
> > > +		return -ENOMEM;
> > > +
> > > +	i2c_set_clientdata(client, data);
> > > +	mutex_init(&data->update_lock);
> > > +
> > > +	ret = ina290_init_client(client, data);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	/* Register sysfs hooks */
> > > +	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
> > > +	if (ret)
> > > +		goto out_restore_conf;
> > > +
> > > +	data->hwmon_dev = hwmon_device_register(&client->dev);
> > > +	if (IS_ERR(data->hwmon_dev)) {
> > > +		ret = PTR_ERR(data->hwmon_dev);
> > > +		goto out_hwmon_device_register;
> > > +	}
> > > +
> > > +	return 0;
> > > +
> > > +out_hwmon_device_register:
> > > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > > +out_restore_conf:
> > > +	ina209_restore_conf(client, data);
> > > +	return ret;
> > > +}
> > > +
> > > +static int ina209_remove(struct i2c_client *client)
> > > +{
> > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > +
> > > +	hwmon_device_unregister(data->hwmon_dev);
> > > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > > +	ina209_restore_conf(client, data);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct i2c_device_id ina209_id[] = {
> > > +	{ "ina209", 0 },
> > > +	{ }
> > > +};
> > > +MODULE_DEVICE_TABLE(i2c, ina209_id);
> > > +
> > > +/* This is the driver that will be inserted */
> > > +static struct i2c_driver ina209_driver = {
> > > +	.class		= I2C_CLASS_HWMON,
> > > +	.driver = {
> > > +		.name	= "ina209",
> > > +	},
> > > +	.probe		= ina209_probe,
> > > +	.remove		= ina209_remove,
> > > +	.id_table	= ina209_id,
> > > +};
> > > +
> > > +module_i2c_driver(ina209_driver);
> > > +
> > > +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
> > > +MODULE_DESCRIPTION("INA209 driver");
> > > +MODULE_LICENSE("GPL");
> > > -- 
> > > 1.7.9.7
> > > 
> > 

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (10 preceding siblings ...)
  (?)
@ 2013-01-22 22:15 ` Ira W. Snyder
  -1 siblings, 0 replies; 37+ messages in thread
From: Ira W. Snyder @ 2013-01-22 22:15 UTC (permalink / raw)
  To: lm-sensors

On Tue, Jan 22, 2013 at 01:43:48PM -0800, Ira W. Snyder wrote:
> On Mon, Jan 14, 2013 at 10:04:28PM -0800, Guenter Roeck wrote:
> > On Mon, Jan 14, 2013 at 01:17:04PM -0800, Ira W. Snyder wrote:
> > > On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> > > > Add support for the TI / Burr-Brown INA209 voltage / current / power
> > > > monitor.
> > > > 
> > > > Cc: Paul Hays <haysp@magma.net>
> > > > Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> > > > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> > > 
> > > Hi Guenter,
> > > 
> > > I didn't notice any issues with a quick review. Thanks for pushing the
> > > driver to mainline.
> > > 
> > Hi Ira,
> > 
> > you are welcome. I actually had the code in my queue for a long time;
> > I just found some time last weekend to finally test it.
> > 
> > It would be great if you can do some testing and send me a Tested-by and/or
> > Acked-by feedback.
> > 
> 
> Hi Guenter,
> 
> I finally got around to testing this today. The driver works great.
> Please add my:
> 
> Tested-by: Ira W. Snyder <iws@ovro.caltech.edu>
> 

One minor suggestion below. Sorry that I didn't catch it earlier.

Ira

> > Thanks,
> > Guenter
> > 
> > > Ira
> > > 
> > > > ---
> > > >  Documentation/hwmon/ina209 |   93 +++++++
> > > >  drivers/hwmon/Kconfig      |   10 +
> > > >  drivers/hwmon/Makefile     |    1 +
> > > >  drivers/hwmon/ina209.c     |  635 ++++++++++++++++++++++++++++++++++++++++++++
> > > >  4 files changed, 739 insertions(+)
> > > >  create mode 100644 Documentation/hwmon/ina209
> > > >  create mode 100644 drivers/hwmon/ina209.c
> > > > 
> > > > diff --git a/Documentation/hwmon/ina209 b/Documentation/hwmon/ina209
> > > > new file mode 100644
> > > > index 0000000..2e11bbc
> > > > --- /dev/null
> > > > +++ b/Documentation/hwmon/ina209
> > > > @@ -0,0 +1,93 @@
> > > > +Kernel driver ina209
> > > > +==========> > > > +
> > > > +Supported chips:
> > > > +  * Burr-Brown / Texas Instruments INA209
> > > > +    Prefix: 'ina209'
> > > > +    Addresses scanned: -
> > > > +    Datasheet:
> > > > +        http://www.ti.com/lit/gpn/ina209
> > > > +
> > > > +Author: Paul Hays <haysp@magma.net>
> > > > +Author: Ira W. Snyder <iws@ovro.caltech.edu>
> > > > +Author: Guenter Roeck <linux@roeck-us.net>
> > > > +
> > > > +
> > > > +Description
> > > > +-----------
> > > > +
> > > > +The TI / Burr-Brown INA209 monitors voltage, current, and power on the high side
> > > > +of a D.C. power supply. It can perform measurements and calculations in the
> > > > +background to supply readings at any time. It includes a programmable
> > > > +calibration multiplier to scale the displayed current and power values.
> > > > +
> > > > +
> > > > +Sysfs entries
> > > > +-------------
> > > > +
> > > > +The INA209 chip is highly configurable both via hardwiring and via
> > > > +the I2C bus. See the datasheet for details.
> > > > +
> > > > +This tries to expose most monitoring features of the hardware via
> > > > +sysfs. It does not support every feature of this chip.
> > > > +
> > > > +
> > > > +in0_input		shunt voltage (mV)
> > > > +in0_input_highest	shunt voltage historical maximum reading (mV)
> > > > +in0_input_lowest	shunt voltage historical minimum reading (mV)
> > > > +in0_reset_history	reset shunt voltage history
> > > > +in0_max			shunt voltage max alarm limit (mV)
> > > > +in0_min			shunt voltage min alarm limit (mV)
> > > > +in0_crit_max		shunt voltage crit max alarm limit (mV)
> > > > +in0_crit_min		shunt voltage crit min alarm limit (mV)
> > > > +in0_max_alarm		shunt voltage max alarm limit exceeded
> > > > +in0_min_alarm		shunt voltage min alarm limit exceeded
> > > > +in0_crit_max_alarm	shunt voltage crit max alarm limit exceeded
> > > > +in0_crit_min_alarm	shunt voltage crit min alarm limit exceeded
> > > > +
> > > > +in1_input		bus voltage (mV)
> > > > +in1_input_highest	bus voltage historical maximum reading (mV)
> > > > +in1_input_lowest	bus voltage historical minimum reading (mV)
> > > > +in1_reset_history	reset bus voltage history
> > > > +in1_max			bus voltage max alarm limit (mV)
> > > > +in1_min			bus voltage min alarm limit (mV)
> > > > +in1_crit_max		bus voltage crit max alarm limit (mV)
> > > > +in1_crit_min		bus voltage crit min alarm limit (mV)
> > > > +in1_max_alarm		bus voltage max alarm limit exceeded
> > > > +in1_min_alarm		bus voltage min alarm limit exceeded
> > > > +in1_crit_max_alarm	bus voltage crit max alarm limit exceeded
> > > > +in1_crit_min_alarm	bus voltage crit min alarm limit exceeded
> > > > +
> > > > +power1_input		power measurement (uW)
> > > > +power1_input_highest	power historical maximum reading (uW)
> > > > +power1_reset_history	reset power history
> > > > +power1_max		power max alarm limit (uW)
> > > > +power1_crit		power crit alarm limit (uW)
> > > > +power1_max_alarm	power max alarm limit exceeded
> > > > +power1_crit_alarm	power crit alarm limit exceeded
> > > > +
> > > > +curr1_input		current measurement (mA)
> > > > +
> > > > +update_interval		data conversion time; affects number of samples used
> > > > +			to average results for shunt and bus voltages.
> > > > +
> > > > +General Remarks
> > > > +---------------
> > > > +
> > > > +The power and current registers in this chip require that the calibration
> > > > +register is programmed correctly before they are used. Normally this is expected
> > > > +to be done in the BIOS. In the absence of BIOS programming, the shunt resistor
> > > > +voltage can be provided using platform data. The driver uses platform data from
> > > > +the ina2xx driver for this purpose. If calibration register data is not provided
> > > > +via platform data, the driver checks if the calibration register has been
> > > > +programmed (ie has a value not equal to zero). If so, this value is retained.
> > > > +Otherwise, a default value reflecting a shunt resistor value of 10 mOhm is
> > > > +programmed into the calibration register.
> > > > +
> > > > +
> > > > +Output Pins
> > > > +-----------
> > > > +
> > > > +Output pin programming is a board feature which depends on the BIOS. It is
> > > > +outside the scope of a hardware monitoring driver to enable or disable output
> > > > +pins.
> > > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > > > index ef57572..5dacd58 100644
> > > > --- a/drivers/hwmon/Kconfig
> > > > +++ b/drivers/hwmon/Kconfig
> > > > @@ -1156,6 +1156,16 @@ config SENSORS_AMC6821
> > > >  	  This driver can also be build as a module.  If so, the module
> > > >  	  will be called amc6821.
> > > >  
> > > > +config SENSORS_INA209
> > > > +	tristate "TI / Burr Brown INA209"
> > > > +	depends on I2C
> > > > +	help
> > > > +	  If you say yes here you get support for the TI / Burr Brown INA209
> > > > +	  voltage / current / power monitor I2C interface.
> > > > +
> > > > +	  This driver can also be built as a module. If so, the module will
> > > > +	  be called ina209.
> > > > +
> > > >  config SENSORS_INA2XX
> > > >  	tristate "Texas Instruments INA219 and compatibles"
> > > >  	depends on I2C
> > > > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > > > index a37a82c..8d6d97e 100644
> > > > --- a/drivers/hwmon/Makefile
> > > > +++ b/drivers/hwmon/Makefile
> > > > @@ -65,6 +65,7 @@ obj-$(CONFIG_SENSORS_ULTRA45)	+= ultra45_env.o
> > > >  obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
> > > >  obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
> > > >  obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
> > > > +obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
> > > >  obj-$(CONFIG_SENSORS_INA2XX)	+= ina2xx.o
> > > >  obj-$(CONFIG_SENSORS_IT87)	+= it87.o
> > > >  obj-$(CONFIG_SENSORS_JC42)	+= jc42.o
> > > > diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
> > > > new file mode 100644
> > > > index 0000000..1070611
> > > > --- /dev/null
> > > > +++ b/drivers/hwmon/ina209.c
> > > > @@ -0,0 +1,635 @@
> > > > +/*
> > > > + * Driver for the Texas Instruments / Burr Brown INA209
> > > > + * Bidirectional Current/Power Monitor
> > > > + *
> > > > + * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net>
> > > > + *
> > > > + * Derived from Ira W. Snyder's original driver submission
> > > > + *	Copyright (C) 2008 Paul Hays <haysp@magma.ca>
> > > > + *	Copyright (C) 2008-2009 Ira W. Snyder <iws@ovro.caltech.edu>
> > > > + *
> > > > + * Aligned with ina2xx driver
> > > > + *	Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
> > > > + *	Thanks to Jan Volkering
> > > > + *
> > > > + * This program is free software; you can redistribute it and/or modify
> > > > + * it under the terms of the GNU General Public License as published by
> > > > + * the Free Software Foundation; version 2 of the License.
> > > > + *
> > > > + * Datasheet:
> > > > + * http://www.ti.com/lit/gpn/ina209
> > > > + */
> > > > +
> > > > +#include <linux/kernel.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/init.h>
> > > > +#include <linux/err.h>
> > > > +#include <linux/slab.h>
> > > > +#include <linux/bug.h>
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/hwmon.h>
> > > > +#include <linux/hwmon-sysfs.h>
> > > > +
> > > > +#include <linux/platform_data/ina2xx.h>
> > > > +
> > > > +/* register definitions */
> > > > +#define INA209_CONFIGURATION		0x00
> > > > +#define INA209_STATUS			0x01
> > > > +#define INA209_STATUS_MASK		0x02
> > > > +#define INA209_SHUNT_VOLTAGE		0x03
> > > > +#define INA209_BUS_VOLTAGE		0x04
> > > > +#define INA209_POWER			0x05
> > > > +#define INA209_CURRENT			0x06
> > > > +#define INA209_SHUNT_VOLTAGE_POS_PEAK	0x07
> > > > +#define INA209_SHUNT_VOLTAGE_NEG_PEAK	0x08
> > > > +#define INA209_BUS_VOLTAGE_MAX_PEAK	0x09
> > > > +#define INA209_BUS_VOLTAGE_MIN_PEAK	0x0a
> > > > +#define INA209_POWER_PEAK		0x0b
> > > > +#define INA209_SHUNT_VOLTAGE_POS_WARN	0x0c
> > > > +#define INA209_SHUNT_VOLTAGE_NEG_WARN	0x0d
> > > > +#define INA209_POWER_WARN		0x0e
> > > > +#define INA209_BUS_VOLTAGE_OVER_WARN	0x0f
> > > > +#define INA209_BUS_VOLTAGE_UNDER_WARN	0x10
> > > > +#define INA209_POWER_OVER_LIMIT		0x11
> > > > +#define INA209_BUS_VOLTAGE_OVER_LIMIT	0x12
> > > > +#define INA209_BUS_VOLTAGE_UNDER_LIMIT	0x13
> > > > +#define INA209_CRITICAL_DAC_POS		0x14
> > > > +#define INA209_CRITICAL_DAC_NEG		0x15
> > > > +#define INA209_CALIBRATION		0x16
> > > > +
> > > > +#define INA209_REGISTERS		0x17
> > > > +
> > > > +#define INA209_CONFIG_DEFAULT		0x3c47	/* PGA=8, full range */
> > > > +#define INA209_SHUNT_DEFAULT		10000	/* uOhm */
> > > > +
> > > > +struct ina209_data {
> > > > +	struct device *hwmon_dev;
> > > > +
> > > > +	struct mutex update_lock;
> > > > +	bool valid;
> > > > +	unsigned long last_updated;	/* in jiffies */
> > > > +
> > > > +	u16 regs[INA209_REGISTERS];	/* All chip registers */
> > > > +
> > > > +	u16 config_orig;		/* Original configuration */
> > > > +	u16 calibration_orig;		/* Original calibration */
> > > > +	u16 update_interval;
> > > > +};
> > > > +
> > > > +static struct ina209_data *ina209_update_device(struct device *dev)
> > > > +{
> > > > +	struct i2c_client *client = to_i2c_client(dev);
> > > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > > +	struct ina209_data *ret = data;
> > > > +	s32 val;
> > > > +	int i;
> > > > +
> > > > +	mutex_lock(&data->update_lock);
> > > > +
> > > > +	if (!data->valid ||
> > > > +	    time_after(jiffies, data->last_updated + data->update_interval)) {
> > > > +		for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
> > > > +			val = i2c_smbus_read_word_swapped(client, i);
> > > > +			if (val < 0) {
> > > > +				ret = ERR_PTR(val);
> > > > +				goto abort;
> > > > +			}
> > > > +			data->regs[i] = val;
> > > > +		}
> > > > +		data->last_updated = jiffies;
> > > > +		data->valid = true;
> > > > +	}
> > > > +abort:
> > > > +	mutex_unlock(&data->update_lock);
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +/*
> > > > + * Read a value from a device register and convert it to the
> > > > + * appropriate sysfs units
> > > > + */
> > > > +static long ina209_from_reg(const u8 reg, const u16 val)
> > > > +{
> > > > +	switch (reg) {
> > > > +	case INA209_SHUNT_VOLTAGE:
> > > > +	case INA209_SHUNT_VOLTAGE_POS_PEAK:
> > > > +	case INA209_SHUNT_VOLTAGE_NEG_PEAK:
> > > > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > > > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > > > +		/* LSB\x10 uV. Convert to mV. */
> > > > +		return DIV_ROUND_CLOSEST(val, 100);
> > > > +
> > > > +	case INA209_BUS_VOLTAGE:
> > > > +	case INA209_BUS_VOLTAGE_MAX_PEAK:
> > > > +	case INA209_BUS_VOLTAGE_MIN_PEAK:
> > > > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > > > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > > > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > > > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > > > +		/* LSB=4 mV, last 3 bits unused */
> > > > +		return (val >> 3) * 4;
> > > > +
> > > > +	case INA209_CRITICAL_DAC_POS:
> > > > +		/* LSB=1 mV, in the upper 8 bits */
> > > > +		return val >> 8;
> > > > +
> > > > +	case INA209_CRITICAL_DAC_NEG:
> > > > +		/* LSB=1 mV, in the upper 8 bits */
> > > > +		return -1 * (val >> 8);
> > > > +
> > > > +	case INA209_POWER:
> > > > +	case INA209_POWER_PEAK:
> > > > +	case INA209_POWER_WARN:
> > > > +	case INA209_POWER_OVER_LIMIT:
> > > > +		/* LSB  mW. Convert to uW */
> > > > +		return val * 20 * 1000L;
> > > > +
> > > > +	case INA209_CURRENT:
> > > > +		/* LSB=1 mA (selected). Is in mA */
> > > > +		return val;
> > > > +	}
> > > > +
> > > > +	/* programmer goofed */
> > > > +	WARN_ON_ONCE(1);
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +/*
> > > > + * Take a value and convert it to register format, clamping the value
> > > > + * to the appropriate range.
> > > > + */
> > > > +static int ina209_to_reg(u8 reg, u16 old, long val)
> > > > +{
> > > > +	switch (reg) {
> > > > +	case INA209_SHUNT_VOLTAGE_POS_WARN:
> > > > +	case INA209_SHUNT_VOLTAGE_NEG_WARN:
> > > > +		/* Limit to +- 320 mV, 10 uV LSB */
> > > > +		return clamp_val(val, -320, 320) * 100;
> > > > +
> > > > +	case INA209_BUS_VOLTAGE_OVER_WARN:
> > > > +	case INA209_BUS_VOLTAGE_UNDER_WARN:
> > > > +	case INA209_BUS_VOLTAGE_OVER_LIMIT:
> > > > +	case INA209_BUS_VOLTAGE_UNDER_LIMIT:
> > > > +		/*
> > > > +		 * Limit to 0-32000 mV, 4 mV LSB
> > > > +		 *
> > > > +		 * The last three bits aren't part of the value, but we'll
> > > > +		 * preserve them in their original state.
> > > > +		 */
> > > > +		return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3)
> > > > +		  | (old & 0x7);
> > > > +
> > > > +	case INA209_CRITICAL_DAC_NEG:
> > > > +		/*
> > > > +		 * Limit to -255-0 mV, 1 mV LSB
> > > > +		 * Convert the value to a positive value for the register
> > > > +		 *
> > > > +		 * The value lives in the top 8 bits only, be careful
> > > > +		 * and keep original value of other bits.
> > > > +		 */
> > > > +		return (clamp_val(-val, 0, 255) << 8) | (old & 0xff);
> > > > +
> > > > +	case INA209_CRITICAL_DAC_POS:
> > > > +		/*
> > > > +		 * Limit to 0-255 mV, 1 mV LSB
> > > > +		 *
> > > > +		 * The value lives in the top 8 bits only, be careful
> > > > +		 * and keep original value of other bits.
> > > > +		 */
> > > > +		return (clamp_val(val, 0, 255) << 8) | (old & 0xff);
> > > > +
> > > > +	case INA209_POWER_WARN:
> > > > +	case INA209_POWER_OVER_LIMIT:
> > > > +		/* 20 mW LSB */
> > > > +		return DIV_ROUND_CLOSEST(val, 20 * 1000);
> > > > +	}
> > > > +
> > > > +	/* Other registers are read-only, return access error */
> > > > +	return -EACCES;
> > > > +}
> > > > +
> > > > +static int ina209_interval_from_reg(u16 reg)
> > > > +{
> > > > +	return 68 >> (15 - ((reg >> 3) & 0x0f));
> > > > +}
> > > > +
> > > > +static u16 ina209_reg_from_interval(u16 config, long interval)
> > > > +{
> > > > +	int i, adc;
> > > > +
> > > > +	if (interval <= 0) {
> > > > +		adc = 8;
> > > > +	} else {
> > > > +		adc = 15;
> > > > +		for (i = 34 + 34 / 2; i; i >>= 1) {
> > > > +			if (i < interval)
> > > > +				break;
> > > > +			adc--;
> > > > +		}
> > > > +	}
> > > > +	return (config & 0xf807) | (adc << 3) | (adc << 7);
> > > > +}
> > > > +
> > > > +static ssize_t ina209_set_interval(struct device *dev,
> > > > +				   struct device_attribute *da,
> > > > +				   const char *buf, size_t count)
> > > > +{
> > > > +	struct i2c_client *client = to_i2c_client(dev);
> > > > +	struct ina209_data *data = ina209_update_device(dev);
> > > > +	long val;
> > > > +	u16 regval;
> > > > +	int ret;
> > > > +
> > > > +	if (IS_ERR(data))
> > > > +		return PTR_ERR(data);
> > > > +
> > > > +	ret = kstrtol(buf, 10, &val);
> > > > +	if (ret < 0)
> > > > +		return ret;
> > > > +
> > > > +	mutex_lock(&data->update_lock);
> > > > +	regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION],
> > > > +					  val);
> > > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, regval);
> > > > +	data->regs[INA209_CONFIGURATION] = regval;
> > > > +	data->update_interval = ina209_interval_from_reg(regval);
> > > > +	mutex_unlock(&data->update_lock);
> > > > +	return count;
> > > > +}
> > > > +
> > > > +static ssize_t ina209_show_interval(struct device *dev,
> > > > +				    struct device_attribute *da, char *buf)
> > > > +{
> > > > +	struct i2c_client *client = to_i2c_client(dev);
> > > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > > +
> > > > +	return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval);
> > > > +}
> > > > +
> > > > +/*
> > > > + * History is reset by writing 1 into bit 0 of the respective peak register.
> > > > + * Since more than one peak register may be affected by the scope of a
> > > > + * reset_history attribute write, use a bit mask in attr->index to identify
> > > > + * which registers are affected.
> > > > + */
> > > > +static u16 ina209_reset_history_regs[] = {
> > > > +	INA209_SHUNT_VOLTAGE_POS_PEAK,
> > > > +	INA209_SHUNT_VOLTAGE_NEG_PEAK,
> > > > +	INA209_BUS_VOLTAGE_MAX_PEAK,
> > > > +	INA209_BUS_VOLTAGE_MIN_PEAK,
> > > > +	INA209_POWER_PEAK
> > > > +};
> > > > +
> > > > +static ssize_t ina209_reset_history(struct device *dev,
> > > > +				    struct device_attribute *da,
> > > > +				    const char *buf,
> > > > +				    size_t count)
> > > > +{
> > > > +	struct i2c_client *client = to_i2c_client(dev);
> > > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > > +	u32 mask = attr->index;
> > > > +	long val;
> > > > +	int i, ret;
> > > > +
> > > > +	ret = kstrtol(buf, 10, &val);
> > > > +	if (ret < 0)
> > > > +		return ret;
> > > > +
> > > > +	mutex_lock(&data->update_lock);
> > > > +	for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) {
> > > > +		if (mask & (1 << i))
> > > > +			i2c_smbus_write_word_swapped(client,
> > > > +					ina209_reset_history_regs[i], 1);
> > > > +	}
> > > > +	data->valid = false;
> > > > +	mutex_unlock(&data->update_lock);
> > > > +	return count;
> > > > +}
> > > > +
> > > > +static ssize_t ina209_set_value(struct device *dev,
> > > > +				struct device_attribute *da,
> > > > +				const char *buf,
> > > > +				size_t count)
> > > > +{
> > > > +	struct i2c_client *client = to_i2c_client(dev);
> > > > +	struct ina209_data *data = ina209_update_device(dev);
> > > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > > +	int reg = attr->index;
> > > > +	long val;
> > > > +	int ret;
> > > > +
> > > > +	if (IS_ERR(data))
> > > > +		return PTR_ERR(data);
> > > > +
> > > > +	ret = kstrtol(buf, 10, &val);
> > > > +	if (ret < 0)
> > > > +		return ret;
> > > > +
> > > > +	mutex_lock(&data->update_lock);
> > > > +	ret = ina209_to_reg(reg, data->regs[reg], val);
> > > > +	if (ret < 0) {
> > > > +		count = ret;
> > > > +		goto abort;
> > > > +	}
> > > > +	i2c_smbus_write_word_swapped(client, reg, ret);
> > > > +	data->regs[reg] = ret;
> > > > +abort:
> > > > +	mutex_unlock(&data->update_lock);
> > > > +	return count;
> > > > +}
> > > > +
> > > > +static ssize_t ina209_show_value(struct device *dev,
> > > > +				 struct device_attribute *da,
> > > > +				 char *buf)
> > > > +{
> > > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > > +	struct ina209_data *data = ina209_update_device(dev);
> > > > +	long val;
> > > > +
> > > > +	if (IS_ERR(data))
> > > > +		return PTR_ERR(data);
> > > > +
> > > > +	val = ina209_from_reg(attr->index, data->regs[attr->index]);
> > > > +	return snprintf(buf, PAGE_SIZE, "%ld\n", val);
> > > > +}
> > > > +
> > > > +static ssize_t ina209_show_alarm(struct device *dev,
> > > > +				 struct device_attribute *da,
> > > > +				 char *buf)
> > > > +{
> > > > +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > > > +	struct ina209_data *data = ina209_update_device(dev);
> > > > +	const unsigned int mask = attr->index;
> > > > +	u16 status;
> > > > +
> > > > +	if (IS_ERR(data))
> > > > +		return PTR_ERR(data);
> > > > +
> > > > +	status = data->regs[INA209_STATUS];
> > > > +
> > > > +	/*
> > > > +	 * All alarms are in the INA209_STATUS register. To avoid a long
> > > > +	 * switch statement, the mask is passed in attr->index
> > > > +	 */
> > > > +	return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask));
> > > > +}
> > > > +
> > > > +/* Shunt voltage, history, limits, alarms */
> > > > +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_SHUNT_VOLTAGE);
> > > > +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_SHUNT_VOLTAGE_POS_PEAK);
> > > > +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_SHUNT_VOLTAGE_NEG_PEAK);
> > > > +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL,
> > > > +			  ina209_reset_history, (1 << 0) | (1 << 1));
> > > > +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN);
> > > > +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN);
> > > > +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_CRITICAL_DAC_POS);
> > > > +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_CRITICAL_DAC_NEG);
> > > > +
> > > > +static SENSOR_DEVICE_ATTR(in0_min_alarm,  S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 11);
> > > > +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 12);
> > > > +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 6);
> > > > +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 7);
> > > > +
> > > > +/* Bus voltage, history, limits, alarms */
> > > > +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_BUS_VOLTAGE);
> > > > +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_BUS_VOLTAGE_MAX_PEAK);
> > > > +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_BUS_VOLTAGE_MIN_PEAK);
> > > > +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
> > > > +			  ina209_reset_history, (1 << 2) | (1 << 3));
> > > > +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN);
> > > > +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN);
> > > > +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT);
> > > > +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT);
> > > > +
> > > > +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 14);
> > > > +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 15);
> > > > +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 9);
> > > > +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 10);
> > > > +
> > > > +/* Power */
> > > > +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_POWER);
> > > > +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value,
> > > > +			  NULL, INA209_POWER_PEAK);
> > > > +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
> > > > +			  ina209_reset_history, 1 << 4);
> > > > +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_POWER_WARN);
> > > > +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value,
> > > > +			  ina209_set_value, INA209_POWER_OVER_LIMIT);
> > > > +
> > > > +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 13);
> > > > +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL,
> > > > +			  1 << 8);
> > > > +
> > > > +/* Current */
> > > > +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL,
> > > > +			  INA209_CURRENT);
> > > > +
> > > > +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
> > > > +			  ina209_show_interval, ina209_set_interval, 0);
> > > > +
> > > > +/*
> > > > + * Finally, construct an array of pointers to members of the above objects,
> > > > + * as required for sysfs_create_group()
> > > > + */
> > > > +static struct attribute *ina209_attributes[] = {
> > > > +	&sensor_dev_attr_in0_input.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_input_highest.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_input_lowest.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_reset_history.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_max.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_min.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_crit_max.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_crit_min.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_max_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_min_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr,
> > > > +
> > > > +	&sensor_dev_attr_in1_input.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_input_highest.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_input_lowest.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_reset_history.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_max.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_min.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_crit_max.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_crit_min.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_max_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr,
> > > > +
> > > > +	&sensor_dev_attr_power1_input.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_input_highest.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_reset_history.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_max.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_crit.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_max_alarm.dev_attr.attr,
> > > > +	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
> > > > +
> > > > +	&sensor_dev_attr_curr1_input.dev_attr.attr,
> > > > +
> > > > +	&sensor_dev_attr_update_interval.dev_attr.attr,
> > > > +
> > > > +	NULL,
> > > > +};
> > > > +
> > > > +static const struct attribute_group ina209_group = {
> > > > +	.attrs = ina209_attributes,
> > > > +};
> > > > +
> > > > +static void ina209_restore_conf(struct i2c_client *client,
> > > > +				struct ina209_data *data)
> > > > +{
> > > > +	/* Restore initial configuration */
> > > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > > > +				     data->config_orig);
> > > > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > > > +				     data->calibration_orig);
> > > > +}
> > > > +
> > > > +static int ina290_init_client(struct i2c_client *client,
> > > > +			      struct ina209_data *data)
> > > > +{
> > > > +	struct ina2xx_platform_data *pdata;

If you do this instead, it shouldn't wrap, and will make
the code below easier to read, IMO.

struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);

> > > > +	long shunt;
> > > > +	int reg;
> > > > +
> > > > +	reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION);
> > > > +	if (reg < 0)
> > > > +		return reg;
> > > > +	data->calibration_orig = reg;
> > > > +
> > > > +	reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION);
> > > > +	if (reg < 0)
> > > > +		return reg;
> > > > +	data->config_orig = reg;
> > > > +
> > > > +	if (client->dev.platform_data) {
> > > > +		pdata > > > > +		  (struct ina2xx_platform_data *)client->dev.platform_data;
> > > > +		shunt = pdata->shunt_uohms;
> > > > +		if (shunt <= 0)
> > > > +			return -ENODEV;

Then this part becomes:

if (pdata) {
	shunt = pdata->shunt_uohms;
	if (shunt <= 0)
		return -ENODEV;

Also, devicetree (OpenFirmware) support would be nice. I like it much
more than platform data on supported architectures (such as PowerPC,
which is what I use). This code is untested, but I think it should work.

#ifdef CONFIG_OF
} else if (!of_property_read_u32(client->dev.of_node, "ina209,shunt", &shunt) {
	/* no code needed */
#endif

But if you don't like OF for some reason, leave it out. I can change my
bootloader to write the INA209 calibration register appropriately.

Thanks,
Ira

> > > > +
> > > > +	} else {
> > > > +		shunt = data->calibration_orig ?
> > > > +		  40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT;
> > > > +	}
> > > > +
> > > > +	i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION,
> > > > +				     INA209_CONFIG_DEFAULT);
> > > > +	data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT);
> > > > +
> > > > +	/*
> > > > +	 * Calibrate current LSB to 1mA. Shunt is in uOhms.
> > > > +	 * See equation 13 in datasheet.
> > > > +	 */
> > > > +	i2c_smbus_write_word_swapped(client, INA209_CALIBRATION,
> > > > +				     clamp_val(40960000 / shunt, 1, 65535));
> > > > +
> > > > +	/* Clear status register */
> > > > +	i2c_smbus_read_word_swapped(client, INA209_STATUS);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int ina209_probe(struct i2c_client *client,
> > > > +			const struct i2c_device_id *id)
> > > > +{
> > > > +	struct i2c_adapter *adapter = client->adapter;
> > > > +	struct ina209_data *data;
> > > > +	int ret;
> > > > +
> > > > +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
> > > > +		return -ENODEV;
> > > > +
> > > > +	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
> > > > +	if (!data)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	i2c_set_clientdata(client, data);
> > > > +	mutex_init(&data->update_lock);
> > > > +
> > > > +	ret = ina290_init_client(client, data);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	/* Register sysfs hooks */
> > > > +	ret = sysfs_create_group(&client->dev.kobj, &ina209_group);
> > > > +	if (ret)
> > > > +		goto out_restore_conf;
> > > > +
> > > > +	data->hwmon_dev = hwmon_device_register(&client->dev);
> > > > +	if (IS_ERR(data->hwmon_dev)) {
> > > > +		ret = PTR_ERR(data->hwmon_dev);
> > > > +		goto out_hwmon_device_register;
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +
> > > > +out_hwmon_device_register:
> > > > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > > > +out_restore_conf:
> > > > +	ina209_restore_conf(client, data);
> > > > +	return ret;
> > > > +}
> > > > +
> > > > +static int ina209_remove(struct i2c_client *client)
> > > > +{
> > > > +	struct ina209_data *data = i2c_get_clientdata(client);
> > > > +
> > > > +	hwmon_device_unregister(data->hwmon_dev);
> > > > +	sysfs_remove_group(&client->dev.kobj, &ina209_group);
> > > > +	ina209_restore_conf(client, data);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static const struct i2c_device_id ina209_id[] = {
> > > > +	{ "ina209", 0 },
> > > > +	{ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(i2c, ina209_id);
> > > > +
> > > > +/* This is the driver that will be inserted */
> > > > +static struct i2c_driver ina209_driver = {
> > > > +	.class		= I2C_CLASS_HWMON,
> > > > +	.driver = {
> > > > +		.name	= "ina209",
> > > > +	},
> > > > +	.probe		= ina209_probe,
> > > > +	.remove		= ina209_remove,
> > > > +	.id_table	= ina209_id,
> > > > +};
> > > > +
> > > > +module_i2c_driver(ina209_driver);
> > > > +
> > > > +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>, Guenter Roeck <linux@roeck-us.net>");
> > > > +MODULE_DESCRIPTION("INA209 driver");
> > > > +MODULE_LICENSE("GPL");
> > > > -- 
> > > > 1.7.9.7
> > > > 
> > > 
> 
> _______________________________________________
> lm-sensors mailing list
> lm-sensors@lm-sensors.org
> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

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

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

* Re: [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209
  2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
                   ` (11 preceding siblings ...)
  (?)
@ 2013-01-23  4:30 ` Guenter Roeck
  -1 siblings, 0 replies; 37+ messages in thread
From: Guenter Roeck @ 2013-01-23  4:30 UTC (permalink / raw)
  To: lm-sensors

On Tue, Jan 22, 2013 at 02:15:30PM -0800, Ira W. Snyder wrote:
> On Tue, Jan 22, 2013 at 01:43:48PM -0800, Ira W. Snyder wrote:
> > On Mon, Jan 14, 2013 at 10:04:28PM -0800, Guenter Roeck wrote:
> > > On Mon, Jan 14, 2013 at 01:17:04PM -0800, Ira W. Snyder wrote:
> > > > On Sat, Jan 12, 2013 at 04:16:04PM -0800, Guenter Roeck wrote:
> > > > > Add support for the TI / Burr-Brown INA209 voltage / current / power
> > > > > monitor.
> > > > > 
> > > > > Cc: Paul Hays <haysp@magma.net>
> > > > > Cc: Ira W. Snyder <iws@ovro.caltech.edu>
> > > > > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> > > > 
> > > > Hi Guenter,
> > > > 
> > > > I didn't notice any issues with a quick review. Thanks for pushing the
> > > > driver to mainline.
> > > > 
> > > Hi Ira,
> > > 
> > > you are welcome. I actually had the code in my queue for a long time;
> > > I just found some time last weekend to finally test it.
> > > 
> > > It would be great if you can do some testing and send me a Tested-by and/or
> > > Acked-by feedback.
> > > 
> > 
> > Hi Guenter,
> > 
> > I finally got around to testing this today. The driver works great.
> > Please add my:
> > 
> > Tested-by: Ira W. Snyder <iws@ovro.caltech.edu>
> > 
> 
> One minor suggestion below. Sorry that I didn't catch it earlier.
> 
Hi Ira,

I just sent a new version of the driver, with added devicetree support as you
suggested. Would be great if you can test it again.

Thanks,
Guenter

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

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

end of thread, other threads:[~2013-01-23  4:30 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-08-31 20:24 [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 tomaz.mertelj
2009-08-31 20:24 ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
2009-09-01 17:56 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Andre Prendel
2009-09-01 17:56   ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andre Prendel
2009-09-02  8:20   ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Tomaz Mertelj
2009-09-02  8:20     ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Tomaz Mertelj
2009-09-02  8:45 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments corentin.labbe
2009-09-02  8:45   ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip corentin.labbe
2009-09-02 23:55 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton
2009-09-02 23:55   ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
2009-09-05 12:08 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments tomaz.mertelj
2009-09-05 12:08   ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
2009-09-09  0:06   ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton
2009-09-09  0:06     ` [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
2009-09-09  7:34     ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Jean Delvare
2009-09-09  7:34       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Jean Delvare
2009-09-09  8:06       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton
2009-09-09  8:06         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
2009-09-09 12:24       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Tomaz Mertelj
2009-09-09 12:24         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Tomaz Mertelj
2009-09-09 12:45         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Jean Delvare
2009-09-09 12:45           ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Jean Delvare
2009-09-21 21:44       ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton
2009-09-21 21:44         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
2009-09-22  5:59         ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Tomaz Mertelj
2009-09-22  5:59           ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Tomaz Mertelj
2009-09-22  6:02           ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton
2009-09-22  6:02             ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip Andrew Morton
2013-01-13  0:16 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments INA209 Guenter Roeck
2013-01-14 21:17 ` Ira W. Snyder
2013-01-15  6:04 ` Guenter Roeck
2013-01-15 22:10 ` Paul Hays
2013-01-15 22:56 ` Guenter Roeck
2013-01-22 21:43 ` Ira W. Snyder
2013-01-22 22:15 ` Ira W. Snyder
2013-01-23  4:30 ` Guenter Roeck
  -- strict thread matches above, loose matches on Subject: below --
2009-09-23  9:32 [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments amc6821 chip tomaz.mertelj
2009-09-30 19:44 ` [lm-sensors] [PATCH] hwmon: Driver for Texas Instruments Andrew Morton

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.