public inbox for linux-i2c@vger.kernel.org
 help / color / mirror / Atom feed
From: Paul Hays <haysp-g2xSkUqgFfVeoWH0uzbU5w@public.gmane.org>
To: i2c-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org
Subject: [PATCH 1/1] Add i2c chip driver for INA209 power monitor
Date: Sun, 10 Feb 2008 00:35:49 -0500	[thread overview]
Message-ID: <1202621749.19303.17.camel@amd2> (raw)

For your consideration, this adds Linux support for the
Texas Instruments / Burr-Brown INA209 power monitoring chip
with i2c interface. (As far as I know, this isn't used like
a sensor on any mainboard; I use it in a solar power system.)

The driver works in kernel 2.6.20-voyage (http://linux.voyage.hk)
on a WRAP sbc from PC Engines (http://www.pcengines.ch).

Kernel 2.6.24 also built without incident after this patch.

Signed-off-by: Paul Hays <haysp-J4oS66wZXds@public.gmane.org>
--

diff -Naur -X b/Documentation/dontdiff a/Documentation/i2c/chips/ina209 b/Documentation/i2c/chips/ina209
--- a/Documentation/i2c/chips/ina209	1969-12-31 19:00:00.000000000 -0500
+++ b/Documentation/i2c/chips/ina209	2008-02-06 14:59:48.000000000 -0500
@@ -0,0 +1,70 @@
+Kernel driver ina209
+=====================
+
+Supported chips:
+  * Burr-Brown / Texas Instruments INA209
+    Prefix: 'ina209'
+    Addresses scanned: 0x40-0x4f
+    Datasheet:
+        http://www.ti.com/lit/gpn/ina209
+
+Author: Paul Hays <haysp-g2xSkUqgFfVeoWH0uzbU5w@public.gmane.org>
+
+
+Description
+-----------
+
+The Burr-Brown INA209 monitors voltage and current 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.
+
+The driver simply exposes the registers of an INA209 via sysfs as
+decimal integers. Some registers (marked "bits" here) contain various
+bit fields, and others use configurable scaling.  (I found it convenient
+to process all of the values in userspace with friendly Perl scripts.)
+
+configuration				bits
+status			read only,	bits
+status_mask				bits
+shunt_v			read only
+bus_v			read only
+power			read only
+bus_current		read only
+shunt_v_pos_peak
+shunt_v_neg_peak
+bus_v_max_peak
+bus_v_min_peak
+power_peak
+shunt_v_pos_warn
+shunt_v_neg_warn
+power_warn
+bus_over_v_warn
+bus_under_v_warn
+power_over_limit
+bus_over_v_over_limit
+bus_under_v_over_limit
+critical_dac_pos
+critical_dac_neg
+calibration
+
+
+General Remarks
+---------------
+
+The layouts of values in some registers surprised me; read
+that datasheet very closely.
+
+Application information in the datasheet shows 10 Ohm resistors
+in series with the sensing inputs to filter noise and for
+transient protection. Experience shows that they also degrade
+accuracy.
+
diff -Naur -X b/Documentation/dontdiff a/drivers/i2c/chips/ina209.c b/drivers/i2c/chips/ina209.c
--- a/drivers/i2c/chips/ina209.c	1969-12-31 19:00:00.000000000 -0500
+++ b/drivers/i2c/chips/ina209.c	2008-02-06 13:16:34.000000000 -0500
@@ -0,0 +1,334 @@
+/*
+ * ina209.c - chip driver for bi-directional power monitor
+ *
+ * Copyright (C) 2008 Paul Hays <haysp-J4oS66wZXds@public.gmane.org>
+ *
+ * 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.
+ *
+ * The INA209 chip transfers 16-bit word data as two successive bytes
+ * on the i2c bus. Transfers go most-significant byte (msb) first,
+ * so writes involve cpu_to_be16() etc.
+ *
+ * This borrows from a similar driver written by Ben Gardner.
+ */
+
+#include <asm/byteorder.h>	/* cpu-to-be16() etc. */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon-sysfs.h>
+
+/* Addresses to probe. The chip can be hardwired to use any of these
+ * addresses using pins A0 and A1. (E.g. connecting both to the SDA pin
+ * selects address 0x4a).
+ */
+static unsigned short normal_i2c[] = {
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+	I2C_CLIENT_END};
+
+/* Insmod parameters */
+I2C_CLIENT_INSMOD_1(ina209);
+
+/* Here are names of the chip's registers (a.k.a. commands) */
+enum ina209_cmd
+{
+	INA209_CONFIGURATION		= 0x00, /*		bits */
+	INA209_STATUS			= 0x01,	/* readonly	bits */
+	INA209_STATUS_MASK		= 0x02,	/*		bits */
+	INA209_SHUNT_V			= 0x03,	/* readonly */
+	INA209_BUS_V			= 0x04,	/* readonly */
+	INA209_POWER			= 0x05,	/* readonly */
+	INA209_BUS_CURRENT		= 0x06,	/* readonly */
+	INA209_SHUNT_V_POS_PEAK		= 0x07,
+	INA209_SHUNT_V_NEG_PEAK		= 0x08,
+	INA209_BUS_V_MAX_PEAK		= 0x09,
+	INA209_BUS_V_MIN_PEAK		= 0x0a,
+	INA209_POWER_PEAK		= 0x0b,
+	INA209_SHUNT_V_POS_WARN		= 0x0c,
+	INA209_SHUNT_V_NEG_WARN		= 0x0d,
+	INA209_POWER_WARN		= 0x0e,
+	INA209_BUS_OVER_V_WARN		= 0x0f,
+	INA209_BUS_UNDER_V_WARN		= 0x10,
+	INA209_POWER_OVER_LIMIT		= 0x11,
+	INA209_BUS_OVER_V_OVER_LIMIT	= 0x12,
+	INA209_BUS_UNDER_V_OVER_LIMIT	= 0x13,
+	INA209_CRITICAL_DAC_POS		= 0x14,
+	INA209_CRITICAL_DAC_NEG		= 0x15,
+	INA209_CALIBRATION		= 0x16,
+};
+
+static int ina209_attach_adapter(struct i2c_adapter *adapter);
+static int ina209_detect(struct i2c_adapter *adapter, int address, int kind);
+static int ina209_detach_client(struct i2c_client *client);
+
+/* This data structure describes the chip driver */
+static struct i2c_driver ina209_driver = {
+	.driver = {
+		.name	= "ina209",
+	},
+	.attach_adapter	= ina209_attach_adapter,
+	.detach_client	= ina209_detach_client,
+};
+
+struct ina209_data {
+	struct i2c_client client;
+};
+
+/* Functions to handle calls to the sysfs device files for the registers */
+
+/* Commands to show values always render the result as a decimal integer.
+ * That is sort of nasty in cases of bit-field registers like "status";
+ * one could add access functions for each bit-field.
+ * (Might be expensive... we'll just handle this with e.g. Perl in userspace.)
+ */
+static ssize_t ina209_show(struct device *dev, struct device_attribute *attr,
+	char *buf)
+{
+	s32 val;
+	struct sensor_device_attribute *psa = to_sensor_dev_attr(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+
+	val = i2c_smbus_read_word_data(client, psa->index);
+	if (val < 0 ) {
+		dev_dbg(dev, "failed to read register 0x%2.2x\n", psa->index);
+		memset(buf, 0, PAGE_SIZE); /* user should not see old data */
+		return (0);
+	}
+	return ((ssize_t) snprintf(buf, PAGE_SIZE, "%hd\n",
+			(short int) be16_to_cpu((u16) val)));
+}
+
+/* Commands to store values accept integers as strings per
+ * function simple_strtol():
+ * strings beginning with 0x or 0X are base 16,
+ * strings beginning with 0 and any digit are base 8
+ * other strings beginning with '-' are negative, and
+ * others are positive
+ */
+static ssize_t ina209_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	u16 val;
+	struct sensor_device_attribute *psa = to_sensor_dev_attr(attr);
+	struct i2c_client *client = to_i2c_client(dev);
+
+	val = (u16) simple_strtol(buf, 0, 0);
+	if (i2c_smbus_write_word_data(client, psa->index, cpu_to_be16(val))
+			 < 0 ) {
+		dev_err(&client->dev, "failed to store \"%.10s\" "
+				"at register 0x%2.2x\n",
+				buf, psa->index);
+		return (0);
+	}
+	return ((ssize_t) count);
+}
+
+/* These macros are used below in constructing device attribute objects
+ * for use with sysfs_create_group() to make a sysfs device file
+ * for each register.
+ */
+
+#define INA209_ENTRY_RO(name, ina209_cmd_idx) \
+	static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
+	ina209_show, 0, ina209_cmd_idx)
+
+#define INA209_ENTRY_RW(name, ina209_cmd_idx) \
+	static SENSOR_DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \
+	ina209_show, ina209_store, ina209_cmd_idx)
+
+/* Construct a sensor_device_attribute structure for each register */
+INA209_ENTRY_RW(configuration,		INA209_CONFIGURATION);
+INA209_ENTRY_RO(status,			INA209_STATUS);
+INA209_ENTRY_RW(status_mask,		INA209_STATUS_MASK);
+INA209_ENTRY_RO(shunt_v,		INA209_SHUNT_V);
+INA209_ENTRY_RO(bus_v,			INA209_BUS_V);
+INA209_ENTRY_RO(power,			INA209_POWER);
+INA209_ENTRY_RO(bus_current,		INA209_BUS_CURRENT);
+INA209_ENTRY_RW(shunt_v_pos_peak,	INA209_SHUNT_V_POS_PEAK);
+INA209_ENTRY_RW(shunt_v_neg_peak,	INA209_SHUNT_V_NEG_PEAK);
+INA209_ENTRY_RW(bus_v_max_peak,		INA209_BUS_V_MAX_PEAK);
+INA209_ENTRY_RW(bus_v_min_peak,		INA209_BUS_V_MIN_PEAK);
+INA209_ENTRY_RW(power_peak,		INA209_POWER_PEAK);
+INA209_ENTRY_RW(shunt_v_pos_warn,	INA209_SHUNT_V_POS_WARN);
+INA209_ENTRY_RW(shunt_v_neg_warn,	INA209_SHUNT_V_NEG_WARN);
+INA209_ENTRY_RW(power_warn,		INA209_POWER_WARN);
+INA209_ENTRY_RW(bus_over_v_warn,	INA209_BUS_OVER_V_WARN);
+INA209_ENTRY_RW(bus_under_v_warn,	INA209_BUS_UNDER_V_WARN);
+INA209_ENTRY_RW(power_over_limit,	INA209_POWER_OVER_LIMIT);
+INA209_ENTRY_RW(bus_over_v_over_limit,	INA209_BUS_OVER_V_OVER_LIMIT);
+INA209_ENTRY_RW(bus_under_v_over_limit,	INA209_BUS_UNDER_V_OVER_LIMIT);
+INA209_ENTRY_RW(critical_dac_pos,	INA209_CRITICAL_DAC_POS);
+INA209_ENTRY_RW(critical_dac_neg,	INA209_CRITICAL_DAC_NEG);
+INA209_ENTRY_RW(calibration,		INA209_CALIBRATION);
+
+/* 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_configuration.dev_attr.attr,
+	&sensor_dev_attr_status.dev_attr.attr,
+	&sensor_dev_attr_status_mask.dev_attr.attr,
+	&sensor_dev_attr_shunt_v.dev_attr.attr,
+	&sensor_dev_attr_bus_v.dev_attr.attr,
+	&sensor_dev_attr_power.dev_attr.attr,
+	&sensor_dev_attr_bus_current.dev_attr.attr,
+	&sensor_dev_attr_shunt_v_pos_peak.dev_attr.attr,
+	&sensor_dev_attr_shunt_v_neg_peak.dev_attr.attr,
+	&sensor_dev_attr_bus_v_max_peak.dev_attr.attr,
+	&sensor_dev_attr_bus_v_min_peak.dev_attr.attr,
+	&sensor_dev_attr_power_peak.dev_attr.attr,
+	&sensor_dev_attr_shunt_v_pos_warn.dev_attr.attr,
+	&sensor_dev_attr_shunt_v_neg_warn.dev_attr.attr,
+	&sensor_dev_attr_power_warn.dev_attr.attr,
+	&sensor_dev_attr_bus_over_v_warn.dev_attr.attr,
+	&sensor_dev_attr_bus_under_v_warn.dev_attr.attr,
+	&sensor_dev_attr_power_over_limit.dev_attr.attr,
+	&sensor_dev_attr_bus_over_v_over_limit.dev_attr.attr,
+	&sensor_dev_attr_bus_under_v_over_limit.dev_attr.attr,
+	&sensor_dev_attr_critical_dac_pos.dev_attr.attr,
+	&sensor_dev_attr_critical_dac_neg.dev_attr.attr,
+	&sensor_dev_attr_calibration.dev_attr.attr,
+	0
+};
+
+static struct attribute_group ina209_defattr_group = {
+	.attrs = ina209_attributes,
+};
+
+static int ina209_attach_adapter(struct i2c_adapter *adapter)
+{
+	return i2c_probe(adapter, &addr_data, ina209_detect);
+}
+
+/* This function is called by i2c_probe when some chip responds to an
+ * as-yet-unused slave address listed in normal_i2c or
+ * when a 'force' parameter is used e.g. with modprobe.
+ * Param kind has -1 for probed detection, >=0 for forced detection.
+ */
+static int ina209_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct ina209_data *data;
+	struct i2c_client *new_client;
+	int err = 0;
+	char *step ;		/* debug info */
+
+	/* Verify that the adapter can read and write 16-bit words */
+	step = "i2c_check_functionality";
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+		goto exit;
+
+	/* OK. For now, we presume we have a valid client. We now create the
+	 * client structure, even though we cannot fill it completely yet.
+	 */
+	if (0 == (data = kzalloc(sizeof *data, GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	new_client = &data->client;
+	i2c_set_clientdata(new_client, data);
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &ina209_driver;
+	new_client->flags = 0;
+
+	if (kind < 0) {		/* probed detection - check the chip type */
+		s32 v;		/* 16 bits from the chip, or -1 for error */
+
+		/* Chip registers 0x00-0x16 are documented. The vendor's
+		 * program INA209EVM.exe hints that register 0x17 is "unused"
+		 * and that 0x18-0x1b provide internal status data.
+		 * i2cdump shows that 0x17 returns 0 and that the chip
+		 * ignores register address bits 0x20 and higher (e.g.
+		 * registers 0x00, 0x20, 0x40 etc through 0xe0 give
+		 * identical values).
+		 */
+		step = "probe1";	/* read "unused" register, expect 0 */
+		if (i2c_smbus_read_word_data(new_client, 0x17) != 0)
+			goto exit_kfree;
+
+		step = "probe2";	/* expect 0 bits in configuration */
+		v = i2c_smbus_read_word_data(new_client, INA209_CONFIGURATION);
+		if (v < 0 || 0 != (be16_to_cpu((u16)v) & 0xc000))
+			goto exit_kfree;
+
+		step = "probe3";	/* chip must ignore address bit 0x20 */
+		if (i2c_smbus_read_word_data(new_client,
+				0x20 | INA209_CONFIGURATION) != v)
+			goto exit_kfree;
+
+		step = "probe4";	/* expect 0 bits in status */
+		v = i2c_smbus_read_word_data(new_client, INA209_STATUS);
+		if ( v < 0 || 0 != (be16_to_cpu((u16)v) & 0x0007))
+			goto exit_kfree;
+	}
+
+	strlcpy(new_client->name, "ina209", I2C_NAME_SIZE);
+
+	/* Tell the I2C layer a new client has arrived */
+	step = "i2c_attach_client";
+	if (0 != (err = i2c_attach_client(new_client)))
+		goto exit_kfree;
+
+	/* Register sysfs hooks */
+	step = "sysfs_create_group";
+	err = sysfs_create_group(&new_client->dev.kobj, &ina209_defattr_group);
+	if (0 != err)
+		goto exit_detach;
+
+	step = "success";
+	goto exit;
+
+exit_detach:
+	i2c_detach_client(new_client);
+exit_kfree:
+	kfree(data);
+exit:
+	printk(KERN_INFO "ina209: i2c bus %u detect addr 0x%02x, (%s),"
+			" returning %d: %s\n",
+			adapter->id, address, kind<0 ? "probed":"forced",
+			err, step);
+	return err;
+}
+
+static int ina209_detach_client(struct i2c_client *client)
+{
+	int err;
+
+	sysfs_remove_group(&client->dev.kobj, &ina209_defattr_group);
+
+	/* If we succeed in detaching client, free its space */
+	if (0 == (err = i2c_detach_client(client)))
+		kfree(i2c_get_clientdata(client));
+
+	printk(KERN_INFO "ina209: detach returning %d\n", err);
+	return err;
+}
+
+static int __init ina209_init(void)
+{
+	int ret = i2c_add_driver(&ina209_driver);
+	if (0 == ret)
+		printk(KERN_INFO "ina209: chip driver loaded\n");
+	else
+		printk(KERN_INFO "ina209: i2c_add_driver failure %d\n", ret);
+	return (ret);
+}
+
+static void __exit ina209_exit(void)
+{
+	i2c_del_driver(&ina209_driver);
+	printk(KERN_INFO "ina209: chip driver unloaded\n");
+}
+
+MODULE_AUTHOR("Paul Hays <haysp-J4oS66wZXds@public.gmane.org>");
+MODULE_DESCRIPTION("INA209 driver");
+MODULE_LICENSE("GPL");
+
+module_init(ina209_init);
+module_exit(ina209_exit);
+
diff -Naur -X b/Documentation/dontdiff a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
--- a/drivers/i2c/chips/Kconfig	2007-04-12 13:15:56.000000000 -0400
+++ b/drivers/i2c/chips/Kconfig	2008-02-06 14:18:09.000000000 -0500
@@ -125,4 +125,15 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called max6875.
 
+config SENSORS_INA209
+	tristate "Burr-Brown / Texas Instruments INA509 (EXPERIMENTAL)"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Burr-Brown / TI INA509
+	  chip for monitoring voltage, current, and power. Enter N unless
+          you are sure your system uses this chip (very few do use it).
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ina209.
+
 endmenu
diff -Naur -X b/Documentation/dontdiff a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
--- a/drivers/i2c/chips/Makefile	2007-04-12 13:15:56.000000000 -0400
+++ b/drivers/i2c/chips/Makefile	2008-01-09 19:08:48.000000000 -0500
@@ -10,6 +10,7 @@
 obj-$(CONFIG_SENSORS_PCA9539)	+= pca9539.o
 obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
+obj-$(CONFIG_SENSORS_INA209)	+= ina209.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
 



_______________________________________________
i2c mailing list
i2c-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org
http://lists.lm-sensors.org/mailman/listinfo/i2c

                 reply	other threads:[~2008-02-10  5:35 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1202621749.19303.17.camel@amd2 \
    --to=haysp-g2xskuqgffveowh0uzbu5w@public.gmane.org \
    --cc=i2c-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox