[RFC][PATCH] hwmon: Analog Devices AD7991/5/9 driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Adding support for the Analog Devices AD7991/5/9 IC's.

Signed-off-by: Sigurd M. Andreassen <sigurdan at stud.ntnu.no>
---
  drivers/hwmon/Kconfig       |   10 +
  drivers/hwmon/Makefile      |    1 +
  drivers/hwmon/ad799x.c      |  461  
+++++++++++++++++++++++++++++++++++++++++++
  3 files changed, 472 insertions(+), 0 deletions(-)
  create mode 100644 drivers/hwmon/ad799x.c

I'm not sure what to do about the configuration bits, export them  
directly to user space? What about the detect function, is it  
acceptable?

Sigurd.

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index d73f5f4..1c52784 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -77,6 +77,16 @@ config SENSORS_AD7418
  	  This driver can also be built as a module. If so, the module
  	  will be called ad7418.

+config SENSORS_AD799X
+	tristate "Analog Devices AD7991, AD7995 and AD7999"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Analog Devices
+	  AD7991, AD7995 and AD7999 4 channel ADC.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called ad799x.
+
  config SENSORS_ADCXX
  	tristate "National Semiconductor ADCxxxSxxx"
  	depends on SPI_MASTER && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0ae2698..2848f30 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
  obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
  obj-$(CONFIG_SENSORS_AD7414)	+= ad7414.o
  obj-$(CONFIG_SENSORS_AD7418)	+= ad7418.o
+obj-$(CONFIG_SENSORS_AD799X)    += ad799x.o
  obj-$(CONFIG_SENSORS_ADCXX)	+= adcxx.o
  obj-$(CONFIG_SENSORS_ADM1021)	+= adm1021.o
  obj-$(CONFIG_SENSORS_ADM1025)	+= adm1025.o
