]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
iio: meter: ina3221: add meter driver for ina3221
authorLaxman Dewangan <ldewangan@nvidia.com>
Fri, 14 Feb 2014 15:25:54 +0000 (20:55 +0530)
committerLaxman Dewangan <ldewangan@nvidia.com>
Tue, 18 Feb 2014 06:52:04 +0000 (22:52 -0800)
The INA3221 is a three-channel, high-side current and
bus voltage monitor with an I2C interface. The INA3221
monitors both shunt voltage drops and bus supply voltages
in addition to having programmable conversion times and
averaging modes for these signals.

Add IIO based meter driver for this device. This device
supports registration from DT only.

Change-Id: I4cafa8d38176c968d876b3489d203c1a6c89bd5d
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/367785

drivers/staging/iio/meter/Kconfig
drivers/staging/iio/meter/Makefile
drivers/staging/iio/meter/ina3221.c [new file with mode: 0644]

index 39f5659429b1038ecf4bdd66dcec818c1dc7cb58..f2ff82c2b9c2b3c64928e2c12749dac15b04dd77 100644 (file)
@@ -68,4 +68,12 @@ config INA230
          CURRENT/POWER MONITOR with I2C Interface.
          Say Y here if you have INA230 hooked to a I2C bus.
 
+config INA3221
+       tristate "TI INA3221 3-Channel Shunt and Bus Voltage Monitor"
+       depends on I2C
+       help
+         TI INA3221 is Triple-Channel, High-Side Measurement, Shunt and Bus
+         Voltage Monitor with I2C Interface
+         Say Y here if you have INA3221 hooked to a I2C bus.
+
 endmenu
index 60b23e9843475fdc65af6d2d8d86906c7524a3f0..375ffc98074465b9d864369a74b632d7be3dccb3 100644 (file)
@@ -14,3 +14,4 @@ obj-$(CONFIG_ADE7854) += ade7854.o
 obj-$(CONFIG_ADE7854_I2C) += ade7854-i2c.o
 obj-$(CONFIG_ADE7854_SPI) += ade7854-spi.o
 obj-$(CONFIG_INA230) += ina230.o
