]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: adc: ad4062: Add IIO Events support
authorJorge Marques <jorge.marques@analog.com>
Wed, 17 Dec 2025 12:13:30 +0000 (13:13 +0100)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Wed, 31 Dec 2025 17:59:25 +0000 (17:59 +0000)
Adds support for IIO Events. Optionally, gp0 is assigned as Threshold
Either signal, if not present, fallback to an I3C IBI with the same
role.

Signed-off-by: Jorge Marques <jorge.marques@analog.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/ad4062.c

index afc8b6969cf088ecea03ca40b79911a42d9fc679..2084f0058627d1f199bf5c282e9b87919abc3ae3 100644 (file)
@@ -14,7 +14,9 @@
 #include <linux/i3c/device.h>
 #include <linux/i3c/master.h>
 #include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
 #include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
 #include <linux/iio/trigger.h>
 #include <linux/iio/trigger_consumer.h>
 #include <linux/iio/triggered_buffer.h>
 #define     AD4062_REG_ADC_CONFIG_SCALE_EN_MSK         BIT(4)
 #define AD4062_REG_AVG_CONFIG                          0x23
 #define AD4062_REG_GP_CONF                             0x24
+#define     AD4062_REG_GP_CONF_MODE_MSK_0              GENMASK(2, 0)
 #define     AD4062_REG_GP_CONF_MODE_MSK_1              GENMASK(6, 4)
 #define AD4062_REG_INTR_CONF                           0x25
+#define     AD4062_REG_INTR_CONF_EN_MSK_0              GENMASK(1, 0)
 #define     AD4062_REG_INTR_CONF_EN_MSK_1              GENMASK(5, 4)
 #define AD4062_REG_TIMER_CONFIG                                0x27
 #define     AD4062_REG_TIMER_CONFIG_FS_MASK            GENMASK(7, 4)
+#define AD4062_REG_MAX_LIMIT                           0x29
+#define AD4062_REG_MIN_LIMIT                           0x2B
+#define AD4062_REG_MAX_HYST                            0x2C
+#define AD4062_REG_MIN_HYST                            0x2D
 #define AD4062_REG_MON_VAL                             0x2F
 #define AD4062_REG_ADC_IBI_EN                          0x31
 #define AD4062_REG_ADC_IBI_EN_CONV_TRIGGER             BIT(2)
+#define AD4062_REG_ADC_IBI_EN_MAX                      BIT(1)
+#define AD4062_REG_ADC_IBI_EN_MIN                      BIT(0)
 #define AD4062_REG_FUSE_CRC                            0x40
 #define AD4062_REG_DEVICE_STATUS                       0x41
 #define     AD4062_REG_DEVICE_STATUS_DEVICE_RESET      BIT(6)
 #define AD4060_PROD_ID         0x7A
 #define AD4062_PROD_ID         0x7C
 
+#define AD4062_GP_INTR         0x1
 #define AD4062_GP_DRDY         0x2
 
+#define AD4062_LIMIT_BITS      12
+
 #define AD4062_INTR_EN_NEITHER 0x0
+#define AD4062_INTR_EN_EITHER  0x3
 
 #define AD4062_TCONV_NS                270
 
@@ -149,10 +163,12 @@ struct ad4062_state {
        struct iio_dev *indio_dev;
        struct i3c_device *i3cdev;
        struct regmap *regmap;
+       bool wait_event;
        int vref_uV;
        unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)];
        bool gpo_irq[2];
        u16 sampling_frequency;
+       u16 events_frequency;
        u8 oversamp_ratio;
        u8 conv_sizeof;
        u8 conv_addr;
@@ -188,6 +204,26 @@ static const struct regmap_access_table ad4062_regmap_wr_table = {
        .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges),
 };
 
+static const struct iio_event_spec ad4062_events[] = {
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_EITHER,
+               .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE),
+       },
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_RISING,
+               .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
+                                     BIT(IIO_EV_INFO_HYSTERESIS),
+       },
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_FALLING,
+               .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
+                                     BIT(IIO_EV_INFO_HYSTERESIS),
+       },
+};
+
 #define AD4062_CHAN(bits) {                                                            \
        .type = IIO_VOLTAGE,                                                            \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) |                            \
@@ -199,6 +235,8 @@ static const struct regmap_access_table ad4062_regmap_wr_table = {
        .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),              \
        .indexed = 1,                                                                   \
        .channel = 0,                                                                   \
+       .event_spec = ad4062_events,                                                    \
+       .num_event_specs = ARRAY_SIZE(ad4062_events),                                   \
        .has_ext_scan_type = 1,                                                         \
        .ext_scan_type = ad4062_scan_type_##bits##_s,                                   \
        .num_ext_scan_type = ARRAY_SIZE(ad4062_scan_type_##bits##_s),                   \
@@ -218,6 +256,73 @@ static const struct ad4062_chip_info ad4062_chip_info = {
        .avg_max = 4096,
 };
 
+static ssize_t sampling_frequency_show(struct device *dev,
+                                      struct device_attribute *attr, char *buf)
+{
+       struct ad4062_state *st = iio_priv(dev_to_iio_dev(dev));
+
+       return sysfs_emit(buf, "%d\n", ad4062_conversion_freqs[st->events_frequency]);
+}
+
+static int sampling_frequency_store_dispatch(struct iio_dev *indio_dev,
+                                            const char *buf)
+{
+       struct ad4062_state *st = iio_priv(indio_dev);
+       int val, ret;
+
+       if (st->wait_event)
+               return -EBUSY;
+
+       ret = kstrtoint(buf, 10, &val);
+       if (ret)
+               return ret;
+
+       st->events_frequency = find_closest_descending(val, ad4062_conversion_freqs,
+                                                      ARRAY_SIZE(ad4062_conversion_freqs));
+       return 0;
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf, size_t len)
+{
+       struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+       int ret;
+
+       if (!iio_device_claim_direct(indio_dev))
+               return -EBUSY;
+
+       ret = sampling_frequency_store_dispatch(indio_dev, buf);
+       iio_device_release_direct(indio_dev);
+       return ret ?: len;
+}
+
+static IIO_DEVICE_ATTR_RW(sampling_frequency, 0);
+
+static ssize_t sampling_frequency_available_show(struct device *dev,
+                                                struct device_attribute *attr,
+                                                char *buf)
+{
+       int ret = 0;
+
+       for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++)
+               ret += sysfs_emit_at(buf, ret, "%d%s", ad4062_conversion_freqs[i],
+                                    i != (ARRAY_SIZE(ad4062_conversion_freqs) - 1) ? " " : "\n");
+       return ret;
+}
+
+static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
+
+static struct attribute *ad4062_event_attributes[] = {
+       &iio_dev_attr_sampling_frequency.dev_attr.attr,
+       &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group ad4062_event_attribute_group = {
+       .attrs = ad4062_event_attributes,
+};
+
 static int ad4062_set_oversampling_ratio(struct ad4062_state *st, int val, int val2)
 {
        const u32 _max = st->chip->avg_max;
@@ -344,9 +449,11 @@ static int ad4062_conversion_frequency_set(struct ad4062_state *st, u8 val)
 static int ad4062_set_operation_mode(struct ad4062_state *st,
                                     enum ad4062_operation_mode mode)
 {
+       const unsigned int samp_freq = mode == AD4062_MONITOR_MODE ?
+                                      st->events_frequency : st->sampling_frequency;
        int ret;
 
-       ret = ad4062_conversion_frequency_set(st, st->sampling_frequency);
+       ret = ad4062_conversion_frequency_set(st, samp_freq);
        if (ret)
                return ret;
 
@@ -355,6 +462,17 @@ static int ad4062_set_operation_mode(struct ad4062_state *st,
        if (ret)
                return ret;
 
+       if (mode == AD4062_MONITOR_MODE) {
+               /* Change address pointer to enter monitor mode */
+               struct i3c_priv_xfer xfer_trigger = {
+                       .data.out = &st->conv_addr,
+                       .len = sizeof(st->conv_addr),
+                       .rnw = false,
+               };
+               st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS;
+               return i3c_device_do_priv_xfers(st->i3cdev, &xfer_trigger, 1);
+       }
+
        return regmap_write(st->regmap, AD4062_REG_MODE_SET,
                            AD4062_REG_MODE_SET_ENTER_ADC);
 }
@@ -385,6 +503,13 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c
        if (IS_ERR(scan_type))
                return PTR_ERR(scan_type);
 
+       ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF,
+                                AD4062_REG_GP_CONF_MODE_MSK_0,
+                                FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0,
+                                           AD4062_GP_INTR));
+       if (ret)
+               return ret;
+
        ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF,
                                 AD4062_REG_GP_CONF_MODE_MSK_1,
                                 FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1,
@@ -404,6 +529,13 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c
        if (ret)
                return ret;
 
+       ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF,
+                                AD4062_REG_INTR_CONF_EN_MSK_0,
+                                FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_0,
+                                           AD4062_INTR_EN_EITHER));
+       if (ret)
+               return ret;
+
        ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF,
                                 AD4062_REG_INTR_CONF_EN_MSK_1,
                                 FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_1,
@@ -416,6 +548,19 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c
                                 &st->buf.be16, sizeof(st->buf.be16));
 }
 
+static irqreturn_t ad4062_irq_handler_thresh(int irq, void *private)
+{
+       struct iio_dev *indio_dev = private;
+
+       iio_push_event(indio_dev,
+                      IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0,
+                                           IIO_EV_TYPE_THRESH,
+                                           IIO_EV_DIR_EITHER),
+                      iio_get_time_ns(indio_dev));
+
+       return IRQ_HANDLED;
+}
+
 static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private)
 {
        struct iio_dev *indio_dev = private;
@@ -434,6 +579,14 @@ static void ad4062_ibi_handler(struct i3c_device *i3cdev,
 {
        struct ad4062_state *st = i3cdev_get_drvdata(i3cdev);
 
+       if (st->wait_event) {
+               iio_push_event(st->indio_dev,
+                              IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0,
+                                                   IIO_EV_TYPE_THRESH,
+                                                   IIO_EV_DIR_EITHER),
+                              iio_get_time_ns(st->indio_dev));
+               return;
+       }
        if (iio_buffer_enabled(st->indio_dev))
                iio_trigger_poll_nested(st->trigger);
        else
@@ -529,6 +682,25 @@ static int ad4062_request_irq(struct iio_dev *indio_dev)
        struct device *dev = &st->i3cdev->dev;
        int ret;
 
+       ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp0");
+       if (ret == -EPROBE_DEFER)
+               return ret;
+
+       if (ret < 0) {
+               ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN,
+                                        AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN,
+                                        AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN);
+               if (ret)
+                       return ret;
+       } else {
+               ret = devm_request_threaded_irq(dev, ret, NULL,
+                                               ad4062_irq_handler_thresh,
+                                               IRQF_ONESHOT, indio_dev->name,
+                                               indio_dev);
+               if (ret)
+                       return ret;
+       }
+
        ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp1");
        if (ret == -EPROBE_DEFER)
                return ret;
@@ -720,6 +892,9 @@ static int ad4062_read_chan_raw(struct ad4062_state *st, int *val)
 static int ad4062_read_raw_dispatch(struct ad4062_state *st,
                                    int *val, int *val2, long info)
 {
+       if (st->wait_event)
+               return -EBUSY;
+
        switch (info) {
        case IIO_CHAN_INFO_RAW:
                return ad4062_read_chan_raw(st, val);
@@ -761,6 +936,9 @@ static int ad4062_read_raw(struct iio_dev *indio_dev,
 static int ad4062_write_raw_dispatch(struct ad4062_state *st, int val, int val2,
                                     long info)
 {
+       if (st->wait_event)
+               return -EBUSY;
+
        switch (info) {
        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                return ad4062_set_oversampling_ratio(st, val, val2);
@@ -789,7 +967,224 @@ static int ad4062_write_raw(struct iio_dev *indio_dev,
                return -EBUSY;
 
        ret = ad4062_write_raw_dispatch(st, val, val2, info);
+       iio_device_release_direct(indio_dev);
+       return ret;
+}
+
+static int pm_ad4062_monitor_mode_enable(struct ad4062_state *st)
+{
+       int ret;
+
+       PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm);
+       ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+       if (ret)
+               return ret;
+
+       return ad4062_set_operation_mode(st, AD4062_MONITOR_MODE);
+}
+
+static int ad4062_monitor_mode_enable(struct ad4062_state *st)
+{
+       int ret;
+
+       ret = pm_ad4062_monitor_mode_enable(st);
+       if (ret)
+               return ret;
+
+       pm_runtime_get_noresume(&st->i3cdev->dev);
+       return 0;
+}
 
+static int ad4062_monitor_mode_disable(struct ad4062_state *st)
+{
+       pm_runtime_put_autosuspend(&st->i3cdev->dev);
+       return 0;
+}
+
+static int ad4062_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 ad4062_state *st = iio_priv(indio_dev);
+
+       return st->wait_event;
+}
+
+static int ad4062_write_event_config_dispatch(struct iio_dev *indio_dev,
+                                             bool state)
+{
+       struct ad4062_state *st = iio_priv(indio_dev);
+       int ret;
+
+       if (st->wait_event == state)
+               ret = 0;
+       else if (state)
+               ret = ad4062_monitor_mode_enable(st);
+       else
+               ret = ad4062_monitor_mode_disable(st);
+       if (ret)
+               return ret;
+
+       st->wait_event = state;
+       return 0;
+}
+
+static int ad4062_write_event_config(struct iio_dev *indio_dev,
+                                    const struct iio_chan_spec *chan,
+                                    enum iio_event_type type,
+                                    enum iio_event_direction dir,
+                                    bool state)
+{
+       int ret;
+
+       if (!iio_device_claim_direct(indio_dev))
+               return -EBUSY;
+
+       ret = ad4062_write_event_config_dispatch(indio_dev, state);
+       iio_device_release_direct(indio_dev);
+       return ret;
+}
+
+static int __ad4062_read_event_info_value(struct ad4062_state *st,
+                                         enum iio_event_direction dir, int *val)
+{
+       int ret;
+       u8 reg;
+
+       if (dir == IIO_EV_DIR_RISING)
+               reg = AD4062_REG_MAX_LIMIT;
+       else
+               reg = AD4062_REG_MIN_LIMIT;
+
+       ret = regmap_bulk_read(st->regmap, reg, &st->buf.be16,
+                              sizeof(st->buf.be16));
+       if (ret)
+               return ret;
+
+       *val = sign_extend32(be16_to_cpu(st->buf.be16), AD4062_LIMIT_BITS - 1);
+
+       return 0;
+}
+
+static int __ad4062_read_event_info_hysteresis(struct ad4062_state *st,
+                                              enum iio_event_direction dir, int *val)
+{
+       u8 reg;
+
+       if (dir == IIO_EV_DIR_RISING)
+               reg = AD4062_REG_MAX_HYST;
+       else
+               reg = AD4062_REG_MIN_HYST;
+       return regmap_read(st->regmap, reg, val);
+}
+
+static int ad4062_read_event_config_dispatch(struct iio_dev *indio_dev,
+                                            enum iio_event_direction dir,
+                                            enum iio_event_info info, int *val)
+{
+       struct ad4062_state *st = iio_priv(indio_dev);
+
+       if (st->wait_event)
+               return -EBUSY;
+
+       switch (info) {
+       case IIO_EV_INFO_VALUE:
+               return __ad4062_read_event_info_value(st, dir, val);
+       case IIO_EV_INFO_HYSTERESIS:
+               return __ad4062_read_event_info_hysteresis(st, dir, val);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ad4062_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)
+{
+       int ret;
+
+       if (!iio_device_claim_direct(indio_dev))
+               return -EBUSY;
+
+       ret = ad4062_read_event_config_dispatch(indio_dev, dir, info, val);
+       iio_device_release_direct(indio_dev);
+       return ret ?: IIO_VAL_INT;
+}
+
+static int __ad4062_write_event_info_value(struct ad4062_state *st,
+                                          enum iio_event_direction dir, int val)
+{
+       u8 reg;
+
+       if (val != sign_extend32(val, AD4062_LIMIT_BITS - 1))
+               return -EINVAL;
+       if (dir == IIO_EV_DIR_RISING)
+               reg = AD4062_REG_MAX_LIMIT;
+       else
+               reg = AD4062_REG_MIN_LIMIT;
+       st->buf.be16 = cpu_to_be16(val);
+
+       return regmap_bulk_write(st->regmap, reg, &st->buf.be16,
+                                sizeof(st->buf.be16));
+}
+
+static int __ad4062_write_event_info_hysteresis(struct ad4062_state *st,
+                                               enum iio_event_direction dir, int val)
+{
+       u8 reg;
+
+       if (val > BIT(7) - 1)
+               return -EINVAL;
+       if (dir == IIO_EV_DIR_RISING)
+               reg = AD4062_REG_MAX_HYST;
+       else
+               reg = AD4062_REG_MIN_HYST;
+
+       return regmap_write(st->regmap, reg, val);
+}
+
+static int ad4062_write_event_value_dispatch(struct iio_dev *indio_dev,
+                                            enum iio_event_type type,
+                                            enum iio_event_direction dir,
+                                            enum iio_event_info info, int val)
+{
+       struct ad4062_state *st = iio_priv(indio_dev);
+
+       if (st->wait_event)
+               return -EBUSY;
+
+       switch (type) {
+       case IIO_EV_TYPE_THRESH:
+               switch (info) {
+               case IIO_EV_INFO_VALUE:
+                       return __ad4062_write_event_info_value(st, dir, val);
+               case IIO_EV_INFO_HYSTERESIS:
+                       return __ad4062_write_event_info_hysteresis(st, dir, val);
+               default:
+                       return -EINVAL;
+               }
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ad4062_write_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)
+{
+       int ret;
+
+       if (!iio_device_claim_direct(indio_dev))
+               return -EBUSY;
+
+       ret = ad4062_write_event_value_dispatch(indio_dev, type, dir, info, val);
        iio_device_release_direct(indio_dev);
        return ret;
 }
@@ -825,6 +1220,9 @@ static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st)
        if (ret)
                return ret;
 
+       if (st->wait_event)
+               return -EBUSY;
+
        ret = ad4062_set_operation_mode(st, st->mode);
        if (ret)
                return ret;
@@ -900,6 +1298,11 @@ static const struct iio_info ad4062_info = {
        .read_raw = ad4062_read_raw,
        .write_raw = ad4062_write_raw,
        .read_avail = ad4062_read_avail,
+       .read_event_config = ad4062_read_event_config,
+       .write_event_config = ad4062_write_event_config,
+       .read_event_value = ad4062_read_event_value,
+       .write_event_value = ad4062_write_event_value,
+       .event_attrs = &ad4062_event_attribute_group,
        .get_current_scan_type = ad4062_get_current_scan_type,
        .debugfs_reg_access = ad4062_debugfs_reg_access,
 };
@@ -980,8 +1383,10 @@ static int ad4062_probe(struct i3c_device *i3cdev)
                                     "Failed to initialize regmap\n");
 
        st->mode = AD4062_SAMPLE_MODE;
+       st->wait_event = false;
        st->chip = chip;
        st->sampling_frequency = 0;
+       st->events_frequency = 0;
        st->oversamp_ratio = 0;
        st->indio_dev = indio_dev;