Re: [PATCH 1/1] iio: adc: Add driver for the TI INA3221 Triple Current/Voltage Monitor

From: Andrew F. Davis
Date: Mon Apr 11 2016 - 12:00:28 EST


On 04/10/2016 10:16 AM, Guenter Roeck wrote:
> On 04/10/2016 04:57 AM, Jonathan Cameron wrote:
>> On 08/04/16 19:19, Andrew F. Davis wrote:
>>> The INA3221 is a three-channel, high-side current and bus voltage
>>> monitor
>>> with an I2C and SMBUS compatible interface.
>>>
>>> Signed-off-by: Andrew F. Davis <afd@xxxxxx>
>> Hi Andrew,
>>
>> My immediate thought on this one is whether it would be better off in
>> hwmon.
>> I'm not convinced either way from a quick glance at the data sheet,
>> but the
>> question needs to be addressed.
>>
>
> It looks like a typical hardware monitoring device to me.
>
>> It's not exactly a clean fit for the IIO ABI either at the moment though
>> I think some elements of that could be easily sorted out.
>> The key think in my mind is to look at what is actually being measured
>> rather than assume all the external components will be as the datasheet
>> assumes them to be...
>>
>> + where do the alert lines actually go?
>>
> In a typical application they would connect to interrupts or to gpio pins.
> They could also trigger some direct action, such as turning on a fan,
> though that is unlikely nowadays. The 'critical' pin might sometimes
> trigger a system reset.
>
> Some more comments inline.
>
> Guenter
>
>> Jonathan
>>> ---
>>> .../ABI/testing/sysfs-bus-iio-adc-ina3221 | 23 ++
>>> drivers/iio/adc/Kconfig | 7 +
>>> drivers/iio/adc/Makefile | 1 +
>>> drivers/iio/adc/ina3221.c | 417
>>> +++++++++++++++++++++
>>> 4 files changed, 448 insertions(+)
>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc-ina3221
>>> create mode 100644 drivers/iio/adc/ina3221.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc-ina3221
>>> b/Documentation/ABI/testing/sysfs-bus-iio-adc-ina3221
>>> new file mode 100644
>>> index 0000000..bbe4f8c
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-ina3221
>>> @@ -0,0 +1,23 @@
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/in_voltageY_shunt_(raw|scale)
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/in_voltageY_bus_(raw|scale)
>>> +Date: April 2016
>>> +KernelVersion: 4.7
>>> +Contact: Andrew F. Davis <afd@xxxxxx>
>>> +Description:
>>> + Shunt and Bus voltage for each channel.
>> Units etc need specifying or at least a reference to the main
>> in_voltageY_raw
>> docs etc. These are really completely separate measurements be it
>> differential measurements where the + on one is the - on the other
>> (second is really a
>> unipolar measurement as it's relative to 0). I wonder if we are
>> better off supporting them as such so define this device as having the
>> channels.
>> in_voltage1-voltage0_raw (shunt voltage 1)
>> in_voltage0_raw (bus voltage 1)
>> in_voltage3-voltage2_raw (shunt voltage 2)
>> in_voltage2_raw (bus voltage 2)
>> in_voltage5-voltage4_raw (shunt voltage 3)
>> in_voltage4_raw
>>
>> That I think reflects the actual measuring options, without applying
>> assumptions on what is connected to them. Yes the datasheet says you
>> should
>> put a shunt between the first two connections and the load between the
>> second connection and the 0 volt line, but I can't see anything
>> preventing
>> this being used differently...
>
> Shunt voltage (or voltage difference) has a LSB of 40uV. Using it for
> anything else but current measurement doesn't really make much sense.
> I would report it not as voltage but as current, but that is from
> my filtered hwmon point of view, so maybe it doesn't really apply here.
>

I would need to know the shunt resistance, I could get this from the
user somehow (DT/sysfs) but I decided to report directly from the ADC
and let the reader do the math so I don't have to make any use assumptions.

>>> +
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/shunt_integration_time[_available]
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/bus_integration_time[_available]
>>> +Date: April 2016
>>> +KernelVersion: 4.7
>>> +Contact: Andrew F. Davis <afd@xxxxxx>
>>> +Description:
>>> + Shunt and Bus integration time for each channel.
>> I'd keep these tightly associated with the channels as then they become
>> standard abi elements, just for channels with extended names.
>>> +
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/in_voltageY_shunt_critical_(raw|scale)
>>> +What:
>>> /sys/bus/iio/devices/iio:deviceX/in_voltageY_shunt_warning_(raw|scale)
>>> +Date: April 2016
>>> +KernelVersion: 4.7
>>> +Contact: Andrew F. Davis <afd@xxxxxx>
>>> +Description:
>>> + Shunt voltage critical and warning trigger levels.
>> This is why I think this may really belong in hwmon.
>> The way you currently have this done is a bodge on the relevant IIO
>> interfaces.
>> If there is good reason to look at multiple equivalent event types on a
>> given channel in IIO we can look at adding that support to the core.
>> This is a lot more common in explicit hardware monitoring chips than in
>> more general ADCs.
>>
>> Also I see nothing in the driver that is actually detecting if these
>> conditions have been passed? If that is assumed to be a problem for
>> external
>> hardware then it should be clearly documented as such.
>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index af4aea7..d713c9c 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -232,6 +232,13 @@ config IMX7D_ADC
>>> This driver can also be built as a module. If so, the module
>>> will be
>>> called imx7d_adc.
>>>
>>> +config INA3221
>>> + tristate "Texas Instruments INA3221 Triple Power Monitor"
>>> + depends on I2C
>>> + select REGMAP_I2C
>>> + help
>>> + Say yes here to build support for TI INA3221 Triple Power
>>> Monitor.
>> Need more detail here really. Something about what it provides and
>> the name
>> of the module would be conventional.
>>> +
>>> config LP8788_ADC
>>> tristate "LP8788 ADC driver"
>>> depends on MFD_LP8788
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 0cb7921..eebe0c6 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -24,6 +24,7 @@ obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
>>> obj-$(CONFIG_HI8435) += hi8435.o
>>> obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o
>>> obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
>>> +obj-$(CONFIG_INA3221) += ina3221.o
>>> obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
>>> obj-$(CONFIG_MAX1027) += max1027.o
>>> obj-$(CONFIG_MAX1363) += max1363.o
>>> diff --git a/drivers/iio/adc/ina3221.c b/drivers/iio/adc/ina3221.c
>>> new file mode 100644
>>> index 0000000..e5b9df97
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/ina3221.c
>>> @@ -0,0 +1,417 @@
>>> +/*
>>> + * INA3221 Triple Current/Voltage Monitor
>>> + *
>>> + * Copyright (C) 2016 Texas Instruments Incorporated -
>>> http://www.ti.com/
>>> + * Andrew F. Davis <afd@xxxxxx>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License version 2 as
>>> + * published by the Free Software Foundation.
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include <linux/i2c.h>
>>> +#include <linux/module.h>
>>> +#include <linux/regmap.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/sysfs.h>
>>> +
>>> +#define INA3221_DRIVER_NAME "ina3221"
>>> +
>>> +#define INA3221_CONFIG 0x00
>>> +#define INA3221_SHUNT1 0x01
>>> +#define INA3221_BUS1 0x02
>>> +#define INA3221_SHUNT2 0x03
>>> +#define INA3221_BUS2 0x04
>>> +#define INA3221_SHUNT3 0x05
>>> +#define INA3221_BUS3 0x06
>>> +#define INA3221_CRIT1 0x07
>>> +#define INA3221_WARN1 0x08
>>> +#define INA3221_CRIT2 0x09
>>> +#define INA3221_WARN2 0x0a
>>> +#define INA3221_CRIT3 0x0b
>>> +#define INA3221_WARN3 0x0c
>>> +#define INA3221_SHUNT_SUM 0x0d
>>> +#define INA3221_SHUNT_SUM_LIMIT 0x0e
>>> +#define INA3221_MASK_ENABLE 0x0f
>>> +#define INA3221_POWERV_HLIMIT 0x10
>>> +#define INA3221_POWERV_LLIMIT 0x11
>>> +
>>> +#define INA3221_CONFIG_MODE_SHUNT BIT(1)
>>> +#define INA3221_CONFIG_MODE_BUS BIT(2)
>>> +#define INA3221_CONFIG_MODE_CONTINUOUS BIT(3)
>>> +
>>> +enum ina3221_fields {
>>> + /* Configuration */
>>> + F_MODE, F_SHUNT_CT, F_BUS_CT, F_AVG,
>>> + F_CHAN3_EN, F_CHAN2_EN, F_CHAN1_EN, F_RST,
>>> +
>>> + /* sentinel */
>>> + F_MAX_FIELDS
>>> +};
>>> +
>>> +static const struct reg_field ina3221_reg_fields[] = {
>>> + [F_MODE] = REG_FIELD(INA3221_CONFIG, 0, 2),
>>> + [F_SHUNT_CT] = REG_FIELD(INA3221_CONFIG, 3, 5),
>>> + [F_BUS_CT] = REG_FIELD(INA3221_CONFIG, 6, 8),
>>> + [F_AVG] = REG_FIELD(INA3221_CONFIG, 9, 11),
>>> + [F_CHAN3_EN] = REG_FIELD(INA3221_CONFIG, 12, 12),
>>> + [F_CHAN2_EN] = REG_FIELD(INA3221_CONFIG, 13, 13),
>>> + [F_CHAN1_EN] = REG_FIELD(INA3221_CONFIG, 14, 14),
>>> + [F_RST] = REG_FIELD(INA3221_CONFIG, 15, 15),
>>> +};
>>> +
>>> +#define is_bus_reg(_reg) \
>>> + (_reg == INA3221_BUS1 || \
>>> + _reg == INA3221_BUS2 || \
>>> + _reg == INA3221_BUS3)
>>> +
>>> +/**
>>> + * struct ina3221_data - device specific information
>>> + * @dev: Device structure
>>> + * @regmap: Register map of the device
>>> + * @fields: Register fields of the device
>>> + */
>>> +struct ina3221_data {
>>> + struct device *dev;
>>> + struct regmap *regmap;
>>> + struct regmap_field *fields[F_MAX_FIELDS];
>>> +};
>>> +
>>> +/**
>>> + * struct ina3221_reg_lookup - value element in iio lookup table map
>>> + * @integer: Integer component of value
>>> + * @fract: Fractional component of value
>>> + */
>>> +struct ina3221_reg_lookup {
>>> + int integer;
>>> + int fract;
>>> +};
>>> +
>>> +static const struct ina3221_reg_lookup ina3221_conv_time_table[] = {
>>> + {.fract = 140}, {.fract = 204}, {.fract = 332}, {.fract = 588},
>>> + {.fract = 1100}, {.fract = 2116}, {.fract = 4156}, {.fract = 8244},
>>> +};
>>> +
>>> +static const int ina3221_avg_table[] = { 1, 4, 16, 64, 128, 256,
>>> 512, 1024 };
>>> +static IIO_CONST_ATTR(oversampling_ratio_available, "1 4 16 64 128
>>> 256 512 1024");
>>> +
>>> +static int ina3221_read_raw(struct iio_dev *indio_dev,
>>> + struct iio_chan_spec const *chan,
>>> + int *val, int *val2, long mask)
>>> +{
>>> + struct ina3221_data *ina = iio_priv(indio_dev);
>>> + unsigned int regval;
>>> + int ret;
>>> +
>>> + switch (mask) {
>>> + case IIO_CHAN_INFO_RAW:
>>> + ret = regmap_read(ina->regmap, chan->address, &regval);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + *val = (s16)sign_extend32(regval >> 3, 12);
>>> +
>>> + return IIO_VAL_INT;
>>> +
>>> + case IIO_CHAN_INFO_SCALE:
>>> + if (is_bus_reg(chan->address)) {
>>> + *val = 8;
>>> + *val2 = 0;
>>> + } else {
>>> + *val = 0;
>>> + *val2 = 40000;
>>> + }
>>> +
>>> + return IIO_VAL_INT_PLUS_MICRO;
>>> +
>>> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
>>> + ret = regmap_field_read(ina->fields[F_AVG], &regval);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + *val = ina3221_avg_table[regval];
>>> +
>>> + return IIO_VAL_INT;
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static int ina3221_write_raw(struct iio_dev *indio_dev,
>>> + struct iio_chan_spec const *chan,
>>> + int val, int val2, long mask)
>>> +{
>>> + struct ina3221_data *ina = iio_priv(indio_dev);
>>> + int i;
>>> +
>>> + switch (mask) {
>>> + case IIO_CHAN_INFO_RAW:
>>> + return regmap_write(ina->regmap, chan->address, val << 3);
>>> +
>>> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
>>> + if (val2)
>>> + return -EINVAL;
>>> + for (i = 0; i < ARRAY_SIZE(ina3221_avg_table); i++)
>>> + if (ina3221_avg_table[i] == val)
>>> + break;
>>> + if (i == ARRAY_SIZE(ina3221_avg_table))
>>> + return -EINVAL;
>>> +
>>> + return regmap_field_write(ina->fields[F_AVG], i);
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +#define INA3221_CHAN(_channel, _address, _name) { \
>>> + .type = IIO_VOLTAGE, \
>>> + .channel = (_channel), \
>>> + .address = (_address), \
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
>>> + BIT(IIO_CHAN_INFO_SCALE), \
>>> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
>>> + .extend_name = _name, \
>>> + .indexed = true, \
>>> +}
>>> +
>>> +static const struct iio_chan_spec ina3221_channels[] = {
>>> + INA3221_CHAN(1, INA3221_SHUNT1, "shunt"),
>>> + INA3221_CHAN(1, INA3221_BUS1, "bus"),
>>> + INA3221_CHAN(1, INA3221_CRIT1, "shunt_critical"),
>>> + INA3221_CHAN(1, INA3221_WARN1, "shunt_warning"),
>>> +
>>> + INA3221_CHAN(2, INA3221_SHUNT2, "shunt"),
>>> + INA3221_CHAN(2, INA3221_BUS2, "bus"),
>>> + INA3221_CHAN(2, INA3221_CRIT2, "shunt_critical"),
>>> + INA3221_CHAN(2, INA3221_WARN2, "shunt_warning"),
>>> +
>>> + INA3221_CHAN(3, INA3221_SHUNT3, "shunt"),
>>> + INA3221_CHAN(3, INA3221_BUS3, "bus"),
>>> + INA3221_CHAN(3, INA3221_CRIT3, "shunt_critical"),
>>> + INA3221_CHAN(3, INA3221_WARN3, "shunt_warning"),
>> I'm not really sure how these work at all... You can set the thresholds
>> but how does the driver know if they have been tripped?
>> Or are we dealing with the assumption that it is a problem for external
>> hardware?
>>
> 'shunt' is really the current reported as voltage. 'bus' is the actual
> voltage. Unless I am missing it, the driver won't know if the thresholds
> have tripped. It would need an interrupt handler and read the status
> register to know that. But that isn't really relevant for an iio driver,
> or is it ? What would it do if the limits are tripped ?
>

I agree, this is really the issue that makes me want to go hwmod side
with this part, although the interrupts could be made to trigger an
IIO_EVENT.

>>> +};
>>> +
>>> +struct ina3221_attr {
>>> + struct device_attribute dev_attr;
>>> + struct device_attribute dev_attr_available;
>>> + unsigned int field;
>>> + const struct ina3221_reg_lookup *table;
>>> + unsigned int table_size;
>>> +};
>>> +
>>> +#define to_ina3221_attr(_dev_attr) \
>>> + container_of(_dev_attr, struct ina3221_attr, dev_attr)
>>> +
>>> +#define to_ina3221_attr_available(_dev_attr) \
>>> + container_of(_dev_attr, struct ina3221_attr, dev_attr_available)
>>> +
>>> +static ssize_t ina3221_show_register(struct device *dev,
>>> + struct device_attribute *attr,
>>> + char *buf)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct ina3221_data *ina = iio_priv(indio_dev);
>>> + struct ina3221_attr *ina3221_attr = to_ina3221_attr(attr);
>>> + unsigned int reg_val;
>>> + int vals[2];
>>> + int ret;
>>> +
>>> + ret = regmap_field_read(ina->fields[ina3221_attr->field],
>>> &reg_val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + vals[0] = ina3221_attr->table[reg_val].integer;
>>> + vals[1] = ina3221_attr->table[reg_val].fract;
>>> +
>>> + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals);
>>> +}
>>> +
>>> +static ssize_t ina3221_store_register(struct device *dev,
>>> + struct device_attribute *attr,
>>> + const char *buf, size_t count)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct ina3221_data *ina = iio_priv(indio_dev);
>>> + struct ina3221_attr *ina3221_attr = to_ina3221_attr(attr);
>>> + long val;
>>> + int integer, fract, ret;
>>> +
>>> + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (integer < 0)
>>> + return -EINVAL;
>>> +
>>> + for (val = 0; val < ina3221_attr->table_size; val++)
>>> + if (ina3221_attr->table[val].integer == integer &&
>>> + ina3221_attr->table[val].fract == fract) {
>>> + ret =
>>> regmap_field_write(ina->fields[ina3221_attr->field], val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return count;
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static ssize_t ina3221_show_available(struct device *dev,
>>> + struct device_attribute *attr,
>>> + char *buf)
>>> +{
>>> + struct ina3221_attr *ina3221_attr =
>>> to_ina3221_attr_available(attr);
>>> + ssize_t len = 0;
>>> + int i;
>>> +
>>> + for (i = 0; i < ina3221_attr->table_size; i++)
>>> + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ",
>>> + ina3221_attr->table[i].integer,
>>> + ina3221_attr->table[i].fract);
>>> +
>>> + if (len > 0)
>>> + buf[len - 1] = '\n';
>>> +
>>> + return len;
>>> +}
>>> +
>>> +#define INA3221_ATTR(_name, _field, _table) \
>>> + struct ina3221_attr ina3221_attr_##_name = { \
>>> + .dev_attr = __ATTR(_name, (S_IRUGO | S_IWUSR), \
>>> + ina3221_show_register, \
>>> + ina3221_store_register), \
>>> + .dev_attr_available = __ATTR(_name##_available, S_IRUGO, \
>>> + ina3221_show_available, NULL), \
>>> + .field = _field, \
>>> + .table = _table, \
>>> + .table_size = ARRAY_SIZE(_table), \
>>> + }
>>> +
>>> +static INA3221_ATTR(shunt_integration_time, F_SHUNT_CT,
>>> ina3221_conv_time_table);
>>> +static INA3221_ATTR(bus_integration_time, F_BUS_CT,
>>> ina3221_conv_time_table);
>>> +
>>> +static struct attribute *ina3221_attributes[] = {
>>> + &ina3221_attr_shunt_integration_time.dev_attr.attr,
>>> + &ina3221_attr_shunt_integration_time.dev_attr_available.attr,
>>> + &ina3221_attr_bus_integration_time.dev_attr.attr,
>>> + &ina3221_attr_bus_integration_time.dev_attr_available.attr,
>>> + &iio_const_attr_oversampling_ratio_available.dev_attr.attr,
>>> + NULL,
>>> +};
>>> +
>>> +static const struct attribute_group ina3221_attribute_group = {
>>> + .attrs = ina3221_attributes,
>>> +};
>>> +
>>> +static const struct iio_info ina3221_iio_info = {
>>> + .driver_module = THIS_MODULE,
>>> + .attrs = &ina3221_attribute_group,
>>> + .read_raw = ina3221_read_raw,
>>> + .write_raw = ina3221_write_raw,
>>> +};
>>> +
>>> +static const struct regmap_range ina3221_yes_ranges[] = {
>>> + regmap_reg_range(INA3221_SHUNT1, INA3221_BUS3),
>>> + regmap_reg_range(INA3221_MASK_ENABLE, INA3221_MASK_ENABLE),
>>> +};
>>> +
>>> +static const struct regmap_access_table ina3221_volatile_table = {
>>> + .yes_ranges = ina3221_yes_ranges,
>>> + .n_yes_ranges = ARRAY_SIZE(ina3221_yes_ranges),
>>> +};
>>> +
>>> +static const struct regmap_config ina3221_regmap_config = {
>>> + .reg_bits = 8,
>>> + .val_bits = 16,
>>> +
>>> + .cache_type = REGCACHE_RBTREE,
>>> + .volatile_table = &ina3221_volatile_table,
>>> +};
>>> +
>>> +#ifdef CONFIG_OF
>>> +static const struct of_device_id ina3221_of_match[] = {
>>> + { .compatible = "ti,ina3221", },
>>> + { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, ina3221_of_match);
>>> +#endif
>>> +
>>> +static int ina3221_probe(struct i2c_client *client,
>>> + const struct i2c_device_id *id)
>>> +{
>>> + struct iio_dev *indio_dev;
>>> + struct ina3221_data *ina;
>>> + int i, ret;
>>> +
>>> + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*ina));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> + i2c_set_clientdata(client, indio_dev);
>>> +
>>> + ina = iio_priv(indio_dev);
>>> +
>>> + ina->dev = &client->dev;
>>> +
>>> + ina->regmap = devm_regmap_init_i2c(client, &ina3221_regmap_config);
>>> + if (IS_ERR(ina->regmap)) {
>>> + dev_err(ina->dev, "Unable to allocate register map\n");
>>> + return PTR_ERR(ina->regmap);
>>> + }
>>> +
>>> + for (i = 0; i < F_MAX_FIELDS; i++) {
>>> + ina->fields[i] = devm_regmap_field_alloc(ina->dev,
>>> + ina->regmap,
>>> + ina3221_reg_fields[i]);
>>> + if (IS_ERR(ina->fields[i])) {
>>> + dev_err(ina->dev, "Unable to allocate regmap fields\n");
>>> + return PTR_ERR(ina->fields[i]);
>>> + }
>>> + }
>>> +
>>> + ret = regmap_field_write(ina->fields[F_RST], true);
>>> + if (ret) {
>>> + dev_err(ina->dev, "Unable to reset device\n");
>>> + return ret;
>>> + }
>>> +
>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>> + indio_dev->dev.parent = ina->dev;
>>> + indio_dev->channels = ina3221_channels;
>>> + indio_dev->num_channels = ARRAY_SIZE(ina3221_channels);
>>> + indio_dev->name = INA3221_DRIVER_NAME;
>>> + indio_dev->info = &ina3221_iio_info;
>>> +
>>> + ret = devm_iio_device_register(ina->dev, indio_dev);
>>> + if (ret) {
>>> + dev_err(ina->dev, "Unable to register IIO device\n");
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct i2c_device_id ina3221_ids[] = {
>>> + { "ina3221", 0 },
>>> + { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(i2c, ina3221_ids);
>>> +
>>> +static struct i2c_driver ina3221_i2c_driver = {
>>> + .driver = {
>>> + .name = INA3221_DRIVER_NAME,
>>> + .of_match_table = of_match_ptr(ina3221_of_match),
>
> Is that really necessary ? I don't see any chip specific properties
> Having said that, specifying shunt resistor values might be useful,
> but since the driver reports it as voltage it would not really
> add any value.
>

Not necessary, I just left it in incase I did want to add a shunt
resistor value to DT like the ina2xx driver. I'll remove this until then.

Andrew

>>> + },
>>> + .probe = ina3221_probe,
>>> + .id_table = ina3221_ids,
>>> +};
>>> +module_i2c_driver(ina3221_i2c_driver);
>>> +
>>> +MODULE_AUTHOR("Andrew F. Davis <afd@xxxxxx>");
>>> +MODULE_DESCRIPTION("Texas Instruments INA3221 Driver");
>>> +MODULE_LICENSE("GPL v2");
>>>
>>
>>
>