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