]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: adc: add support for pac1921
authorMatteo Martelli <matteomartelli3@gmail.com>
Wed, 24 Jul 2024 09:08:33 +0000 (11:08 +0200)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sat, 3 Aug 2024 09:13:44 +0000 (10:13 +0100)
Add support for Microchip PAC1921 Power/Current monitor.

Implemented features:
* capture of bus voltage, sense voltage, current and power measurements
  in free-run integration mode
* support for both raw and triggered buffer reading
* support for overflow events
* scale attributes to control voltage and current gains
* oversampling ratio attribute to control the number of integration
  samples
* sampling rate attribute that reflects the integration period
* userspace attribute and DT parameter to control shunt resistor
* simple power management support

Limitations:
* operation mode fixed to free-run integration
* READ/INT pin and OUT pin not supported
* no controls for measurement resolutions and filters

Signed-off-by: Matteo Martelli <matteomartelli3@gmail.com>
Link: https://patch.msgid.link/20240724-iio-pac1921-v4-3-723698e903a3@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
MAINTAINERS
drivers/iio/adc/Kconfig
drivers/iio/adc/Makefile
drivers/iio/adc/pac1921.c [new file with mode: 0644]

index aece4cf6e81d49e13ee0f98c3a073e89ee30b889..9dbdb0ce30c022f05482577f50874deee34784ee 100644 (file)
@@ -15054,6 +15054,13 @@ F:     Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
 F:     drivers/nvmem/microchip-otpc.c
 F:     include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
 
+MICROCHIP PAC1921 POWER/CURRENT MONITOR DRIVER
+M:     Matteo Martelli <matteomartelli3@gmail.com>
+L:     linux-iio@vger.kernel.org
+S:     Supported
+F:     Documentation/devicetree/bindings/iio/adc/microchip,pac1921.yaml
+F:     drivers/iio/adc/pac1921.c
+
 MICROCHIP PAC1934 POWER/ENERGY MONITOR DRIVER
 M:     Marius Cristea <marius.cristea@microchip.com>
 L:     linux-iio@vger.kernel.org
index 81464bb209ea4040f8d98803bc3414f46ec8613a..bd028d59db63778c17ead67c0f2276c4e7424a2e 100644 (file)
@@ -1002,6 +1002,19 @@ config NPCM_ADC
          This driver can also be built as a module. If so, the module
          will be called npcm_adc.
 
+config PAC1921
+       tristate "Microchip Technology PAC1921 driver"
+       depends on I2C
+       select REGMAP_I2C
+       select IIO_BUFFER
+       select IIO_TRIGGERED_BUFFER
+       help
+         Say yes here to build support for Microchip Technology's PAC1921
+         High-Side Power/Current Monitor with Analog Output.
+
+         This driver can also be built as a module. If so, the module
+         will be called pac1921.
+
 config PAC1934
        tristate "Microchip Technology PAC1934 driver"
        depends on I2C
index b1bda281c10fffbf3aaceea9a59c3203c45c383e..21c515986f1731006a6b2c2c5a624d158f4ebb37 100644 (file)
@@ -91,6 +91,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
 obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
+obj-$(CONFIG_PAC1921) += pac1921.o
 obj-$(CONFIG_PAC1934) += pac1934.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
 obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o
diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c
new file mode 100644 (file)
index 0000000..d04c668
--- /dev/null
@@ -0,0 +1,1261 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for PAC1921 High-Side Power/Current Monitor
+ *
+ * Copyright (C) 2024 Matteo Martelli <matteomartelli3@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+/* pac1921 registers */
+#define PAC1921_REG_GAIN_CFG           0x00
+#define PAC1921_REG_INT_CFG            0x01
+#define PAC1921_REG_CONTROL            0x02
+#define PAC1921_REG_VBUS               0x10
+#define PAC1921_REG_VSENSE             0x12
+#define PAC1921_REG_OVERFLOW_STS       0x1C
+#define PAC1921_REG_VPOWER             0x1D
+
+/* pac1921 gain configuration bits */
+#define PAC1921_GAIN_DI_GAIN_MASK      GENMASK(5, 3)
+#define PAC1921_GAIN_DV_GAIN_MASK      GENMASK(2, 0)
+
+/* pac1921 integration configuration bits */
+#define PAC1921_INT_CFG_SMPL_MASK      GENMASK(7, 4)
+#define PAC1921_INT_CFG_VSFEN          BIT(3)
+#define PAC1921_INT_CFG_VBFEN          BIT(2)
+#define PAC1921_INT_CFG_RIOV           BIT(1)
+#define PAC1921_INT_CFG_INTEN          BIT(0)
+
+/* pac1921 control bits */
+#define PAC1921_CONTROL_MXSL_MASK      GENMASK(7, 6)
+enum pac1921_mxsl {
+       PAC1921_MXSL_VPOWER_PIN = 0,
+       PAC1921_MXSL_VSENSE_FREE_RUN = 1,
+       PAC1921_MXSL_VBUS_FREE_RUN = 2,
+       PAC1921_MXSL_VPOWER_FREE_RUN = 3,
+};
+#define PAC1921_CONTROL_SLEEP          BIT(2)
+
+/* pac1921 result registers mask and resolution */
+#define PAC1921_RES_MASK               GENMASK(15, 6)
+#define PAC1921_RES_RESOLUTION         1023
+
+/* pac1921 overflow status bits */
+#define PAC1921_OVERFLOW_VSOV          BIT(2)
+#define PAC1921_OVERFLOW_VBOV          BIT(1)
+#define PAC1921_OVERFLOW_VPOV          BIT(0)
+
+/* pac1921 constants */
+#define PAC1921_MAX_VSENSE_MV          100
+#define PAC1921_MAX_VBUS_V             32
+/* Time to first communication after power up (tINT_T) */
+#define PAC1921_POWERUP_TIME_MS                20
+/* Time from Sleep State to Start of Integration Period (tSLEEP_TO_INT) */
+#define PAC1921_SLEEP_TO_INT_TIME_US   86
+
+/* pac1921 defaults */
+#define PAC1921_DEFAULT_DV_GAIN                0 /* 2^(value): 1x gain (HW default) */
+#define PAC1921_DEFAULT_DI_GAIN                0 /* 2^(value): 1x gain (HW default) */
+#define PAC1921_DEFAULT_NUM_SAMPLES    0 /* 2^(value): 1 sample (HW default) */
+
+/*
+ * Pre-computed scale factors for BUS voltage
+ * format: IIO_VAL_INT_PLUS_NANO
+ * unit: mV
+ *
+ * Vbus scale (mV) = max_vbus (mV) / dv_gain / resolution
+ */
+static const int pac1921_vbus_scales[][2] = {
+       { 31, 280547409 },      /* dv_gain x1 */
+       { 15, 640273704 },      /* dv_gain x2 */
+       { 7, 820136852 },       /* dv_gain x4 */
+       { 3, 910068426 },       /* dv_gain x8 */
+       { 1, 955034213 },       /* dv_gain x16 */
+       { 0, 977517106 },       /* dv_gain x32 */
+};
+
+/*
+ * Pre-computed scales for SENSE voltage
+ * format: IIO_VAL_INT_PLUS_NANO
+ * unit: mV
+ *
+ * Vsense scale (mV) = max_vsense (mV) / di_gain / resolution
+ */
+static const int pac1921_vsense_scales[][2] = {
+       { 0, 97751710 },        /* di_gain x1 */
+       { 0, 48875855 },        /* di_gain x2 */
+       { 0, 24437927 },        /* di_gain x4 */
+       { 0, 12218963 },        /* di_gain x8 */
+       { 0, 6109481 },         /* di_gain x16 */
+       { 0, 3054740 },         /* di_gain x32 */
+       { 0, 1527370 },         /* di_gain x64 */
+       { 0, 763685 },          /* di_gain x128 */
+};
+
+/*
+ * Numbers of samples used to integrate measurements at the end of an
+ * integration period.
+ *
+ * Changing the number of samples affects the integration period: higher the
+ * number of samples, longer the integration period.
+ *
+ * These correspond to the oversampling ratios available exposed to userspace.
+ */
+static const int pac1921_int_num_samples[] = {
+       1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048
+};
+
+/*
+ * The integration period depends on the configuration of number of integration
+ * samples, measurement resolution and post filters. The following array
+ * contains integration periods, in microsecs unit, based on table 4-5 from
+ * datasheet considering power integration mode, 14-Bit resolution and post
+ * filters on. Each index corresponds to a specific number of samples from 1
+ * to 2048.
+ */
+static const unsigned int pac1921_int_periods_usecs[] = {
+       2720,           /* 1 sample */
+       4050,           /* 2 samples */
+       6790,           /* 4 samples */
+       12200,          /* 8 samples */
+       23000,          /* 16 samples */
+       46000,          /* 32 samples */
+       92000,          /* 64 samples */
+       184000,         /* 128 samples */
+       368000,         /* 256 samples */
+       736000,         /* 512 samples */
+       1471000,        /* 1024 samples */
+       2941000         /* 2048 samples */
+};
+
+/* pac1921 regmap configuration */
+static const struct regmap_range pac1921_regmap_wr_ranges[] = {
+       regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL),
+};
+
+static const struct regmap_access_table pac1921_regmap_wr_table = {
+       .yes_ranges = pac1921_regmap_wr_ranges,
+       .n_yes_ranges = ARRAY_SIZE(pac1921_regmap_wr_ranges),
+};
+
+static const struct regmap_range pac1921_regmap_rd_ranges[] = {
+       regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL),
+       regmap_reg_range(PAC1921_REG_VBUS, PAC1921_REG_VPOWER + 1),
+};
+
+static const struct regmap_access_table pac1921_regmap_rd_table = {
+       .yes_ranges = pac1921_regmap_rd_ranges,
+       .n_yes_ranges = ARRAY_SIZE(pac1921_regmap_rd_ranges),
+};
+
+static const struct regmap_config pac1921_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .rd_table = &pac1921_regmap_rd_table,
+       .wr_table = &pac1921_regmap_wr_table,
+};
+
+enum pac1921_channels {
+       PAC1921_CHAN_VBUS = 0,
+       PAC1921_CHAN_VSENSE = 1,
+       PAC1921_CHAN_CURRENT = 2,
+       PAC1921_CHAN_POWER = 3,
+};
+#define PAC1921_NUM_MEAS_CHANS 4
+
+struct pac1921_priv {
+       struct i2c_client *client;
+       struct regmap *regmap;
+       struct regulator *vdd;
+       struct iio_info iio_info;
+
+       /*
+        * Synchronize access to private members, and ensure atomicity of
+        * consecutive regmap operations.
+        */
+       struct mutex lock;
+
+       u32 rshunt_uohm; /* uOhm */
+       u8 dv_gain;
+       u8 di_gain;
+       u8 n_samples;
+       u8 prev_ovf_flags;
+       u8 ovf_enabled_events;
+
+       bool first_integr_started;
+       bool first_integr_done;
+       unsigned long integr_started_time_jiffies;
+       unsigned int integr_period_usecs;
+
+       int current_scales[ARRAY_SIZE(pac1921_vsense_scales)][2];
+
+       struct {
+               u16 chan[PAC1921_NUM_MEAS_CHANS];
+               s64 timestamp __aligned(8);
+       } scan;
+};
+
+/*
+ * Check if first integration after configuration update has completed.
+ *
+ * Must be called with lock held.
+ */
+static bool pac1921_data_ready(struct pac1921_priv *priv)
+{
+       if (!priv->first_integr_started)
+               return false;
+
+       if (!priv->first_integr_done) {
+               unsigned long t_ready;
+
+               /*
+                * Data valid after the device entered into integration state,
+                * considering worst case where the device was in sleep state,
+                * and completed the first integration period.
+                */
+               t_ready = priv->integr_started_time_jiffies +
+                         usecs_to_jiffies(PAC1921_SLEEP_TO_INT_TIME_US) +
+                         usecs_to_jiffies(priv->integr_period_usecs);
+
+               if (time_before(jiffies, t_ready))
+                       return false;
+
+               priv->first_integr_done = true;
+       }
+
+       return true;
+}
+
+static inline void pac1921_calc_scale(int dividend, int divisor, int *val,
+                                     int *val2)
+{
+       s64 tmp;
+
+       tmp = div_s64(dividend * (s64)NANO, divisor);
+       *val = (int)div_s64_rem(tmp, NANO, val2);
+}
+
+/*
+ * Fill the table of scale factors for current
+ * format: IIO_VAL_INT_PLUS_NANO
+ * unit: mA
+ *
+ * Vsense LSB (nV) = max_vsense (nV) * di_gain / resolution
+ * Current scale (mA) = Vsense LSB (nV) / shunt (uOhm)
+ *
+ * Must be called with held lock when updating after first initialization.
+ */
+static void pac1921_calc_current_scales(struct pac1921_priv *priv)
+{
+       for (unsigned int i = 0; i < ARRAY_SIZE(priv->current_scales); i++) {
+               int max = (PAC1921_MAX_VSENSE_MV * MICRO) >> i;
+               int vsense_lsb = DIV_ROUND_CLOSEST(max, PAC1921_RES_RESOLUTION);
+
+               pac1921_calc_scale(vsense_lsb, (int)priv->rshunt_uohm,
+                                  &priv->current_scales[i][0],
+                                  &priv->current_scales[i][1]);
+       }
+}
+
+/*
+ * Check if overflow occurred and if so, push the corresponding events.
+ *
+ * Must be called with lock held.
+ */
+static int pac1921_check_push_overflow(struct iio_dev *indio_dev, s64 timestamp)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       unsigned int flags;
+       int ret;
+
+       ret = regmap_read(priv->regmap, PAC1921_REG_OVERFLOW_STS, &flags);
+       if (ret)
+               return ret;
+
+       if (flags & PAC1921_OVERFLOW_VBOV &&
+           !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VBOV) &&
+           priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV) {
+               iio_push_event(indio_dev,
+                              IIO_UNMOD_EVENT_CODE(
+                                      IIO_VOLTAGE, PAC1921_CHAN_VBUS,
+                                      IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
+                              timestamp);
+       }
+       if (flags & PAC1921_OVERFLOW_VSOV &&
+           !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VSOV) &&
+           priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV) {
+               iio_push_event(indio_dev,
+                              IIO_UNMOD_EVENT_CODE(
+                                      IIO_VOLTAGE, PAC1921_CHAN_VSENSE,
+                                      IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
+                              timestamp);
+               iio_push_event(indio_dev,
+                              IIO_UNMOD_EVENT_CODE(
+                                      IIO_CURRENT, PAC1921_CHAN_CURRENT,
+                                      IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
+                              timestamp);
+       }
+       if (flags & PAC1921_OVERFLOW_VPOV &&
+           !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VPOV) &&
+           priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV) {
+               iio_push_event(indio_dev,
+                              IIO_UNMOD_EVENT_CODE(
+                                      IIO_POWER, PAC1921_CHAN_POWER,
+                                      IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
+                              timestamp);
+       }
+
+       priv->prev_ovf_flags = (u8)flags;
+
+       return 0;
+}
+
+/*
+ * Read the value from a result register
+ *
+ * Result registers contain the most recent averaged values of Vbus, Vsense and
+ * Vpower. Each value is 10 bits wide and spread across two consecutive 8 bit
+ * registers, with 6 bit LSB zero padding.
+ */
+static int pac1921_read_res(struct pac1921_priv *priv, unsigned long reg,
+                           u16 *val)
+{
+       int ret = regmap_bulk_read(priv->regmap, (unsigned int)reg, val,
+                                  sizeof(*val));
+       if (ret)
+               return ret;
+
+       *val = FIELD_GET(PAC1921_RES_MASK, get_unaligned_be16(val));
+
+       return 0;
+}
+
+static int pac1921_read_raw(struct iio_dev *indio_dev,
+                           struct iio_chan_spec const *chan, int *val,
+                           int *val2, long mask)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+
+       guard(mutex)(&priv->lock);
+
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW: {
+               s64 ts;
+               u16 res_val;
+               int ret;
+
+               if (!pac1921_data_ready(priv))
+                       return -EBUSY;
+
+               ts = iio_get_time_ns(indio_dev);
+
+               ret = pac1921_check_push_overflow(indio_dev, ts);
+               if (ret)
+                       return ret;
+
+               ret = pac1921_read_res(priv, chan->address, &res_val);
+               if (ret)
+                       return ret;
+
+               *val = (int)res_val;
+
+               return IIO_VAL_INT;
+       }
+       case IIO_CHAN_INFO_SCALE:
+               switch (chan->channel) {
+               case PAC1921_CHAN_VBUS:
+                       *val = pac1921_vbus_scales[priv->dv_gain][0];
+                       *val2 = pac1921_vbus_scales[priv->dv_gain][1];
+                       return IIO_VAL_INT_PLUS_NANO;
+
+               case PAC1921_CHAN_VSENSE:
+                       *val = pac1921_vsense_scales[priv->di_gain][0];
+                       *val2 = pac1921_vsense_scales[priv->di_gain][1];
+                       return IIO_VAL_INT_PLUS_NANO;
+
+               case PAC1921_CHAN_CURRENT:
+                       *val = priv->current_scales[priv->di_gain][0];
+                       *val2 = priv->current_scales[priv->di_gain][1];
+                       return IIO_VAL_INT_PLUS_NANO;
+
+               case PAC1921_CHAN_POWER: {
+                       /*
+                        * Power scale factor in mW:
+                        * Current scale (mA) * max_vbus (V) / dv_gain
+                        */
+
+                       /* Get current scale based on di_gain */
+                       int *curr_scale = priv->current_scales[priv->di_gain];
+
+                       /* Convert current_scale from INT_PLUS_NANO to INT */
+                       s64 tmp = curr_scale[0] * (s64)NANO + curr_scale[1];
+
+                       /* Multiply by max_vbus (V) / dv_gain */
+                       tmp *= PAC1921_MAX_VBUS_V >> (int)priv->dv_gain;
+
+                       /* Convert back to INT_PLUS_NANO */
+                       *val = (int)div_s64_rem(tmp, NANO, val2);
+
+                       return IIO_VAL_INT_PLUS_NANO;
+               }
+               default:
+                       return -EINVAL;
+               }
+
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               *val = pac1921_int_num_samples[priv->n_samples];
+               return IIO_VAL_INT;
+
+       case IIO_CHAN_INFO_SAMP_FREQ:
+               /*
+                * The sampling frequency (Hz) is read-only and corresponds to
+                * how often the device provides integrated measurements into
+                * the result registers, thus it's 1/integration_period.
+                * The integration period depends on the number of integration
+                * samples, measurement resolution and post filters.
+                *
+                * 1/(integr_period_usecs/MICRO) = MICRO/integr_period_usecs
+                */
+               *val = MICRO;
+               *val2 = (int)priv->integr_period_usecs;
+               return IIO_VAL_FRACTIONAL;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int pac1921_read_avail(struct iio_dev *indio_dev,
+                             struct iio_chan_spec const *chan,
+                             const int **vals, int *type, int *length,
+                             long mask)
+{
+       switch (mask) {
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               *type = IIO_VAL_INT;
+               *vals = pac1921_int_num_samples;
+               *length = ARRAY_SIZE(pac1921_int_num_samples);
+               return IIO_AVAIL_LIST;
+       default:
+               return -EINVAL;
+       }
+}
+
+/*
+ * Perform configuration update sequence: set the device into read state, then
+ * write the config register and set the device back into integration state.
+ * Also reset integration start time and mark first integration to be yet
+ * completed.
+ *
+ * Must be called with lock held.
+ */
+static int pac1921_update_cfg_reg(struct pac1921_priv *priv, unsigned int reg,
+                                 unsigned int mask, unsigned int val)
+{
+       /* Enter READ state before configuration */
+       int ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
+                                    PAC1921_INT_CFG_INTEN, 0);
+       if (ret)
+               return ret;
+
+       /* Update configuration value */
+       ret = regmap_update_bits(priv->regmap, reg, mask, val);
+       if (ret)
+               return ret;
+
+       /* Re-enable integration */
+       ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
+                                PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN);
+       if (ret)
+               return ret;
+
+       /*
+        * Reset integration started time and mark this integration period as
+        * the first one so that new measurements will be considered as valid
+        * only at the end of this integration period.
+        */
+       priv->integr_started_time_jiffies = jiffies;
+       priv->first_integr_done = false;
+
+       return 0;
+}
+
+/*
+ * Retrieve the index of the given scale (represented by scale_val and
+ * scale_val2) from scales_tbl. The returned index (if found) is the log2 of
+ * the gain corresponding to the given scale.
+ *
+ * Must be called with lock held if the scales_tbl can change runtime (e.g. for
+ * the current scales table)
+ */
+static int pac1921_lookup_scale(const int (*const scales_tbl)[2], size_t size,
+                               int scale_val, int scale_val2)
+{
+       for (unsigned int i = 0; i < size; i++)
+               if (scales_tbl[i][0] == scale_val &&
+                   scales_tbl[i][1] == scale_val2)
+                       return (int)i;
+
+       return -EINVAL;
+}
+
+/*
+ * Configure device with the given gain (only if changed)
+ *
+ * Must be called with lock held.
+ */
+static int pac1921_update_gain(struct pac1921_priv *priv, u8 *priv_val, u8 gain,
+                              unsigned int mask)
+{
+       unsigned int reg_val;
+       int ret;
+
+       if (*priv_val == gain)
+               return 0;
+
+       reg_val = (gain << __ffs(mask)) & mask;
+       ret = pac1921_update_cfg_reg(priv, PAC1921_REG_GAIN_CFG, mask, reg_val);
+       if (ret)
+               return ret;
+
+       *priv_val = gain;
+
+       return 0;
+}
+
+/*
+ * Given a scale factor represented by scale_val and scale_val2 with format
+ * IIO_VAL_INT_PLUS_NANO, find the corresponding gain value and write it to the
+ * device.
+ *
+ * Must be called with lock held.
+ */
+static int pac1921_update_gain_from_scale(struct pac1921_priv *priv,
+                                         struct iio_chan_spec const *chan,
+                                         int scale_val, int scale_val2)
+{
+       int ret;
+
+       switch (chan->channel) {
+       case PAC1921_CHAN_VBUS:
+               ret = pac1921_lookup_scale(pac1921_vbus_scales,
+                                          ARRAY_SIZE(pac1921_vbus_scales),
+                                          scale_val, scale_val2);
+               if (ret < 0)
+                       return ret;
+
+               return pac1921_update_gain(priv, &priv->dv_gain, (u8)ret,
+                                          PAC1921_GAIN_DV_GAIN_MASK);
+       case PAC1921_CHAN_VSENSE:
+               ret = pac1921_lookup_scale(pac1921_vsense_scales,
+                                          ARRAY_SIZE(pac1921_vsense_scales),
+                                          scale_val, scale_val2);
+               if (ret < 0)
+                       return ret;
+
+               return pac1921_update_gain(priv, &priv->di_gain, (u8)ret,
+                                          PAC1921_GAIN_DI_GAIN_MASK);
+       case PAC1921_CHAN_CURRENT:
+               ret = pac1921_lookup_scale(priv->current_scales,
+                                          ARRAY_SIZE(priv->current_scales),
+                                          scale_val, scale_val2);
+               if (ret < 0)
+                       return ret;
+
+               return pac1921_update_gain(priv, &priv->di_gain, (u8)ret,
+                                          PAC1921_GAIN_DI_GAIN_MASK);
+       default:
+               return -EINVAL;
+       }
+}
+
+/*
+ * Retrieve the index of the given number of samples from the constant table.
+ * The returned index (if found) is the log2 of the given num_samples.
+ */
+static int pac1921_lookup_int_num_samples(int num_samples)
+{
+       for (unsigned int i = 0; i < ARRAY_SIZE(pac1921_int_num_samples); i++)
+               if (pac1921_int_num_samples[i] == num_samples)
+                       return (int)i;
+
+       return -EINVAL;
+}
+
+/*
+ * Update the device with the given number of integration samples.
+ *
+ * Must be called with lock held.
+ */
+static int pac1921_update_int_num_samples(struct pac1921_priv *priv,
+                                         int num_samples)
+{
+       unsigned int reg_val;
+       u8 n_samples;
+       int ret;
+
+       ret = pac1921_lookup_int_num_samples(num_samples);
+       if (ret < 0)
+               return ret;
+
+       n_samples = (u8)ret;
+
+       if (priv->n_samples == n_samples)
+               return 0;
+
+       reg_val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, n_samples);
+
+       ret = pac1921_update_cfg_reg(priv, PAC1921_REG_INT_CFG,
+                                    PAC1921_INT_CFG_SMPL_MASK, reg_val);
+       if (ret)
+               return ret;
+
+       priv->n_samples = n_samples;
+
+       priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples];
+
+       return 0;
+}
+
+static int pac1921_write_raw_get_fmt(struct iio_dev *indio_dev,
+                                    struct iio_chan_spec const *chan,
+                                    long info)
+{
+       switch (info) {
+       case IIO_CHAN_INFO_SCALE:
+               return IIO_VAL_INT_PLUS_NANO;
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               return IIO_VAL_INT;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int pac1921_write_raw(struct iio_dev *indio_dev,
+                            struct iio_chan_spec const *chan, int val,
+                            int val2, long mask)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+
+       guard(mutex)(&priv->lock);
+
+       switch (mask) {
+       case IIO_CHAN_INFO_SCALE:
+               return pac1921_update_gain_from_scale(priv, chan, val, val2);
+       case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+               return pac1921_update_int_num_samples(priv, val);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int pac1921_read_label(struct iio_dev *indio_dev,
+                             struct iio_chan_spec const *chan, char *label)
+{
+       switch (chan->channel) {
+       case PAC1921_CHAN_VBUS:
+               return sprintf(label, "vbus\n");
+       case PAC1921_CHAN_VSENSE:
+               return sprintf(label, "vsense\n");
+       case PAC1921_CHAN_CURRENT:
+               return sprintf(label, "current\n");
+       case PAC1921_CHAN_POWER:
+               return sprintf(label, "power\n");
+       default:
+               return -EINVAL;
+       }
+}
+
+static int pac1921_read_event_config(struct iio_dev *indio_dev,
+                                    const struct iio_chan_spec *chan,
+                                    enum iio_event_type type,
+                                    enum iio_event_direction dir)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+
+       guard(mutex)(&priv->lock);
+
+       switch (chan->channel) {
+       case PAC1921_CHAN_VBUS:
+               return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV);
+       case PAC1921_CHAN_VSENSE:
+       case PAC1921_CHAN_CURRENT:
+               return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV);
+       case PAC1921_CHAN_POWER:
+               return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int pac1921_write_event_config(struct iio_dev *indio_dev,
+                                     const struct iio_chan_spec *chan,
+                                     enum iio_event_type type,
+                                     enum iio_event_direction dir, int state)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       u8 ovf_bit;
+
+       guard(mutex)(&priv->lock);
+
+       switch (chan->channel) {
+       case PAC1921_CHAN_VBUS:
+               ovf_bit = PAC1921_OVERFLOW_VBOV;
+               break;
+       case PAC1921_CHAN_VSENSE:
+       case PAC1921_CHAN_CURRENT:
+               ovf_bit = PAC1921_OVERFLOW_VSOV;
+               break;
+       case PAC1921_CHAN_POWER:
+               ovf_bit = PAC1921_OVERFLOW_VPOV;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (state)
+               priv->ovf_enabled_events |= ovf_bit;
+       else
+               priv->ovf_enabled_events &= ~ovf_bit;
+
+       return 0;
+}
+
+static int pac1921_read_event_value(struct iio_dev *indio_dev,
+                                   const struct iio_chan_spec *chan,
+                                   enum iio_event_type type,
+                                   enum iio_event_direction dir,
+                                   enum iio_event_info info, int *val,
+                                   int *val2)
+{
+       switch (info) {
+       case IIO_EV_INFO_VALUE:
+               *val = PAC1921_RES_RESOLUTION;
+               return IIO_VAL_INT;
+       default:
+               return -EINVAL;
+       }
+}
+
+static const struct iio_info pac1921_iio = {
+       .read_raw = pac1921_read_raw,
+       .read_avail = pac1921_read_avail,
+       .write_raw = pac1921_write_raw,
+       .write_raw_get_fmt = pac1921_write_raw_get_fmt,
+       .read_label = pac1921_read_label,
+       .read_event_config = pac1921_read_event_config,
+       .write_event_config = pac1921_write_event_config,
+       .read_event_value = pac1921_read_event_value,
+};
+
+static ssize_t pac1921_read_shunt_resistor(struct iio_dev *indio_dev,
+                                           uintptr_t private,
+                                           const struct iio_chan_spec *chan,
+                                           char *buf)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       int vals[2];
+
+       if (chan->channel != PAC1921_CHAN_CURRENT)
+               return -EINVAL;
+
+       guard(mutex)(&priv->lock);
+
+       vals[0] = (int)priv->rshunt_uohm;
+       vals[1] = MICRO;
+
+       return iio_format_value(buf, IIO_VAL_FRACTIONAL, 1, vals);
+}
+
+static ssize_t pac1921_write_shunt_resistor(struct iio_dev *indio_dev,
+                                           uintptr_t private,
+                                           const struct iio_chan_spec *chan,
+                                           const char *buf, size_t len)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       u64 rshunt_uohm;
+       int val, val_fract;
+       int ret;
+
+       if (chan->channel != PAC1921_CHAN_CURRENT)
+               return -EINVAL;
+
+       ret = iio_str_to_fixpoint(buf, 100000, &val, &val_fract);
+       if (ret)
+               return ret;
+
+       rshunt_uohm = (u32)val * MICRO + (u32)val_fract;
+       if (rshunt_uohm == 0 || rshunt_uohm > INT_MAX)
+               return -EINVAL;
+
+       guard(mutex)(&priv->lock);
+
+       priv->rshunt_uohm = (u32)rshunt_uohm;
+
+       pac1921_calc_current_scales(priv);
+
+       return len;
+}
+
+/*
+ * Emit on sysfs the list of available scales contained in scales_tbl
+ *
+ * TODO:: this function can be replaced with iio_format_avail_list() if the
+ * latter will ever be exported.
+ *
+ * Must be called with lock held if the scales_tbl can change runtime (e.g. for
+ * the current scales table)
+ */
+static ssize_t pac1921_format_scale_avail(const int (*const scales_tbl)[2],
+                                         size_t size, char *buf)
+{
+       ssize_t len = 0;
+
+       for (unsigned int i = 0; i < size; i++) {
+               if (i != 0) {
+                       len += sysfs_emit_at(buf, len, " ");
+                       if (len >= PAGE_SIZE)
+                               return -EFBIG;
+               }
+               len += sysfs_emit_at(buf, len, "%d.%09d", scales_tbl[i][0],
+                                    scales_tbl[i][1]);
+               if (len >= PAGE_SIZE)
+                       return -EFBIG;
+       }
+
+       len += sysfs_emit_at(buf, len, "\n");
+       return len;
+}
+
+/*
+ * Read available scales for a specific channel
+ *
+ * NOTE: using extended info insted of iio.read_avail() because access to
+ * current scales must be locked as they depend on shunt resistor which may
+ * change runtime. Caller of iio.read_avail() would access the table unlocked
+ * instead.
+ */
+static ssize_t pac1921_read_scale_avail(struct iio_dev *indio_dev,
+                                       uintptr_t private,
+                                       const struct iio_chan_spec *chan,
+                                       char *buf)
+{
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       const int (*scales_tbl)[2];
+       size_t size;
+
+       switch (chan->channel) {
+       case PAC1921_CHAN_VBUS:
+               scales_tbl = pac1921_vbus_scales;
+               size = ARRAY_SIZE(pac1921_vbus_scales);
+               return pac1921_format_scale_avail(scales_tbl, size, buf);
+
+       case PAC1921_CHAN_VSENSE:
+               scales_tbl = pac1921_vsense_scales;
+               size = ARRAY_SIZE(pac1921_vsense_scales);
+               return pac1921_format_scale_avail(scales_tbl, size, buf);
+
+       case PAC1921_CHAN_CURRENT: {
+               guard(mutex)(&priv->lock);
+               scales_tbl = priv->current_scales;
+               size = ARRAY_SIZE(priv->current_scales);
+               return pac1921_format_scale_avail(scales_tbl, size, buf);
+       }
+       default:
+               return -EINVAL;
+       }
+}
+
+#define PAC1921_EXT_INFO_SCALE_AVAIL {                                 \
+       .name = "scale_available",                                      \
+       .read = pac1921_read_scale_avail,                               \
+       .shared = IIO_SEPARATE,                                         \
+}
+
+static const struct iio_chan_spec_ext_info pac1921_ext_info_voltage[] = {
+       PAC1921_EXT_INFO_SCALE_AVAIL,
+       {}
+};
+
+static const struct iio_chan_spec_ext_info pac1921_ext_info_current[] = {
+       PAC1921_EXT_INFO_SCALE_AVAIL,
+       {
+               .name = "shunt_resistor",
+               .read = pac1921_read_shunt_resistor,
+               .write = pac1921_write_shunt_resistor,
+               .shared = IIO_SEPARATE,
+       },
+       {}
+};
+
+static const struct iio_event_spec pac1921_overflow_event[] = {
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_RISING,
+               .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
+               .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+       },
+};
+
+static const struct iio_chan_spec pac1921_channels[] = {
+       {
+               .type = IIO_VOLTAGE,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_SCALE),
+               .info_mask_shared_by_all =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
+               .info_mask_shared_by_all_available =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .channel = PAC1921_CHAN_VBUS,
+               .address = PAC1921_REG_VBUS,
+               .scan_index = PAC1921_CHAN_VBUS,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 10,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU
+               },
+               .indexed = 1,
+               .event_spec = pac1921_overflow_event,
+               .num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
+               .ext_info = pac1921_ext_info_voltage,
+       },
+       {
+               .type = IIO_VOLTAGE,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_SCALE),
+               .info_mask_shared_by_all =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
+               .info_mask_shared_by_all_available =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .channel = PAC1921_CHAN_VSENSE,
+               .address = PAC1921_REG_VSENSE,
+               .scan_index = PAC1921_CHAN_VSENSE,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 10,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU
+               },
+               .indexed = 1,
+               .event_spec = pac1921_overflow_event,
+               .num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
+               .ext_info = pac1921_ext_info_voltage,
+       },
+       {
+               .type = IIO_CURRENT,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_SCALE),
+               .info_mask_shared_by_all =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
+               .info_mask_shared_by_all_available =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .channel = PAC1921_CHAN_CURRENT,
+               .address = PAC1921_REG_VSENSE,
+               .scan_index = PAC1921_CHAN_CURRENT,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 10,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU
+               },
+               .event_spec = pac1921_overflow_event,
+               .num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
+               .ext_info = pac1921_ext_info_current,
+       },
+       {
+               .type = IIO_POWER,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_SCALE),
+               .info_mask_shared_by_all =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
+                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
+               .info_mask_shared_by_all_available =
+                       BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+               .channel = PAC1921_CHAN_POWER,
+               .address = PAC1921_REG_VPOWER,
+               .scan_index = PAC1921_CHAN_POWER,
+               .scan_type = {
+                       .sign = 'u',
+                       .realbits = 10,
+                       .storagebits = 16,
+                       .endianness = IIO_CPU
+               },
+               .event_spec = pac1921_overflow_event,
+               .num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
+       },
+       IIO_CHAN_SOFT_TIMESTAMP(PAC1921_NUM_MEAS_CHANS),
+};
+
+static irqreturn_t pac1921_trigger_handler(int irq, void *p)
+{
+       struct iio_poll_func *pf = p;
+       struct iio_dev *idev = pf->indio_dev;
+       struct pac1921_priv *priv = iio_priv(idev);
+       int ret;
+       int bit;
+       int ch = 0;
+
+       guard(mutex)(&priv->lock);
+
+       if (!pac1921_data_ready(priv))
+               goto done;
+
+       ret = pac1921_check_push_overflow(idev, pf->timestamp);
+       if (ret)
+               goto done;
+
+       iio_for_each_active_channel(idev, bit) {
+               u16 val;
+
+               ret = pac1921_read_res(priv, idev->channels[ch].address, &val);
+               if (ret)
+                       goto done;
+
+               priv->scan.chan[ch++] = val;
+       }
+
+       iio_push_to_buffers_with_timestamp(idev, &priv->scan, pf->timestamp);
+
+done:
+       iio_trigger_notify_done(idev->trig);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Initialize device by writing initial configuration and putting it into
+ * integration state.
+ *
+ * Must be called with lock held when called after first initialization
+ * (e.g. from pm resume)
+ */
+static int pac1921_init(struct pac1921_priv *priv)
+{
+       unsigned int val;
+       int ret;
+
+       /* Enter READ state before configuration */
+       ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
+                                PAC1921_INT_CFG_INTEN, 0);
+       if (ret)
+               return ret;
+
+       /* Configure gains, use 14-bits measurement resolution (HW default) */
+       val = FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) |
+             FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain);
+       ret = regmap_write(priv->regmap, PAC1921_REG_GAIN_CFG, val);
+       if (ret)
+               return ret;
+
+       /*
+        * Configure integration:
+        * - num of integration samples
+        * - filters enabled (HW default)
+        * - set READ/INT pin override (RIOV) to control operation mode via
+        *   register instead of pin
+        */
+       val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, priv->n_samples) |
+             PAC1921_INT_CFG_VSFEN | PAC1921_INT_CFG_VBFEN |
+             PAC1921_INT_CFG_RIOV;
+       ret = regmap_write(priv->regmap, PAC1921_REG_INT_CFG, val);
+       if (ret)
+               return ret;
+
+       /*
+        * Init control register:
+        * - VPower free run integration mode
+        * - OUT pin full scale range: 3V (HW detault)
+        * - no timeout, no sleep, no sleep override, no recalc (HW defaults)
+        */
+       val = FIELD_PREP(PAC1921_CONTROL_MXSL_MASK,
+                        PAC1921_MXSL_VPOWER_FREE_RUN);
+       ret = regmap_write(priv->regmap, PAC1921_REG_CONTROL, val);
+       if (ret)
+               return ret;
+
+       /* Enable integration */
+       ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
+                                PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN);
+       if (ret)
+               return ret;
+
+       priv->first_integr_started = true;
+       priv->integr_started_time_jiffies = jiffies;
+       priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples];
+
+       return 0;
+}
+
+static int pac1921_suspend(struct device *dev)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       int ret;
+
+       guard(mutex)(&priv->lock);
+
+       priv->first_integr_started = false;
+       priv->first_integr_done = false;
+
+       ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
+                                    PAC1921_INT_CFG_INTEN, 0);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, PAC1921_REG_CONTROL,
+                                PAC1921_CONTROL_SLEEP, PAC1921_CONTROL_SLEEP);
+       if (ret)
+               return ret;
+
+       return regulator_disable(priv->vdd);
+
+}
+
+static int pac1921_resume(struct device *dev)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct pac1921_priv *priv = iio_priv(indio_dev);
+       int ret;
+
+       guard(mutex)(&priv->lock);
+
+       ret = regulator_enable(priv->vdd);
+       if (ret)
+               return ret;
+
+       msleep(PAC1921_POWERUP_TIME_MS);
+
+       return pac1921_init(priv);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pac1921_pm_ops, pac1921_suspend,
+                               pac1921_resume);
+
+static void pac1921_regulator_disable(void *data)
+{
+       struct regulator *regulator = data;
+
+       regulator_disable(regulator);
+}
+
+static int pac1921_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct pac1921_priv *priv;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       priv = iio_priv(indio_dev);
+       priv->client = client;
+       i2c_set_clientdata(client, indio_dev);
+
+       priv->regmap = devm_regmap_init_i2c(client, &pac1921_regmap_config);
+       if (IS_ERR(priv->regmap))
+               dev_err_probe(dev, (int)PTR_ERR(priv->regmap),
+                             "Cannot initialize register map\n");
+
+       devm_mutex_init(dev, &priv->lock);
+
+       priv->dv_gain = PAC1921_DEFAULT_DV_GAIN;
+       priv->di_gain = PAC1921_DEFAULT_DI_GAIN;
+       priv->n_samples = PAC1921_DEFAULT_NUM_SAMPLES;
+
+       ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms",
+                                      &priv->rshunt_uohm);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                                    "Cannot read shunt resistor property\n");
+       if (priv->rshunt_uohm == 0 || priv->rshunt_uohm > INT_MAX)
+               return dev_err_probe(dev, -EINVAL,
+                                    "Invalid shunt resistor: %u\n",
+                                    priv->rshunt_uohm);
+
+       pac1921_calc_current_scales(priv);
+
+       priv->vdd = devm_regulator_get(dev, "vdd");
+       if (IS_ERR(priv->vdd))
+               return dev_err_probe(dev, (int)PTR_ERR(priv->vdd),
+                                    "Cannot get vdd regulator\n");
+
+       ret = regulator_enable(priv->vdd);
+       if (ret)
+               return dev_err_probe(dev, ret, "Cannot enable vdd regulator\n");
+
+       ret = devm_add_action_or_reset(dev, pac1921_regulator_disable,
+                                      priv->vdd);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                       "Cannot add action for vdd regulator disposal\n");
+
+       msleep(PAC1921_POWERUP_TIME_MS);
+
+       ret = pac1921_init(priv);
+       if (ret)
+               return dev_err_probe(dev, ret, "Cannot initialize device\n");
+
+       priv->iio_info = pac1921_iio;
+
+       indio_dev->name = "pac1921";
+       indio_dev->info = &priv->iio_info;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+       indio_dev->channels = pac1921_channels;
+       indio_dev->num_channels = ARRAY_SIZE(pac1921_channels);
+
+       ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+                                             &iio_pollfunc_store_time,
+                                             &pac1921_trigger_handler, NULL);
+       if (ret)
+               return dev_err_probe(dev, ret,
+                                    "Cannot setup IIO triggered buffer\n");
+
+       ret = devm_iio_device_register(dev, indio_dev);
+       if (ret)
+               return dev_err_probe(dev, ret, "Cannot register IIO device\n");
+
+       return 0;
+}
+
+static const struct i2c_device_id pac1921_id[] = {
+       { .name = "pac1921", 0 },
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, pac1921_id);
+
+static const struct of_device_id pac1921_of_match[] = {
+       { .compatible = "microchip,pac1921" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, pac1921_of_match);
+
+static struct i2c_driver pac1921_driver = {
+       .driver  = {
+               .name = "pac1921",
+               .pm = pm_sleep_ptr(&pac1921_pm_ops),
+               .of_match_table = pac1921_of_match,
+       },
+       .probe = pac1921_probe,
+       .id_table = pac1921_id,
+};
+
+module_i2c_driver(pac1921_driver);
+
+MODULE_AUTHOR("Matteo Martelli <matteomartelli3@gmail.com>");
+MODULE_DESCRIPTION("IIO driver for PAC1921 High-Side Power/Current Monitor");
+MODULE_LICENSE("GPL");