On 03/06/16 11:06, Jonathan Cameron wrote:
> On 01/06/16 13:34, Laxman Dewangan wrote:
>> The INA3221 is a three-channel, high-side current and bus voltage monitor
>> with an I2C interface from Texas Instruments. The INA3221 monitors both
>> shunt voltage drops and bus supply voltages in addition to having
>> programmable conversion times and averaging modes for these signals.
>> The INA3221 offers both critical and warning alerts to detect multiple
>> programmable out-of-range conditions for each channel.
>>
>> Add support for INA3221 SW driver via IIO ADC interface. The device is
>> register as iio-device and provides interface for voltage/current and power
>> monitor. Also provide interface for setting oneshot/continuous mode and
>> critical/warning threshold for the shunt voltage drop.
>>
>> Signed-off-by: Laxman Dewangan <ldewan...@nvidia.com>
> Hi Laxman,
> 
> As ever with any driver lying on the border of IIO and hwmon, please include
> a short justification of why you need an IIO driver and also cc the
> hwmon list + maintainers. (cc'd on this reply).
> 
> I simply won't take a driver where the hwmon maintainers aren't happy.
> As it stands I'm not seeing obvious reasons in the code for why this
> should be an IIO device.
> 
> Funily enough I know this datasheet a little as was evaluating
> it for use on some boards at the day job a week or so ago.
> 
> Various comments inline. Major points are:
> * Don't use 'fake' channels to control events. If the events infrastructure
> doesn't handle your events, then fix that rather than working around it.
> * There is a lot of ABI in here concerned with oneshot vs continuous.
> This seems to me to be more than it should be. We wouldn't expect to
> see stuff changing as a result of switching between these modes other
> than wrt to when the data shows up.  So I'd expect to not see this
> directly exposed at all - but rather sit in oneshot unless either:
> 1) Buffered mode is running (not currently supported)
> 2) Alerts are on - which I think requires it to be in continuous mode.
> 
> Other question to my mind is whether we should be reporting vshunt or
> (using device tree to pass resistance) current.
> 
> Code looks good, bu these more fundamental bits need sorting.
Another minor point - why do the power calculations in driver?
no hardware support for it, so why not just leave it to userspace?
> 
> Thanks,
> 
> Jonathan
>> ---
>>  drivers/iio/adc/Kconfig   |   12 +
>>  drivers/iio/adc/Makefile  |    1 +
>>  drivers/iio/adc/ina3221.c | 1175 
>> +++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 1188 insertions(+)
>>  create mode 100644 drivers/iio/adc/ina3221.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 25378c5..65f3c27 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -223,6 +223,18 @@ config INA2XX_ADC
>>        Say yes here to build support for TI INA2xx family of Power Monitors.
>>        This driver is mutually exclusive with the HWMON version.
>>  
>> +config INA3221
>> +    tristate "TI INA3221 3-Channel Shunt and Bus Voltage Monitor"
>> +    depends on I2C
>> +    select REGMAP_I2C
>> +    help
>> +      INA3221 is Triple-Channel, High-Side Measurement, Shunt and Bus
>> +      Voltage Monitor device from TI. This driver support the reading
>> +      of all channel's voltage/current and power via IIO interface.
>> +      Say yes here to build support for TI INA3221. To compile this
>> +      driver as a module, choose M here: the module will be called
>> +      ina3221.
>> +
>>  config IMX7D_ADC
>>      tristate "IMX7D ADC driver"
>>      depends on ARCH_MXC || COMPILE_TEST
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index 38638d4..c24f455 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_LPC18XX_ADC) += lpc18xx_adc.o
>>  obj-$(CONFIG_MAX1027) += max1027.o
>> diff --git a/drivers/iio/adc/ina3221.c b/drivers/iio/adc/ina3221.c
>> new file mode 100644
>> index 0000000..a17f688
>> --- /dev/null
>> +++ b/drivers/iio/adc/ina3221.c
>> @@ -0,0 +1,1175 @@
>> +/*
>> + * IIO driver for INA3221
>> + *
>> + * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
>> + *
>> + * 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.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/iio/kfifo_buf.h>
>> +#include <linux/iio/sysfs.h>
>> +#include <linux/module.h>
>> +#include <linux/regmap.h>
>> +#include <linux/util_macros.h>
>> +
>> +/* INA3221 registers definition */
>> +#define INA3221_CONFIG                              0x00
>> +#define INA3221_SHUNT_VOL_CHAN1                     0x01
>> +#define INA3221_BUS_VOL_CHAN1                       0x02
>> +#define INA3221_SHUNT_VOL_CHAN2                     0x03
>> +#define INA3221_BUS_VOL_CHAN2                       0x04
>> +#define INA3221_SHUNT_VOL_CHAN3                     0x05
>> +#define INA3221_BUS_VOL_CHAN3                       0x06
>> +#define INA3221_CRIT_CHAN1                  0x07
>> +#define INA3221_WARN_CHAN1                  0x08
>> +#define INA3221_CRIT_CHAN2                  0x09
>> +#define INA3221_WARN_CHAN2                  0x0A
>> +#define INA3221_CRIT_CHAN3                  0x0B
>> +#define INA3221_WARN_CHAN3                  0x0C
>> +#define INA3221_MASK_ENABLE                 0x0F
>> +#define INA3221_POWER_VALID_UPPER_LIMIT             0x10
>> +#define INA3221_POWER_VALID_LOWER_LIMIT             0x11
>> +#define INA3221_MAN_ID                              0xFE
>> +#define INA3221_DEV_ID                              0xFF
>> +
>> +#define INA3221_CONFIG_RESET_MASK           BIT(15)
>> +#define INA3221_CONFIG_RESET_EN                     BIT(15)
>> +
>> +#define INA3221_CONFIG_MODE_MASK            GENMASK(2, 0)
>> +#define INA3221_CONFIG_MODE_POWER_DOWN              0
>> +
>> +#define INA3221_CONFIG_AVG_MASK                     GENMASK(11, 9)
>> +#define INA3221_CONFIG_AVG(val)                     ((val) << 9)
>> +
>> +#define INA3221_CONFIG_VBUSCT_MASK          GENMASK(8, 6)
>> +#define INA3221_CONFIG_VBUSCT(val)          ((val) << 6)
>> +
>> +#define INA3221_CONFIG_SHUNTCT_MASK         GENMASK(5, 3)
>> +#define INA3221_CONFIG_SHUNTCT(val)         ((val) << 3)
>> +
>> +#define INA3221_REG_MASK_WEN                        BIT(11)
>> +#define INA3221_REG_MASK_CEN                        BIT(10)
>> +#define INA3221_REG_MASK_CVRF                       BIT(0)
>> +
>> +#define PACK_MODE_CHAN(mode, chan)          ((mode) | ((chan) << 8))
>> +#define UNPACK_MODE(address)                        ((address) & 0xFF)
>> +#define UNPACK_CHAN(address)                        (((address) >> 8) & 
>> 0xFF)
>> +
>> +#define INA3221_NUMBER_OF_CHANNELS          3
>> +#define INA3221_MAX_CONVERSION_TRIALS               10
>> +#define INA3221_CONFIG_AVG_SAMPLE_DEFAULT   4
>> +#define INA3221_CONFIG_VBUS_CONV_TIME_DEFAULT       150
>> +#define INA3221_CONFIG_SHUNT_CONV_TIME_DEFAULT      150
>> +
>> +#define INA3221_SHUNT_VOL(i)                (INA3221_SHUNT_VOL_CHAN1 + (i) 
>> * 2)
>> +#define INA3221_BUS_VOL(i)          (INA3221_BUS_VOL_CHAN1 + (i) * 2)
>> +#define INA3221_CRIT(i)                     (INA3221_CRIT_CHAN1 + (i) * 2)
>> +#define INA3221_WARN(i)                     (INA3221_WARN_CHAN1 + (i) * 2)
>> +
>> +static const struct regmap_range ina3221_readable_ranges[] = {
>> +    regmap_reg_range(INA3221_CONFIG, INA3221_POWER_VALID_LOWER_LIMIT),
>> +    regmap_reg_range(INA3221_MAN_ID, INA3221_DEV_ID),
>> +};
>> +
>> +static const struct regmap_access_table ina3221_readable_table = {
>> +    .yes_ranges = ina3221_readable_ranges,
>> +    .n_yes_ranges = ARRAY_SIZE(ina3221_readable_ranges),
>> +};
>> +
>> +static const struct regmap_range ina3221_no_writable_ranges[] = {
>> +    regmap_reg_range(INA3221_SHUNT_VOL_CHAN1, INA3221_BUS_VOL_CHAN3),
>> +};
>> +
>> +static const struct regmap_access_table ina3221_writable_table = {
>> +    .no_ranges = ina3221_no_writable_ranges,
>> +    .n_no_ranges =  ARRAY_SIZE(ina3221_no_writable_ranges),
>> +};
>> +
>> +static const struct regmap_range ina3221_no_volatile_ranges[] = {
>> +    regmap_reg_range(INA3221_CONFIG, INA3221_CONFIG),
>> +    regmap_reg_range(INA3221_CRIT_CHAN1, INA3221_WARN_CHAN3),
>> +    regmap_reg_range(INA3221_POWER_VALID_UPPER_LIMIT,
>> +                     INA3221_POWER_VALID_LOWER_LIMIT),
>> +    regmap_reg_range(INA3221_MAN_ID, INA3221_DEV_ID),
>> +};
>> +
>> +static const struct regmap_access_table ina3221_volatile_table = {
>> +    .no_ranges = ina3221_no_volatile_ranges,
>> +    .n_no_ranges =  ARRAY_SIZE(ina3221_no_volatile_ranges),
>> +};
>> +
>> +static const struct regmap_config ina3221_regmap_config = {
>> +    .reg_bits = 8,
>> +    .val_bits = 16,
>> +    .max_register = INA3221_DEV_ID + 1,
>> +    .rd_table = &ina3221_readable_table,
>> +    .wr_table = &ina3221_writable_table,
>> +    .volatile_table = &ina3221_volatile_table,
>> +};
>> +
>> +struct ina3221_channel_data {
>> +    const char *name;
>> +    int warn_limits;
>> +    int crit_limits;
>> +    int shunt_resistance;
>> +};
>> +
>> +struct ina3221_platform_data {
>> +    struct ina3221_channel_data channel_data[INA3221_NUMBER_OF_CHANNELS];
>> +    bool enable_power;
>> +    int oneshot_avg_sample;
>> +    int oneshot_vbus_conv_time;
>> +    int oneshot_shunt_conv_time;
>> +    int cont_avg_sample;
>> +    int cont_vbus_conv_time;
>> +    int cont_shunt_conv_time;
>> +    int continuous_mode;
>> +    bool warn_alert;
>> +    bool crit_alert;
>> +    int active_channel;
>> +};
>> +
>> +struct ina3221_chip_info {
>> +    struct device *dev;
>> +    struct regmap *rmap;
>> +    int oneshot_config;
>> +    int continuous_config;
>> +    int continuous_mode;
>> +    struct mutex state_lock;
>> +    struct ina3221_platform_data *pdata;
>> +};
>> +
>> +enum ina3221_address {
>> +    INA3221_CHANNEL_NAME,
>> +    INA3221_CRIT_CURRENT_LIMIT,
>> +    INA3221_WARN_CURRENT_LIMIT,
>> +    INA3221_MEASURED_VALUE,
>> +    INA3221_OPERATING_MODE,
>> +    INA3221_OVERSAMPLING_RATIO,
>> +    INA3221_VBUS_CONV_TIME,
>> +    INA3221_VSHUNT_CONV_TIME,
>> +    INA3221_CHANNEL_ONESHOT,
>> +    INA3221_CHANNEL_CONTINUOUS,
>> +};
>> +
>> +static inline int shuntv_register_to_uv(u16 reg)
>> +{
>> +    int ret = (s16)reg;
>> +
>> +    return (ret >> 3) * 40;
>> +}
>> +
>> +static inline u16 uv_to_shuntv_register(s32 uv)
>> +{
>> +    return (u16)(uv / 5);
>> +}
>> +
>> +static inline int busv_register_to_mv(u16 reg)
>> +{
>> +    int ret = (s16)reg;
>> +
>> +    return (ret >> 3) * 8;
>> +}
>> +
>> +/* convert shunt voltage register value to current (in mA) */
>> +static int shuntv_register_to_ma(u16 reg, int resistance)
>> +{
>> +    int uv, ma;
>> +
>> +    uv = (s16)reg;
>> +    uv = ((uv >> 3) * 40); /* LSB (4th bit) is 40uV */
>> +    /*
>> +     * calculate uv/resistance with rounding knowing that C99 truncates
>> +     * towards zero
>> +     */
>> +    if (uv > 0)
>> +            ma = ((uv * 2 / resistance) + 1) / 2;
>> +    else
>> +            ma = ((uv * 2 / resistance) - 1) / 2;
>> +    return ma;
>> +}
>> +
>> +static int ina3221_get_closest_index(const int *list, int n_list,
>> +                                 int val)
>> +{
>> +    if (val > list[n_list - 1] || (val < list[0]))
>> +            return -EINVAL;
>> +
>> +    return find_closest(val, list, n_list);
>> +}
>> +
>> +/*
>> + * Available averaging rates for INA3221. The indices correspond with
>> + * the bit values expected by the chip (according to the INA3221 datasheet,
>> + * table 3 AVG bit settings, found at
>> + */
>> +static const int ina3221_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024};
>> +
>> +static int ina3221_set_average(struct ina3221_chip_info *chip, unsigned int 
>> val,
>> +                           unsigned int *config)
>> +{
>> +    int bits;
>> +
>> +    bits = ina3221_get_closest_index(ina3221_avg_tab,
>> +                                     ARRAY_SIZE(ina3221_avg_tab), val);
>> +    if (bits < 0)
>> +            return bits;
>> +
>> +    *config &= ~INA3221_CONFIG_AVG_MASK;
>> +    *config |= INA3221_CONFIG_AVG(bits) & INA3221_CONFIG_AVG_MASK;
>> +
>> +    return 0;
>> +}
>> +
>> +/* Conversion times in uS */
>> +static const int ina3221_conv_time_tab[] = { 140, 204, 332, 588, 1100,
>> +                                        2116, 4156, 8244};
>> +
>> +static int ina3221_set_int_time_vbus(struct ina3221_chip_info *chip,
>> +                                 unsigned int val_us, unsigned int *config)
>> +{
>> +    int bits;
>> +
>> +    bits = ina3221_get_closest_index(ina3221_conv_time_tab,
>> +                                     ARRAY_SIZE(ina3221_conv_time_tab),
>> +                                     val_us);
>> +    if (bits < 0)
>> +            return bits;
>> +
>> +    *config &= ~INA3221_CONFIG_VBUSCT_MASK;
>> +    *config |= INA3221_CONFIG_VBUSCT(bits) & INA3221_CONFIG_VBUSCT_MASK;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_set_int_time_vshunt(struct ina3221_chip_info *chip,
>> +                                   unsigned int val_us,
>> +                                   unsigned int *config)
>> +{
>> +    int bits;
>> +
>> +    bits = ina3221_get_closest_index(ina3221_conv_time_tab,
>> +                                     ARRAY_SIZE(ina3221_conv_time_tab),
>> +                                     val_us);
>> +    if (bits < 0)
>> +            return bits;
>> +
>> +    *config &= ~INA3221_CONFIG_SHUNTCT_MASK;
>> +    *config |= INA3221_CONFIG_SHUNTCT(bits) & INA3221_CONFIG_SHUNTCT_MASK;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_do_one_shot_conversion(struct ina3221_chip_info *chip,
>> +                                      int chan, u16 *vbus, u16 *vsh)
>> +{
>> +    unsigned int value;
>> +    int trials = 0;
>> +    int ret;
>> +    int conv_time;
>> +
>> +    conv_time = max(chip->pdata->oneshot_vbus_conv_time,
>> +                    chip->pdata->oneshot_shunt_conv_time);
>> +
>> +    ret = regmap_write(chip->rmap, INA3221_CONFIG, chip->oneshot_config);
>> +    if (ret < 0)
>> +            return 0;
>> +
>> +    /* Read conversion status */
>> +    do {
>> +            ret = regmap_read(chip->rmap, INA3221_MASK_ENABLE, &value);
>> +            if (ret < 0) {
>> +                    dev_err(chip->dev,
>> +                            "Failed to read conversion status: %d\n", ret);
>> +                    return ret;
>> +            }
>> +
>> +            if (value & INA3221_REG_MASK_CVRF)
>> +                    break;
>> +            usleep_range(conv_time, conv_time * 2);
>> +    } while (++trials < INA3221_MAX_CONVERSION_TRIALS);
>> +
>> +    if (trials == INA3221_MAX_CONVERSION_TRIALS) {
>> +            dev_err(chip->dev,
>> +                    "Conversion not completed for maximum trials\n");
>> +            return -EAGAIN;
>> +    }
>> +
>> +    if (vsh) {
>> +            ret = regmap_read(chip->rmap, INA3221_SHUNT_VOL(chan), &value);
>> +            if (ret < 0)
>> +                    return ret;
>> +            *vsh = (u16)value;
>> +    }
>> +
>> +    if (vbus) {
>> +            ret = regmap_read(chip->rmap, INA3221_BUS_VOL(chan), &value);
>> +            if (ret < 0)
>> +                    return ret;
>> +            *vbus = (u16)value;
>> +    }
>> +
>> +    ret = regmap_write(chip->rmap, INA3221_CONFIG, 0);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_read_continuous_conversion(struct ina3221_chip_info 
>> *chip,
>> +                                          int chan, u16 *vbus, u16 *vsh)
>> +{
>> +    unsigned int value;
>> +    int ret;
>> +
>> +    if (vsh) {
>> +            ret = regmap_read(chip->rmap, INA3221_SHUNT_VOL(chan), &value);
>> +            if (ret < 0)
>> +                    return ret;
>> +            *vsh = (u16)value;
>> +    }
>> +
>> +    if (vbus) {
>> +            ret = regmap_read(chip->rmap, INA3221_BUS_VOL(chan), &value);
>> +            if (ret < 0)
>> +                    return ret;
>> +            *vbus = (u16)value;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static int ina3221_read_vbus_vshunt(struct ina3221_chip_info *chip,
>> +                                int ch, u16 *vbus, u16 *vsh)
>> +{
>> +    if (chip->continuous_mode)
>> +            return ina3221_read_continuous_conversion(chip, ch, vbus, vsh);
>> +
>> +    return ina3221_do_one_shot_conversion(chip, ch, vbus, vsh);
>> +}
>> +
>> +static int ina3221_get_channel_voltage(struct ina3221_chip_info *chip,
>> +                                   int chan, int *voltage_uv)
>> +{
>> +    u16 vbus;
>> +    int ret;
>> +
>> +    ret = ina3221_read_vbus_vshunt(chip, chan, &vbus, NULL);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    *voltage_uv = busv_register_to_mv(vbus) * 1000;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_get_channel_current(struct ina3221_chip_info *chip,
>> +                                   int chan, int *current_ua)
>> +{
>> +    int shunt_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    u16 vsh;
>> +    int ret;
>> +
>> +    ret = ina3221_do_one_shot_conversion(chip, chan, NULL, &vsh);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    *current_ua = shuntv_register_to_ma(vsh, shunt_res) * 1000;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_get_channel_power(struct ina3221_chip_info *chip,
>> +                                 int chan, int *power_uw)
>> +{
>> +    int shunt_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    u16 vbus, vsh;
>> +    int ret;
>> +    int current_ma, voltage_mv;
>> +
>> +    ret = ina3221_do_one_shot_conversion(chip, chan, &vbus, &vsh);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    current_ma = shuntv_register_to_ma(vsh, shunt_res);
>> +    voltage_mv = busv_register_to_mv(vbus);
>> +    *power_uw = (voltage_mv * current_ma);
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_get_channel_critical(struct ina3221_chip_info *chip,
>> +                                    int chan, int *curr_limit_ua)
>> +{
>> +    *curr_limit_ua = chip->pdata->channel_data[chan].crit_limits;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_set_channel_critical(struct ina3221_chip_info *chip,
>> +                                    int chan, int crit_limit_ua)
>> +{
>> +    int s_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    int shunt_volt_limit;
>> +    int crit_limit = crit_limit_ua / 1000;
>> +    int ret;
>> +
>> +    if (crit_limit < 0)
>> +            return 0;
>> +
>> +    shunt_volt_limit = crit_limit * s_res;
>> +    shunt_volt_limit = uv_to_shuntv_register(shunt_volt_limit);
>> +
>> +    ret = regmap_write(chip->rmap, INA3221_CRIT(chan), shunt_volt_limit);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to write critical reg 0x%02x: %d\n",
>> +                    INA3221_CRIT(chan), ret);
>> +            return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_read_default_channel_critical(struct ina3221_chip_info 
>> *chip,
>> +                                             int chan, int *shunt_curr_ua)
>> +{
>> +    int shunt_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    int shunt_volt;
>> +    unsigned int value;
>> +    int ret;
>> +
>> +    if (shunt_res <= 0) {
>> +            dev_err(chip->dev, "Channel %d have invalid shunt resistor\n",
>> +                    chan);
>> +            return -EINVAL;
>> +    }
>> +
>> +    ret = regmap_read(chip->rmap, INA3221_CRIT(chan), &value);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to read critical reg 0x%02x: %d\n",
>> +                    INA3221_CRIT(chan), ret);
>> +            return ret;
>> +    }
>> +    shunt_volt = shuntv_register_to_uv((u16)value);
>> +    *shunt_curr_ua = (shunt_volt / shunt_res) * 1000;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_get_channel_warning(struct ina3221_chip_info *chip,
>> +                                   int chan, int *curr_limit_ua)
>> +{
>> +    *curr_limit_ua = chip->pdata->channel_data[chan].warn_limits;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_set_channel_warning(struct ina3221_chip_info *chip,
>> +                                   int chan, int warn_limit_ua)
>> +{
>> +    int s_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    int shunt_volt_limit;
>> +    int warn_limit = warn_limit_ua / 1000;
>> +    int ret;
>> +
>> +    if (warn_limit < 0)
>> +            return 0;
>> +
>> +    shunt_volt_limit = warn_limit * s_res;
>> +    shunt_volt_limit = uv_to_shuntv_register(shunt_volt_limit);
>> +
>> +    ret = regmap_write(chip->rmap, INA3221_WARN(chan), shunt_volt_limit);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to write warning reg 0x%02x: %d\n",
>> +                    INA3221_CRIT(chan), ret);
>> +            return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_read_default_channel_warning(struct ina3221_chip_info 
>> *chip,
>> +                                            int chan, int *shunt_curr_ua)
>> +{
>> +    int shunt_res = chip->pdata->channel_data[chan].shunt_resistance;
>> +    int shunt_volt;
>> +    unsigned int value;
>> +    int ret;
>> +
>> +    if (shunt_res <= 0) {
>> +            dev_err(chip->dev, "Channel %d have invalid shunt resistor\n",
>> +                    chan);
>> +            return -EINVAL;
>> +    }
>> +
>> +    ret = regmap_read(chip->rmap, INA3221_WARN(chan), &value);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to read critical reg 0x%02x: %d\n",
>> +                    INA3221_CRIT(chan), ret);
>> +            return ret;
>> +    }
>> +    shunt_volt = shuntv_register_to_uv((u16)value);
>> +    *shunt_curr_ua = (shunt_volt / shunt_res) * 1000;
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_get_operating_mode(struct ina3221_chip_info *chip)
>> +{
>> +    return chip->continuous_mode;
>> +}
>> +
>> +static int ina3221_set_operating_mode(struct ina3221_chip_info *chip,
>> +                                  int is_continuous)
>> +{
>> +    unsigned int mask, val;
>> +    int ret;
>> +
>> +    if (!is_continuous) {
>> +            ret = regmap_write(chip->rmap, INA3221_CONFIG, 0);
>> +            if (ret < 0) {
>> +                    dev_err(chip->dev,
>> +                            "Failed to set mode of device: %d\n", ret);
>> +                    return ret;
>> +            }
>> +
>> +            goto done;
>> +    }
>> +
>> +    if ((chip->pdata->warn_alert || chip->pdata->crit_alert)) {
>> +            mask = INA3221_REG_MASK_CEN | INA3221_REG_MASK_WEN;
>> +            val = (chip->pdata->warn_alert) ? INA3221_REG_MASK_WEN : 0;
>> +            val |= (chip->pdata->crit_alert) ? INA3221_REG_MASK_CEN : 0;
>> +
>> +            ret = regmap_update_bits(chip->rmap, INA3221_MASK_ENABLE,
>> +                                     mask, val);
>> +            if (ret < 0) {
>> +                    dev_err(chip->dev,
>> +                            "Failed to enable warn/crit alert: %d\n", ret);
>> +                    return ret;
>> +            }
>> +    }
>> +
>> +    ret = regmap_write(chip->rmap, INA3221_CONFIG, chip->continuous_config);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to set cont mode: %d\n", ret);
>> +            return ret;
>> +    }
>> +
>> +done:
>> +    chip->continuous_mode = is_continuous;
>> +
>> +    return ret;
>> +}
>> +
>> +static int ina3221_read_raw(struct iio_dev *indio_dev,
>> +                        struct iio_chan_spec const *cspec,
>> +                        int *val, int *val2, long mask)
>> +{
>> +    struct ina3221_chip_info *chip = iio_priv(indio_dev);
>> +    int ch = cspec->channel;
>> +    int ret = -EINVAL;
>> +
>> +    if (mask != IIO_CHAN_INFO_PROCESSED)
>> +            return -EINVAL;
>> +
>> +    mutex_lock(&chip->state_lock);
>> +
>> +    switch (cspec->type) {
>> +    case IIO_VOLTAGE:
>> +            ret = ina3221_get_channel_voltage(chip, ch, val);
>> +            break;
>> +
>> +    case IIO_CURRENT:
>> +            switch (cspec->address) {
>> +            case INA3221_MEASURED_VALUE:
>> +                    ret = ina3221_get_channel_current(chip, ch, val);
>> +                    break;
>> +
>> +            case INA3221_CRIT_CURRENT_LIMIT:
>> +                    ret = ina3221_get_channel_critical(chip, ch, val);
>> +                    break;
>> +
>> +            case INA3221_WARN_CURRENT_LIMIT:
>> +                    ret = ina3221_get_channel_warning(chip, ch, val);
>> +                    break;
>> +            }
>> +            break;
>> +
>> +    case IIO_POWER:
>> +            ret = ina3221_get_channel_power(chip, ch, val);
>> +            break;
>> +
>> +    default:
>> +            break;
>> +    }
>> +
>> +    mutex_unlock(&chip->state_lock);
>> +
>> +    return (ret < 0) ? ret : IIO_VAL_INT;
>> +}
>> +
>> +static int ina3221_write_raw(struct iio_dev *indio_dev,
>> +                         struct iio_chan_spec const *cspec,
>> +                         int val, int val2, long mask)
>> +{
>> +    struct ina3221_chip_info *chip = iio_priv(indio_dev);
>> +    int ch = cspec->channel;
>> +    int ret = -EINVAL;
>> +
>> +    if (mask != IIO_CHAN_INFO_PROCESSED)
>> +            return -EINVAL;
>> +
>> +    if (cspec->type != IIO_CURRENT)
>> +            return -EINVAL;
>> +
>> +    mutex_lock(&chip->state_lock);
>> +    switch (cspec->address) {
>> +    case INA3221_CRIT_CURRENT_LIMIT:
>> +            ret = ina3221_set_channel_critical(chip, ch, val);
>> +            if (!ret)
>> +                    chip->pdata->channel_data[ch].crit_limits = val;
>> +            break;
>> +
>> +    case INA3221_WARN_CURRENT_LIMIT:
>> +            ret = ina3221_set_channel_warning(chip, ch, val);
>> +            if (!ret)
>> +                    chip->pdata->channel_data[ch].warn_limits = val;
>> +            break;
>> +
>> +    default:
>> +            break;
>> +    }
>> +
>> +    mutex_unlock(&chip->state_lock);
>> +
>> +    return ret;
>> +}
>> +
>> +static ssize_t ina3221_show_channel(struct device *dev,
>> +                                struct device_attribute *attr, char *buf)
>> +{
>> +    struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>> +    struct ina3221_chip_info *chip = iio_priv(indio_dev);
>> +    struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
>> +    int mode = UNPACK_MODE(this_attr->address);
>> +    int address = UNPACK_CHAN(this_attr->address);
>> +    int ret;
>> +    int val;
>> +
>> +    switch (mode) {
>> +    case INA3221_CHANNEL_NAME:
>> +            return snprintf(buf, PAGE_SIZE, "%s\n",
>> +                            chip->pdata->channel_data[address].name);
>> +
>> +    case INA3221_OPERATING_MODE:
>> +            ret = ina3221_get_operating_mode(chip);
>> +            if (ret)
>> +                    return snprintf(buf, PAGE_SIZE, "continuous\n");
>> +            return snprintf(buf, PAGE_SIZE, "oneshot\n");
>> +
>> +    case INA3221_OVERSAMPLING_RATIO:
>> +            if (address == INA3221_CHANNEL_ONESHOT)
>> +                    val = chip->pdata->oneshot_avg_sample;
>> +            else
>> +                    val = chip->pdata->cont_avg_sample;
>> +            return snprintf(buf, PAGE_SIZE, "%d\n", val);
>> +
>> +    case INA3221_VBUS_CONV_TIME:
>> +            if (address == INA3221_CHANNEL_ONESHOT)
>> +                    val = chip->pdata->oneshot_vbus_conv_time;
>> +            else
>> +                    val = chip->pdata->cont_vbus_conv_time;
>> +            return snprintf(buf, PAGE_SIZE, "%d\n", val);
>> +
>> +    case INA3221_VSHUNT_CONV_TIME:
>> +            if (address == INA3221_CHANNEL_ONESHOT)
>> +                    val = chip->pdata->oneshot_shunt_conv_time;
>> +            else
>> +                    val = chip->pdata->cont_shunt_conv_time;
>> +            return snprintf(buf, PAGE_SIZE, "%d\n", val);
>> +
>> +    default:
>> +            break;
>> +    }
>> +
>> +    return -EINVAL;
>> +}
>> +
>> +static ssize_t ina3221_set_channel(struct device *dev,
>> +                               struct device_attribute *attr,
>> +                               const char *buf, size_t len)
>> +{
>> +    struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>> +    struct ina3221_chip_info *chip = iio_priv(indio_dev);
>> +    struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
>> +    int mode = UNPACK_MODE(this_attr->address);
>> +    int address = UNPACK_CHAN(this_attr->address);
> I'd personally use address as an index into an array of static const
> struct. Leads to slightly easier to read and more extensible code.
> 
> Minor point though.
>> +    int o_conf, c_conf;
>> +    long val;
>> +    int *cont_param = NULL;
>> +    int ret = -EINVAL;
>> +
>> +    if (mode == INA3221_OPERATING_MODE) {
>> +            val = ((*buf == 'c') || (*buf == 'C')) ? 1 : 0;
>> +    } else {
>> +            if (kstrtol(buf, 10, &val) < 0)
>> +                    return -EINVAL;
>> +    }
>> +
>> +    mutex_lock(&chip->state_lock);
>> +
>> +    o_conf = chip->oneshot_config;
>> +    c_conf = chip->continuous_config;
>> +
>> +    switch (mode) {
>> +    case INA3221_OPERATING_MODE:
>> +            if (chip->continuous_mode == val)
>> +                    break;
>> +
>> +            ret = ina3221_set_operating_mode(chip, val);
>> +            break;
>> +
>> +    case INA3221_OVERSAMPLING_RATIO:
>> +            if (address == INA3221_CHANNEL_ONESHOT) {
>> +                    ret = ina3221_set_average(chip, val, &o_conf);
>> +                    if (!ret)
>> +                            chip->pdata->oneshot_avg_sample = val;
>> +            } else {
>> +                    ret = ina3221_set_average(chip, val, &c_conf);
>> +                    if (!ret)
>> +                            cont_param = &chip->pdata->cont_avg_sample;
>> +            }
>> +            break;
>> +
>> +    case INA3221_VBUS_CONV_TIME:
>> +            if (address == INA3221_CHANNEL_ONESHOT) {
>> +                    ret = ina3221_set_int_time_vbus(chip, val, &o_conf);
>> +                    if (!ret)
>> +                            chip->pdata->oneshot_vbus_conv_time = val;
>> +            } else {
>> +                    ret = ina3221_set_int_time_vbus(chip, val, &c_conf);
>> +                    if (!ret)
>> +                            cont_param = &chip->pdata->cont_vbus_conv_time;
>> +            }
>> +            break;
>> +
>> +    case INA3221_VSHUNT_CONV_TIME:
>> +            if (address == INA3221_CHANNEL_ONESHOT) {
>> +                    ret = ina3221_set_int_time_vshunt(chip, val, &o_conf);
>> +                    if (!ret)
>> +                            chip->pdata->oneshot_shunt_conv_time = val;
>> +            } else {
>> +                    ret = ina3221_set_int_time_vshunt(chip, val, &c_conf);
>> +                    if (!ret)
>> +                            cont_param = &chip->pdata->cont_shunt_conv_time;
>> +            }
>> +            break;
>> +
>> +    default:
>> +            break;
>> +    }
>> +
>> +    if (ret < 0)
>> +            goto exit;
>> +
>> +    chip->oneshot_config = o_conf;
>> +    if (chip->continuous_mode && chip->continuous_config != c_conf) {
>> +            ret = regmap_write(chip->rmap, INA3221_CONFIG, c_conf);
>> +            if (ret < 0)
>> +                    goto exit;
>> +    }
>> +    chip->continuous_config = c_conf;
>> +    if (cont_param)
>> +            *cont_param = val;
>> +
>> +exit:
>> +    mutex_unlock(&chip->state_lock);
>> +
>> +    return len;
>> +}
>> +
>> +static IIO_DEVICE_ATTR(rail_name_0, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_CHANNEL_NAME, 0));
>> +
>> +static IIO_DEVICE_ATTR(rail_name_1, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_CHANNEL_NAME, 1));
>> +
>> +static IIO_DEVICE_ATTR(rail_name_2, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_CHANNEL_NAME, 2));
>> +
>> +static IIO_DEVICE_ATTR(operating_mode, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_OPERATING_MODE, 0));
>> +
>> +static IIO_DEVICE_ATTR(oneshot_oversampling_ratio, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_OVERSAMPLING_RATIO,
>> +                           INA3221_CHANNEL_ONESHOT));
>> +
>> +static IIO_DEVICE_ATTR(continuous_oversampling_ratio, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_OVERSAMPLING_RATIO,
>> +                           INA3221_CHANNEL_CONTINUOUS));
>> +
>> +static IIO_DEVICE_ATTR(oneshot_vbus_conv_time, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_VBUS_CONV_TIME,
>> +                           INA3221_CHANNEL_ONESHOT));
>> +
>> +static IIO_DEVICE_ATTR(continuous_vbus_conv_time, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_VBUS_CONV_TIME,
>> +                           INA3221_CHANNEL_CONTINUOUS));
>> +
>> +static IIO_DEVICE_ATTR(oneshot_vshunt_conv_time, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_VSHUNT_CONV_TIME,
>> +                           INA3221_CHANNEL_ONESHOT));
>> +
>> +static IIO_DEVICE_ATTR(continuous_vshunt_conv_time, S_IRUGO | S_IWUSR,
>> +            ina3221_show_channel, ina3221_set_channel,
>> +            PACK_MODE_CHAN(INA3221_VSHUNT_CONV_TIME,
>> +                           INA3221_CHANNEL_CONTINUOUS));
>> +
>> +static struct attribute *ina3221_attributes[] = {
>> +    &iio_dev_attr_rail_name_0.dev_attr.attr,
>> +    &iio_dev_attr_rail_name_1.dev_attr.attr,
>> +    &iio_dev_attr_rail_name_2.dev_attr.attr,
>> +    &iio_dev_attr_oneshot_oversampling_ratio.dev_attr.attr,
>> +    &iio_dev_attr_continuous_oversampling_ratio.dev_attr.attr,
>> +    &iio_dev_attr_oneshot_vbus_conv_time.dev_attr.attr,
>> +    &iio_dev_attr_continuous_vbus_conv_time.dev_attr.attr,
>> +    &iio_dev_attr_oneshot_vshunt_conv_time.dev_attr.attr,
>> +    &iio_dev_attr_continuous_vshunt_conv_time.dev_attr.attr,
>> +    &iio_dev_attr_operating_mode.dev_attr.attr,
> Was about to complain about docs then noticed patch 3.  There's a lot
> here and it's mostly non standard.. hmm.
>> +    NULL,
>> +};
>> +
>> +static const struct attribute_group ina3221_groups = {
>> +    .attrs = ina3221_attributes,
>> +};
>> +
>> +#define channel_type(_type, _add, _channel, _name) {                        
>> \
>> +    .type = _type,                                                  \
>> +    .indexed = 1,                                                   \
>> +    .address = _add,                                                \
>> +    .channel = _channel,                                            \
>> +    .extend_name = _name,                                           \
>> +    .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED)              \
>> +}
>> +
>> +#define channel_spec(ch)                                                   \
>> +    channel_type(IIO_VOLTAGE, 0, ch, NULL),                                \
>> +    channel_type(IIO_CURRENT, INA3221_MEASURED_VALUE, ch, NULL),           \
>> +    channel_type(IIO_CURRENT, INA3221_CRIT_CURRENT_LIMIT, ch, "warning"),  \
>> +    channel_type(IIO_CURRENT, INA3221_WARN_CURRENT_LIMIT, ch, "critical"), \
> These aren't channels that I can see but rather events on a given channel.
> There's an issue here as well in that IIO doesn't currently support two
> events of the same type on a single channel - our event codes have no
> way of distguishing between them.  This needs fixing but we haven't done
> it yet.
> 
> Also these particular events do make this seem rather more or a power
> monitoring chip than we'd normally expect to see in IIO - hence the
> need for that justification in the patch description ;)
>> +    channel_type(IIO_POWER, 0, ch, NULL)
>> +
>> +static const struct iio_chan_spec ina3221_channels_spec[] = {
>> +    channel_spec(0),
>> +    channel_spec(1),
>> +    channel_spec(2),
>> +};
>> +
>> +static const struct iio_info ina3221_info = {
>> +    .driver_module = THIS_MODULE,
>> +    .attrs = &ina3221_groups,
>> +    .read_raw = ina3221_read_raw,
>> +    .write_raw = ina3221_write_raw,
>> +};
>> +
>> +static int ina3221_process_pdata(struct ina3221_chip_info *chip,
>> +                             struct ina3221_platform_data *pdata)
>> +{
>> +    unsigned int o_conf;
>> +    unsigned int c_conf;
>> +    int ret;
>> +
>> +    o_conf = 0x7 << 12;
>> +    c_conf = pdata->active_channel << 12;
>> +
>> +    o_conf |= (chip->pdata->enable_power) ? 0x3 : 0x2;
>> +    c_conf |= (chip->pdata->enable_power) ? 0x7 : 0x6;
>> +
>> +    ret = ina3221_set_average(chip, pdata->oneshot_avg_sample, &o_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +    ret = ina3221_set_average(chip, pdata->cont_avg_sample, &c_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    ret = ina3221_set_int_time_vbus(chip, pdata->oneshot_vbus_conv_time,
>> +                                    &o_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    ret = ina3221_set_int_time_vbus(chip, pdata->cont_vbus_conv_time,
>> +                                    &c_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    ret = ina3221_set_int_time_vshunt(chip, pdata->oneshot_shunt_conv_time,
>> +                                      &o_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    ret = ina3221_set_int_time_vshunt(chip, pdata->cont_shunt_conv_time,
>> +                                      &c_conf);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    chip->oneshot_config = o_conf;
>> +    chip->continuous_config = c_conf;
>> +    return 0;
>> +}
>> +
> There is a lot of moderately controversial stuff in here - I'll reply to
> the binding doc instead of here on these though.
>> +static int ina3221_get_platform_data_dt(struct ina3221_chip_info *chip)
>> +{
>> +    struct device *dev = chip->dev;
>> +    struct device_node *np = dev->of_node;
>> +    struct device_node *np_chan;
>> +    struct ina3221_platform_data *pdata;
>> +    struct ina3221_channel_data *cdata;
>> +    char channel_name[20];
>> +    u32 value;
>> +    int curr_ua;
>> +    int id;
>> +    int ret;
>> +
>> +    pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
>> +    if (!pdata)
>> +            return -ENOMEM;
>> +
>> +    chip->pdata = pdata;
>> +
>> +    ret = of_property_read_u32(np, "one-shot-average-sample", &value);
>> +    if (!ret)
>> +            pdata->oneshot_avg_sample = value;
>> +    else
>> +            pdata->oneshot_avg_sample = INA3221_CONFIG_AVG_SAMPLE_DEFAULT;
>> +
>> +    ret = of_property_read_u32(np, "one-shot-vbus-conv-time-us", &value);
>> +    if (!ret)
>> +            pdata->oneshot_vbus_conv_time = value;
>> +    else
>> +            pdata->oneshot_vbus_conv_time =
>> +                                    INA3221_CONFIG_VBUS_CONV_TIME_DEFAULT;
>> +
>> +    ret = of_property_read_u32(np, "one-shot-shunt-conv-time-us",
>> +                               &value);
>> +    if (!ret)
>> +            pdata->oneshot_shunt_conv_time = value;
>> +    else
>> +            pdata->oneshot_shunt_conv_time =
>> +                                    INA3221_CONFIG_SHUNT_CONV_TIME_DEFAULT;
>> +
>> +    ret = of_property_read_u32(np, "continuous-average-sample", &value);
>> +    if (!ret)
>> +            pdata->cont_avg_sample = value;
>> +    else
>> +            pdata->cont_avg_sample = INA3221_CONFIG_AVG_SAMPLE_DEFAULT;
>> +
>> +    ret = of_property_read_u32(np, "continuous-vbus-conv-time-us", &value);
>> +    if (!ret)
>> +            pdata->cont_vbus_conv_time = value;
>> +    else
>> +            pdata->cont_vbus_conv_time =
>> +                                    INA3221_CONFIG_VBUS_CONV_TIME_DEFAULT;
>> +
>> +    ret = of_property_read_u32(np, "continuous-shunt-conv-time-us", &value);
>> +    if (!ret)
>> +            pdata->cont_shunt_conv_time = value;
>> +    else
>> +            pdata->cont_shunt_conv_time =
>> +                                    INA3221_CONFIG_SHUNT_CONV_TIME_DEFAULT;
>> +
>> +    pdata->enable_power =  of_property_read_bool(np,
>> +                                                 "enable-power-monitor");
>> +    pdata->continuous_mode = of_property_read_bool(np,
>> +                                            "enable-continuous-mode");
>> +    pdata->warn_alert =  of_property_read_bool(np, "enable-warning-alert");
>> +    pdata->crit_alert =  of_property_read_bool(np, "enable-critical-alert");
>> +
>> +    for (id = 0; id < INA3221_NUMBER_OF_CHANNELS; ++id) {
>> +            sprintf(channel_name, "channel%d", id);
>> +            np_chan = of_get_child_by_name(np, channel_name);
>> +            if (!np_chan)
>> +                    continue;
>> +
>> +            cdata = &pdata->channel_data[id];
>> +
>> +            ret = of_property_read_string(np_chan, "label", &cdata->name);
>> +            if (ret < 0) {
>> +                    dev_err(dev, "Channel %s does not have label\n",
>> +                            np_chan->full_name);
>> +                    continue;
>> +            }
>> +
>> +            ret = of_property_read_u32(np_chan,
>> +                                       "warning-current-limit-microamp",
>> +                                       &value);
>> +            cdata->warn_limits = (!ret) ? value : ret;
>> +
>> +            ret = of_property_read_u32(np_chan,
>> +                                       "critical-current-limit-microamp",
>> +                                       &value);
>> +            cdata->crit_limits = (!ret) ? value : ret;
>> +
>> +            ret = of_property_read_u32(np_chan, "shunt-resistor-mohm",
>> +                                       &value);
>> +            if (!ret)
>> +                    cdata->shunt_resistance = value;
>> +
>> +            pdata->active_channel |= BIT(INA3221_NUMBER_OF_CHANNELS - id -
>> +                                         1);
>> +
>> +            if (cdata->crit_limits < 0) {
>> +                    ret = ina3221_read_default_channel_critical(chip, id,
>> +                                                                &curr_ua);
>> +                    if (ret < 0)
>> +                            return ret;
>> +                    cdata->crit_limits = curr_ua;
>> +            } else {
>> +                    ret = ina3221_set_channel_critical(chip, id,
>> +                                                       cdata->crit_limits);
>> +                    if (ret < 0)
>> +                            return ret;
>> +            }
>> +
>> +            if (cdata->warn_limits < 0) {
>> +                    ret = ina3221_read_default_channel_warning(chip, id,
>> +                                                               &curr_ua);
>> +                    if (ret < 0)
>> +                            return ret;
>> +                    cdata->warn_limits = curr_ua;
>> +            } else {
>> +                    ret = ina3221_set_channel_warning(chip, id,
>> +                                                      cdata->warn_limits);
>> +                    if (ret < 0)
>> +                            return ret;
>> +            }
>> +    }
>> +
>> +    if (!pdata->active_channel)
>> +            return -EINVAL;
>> +
>> +    ret = ina3221_process_pdata(chip, pdata);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to process platform data: %d\n",
>> +                    ret);
>> +            return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_do_reset(struct ina3221_chip_info *chip)
>> +{
>> +    int ret;
>> +
>> +    ret = regmap_update_bits(chip->rmap, INA3221_CONFIG,
>> +                             INA3221_CONFIG_RESET_MASK,
>> +                             INA3221_CONFIG_RESET_EN);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to reset device: %d\n", ret);
>> +            return ret;
>> +    }
>> +
>> +    ret = regmap_update_bits(chip->rmap, INA3221_CONFIG,
>> +                             INA3221_CONFIG_RESET_MASK, 0);
>> +    if (ret < 0) {
>> +            dev_err(chip->dev, "Failed to reset device: %d\n", ret);
>> +            return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int ina3221_probe(struct i2c_client *client,
>> +                     const struct i2c_device_id *id)
>> +{
>> +    struct ina3221_chip_info *chip;
>> +    struct iio_dev *indio_dev;
>> +    int ret;
>> +
>> +    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
>> +    if (!indio_dev)
>> +            return -ENOMEM;
>> +
>> +    chip = iio_priv(indio_dev);
>> +
>> +    chip->rmap = devm_regmap_init_i2c(client, &ina3221_regmap_config);
>> +    if (IS_ERR(chip->rmap)) {
>> +            ret = PTR_ERR(chip->rmap);
>> +            dev_err(&client->dev, "Failed to initialise regmap: %d\n", ret);
>> +            return ret;
>> +    }
>> +
>> +    chip->dev = &client->dev;
>> +    mutex_init(&chip->state_lock);
>> +    i2c_set_clientdata(client, indio_dev);
>> +
>> +    ret = ina3221_do_reset(chip);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    ret = ina3221_get_platform_data_dt(chip);
>> +    if (ret < 0) {
>> +            dev_err(&client->dev, "Failed to get platform data: %d\n", ret);
>> +            return ret;
>> +    }
>> +
>> +    ret = ina3221_set_operating_mode(chip, chip->pdata->continuous_mode);
>> +    if (ret < 0)
>> +            return ret;
>> +
>> +    indio_dev->modes = INDIO_DIRECT_MODE;
>> +    indio_dev->dev.parent = &client->dev;
>> +    indio_dev->channels = ina3221_channels_spec;
>> +    indio_dev->num_channels = ARRAY_SIZE(ina3221_channels_spec);
>> +    indio_dev->name = id->name;
>> +    indio_dev->info = &ina3221_info;
>> +
>> +    return iio_device_register(indio_dev);
>> +}
>> +
>> +static int ina3221_remove(struct i2c_client *client)
>> +{
>> +    struct iio_dev *indio_dev = i2c_get_clientdata(client);
>> +    struct ina3221_chip_info *chip = iio_priv(indio_dev);
>> +
>> +    iio_device_unregister(indio_dev);
>> +
>> +    /* Powerdown */
>> +    return regmap_update_bits(chip->rmap, INA3221_CONFIG,
>> +                              INA3221_CONFIG_MODE_MASK,
>> +                              INA3221_CONFIG_MODE_POWER_DOWN);
>> +}
>> +
>> +static const struct i2c_device_id ina3221_id[] = {
>> +    {.name = "ina3221"},
>> +    {},
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ina3221_id);
>> +
>> +static struct i2c_driver ina3221_driver = {
>> +    .driver = {
>> +               .name = "ina3221",
>> +    },
>> +    .probe = ina3221_probe,
>> +    .remove = ina3221_remove,
>> +    .id_table = ina3221_id,
>> +};
>> +module_i2c_driver(ina3221_driver);
>> +
>> +MODULE_DESCRIPTION("Texas Instruments INA3221 ADC driver");
>> +MODULE_AUTHOR("Laxman Dewangan <ldewan...@nvidia.com>");
>> +MODULE_LICENSE("GPL v2");
>>
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majord...@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to