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@xxxxxxxxx; please include > Paul Hays <Paul.Hays@xxxxxxxxxx> 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@xxxxxxxxx> > >>Cc: Ira W. Snyder <iws@xxxxxxxxxxxxxxxx> > >>Signed-off-by: Guenter Roeck <linux@xxxxxxxxxxxx> > > > >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@xxxxxxxxx> > >>+Author: Ira W. Snyder <iws@xxxxxxxxxxxxxxxx> > >>+Author: Guenter Roeck <linux@xxxxxxxxxxxx> > >>+ > >>+ > >>+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@xxxxxxxxxxxx> > >>+ * > >>+ * Derived from Ira W. Snyder's original driver submission > >>+ * Copyright (C) 2008 Paul Hays <haysp@xxxxxxxx> > >>+ * Copyright (C) 2008-2009 Ira W. Snyder <iws@xxxxxxxxxxxxxxxx> > >>+ * > >>+ * Aligned with ina2xx driver > >>+ * Copyright (C) 2012 Lothar Felten <l-felten@xxxxxx> > >>+ * 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=10 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=20 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@xxxxxxxxxxxxxxxx>, Guenter Roeck <linux@xxxxxxxxxxxx>"); > >>+MODULE_DESCRIPTION("INA209 driver"); > >>+MODULE_LICENSE("GPL"); > >>-- > >>1.7.9.7 > >> > > > > > _______________________________________________ lm-sensors mailing list lm-sensors@xxxxxxxxxxxxxx http://lists.lm-sensors.org/mailman/listinfo/lm-sensors