+obj-$(CONFIG_INA3221) += ina3221.o
diff --git a/drivers/staging/iio/meter/ina3221.c b/drivers/staging/iio/meter/ina3221.c
new file mode 100644 (file)
index 0000000..cfef9bb
--- /dev/null
@@ -0,0 +1,998 @@
+/*
+ * ina3221.c - driver for TI INA3221
+ *
+ * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * 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.
+ *
+ * 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/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+
+#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_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)
+
+#define INA3221_RESET                  0x8000
+#define INA3221_POWER_DOWN             0
+#define INA3221_ENABLE_CHAN            (7 << 12) /* enable all 3 channels */
+#define INA3221_AVG                    (3 << 9) /* 64 averages */
+#define INA3221_VBUS_CT                        (4 << 6) /* Vbus 1.1 mS conv time */
+#define INA3221_VSHUNT_CT              (4 << 3) /* Vshunt 1.1 mS conv time */
+#define INA3221_CONT_MODE              7 /* continuous bus n shunt V measure */
+#define INA3221_TRIG_MODE              3 /* triggered bus n shunt V measure */
+
+#define INA3221_CONT_CONFIG_DATA       (INA3221_ENABLE_CHAN | INA3221_AVG | \
+                                       INA3221_VBUS_CT | INA3221_VSHUNT_CT | \
+                                       INA3221_CONT_MODE) /* 0x7727 */
+
+#define INA3221_TRIG_CONFIG_DATA       (INA3221_ENABLE_CHAN | \
+                                       INA3221_TRIG_MODE) /* 0x7723 */
+#define INA3221_NUMBER_OF_RAILS                3
+
+#define CPU_THRESHOLD                  2
+#define CPU_FREQ_THRESHOLD             102000
+
+#define PACK_MODE_CHAN(mode, chan)     ((mode) | ((chan) << 8))
+#define UNPACK_MODE(address)           ((address) & 0xFF)
+#define UNPACK_CHAN(address)           (((address) >> 8) & 0xFF)
+
+#define U32_MINUS_1    ((u32) -1)
+enum {
+       CHANNEL_NAME = 0,
+       CRIT_CURRENT_LIMIT,
+       RUNNING_MODE,
+       VBUS_VOLTAGE_CURRENT,
+};
+
+enum mode {
+       TRIGGERED = 0,
+       CONTINUOUS,
+       FORCED_CONTINUOUS,
+};
+
+struct ina3221_chan_pdata {
+       const char *rail_name;
+       u32 warn_conf_limits;
+       u32 crit_conf_limits;
+       u32 shunt_resistor;
+};
+
+struct ina3221_platform_data {
+       u16 cont_conf_data;
+       u16 trig_conf_data;
+       struct ina3221_chan_pdata cpdata[INA3221_NUMBER_OF_RAILS];
+};
+
+struct ina3221_chip {
+       struct device *dev;
+       struct i2c_client *client;
+       struct ina3221_platform_data *pdata;
+       struct mutex mutex;
+       int shutdown_complete;
+       int is_suspended;
+       int mode;
+       struct notifier_block nb_hot;
+       struct notifier_block nb_cpufreq;
+};
+
+static int __locked_ina3221_switch_mode(struct ina3221_chip *chip,
+               int cpus, int cpufreq);
+
+static inline struct ina3221_chip *to_ina3221_chip(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct iio_dev *indio_dev = i2c_get_clientdata(client);
+       return iio_priv(indio_dev);
+}
+
+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 __locked_power_down_ina3221(struct ina3221_chip *chip)
+{
+       int ret;
+
+       ret = i2c_smbus_write_word_data(chip->client, INA3221_CONFIG,
+                               INA3221_POWER_DOWN);
+       if (ret < 0)
+               dev_err(chip->dev, "Power down failed: %d", ret);
+       return ret;
+}
+
+static int __locked_power_up_ina3221(struct ina3221_chip *chip, int config)
+{
+       int ret;
+
+       ret = i2c_smbus_write_word_data(chip->client, INA3221_CONFIG,
+                                       cpu_to_be16(config));
+       if (ret < 0)
+               dev_err(chip->dev, "Power up failed: %d\n", ret);
+       return ret;
+}
+
+static int __locked_start_conversion(struct ina3221_chip *chip)
+{
+       int ret = 0;
+
+       if (chip->mode == TRIGGERED)
+               ret = __locked_power_up_ina3221(chip,
+                                       chip->pdata->trig_conf_data);
+       return ret;
+}
+
+static int __locked_end_conversion(struct ina3221_chip *chip)
+{
+       int ret = 0;
+
+       if (chip->mode == TRIGGERED)
+               ret = __locked_power_down_ina3221(chip);
+
+       return ret;
+}
+
+static int __locked_do_conversion(struct ina3221_chip *chip, u16 *vsh,
+                 u16 *vbus, int ch)
+{
+       struct i2c_client *client = chip->client;
+       int ret;
+
+       ret = __locked_start_conversion(chip);
+       if (ret < 0)
+               return ret;
+
+       if (vsh) {
+               ret = i2c_smbus_read_word_data(client, INA3221_SHUNT_VOL(ch));
+               if (ret < 0)
+                       return ret;
+               *vsh = be16_to_cpu(ret);
+       }
+
+       if (vbus) {
+               ret = i2c_smbus_read_word_data(client, INA3221_BUS_VOL(ch));
+               if (ret < 0)
+                       return ret;
+               *vbus = be16_to_cpu(ret);
+       }
+
+       return __locked_end_conversion(chip);
+}
+
+static int ina3221_get_mode(struct ina3221_chip *chip, char *buf)
+{
+       int v;
+
+       mutex_lock(&chip->mutex);
+       v = (chip->mode == TRIGGERED) ? 0 : 1;
+       mutex_unlock(&chip->mutex);
+       return sprintf(buf, "%d\n", v);
+}
+
+static int ina3221_set_mode(struct ina3221_chip *chip,
+               const char *buf, size_t count)
+{
+       int cpufreq;
+       int cpus;
+       long val;
+       int ret = 0;
+
+       if (kstrtol(buf, 10, &val) < 0)
+               return -EINVAL;
+
+       mutex_lock(&chip->mutex);
+       if (val != TRIGGERED) {
+               ret = __locked_power_up_ina3221(chip,
+                               chip->pdata->cont_conf_data);
+               if (!ret)
+                       chip->mode = FORCED_CONTINUOUS;
+       } else if (chip->mode == FORCED_CONTINUOUS) {
+               chip->mode = CONTINUOUS;
+               cpufreq = cpufreq_quick_get(0);
+               cpus = num_online_cpus();
+               ret = __locked_ina3221_switch_mode(chip, cpus, cpufreq);
+       }
+       mutex_unlock(&chip->mutex);
+       return ret ? ret : count;
+}
+
+static int ina3221_get_channel_voltage(struct ina3221_chip *chip,
+               int channel, int *voltage_mv)
+{
+       u16 vbus;
+       int ret;
+
+       mutex_lock(&chip->mutex);
+
+       ret = __locked_do_conversion(chip, NULL, &vbus, channel);
+       if (ret < 0) {
+               dev_err(chip->dev, "Voltage read on channel %d failed: %d\n",
+                       channel, ret);
+               goto exit;
+       }
+       *voltage_mv = busv_register_to_mv(vbus);
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_get_channel_current(struct ina3221_chip *chip,
+               int channel, int trigger, int *current_ma)
+{
+       u16 vsh;
+       int ret = 0;
+
+       mutex_lock(&chip->mutex);
+
+       /* return 0 if INA is off */
+       if (trigger && (chip->mode == TRIGGERED)) {
+               *current_ma = 0;
+               goto exit;
+       }
+
+       ret = __locked_do_conversion(chip, &vsh, NULL, channel);
+       if (ret < 0) {
+               dev_err(chip->dev, "Current read on channel %d failed: %d\n",
+                       channel, ret);
+               goto exit;
+       }
+       *current_ma = shuntv_register_to_ma(vsh,
+                        chip->pdata->cpdata[channel].shunt_resistor);
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_get_channel_power(struct ina3221_chip *chip,
+               int channel, int trigger, int *power_mw)
+{
+       u16 vsh, vbus;
+       int current_ma, voltage_mv;
+       int ret = 0;
+
+       mutex_lock(&chip->mutex);
+
+       if (trigger && (chip->mode == TRIGGERED)) {
+               *power_mw = 0;
+               goto exit;
+       }
+
+       ret = __locked_do_conversion(chip, &vsh, &vbus, channel);
+       if (ret < 0) {
+               dev_err(chip->dev, "Read on channel %d failed: %d\n",
+                       channel, ret);
+               goto exit;
+       }
+
+       current_ma = shuntv_register_to_ma(vsh,
+                       chip->pdata->cpdata[channel].shunt_resistor);
+       voltage_mv = busv_register_to_mv(vbus);
+       *power_mw = (voltage_mv * current_ma) / 1000;
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_get_channel_vbus_voltage_current(struct ina3221_chip *chip,
+               int channel, int *current_ma, int *voltage_mv)
+{
+       u16 vsh, vbus;
+       int ret = 0;
+
+       mutex_lock(&chip->mutex);
+
+       ret = __locked_do_conversion(chip, &vsh, &vbus, channel);
+       if (ret < 0) {
+               dev_err(chip->dev, "Read on channel %d failed: %d\n",
+                       channel, ret);
+               goto exit;
+       }
+
+       *current_ma = shuntv_register_to_ma(vsh,
+                       chip->pdata->cpdata[channel].shunt_resistor);
+       *voltage_mv = busv_register_to_mv(vbus);
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int __locked_set_crit_warn_register(struct ina3221_chip *chip,
+       u32 channel, u32 reg_addr)
+{
+       struct ina3221_chan_pdata *cpdata = &chip->pdata->cpdata[channel];
+       int shunt_volt_limit;
+
+       shunt_volt_limit = cpdata->crit_conf_limits * cpdata->shunt_resistor;
+       shunt_volt_limit = uv_to_shuntv_register(shunt_volt_limit);
+
+       return i2c_smbus_write_word_data(chip->client, reg_addr,
+                               cpu_to_be16(shunt_volt_limit));
+}
+
+static int __locked_set_crit_warn_limits(struct ina3221_chip *chip)
+{
+       struct ina3221_chan_pdata *cpdata;
+       u32 crit_reg_addr;
+       u32 warn_reg_addr;
+       int i;
+       int ret = 0;
+
+       for (i = 0; i < INA3221_NUMBER_OF_RAILS; i++) {
+               crit_reg_addr = INA3221_CRIT(i);
+               warn_reg_addr = INA3221_WARN(i);
+               cpdata = &chip->pdata->cpdata[i];
+
+               if (cpdata->crit_conf_limits != U32_MINUS_1) {
+                       ret = __locked_set_crit_warn_register(chip,
+                                               i, crit_reg_addr);
+                       if (ret < 0)
+                               break;
+               }
+
+               if (cpdata->warn_conf_limits != U32_MINUS_1) {
+                       ret = __locked_set_crit_warn_register(chip,
+                                               i, warn_reg_addr);
+                       if (ret < 0)
+                               break;
+               }
+       }
+       return ret;
+}
+
+static int ina3221_set_channel_critical(struct ina3221_chip *chip,
+       int channel, int curr_limit)
+{
+       struct ina3221_chan_pdata *cpdata = &chip->pdata->cpdata[channel];
+       u32 crit_reg_addr = INA3221_CRIT(channel);
+       int ret;
+
+       mutex_lock(&chip->mutex);
+       cpdata->crit_conf_limits = curr_limit;
+       ret = __locked_set_crit_warn_register(chip, channel, crit_reg_addr);
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_get_channel_critical(struct ina3221_chip *chip,
+       int channel, int *curr_limit)
+{
+       struct ina3221_chan_pdata *cpdata = &chip->pdata->cpdata[channel];
+       u32 crit_reg_addr = INA3221_CRIT(channel);
+       int ret;
+
+       mutex_lock(&chip->mutex);
+
+       /* getting voltage readings in micro volts*/
+       ret = i2c_smbus_read_word_data(chip->client, crit_reg_addr);
+       if (ret < 0) {
+               dev_err(chip->dev, "Channel %d crit register read failed: %d\n",
+                       channel, ret);
+               goto exit;
+       }
+
+       *curr_limit = shuntv_register_to_ma(be16_to_cpu(ret),
+                       cpdata->shunt_resistor);
+       ret = 0;
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int __locked_ina3221_switch_mode(struct ina3221_chip *chip,
+                  int cpus, int cpufreq)
+{
+       int ret = 0;
+
+       switch (chip->mode) {
+       case TRIGGERED:
+               if ((cpus >= CPU_THRESHOLD) ||
+                               (cpufreq >= CPU_FREQ_THRESHOLD)) {
+                       /**
+                        * Turn INA on when cpu frequency crosses threshold or
+                        * number of cpus crosses threshold
+                        */
+                       dev_vdbg(chip->dev, "Turn-on cpus:%d, cpufreq:%d\n",
+                               cpus, cpufreq);
+
+                       ret = __locked_power_up_ina3221(chip,
+                                       chip->pdata->cont_conf_data);
+                       if (ret < 0) {
+                               dev_err(chip->dev, "INA power up failed: %d\n",
+                                       ret);
+                               return ret;
+                       }
+                       chip->mode = CONTINUOUS;
+               }
+               break;
+       case CONTINUOUS:
+                if ((cpus < CPU_THRESHOLD) && (cpufreq < CPU_FREQ_THRESHOLD)) {
+                       /*
+                        * Turn off ina when number of cpu cores on are below
+                        * threshold and cpu frequency are below threshold
+                        */
+                       dev_vdbg(chip->dev, "Turn-off, cpus:%d, cpufreq:%d\n",
+                               cpus, cpufreq);
+
+                       ret = __locked_power_down_ina3221(chip);
+                       if (ret < 0) {
+                               dev_err(chip->dev,
+                                       "INA power down failed:%d\n", ret);
+                               return ret;
+                       }
+                       chip->mode = TRIGGERED;
+               }
+               break;
+       case FORCED_CONTINUOUS:
+       default:
+               break;
+       }
+       return 0;
+}
+
+static int ina3221_cpufreq_notify(struct notifier_block *nb,
+               unsigned long event, void *hcpu)
+{
+       struct ina3221_chip *chip = container_of(nb,
+                                       struct ina3221_chip, nb_cpufreq);
+       int cpufreq;
+       int cpus;
+       int ret = 0;
+
+       if (event != CPUFREQ_POSTCHANGE)
+               return 0;
+
+       mutex_lock(&chip->mutex);
+       if (chip->is_suspended)
+               goto exit;
+
+       cpufreq = ((struct cpufreq_freqs *)hcpu)->new;
+       cpus = num_online_cpus();
+       dev_vdbg(chip->dev, "CPUfreq notified freq:%d cpus:%d\n",
+                       cpufreq, cpus);
+       ret = __locked_ina3221_switch_mode(chip, cpus, cpufreq);
+       if (ret < 0) {
+               dev_err(chip->dev, "INA change mode failed %d\n", ret);
+               goto exit;
+       }
+exit:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_hotplug_notify(struct notifier_block *nb,
+               unsigned long event, void *hcpu)
+{
+       struct ina3221_chip *chip = container_of(nb,
+                                       struct ina3221_chip, nb_hot);
+       int cpus;
+       int cpufreq = 0;
+       int ret = 0;
+
+       if (event == CPU_ONLINE || event == CPU_DEAD) {
+               mutex_lock(&chip->mutex);
+               cpufreq = cpufreq_quick_get(0);
+               cpus = num_online_cpus();
+               dev_vdbg(chip->dev, "hotplug notified cpufreq:%d cpus:%d\n",
+                               cpufreq, cpus);
+               ret = __locked_ina3221_switch_mode(chip, cpus, cpufreq);
+               mutex_unlock(&chip->mutex);
+
+               if (ret < 0)
+                       dev_err(chip->dev, "INA switch mode failed: %d\n", ret);
+       }
+       return ret;
+}
+
+static int ina3221_read_raw(struct iio_dev *indio_dev,
+       struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+       struct ina3221_chip *chip = iio_priv(indio_dev);
+       struct device *dev = chip->dev;
+       int type = chan->type;
+       int channel = chan->channel;
+       int address = chan->address;
+       int ret = 0;
+
+       if (channel >= 3) {
+               dev_err(dev, "Invalid channel Id %d\n", channel);
+               return -EINVAL;
+       }
+       if (mask != IIO_CHAN_INFO_PROCESSED) {
+               dev_err(dev, "Invalid mask 0x%08lx\n", mask);
+               return -EINVAL;
+       }
+
+       switch (type) {
+       case IIO_VOLTAGE:
+               ret = ina3221_get_channel_voltage(chip, channel, val);
+               break;
+
+       case IIO_CURRENT:
+               ret = ina3221_get_channel_current(chip, channel, address, val);
+               break;
+
+       case IIO_POWER:
+               ret = ina3221_get_channel_power(chip, channel, address, val);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       if (!ret)
+               ret = IIO_VAL_INT;
+       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 *chip = iio_priv(indio_dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int mode = UNPACK_MODE(this_attr->address);
+       int channel = UNPACK_CHAN(this_attr->address);
+       int ret;
+       int current_ma;
+       int voltage_mv;
+
+       if (channel >= 3) {
+               dev_err(dev, "Invalid channel Id %d\n", channel);
+               return -EINVAL;
+       }
+
+       switch (mode) {
+       case CHANNEL_NAME:
+               return sprintf(buf, "%s\n",
+                       chip->pdata->cpdata[channel].rail_name);
+
+       case CRIT_CURRENT_LIMIT:
+               ret = ina3221_get_channel_critical(chip, channel, &current_ma);
+               if (!ret)
+                       return sprintf(buf, "%d ma\n", current_ma);
+               return ret;
+
+       case RUNNING_MODE:
+               return ina3221_get_mode(chip, buf);
+
+       case VBUS_VOLTAGE_CURRENT:
+               ret = ina3221_get_channel_vbus_voltage_current(chip,
+                                       channel, &current_ma, &voltage_mv);
+               if (!ret)
+                       return sprintf(buf, "%d %d\n", voltage_mv, current_ma);
+               return ret;
+
+       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 *chip = iio_priv(indio_dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int mode = UNPACK_MODE(this_attr->address);
+       int channel = UNPACK_CHAN(this_attr->address);
+       long val;
+       int current_ma;
+
+       if (channel >= 3) {
+               dev_err(dev, "Invalid channel Id %d\n", channel);
+               return -EINVAL;
+       }
+
+       switch (mode) {
+       case CRIT_CURRENT_LIMIT:
+               if (kstrtol(buf, 10, &val) < 0)
+                       return -EINVAL;
+
+               current_ma = (int) val;
+               return ina3221_set_channel_critical(chip, channel, current_ma);
+
+       case RUNNING_MODE:
+               return ina3221_set_mode(chip, buf, len);
+       }
+       return -EINVAL;
+}
+
+static IIO_DEVICE_ATTR(rail_name_0, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CHANNEL_NAME, 0));
+static IIO_DEVICE_ATTR(rail_name_1, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CHANNEL_NAME, 1));
+static IIO_DEVICE_ATTR(rail_name_2, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CHANNEL_NAME, 2));
+
+static IIO_DEVICE_ATTR(crit_current_limit_0, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CRIT_CURRENT_LIMIT, 0));
+static IIO_DEVICE_ATTR(crit_current_limit_1, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CRIT_CURRENT_LIMIT, 1));
+static IIO_DEVICE_ATTR(crit_current_limit_2, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(CRIT_CURRENT_LIMIT, 2));
+
+static IIO_DEVICE_ATTR(ui_input_0, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(VBUS_VOLTAGE_CURRENT, 0));
+static IIO_DEVICE_ATTR(ui_input_1, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(VBUS_VOLTAGE_CURRENT, 1));
+static IIO_DEVICE_ATTR(ui_input_2, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(VBUS_VOLTAGE_CURRENT, 2));
+
+static IIO_DEVICE_ATTR(running_mode, S_IRUGO | S_IWUSR,
+               ina3221_show_channel, ina3221_set_channel,
+               PACK_MODE_CHAN(RUNNING_MODE, 0));
+
+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_crit_current_limit_0.dev_attr.attr,
+       &iio_dev_attr_crit_current_limit_1.dev_attr.attr,
+       &iio_dev_attr_crit_current_limit_2.dev_attr.attr,
+       &iio_dev_attr_ui_input_0.dev_attr.attr,
+       &iio_dev_attr_ui_input_1.dev_attr.attr,
+       &iio_dev_attr_ui_input_2.dev_attr.attr,
+       &iio_dev_attr_running_mode.dev_attr.attr,
+       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(chan)                                             \
+       channel_type(IIO_VOLTAGE, 0, chan, NULL),                       \
+       channel_type(IIO_CURRENT, 0, chan, NULL),                       \
+       channel_type(IIO_CURRENT, 1, chan, "trigger"),                  \
+       channel_type(IIO_POWER, 0, chan, NULL),                         \
+       channel_type(IIO_POWER, 1, chan, "trigger")
+
+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 = {
+       .attrs = &ina3221_groups,
+       .driver_module = THIS_MODULE,
+       .read_raw = &ina3221_read_raw,
+};
+
+static struct ina3221_platform_data *ina3221_get_platform_data_dt(
+       struct i2c_client *client)
+{
+       struct ina3221_platform_data *pdata;
+       struct device *dev = &client->dev;
+       struct device_node *np = dev->of_node;
+       struct device_node *child;
+       u32 reg;
+       int ret;
+       u32 pval;
+       int valid_channel = 0;
+
+       if (!np) {
+               dev_err(&client->dev, "Only DT supported\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata) {
+               dev_err(&client->dev, "pdata allocation failed\n");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       ret = of_property_read_u32(np, "ti,continuous-config", &pval);
+       if (!ret)
+               pdata->cont_conf_data = (u16)pval;
+
+       ret = of_property_read_u32(np, "ti,trigger-config", &pval);
+       if (!ret)
+               pdata->trig_conf_data = (u16)pval;
+
+       for_each_child_of_node(np, child) {
+               ret = of_property_read_u32(child, "reg", &reg);
+               if (ret || reg >= 3) {
+                       dev_err(dev, "reg property invalid on node %s\n",
+                               child->name);
+                       continue;
+               }
+
+               pdata->cpdata[reg].rail_name =  of_get_property(child,
+                                               "ti,rail-name", NULL);
+               if (!pdata->cpdata[reg].rail_name) {
+                       dev_err(dev, "Rail name is not provided on node %s\n",
+                               child->full_name);
+                       continue;
+               }
+
+               ret = of_property_read_u32(child, "ti,current-warning-limit-ma",
+                               &pval);
+               if (!ret)
+                       pdata->cpdata[reg].warn_conf_limits = pval;
+               else
+                       pdata->cpdata[reg].warn_conf_limits = U32_MINUS_1;
+
+               ret = of_property_read_u32(child,
+                               "ti,current-critical-limit-ma", &pval);
+               if (!ret)
+                       pdata->cpdata[reg].crit_conf_limits = pval;
+               else
+                       pdata->cpdata[reg].crit_conf_limits = U32_MINUS_1;
+
+               ret = of_property_read_u32(child, "ti,shunt-resistor-mohm",
+                               &pval);
+               if (!ret)
+                       pdata->cpdata[reg].shunt_resistor = pval;
+
+               valid_channel++;
+       }
+
+       if (!valid_channel)
+               return ERR_PTR(-EINVAL);
+
+       return pdata;
+}
+
+static int ina3221_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct ina3221_chip *chip;
+       struct iio_dev *indio_dev;
+       struct ina3221_platform_data *pdata;
+       int ret;
+
+       pdata = ina3221_get_platform_data_dt(client);
+       if (IS_ERR(pdata)) {
+               ret = PTR_ERR(pdata);
+               dev_err(&client->dev, "platform data processing failed %d\n",
+                       ret);
+               return ret;
+       }
+
+       indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+       if (!indio_dev) {
+               dev_err(&client->dev, "iio allocation fails\n");
+               return -ENOMEM;
+       }
+
+       chip = iio_priv(indio_dev);
+       chip->dev = &client->dev;
+       chip->client = client;
+       i2c_set_clientdata(client, indio_dev);
+       chip->pdata = pdata;
+       mutex_init(&chip->mutex);
+
+       chip->mode = TRIGGERED;
+       chip->shutdown_complete = 0;
+       chip->is_suspended = 0;
+
+       indio_dev->info = &ina3221_info;
+       indio_dev->channels = ina3221_channels_spec;
+       indio_dev->num_channels = ARRAY_SIZE(ina3221_channels_spec);
+       indio_dev->name = id->name;
+       indio_dev->dev.parent = &client->dev;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+       ret = devm_iio_device_register(chip->dev, indio_dev);
+       if (ret < 0) {
+               dev_err(chip->dev, "iio registration fails with error %d\n",
+                       ret);
+               return ret;
+       }
+
+       /* reset ina3221 */
+       ret = i2c_smbus_write_word_data(client, INA3221_CONFIG,
+               __constant_cpu_to_be16((INA3221_RESET)));
+       if (ret < 0) {
+               dev_err(&client->dev, "ina3221 reset failure status: 0x%x\n",
+                       ret);
+               return ret;
+       }
+
+       chip->nb_hot.notifier_call = ina3221_hotplug_notify;
+       chip->nb_cpufreq.notifier_call = ina3221_cpufreq_notify;
+       register_hotcpu_notifier(&(chip->nb_hot));
+       cpufreq_register_notifier(&(chip->nb_cpufreq),
+                       CPUFREQ_TRANSITION_NOTIFIER);
+
+       ret = __locked_set_crit_warn_limits(chip);
+       if (ret < 0) {
+               dev_info(&client->dev, "Not able to set warn and crit limits!\n");
+               /*Not an error condition, could let the probe continue*/
+       }
+
+       /* set ina3221 to power down mode */
+       ret = __locked_power_down_ina3221(chip);
+       if (ret < 0) {
+               dev_err(&client->dev, "INA power down failed: %d\n", ret);
+               goto exit_pd;
+       }
+       return 0;
+
+exit_pd:
+       unregister_hotcpu_notifier(&(chip->nb_hot));
+       cpufreq_unregister_notifier(&(chip->nb_cpufreq),
+                       CPUFREQ_TRANSITION_NOTIFIER);
+       return ret;
+}
+
+static int ina3221_remove(struct i2c_client *client)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(client);
+       struct ina3221_chip *chip = iio_priv(indio_dev);
+
+       mutex_lock(&chip->mutex);
+       __locked_power_down_ina3221(chip);
+       mutex_unlock(&chip->mutex);
+       unregister_hotcpu_notifier(&(chip->nb_hot));
+       cpufreq_unregister_notifier(&(chip->nb_cpufreq),
+                       CPUFREQ_TRANSITION_NOTIFIER);
+       return 0;
+}
+
+static void ina3221_shutdown(struct i2c_client *client)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(client);
+       struct ina3221_chip *chip = iio_priv(indio_dev);
+
+       mutex_lock(&chip->mutex);
+       __locked_power_down_ina3221(chip);
+       chip->shutdown_complete = 1;
+       mutex_unlock(&chip->mutex);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ina3221_suspend(struct device *dev)
+{
+       struct ina3221_chip *chip = to_ina3221_chip(dev);
+       int ret = 0;
+
+       mutex_lock(&chip->mutex);
+       ret = __locked_power_down_ina3221(chip);
+       if (ret < 0) {
+               dev_err(dev, "INA can't be turned off: 0x%x\n", ret);
+               goto error;
+       }
+       chip->mode = TRIGGERED;
+       chip->is_suspended = 1;
+error:
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+
+static int ina3221_resume(struct device *dev)
+{
+       struct ina3221_chip *chip = to_ina3221_chip(dev);
+       int cpufreq, cpus;
+       int ret = 0;
+
+       mutex_lock(&chip->mutex);
+       cpufreq = cpufreq_quick_get(0);
+       cpus = num_online_cpus();
+       ret = __locked_ina3221_switch_mode(chip, cpus, cpufreq);
+       if (ret < 0)
+               dev_err(dev, "INA can't be turned off/on: 0x%x\n", ret);
+       chip->is_suspended = 0;
+       mutex_unlock(&chip->mutex);
+       return ret;
+}
+#endif
+
+static const struct dev_pm_ops ina3221_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(ina3221_suspend,
+                               ina3221_resume)
+};
+
+static const struct i2c_device_id ina3221_id[] = {
+       {.name = "ina3221x",},
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, ina3221_id);
+
+static struct i2c_driver ina3221_driver = {
+       .driver = {
+               .name   = "ina3221x",
+               .owner = THIS_MODULE,
+               .pm = &ina3221_pm_ops,
+       },
+       .probe          = ina3221_probe,
+       .remove         = ina3221_remove,
+       .shutdown       = ina3221_shutdown,
+       .id_table       = ina3221_id,
+};
+
+module_i2c_driver(ina3221_driver);
+
+MODULE_DESCRIPTION("TI INA3221 3-Channel Shunt and Bus Voltage Monitor");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");