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 <[EMAIL PROTECTED]> -- 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 <[EMAIL PROTECTED]> + + +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 <[EMAIL PROTECTED]> + * + * 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 <[EMAIL PROTECTED]>"); +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 [email protected] http://lists.lm-sensors.org/mailman/listinfo/i2c