diff --git a/drivers/hwmon/ad799x.c b/drivers/hwmon/ad799x.c
new file mode 100644
index 0000000..9f38a1d
--- /dev/null
+++ b/drivers/hwmon/ad799x.c
@@ -0,0 +1,461 @@
+/*
+ * ad799x.c - Linux kernel module for hardware monitoring
+ *
+ * Copyright (C) 2009  Sigurd M. Andreassen <sigurdan at stud.ntnu.no>
+ *
+ * Based on the lm90 driver. This driver was written for the ad7991
+ * IC by Analog devices, but also supports ad7995 and ad7999. This
+ * series of adc's has a 4 channel multiplexed input where one
+ * input channel, number 3, can be configured as voltage reference.
+ * Default voltage reference is the power pin Vcc.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>		/*	kzalloc() 		*/
+#include <linux/sysfs.h>	/*	sysfs_create_group() 	*/
+#include <linux/sched.h>	/*	jiffies			*/
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+
+/* Configuration register bits */
+#define AD799X_CON_CH3 		7
+#define AD799X_CON_CH2		6
+#define AD799X_CON_CH1 		5
+#define AD799X_CON_CH0 		4
+#define AD799X_CON_REF_SEL 	3
+#define AD799X_CON_FLTR 	2
+#define AD799X_CON_BIT_TR_DL 	1
+#define AD799X_CON_SMP_DL	0
+
+/* 	Addresses to scan -> joins with I2C_CLIENT macro 	*/
+static const unsigned short normal_i2c[] = { 0x28, 0x29, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_3(ad7991, ad7995, ad7999);
+
+/* 	Each client has this additional data 	*/
+struct ad799x_data {
+	struct device *hwmon_dev;
+	int kind;			/* enum defined by INSMOD_3 macro */
+	u32 reference;			/* [mV]				*/
+	struct mutex lock;
+};
+
+/*	sysfs hook function
+ *
+ *	Returns bytes written to buf on success, -EINVAL if input value
+ *	is invalid, -ERESTARTSYS on problems locking mutex and return value
+ *	from smbus routine if call fails.
+ *
+ */
+static ssize_t ad799x_read(struct device *dev, struct  
device_attribute *devattr,
+								char *buf)
+{
+
+	s32 ret;
+	u16 max_value;
+	u8 config_reg;
+	u8 return_values[2];
+	struct ad799x_data *ad799x;
+
+	struct attribute ad799x_attr = devattr->attr;
+
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/*	Switching by filename number, in<number>_input	*/
+	switch (ad799x_attr.name[2]) {
+	case '0' :
+		config_reg = (1 << AD799X_CON_CH0);
+		break;
+	case '1' :
+		config_reg = (1 << AD799X_CON_CH1);
+		break;
+	case '2' :
+		config_reg = (1 << AD799X_CON_CH2);
+		break;
+	case '3' 	:
+		config_reg = (1 << AD799X_CON_CH3);
+		break;
+	default :
+		return -EINVAL;
+     	}
+
+	/*	Writing ADC sample channel according to attribut	*/
+	ret = i2c_smbus_read_i2c_block_data(client, config_reg, 2,
+							&return_values[0]);
+
+	if (ret != 2) {
+		dev_dbg(&client->dev, "ad799x: Problems reading from adc"
+						" @ 0x%x\n", client->addr);
+		return ret;
+	}
+
+	ret = (return_values[0] << 8) | return_values[1];
+
+	/*	Removing channel ID bits	*/
+	ret &= 0x0FFF;
+
+	ad799x = i2c_get_clientdata(client);
+
+	/*	Lock mutex and fetch reference value	*/
+	if (mutex_lock_interruptible(&ad799x->lock))
+		return -ERESTARTSYS;
+
+	if (ad799x->kind == ad7995) {
+		/*	10-bit	*/
+		ret = (ret >> 2);
+		max_value = 1023;
+	}
+	else if (ad799x->kind == ad7999) {
+		/*	8-bit	*/
+		ret = (ret >> 4);
+		max_value = 255;
+	}
+	else if (ad799x->kind == ad7991) {
+		/*	12-bit	*/
+		max_value = 4095;
+	}
+	else {
+		return -EINVAL;
+	}
+
+	ret = ret * ad799x->reference;
+
+	mutex_unlock(&ad799x->lock);
+
+	ret = ret / max_value;
+
+	return sprintf(buf, "%d\n", ret);
+}
+
+/*
+ *	Returns bytes written to buffer on success, -EINVAL if input value
+ *	is invalid and -ERESTARTSYS on problems locking mutex.
+ *
+ */
+static ssize_t ad799x_show(struct device *dev, struct device_attribute *attr,
+								char *buf)
+{
+
+	struct i2c_client *client;
+	struct ad799x_data *ad799x;
+
+	u32 temp_value = 0;
+
+	u8 nr = to_sensor_dev_attr(attr)->index;
+
+	switch (nr) {
+	case 0 :
+		/*	No other then ground level	*/
+		temp_value = 0;
+		break;
+	case 1 :
+		/*	Fetch client data struct	*/
+		client = to_i2c_client(dev);
+		ad799x = i2c_get_clientdata(client);
+
+		/*	Lock mutex and fetch reference value	*/
+		if (mutex_lock_interruptible(&ad799x->lock))
+			return -ERESTARTSYS;
+
+		temp_value = ad799x->reference;
+
+		mutex_unlock(&ad799x->lock);
+		break;
+	default :
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%d\n", temp_value);
+}
+
+/*
+ *	Returns bytes written on success, -EINVAL if input value
+ *	is invalid and -ERESTARTSYS on problems locking mutex.
+ *
+ */
+static ssize_t ad799x_set(struct device *dev, struct device_attribute  
*devattr,
+			const char *buf, size_t count)
+{
+
+	unsigned long new_ref;
+
+	struct i2c_client *client = to_i2c_client(dev);
+	struct ad799x_data *ad799x = i2c_get_clientdata(client);
+
+	/*	Fetch incoming value and check it for validity	*/
+	if (strict_strtoul(buf, 10, &new_ref))
+		return -EINVAL;
+
+	if (new_ref > 5500 || new_ref < 2700)
+		return -EINVAL;
+
+	/*	Lock data struct and set new input reference value	*/
+	if (mutex_lock_interruptible(&ad799x->lock))
+		return -ERESTARTSYS;
+
+	ad799x->reference = new_ref;
+
+	mutex_unlock(&ad799x->lock);
+
+	return count;
+}
+
+/*	Tighing attribute names to functions	*/
+static DEVICE_ATTR(in0_input, S_IRUGO, ad799x_read, NULL);
+static DEVICE_ATTR(in1_input, S_IRUGO, ad799x_read, NULL);
+static DEVICE_ATTR(in2_input, S_IRUGO, ad799x_read, NULL);
+static DEVICE_ATTR(in3_input, S_IRUGO, ad799x_read, NULL);
+static SENSOR_DEVICE_ATTR(in_min, S_IRUGO, ad799x_show, NULL, 0);
+static SENSOR_DEVICE_ATTR(in_max, S_IWUSR | S_IRUGO, ad799x_show,
+								ad799x_set, 1);
+
+
+/*	Sysfs attributes	*/
+static struct attribute *ad799x_attributes[] = {
+	&dev_attr_in0_input.attr,
+	&dev_attr_in1_input.attr,
+	&dev_attr_in2_input.attr,
+	&dev_attr_in3_input.attr,
+	&sensor_dev_attr_in_min.dev_attr.attr,
+	&sensor_dev_attr_in_max.dev_attr.attr,
+	NULL
+};
+
+/*	Attribute group	*/
+static const struct attribute_group ad799x_attr_group = {
+	.attrs = ad799x_attributes,
+};
+
+
+/* Return 0 if detection is successful, -ENODEV otherwise
+ *
+ *
+ *
+ */
+static int ad799x_detect(struct i2c_client *new_client, int kind,
+						struct i2c_board_info *info)
+{
+
+	s32 ret;
+	u8 return_values[2];
+	u8 id_flags[4] = {0x00, 0x10, 0x20, 0x30};
+	u8 i, mask = = (1 << AD799X_CON_CH0);
+	const char *type_name;
+
+	struct i2c_adapter *adapter = new_client->adapter;
+
+	/*	We only need block functionality	*/
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BLOCK_DATA))
+		return -ENODEV;
+
+	/*	If not forced	*/
+	if (kind < 0) {
+
+		/*	Channel sequence: 0, 1, 2, 3	*/
+		for (i = 0; i < 4; i++) {
+
+			ret = i2c_smbus_read_i2c_block_data(new_client, mask, 2,
+							&return_values[0]);
+
+			if ((return_values[0] & 0xF0) != id_flags[i])
+				return -ENODEV;
+
+			/*	Compensate for new config mask	*/
+			mask <<= 1;
+		}
+
+		kind = ad7999;
+
+		/*	Looping, trying to disproof statment above	*/
+		for (i = 0; i < 50; i++) {
+
+			ret = i2c_smbus_read_i2c_block_data(new_client, mask, 2,
+							 &return_values[0]);
+
+			if (return_values[1] & 0x0F) {
+				if (return_values[1] & 0x03) {
+					kind = ad7991;
+					break;
+				} else {
+					kind = ad7995;
+				}
+			}
+			/*	Switching adc input channel	*/
+			if (mask == 0x80)
+				mask = (1 << AD799X_CON_CH0);
+			else
+				mask <<= 1;
+		}
+
+		/*	No ad7999 @ 0x28	*/
+		if (new_client->addr == 0x28 && kind == ad7999)
+			return -ENODEV;
+
+		/*	The rest is done as if with force parameter	*/
+	}
+
+	/*	If force parameter 	*/
+	switch (kind) {
+	case ad7991 :
+		type_name = "ad7991";
+		break;
+	case ad7995 :
+		type_name = "ad7995";
+		break;
+	case ad7999 :
+		type_name = "ad7999";
+		break;
+	default :
+		return -ENODEV;
+	}
+
+	pr_info("ad799x: Found Analog Devices %s chip\n", type_name);
+
+	strlcpy(info->type, type_name, I2C_NAME_SIZE);
+
+        return 0;
+}
+
+/*
+ *	Returns 0 on success and error code on failure.
+ *
+ *
+ */
+static int ad799x_probe(struct i2c_client *client,
+						const struct i2c_device_id *id)
+{
+
+	int err = 0;
+	struct ad799x_data *ad799x;
+
+
+	/*	Allocating memory to hold device/client specific values	*/
+	if (!(ad799x = kzalloc(sizeof(struct ad799x_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	/*	Init dev data mutex	*/
+	mutex_init(&ad799x->lock);
+
+	ad799x->hwmon_dev = hwmon_device_register(&client->dev);
+
+	if (ad799x->hwmon_dev == NULL) {
+		err = PTR_ERR(ad799x->hwmon_dev);
+		goto hwmon_reg_failed;
+	}
+
+	/*	Tighing dev data to client struct	*/
+	dev_set_drvdata(&client->dev, ad799x);
+
+	/*	Register sysfs hook	*/
+	if ((err = sysfs_create_group(&client->dev.kobj, &ad799x_attr_group)))
+		goto sysfs_create_group_failed;
+
+	/*	Default reference voltage 3300 mV	*/
+	if (mutex_lock_interruptible(&ad799x->lock))
+		return -ERESTARTSYS;
+
+	/*	Setting chip kind from ad799x_idtable passed by id struct */
+	ad799x->kind = id->driver_data;
+
+	ad799x->reference = 3300;
+
+	mutex_unlock(&ad799x->lock);
+
+	return 0;
+
+sysfs_create_group_failed:
+	hwmon_device_unregister(ad799x->hwmon_dev);
+hwmon_reg_failed:
+	dev_set_drvdata(&client->dev, NULL);
+	kfree(ad799x);
+exit:
+	return err;
+
+}
+
+
+/*	Removing different device registrations	(buttom of probe func.)	*/
+static int __devexit ad799x_remove(struct i2c_client *client)
+{
+
+	struct ad799x_data *ad799x = i2c_get_clientdata(client);
+
+	sysfs_remove_group(&client->dev.kobj, &ad799x_attr_group);
+
+	hwmon_device_unregister(ad799x->hwmon_dev);
+
+	dev_set_drvdata(&client->dev, NULL);
+	kfree(ad799x);
+
+	return 0;
+
+}
+
+/*	All supported devices	*/
+static struct i2c_device_id ad799x_idtable[] = {
+	{ "ad7991", ad7991 },
+	{ "ad7995", ad7995 },
+	{ "ad7999", ad7999 },
+	{ }
+};
+
+/*	Tighs id_table to device 	*/
+MODULE_DEVICE_TABLE(i2c, ad799x_idtable);
+
+
+static struct i2c_driver ad799x_driver = {
+
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.owner  = THIS_MODULE,
+		.name	= "ad799x",
+	},
+
+	.probe		= ad799x_probe,
+	.remove		= __devexit_p(ad799x_remove),
+	.id_table	= ad799x_idtable,
+
+	.address_data	= &addr_data,
+	.detect		= ad799x_detect,
+
+};
+
+
+static int __init ad799x_init(void)
+{
+	return i2c_add_driver(&ad799x_driver);
+}
+
+static void __exit ad799x_cleanup(void)
+{
+	i2c_del_driver(&ad799x_driver);
+}
+
+MODULE_AUTHOR("Sigurd Myhre Andreassen <sigurdan at stud.ntnu.no>");
+MODULE_DESCRIPTION("Driver for the Norwegian Defence Research"
+		"Establishment (FFI). Analog Devices AD7991/5/9 driver");
+MODULE_LICENSE("GPL");
+
+module_init(ad799x_init);
+module_exit(ad799x_cleanup);
+




[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux