]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
iio: adc: ad4170-4: Add digital filter and sample frequency config support
authorMarcelo Schmitt <marcelo.schmitt@analog.com>
Mon, 7 Jul 2025 13:52:25 +0000 (10:52 -0300)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Mon, 14 Jul 2025 18:20:52 +0000 (19:20 +0100)
Add support for sinc3, sinc5, and averaged sinc5 digital filters along with
sample frequency configuration.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
Link: https://patch.msgid.link/31f4226b4172b0bbb26daa054b74b25b1966c7b2.1751895245.git.marcelo.schmitt@analog.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/ad4170-4.c

index 3b880d7a7ed0dd5d917ff546c9c0b4c93561b02b..6ad4637f982933563ce2c939074df3153ea193e3 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/math64.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
@@ -90,6 +91,9 @@
 #define AD4170_AFE_BIPOLAR_MSK                         BIT(4)
 #define AD4170_AFE_PGA_GAIN_MSK                                GENMASK(3, 0)
 
+/* AD4170_FILTER_REG */
+#define AD4170_FILTER_FILTER_TYPE_MSK                  GENMASK(3, 0)
+
 /* AD4170 register constants */
 
 /* AD4170_CHAN_MAP_REG constants */
 #define AD4170_ADC_CTRL_MODE_SINGLE                    0x4
 #define AD4170_ADC_CTRL_MODE_IDLE                      0x7
 
+/* AD4170_FILTER_REG constants */
+#define AD4170_FILTER_FILTER_TYPE_SINC5_AVG            0x0
+#define AD4170_FILTER_FILTER_TYPE_SINC5                        0x4
+#define AD4170_FILTER_FILTER_TYPE_SINC3                        0x6
+
 /* Device properties and auxiliary constants */
 
 #define AD4170_NUM_ANALOG_PINS                         9
 #define AD4170_INVALID_SETUP                           9
 #define AD4170_SPI_INST_PHASE_LEN                      2
 #define AD4170_SPI_MAX_XFER_LEN                                6
+#define AD4170_DEFAULT_SAMP_RATE                       (125 * HZ_PER_KHZ)
 
 #define AD4170_INT_REF_2_5V                            2500000
 
 
 #define AD4170_NUM_PGA_OPTIONS                         10
 
+/* Digital filter properties */
+#define AD4170_SINC3_MIN_FS                            4
+#define AD4170_SINC3_MAX_FS                            65532
+#define AD4170_SINC5_MIN_FS                            1
+#define AD4170_SINC5_MAX_FS                            256
+
 #define AD4170_GAIN_REG_DEFAULT                                0x555555
 
 static const unsigned int ad4170_reg_size[] = {
@@ -192,6 +208,12 @@ enum ad4170_ref_select {
        AD4170_REF_AVDD,
 };
 
+enum ad4170_filter_type {
+       AD4170_SINC5_AVG,
+       AD4170_SINC5,
+       AD4170_SINC3,
+};
+
 enum ad4170_regulator {
        AD4170_AVDD_SUP,
        AD4170_AVSS_SUP,
@@ -213,6 +235,18 @@ static const char * const ad4170_int_pin_names[] = {
        [AD4170_INT_PIN_DIG_AUX1] = "dig_aux1",
 };
 
+static const unsigned int ad4170_sinc3_filt_fs_tbl[] = {
+       4, 8, 12, 16, 20, 40, 48, 80,                   /*  0 -  7 */
+       100, 256, 500, 1000, 5000, 8332, 10000, 25000,  /*  8 - 15 */
+       50000, 65532,                                   /* 16 - 17 */
+};
+
+#define AD4170_MAX_FS_TBL_SIZE         ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl)
+
+static const unsigned int ad4170_sinc5_filt_fs_tbl[] = {
+       1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256,
+};
+
 enum ad4170_sensor_enum {
        AD4170_ADC_SENSOR = 0,
 };
@@ -274,6 +308,12 @@ struct ad4170_chan_info {
        bool enabled;
 };
 
+static const char * const ad4170_filt_names[] = {
+       [AD4170_SINC5_AVG] = "sinc5+avg",
+       [AD4170_SINC5] = "sinc5",
+       [AD4170_SINC3] = "sinc3",
+};
+
 struct ad4170_state {
        struct mutex lock; /* Protect read-modify-write and multi write sequences */
        int vrefs_uv[AD4170_MAX_SUP];
@@ -284,6 +324,7 @@ struct ad4170_state {
        struct iio_chan_spec chans[AD4170_MAX_ADC_CHANNELS];
        struct spi_device *spi;
        struct regmap *regmap;
+       int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
        unsigned int pins_fn[AD4170_NUM_ANALOG_PINS];
        /*
         * DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -292,6 +333,38 @@ struct ad4170_state {
        u8 rx_buf[4] __aligned(IIO_DMA_MINALIGN);
 };
 
+static void ad4170_fill_sps_tbl(struct ad4170_state *st)
+{
+       unsigned int tmp0, tmp1, i;
+
+       /*
+        * The ODR can be calculated the same way for sinc5+avg, sinc5, and
+        * sinc3 filter types with the exception that sinc5 filter has a
+        * narrowed range of allowed FILTER_FS values.
+        */
+       for (i = 0; i < ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); i++) {
+               tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc3_filt_fs_tbl[i],
+                                  &tmp1);
+               tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc3_filt_fs_tbl[i]);
+               /* Fill sinc5+avg filter SPS table */
+               st->sps_tbl[AD4170_SINC5_AVG][i][0] = tmp0; /* Integer part */
+               st->sps_tbl[AD4170_SINC5_AVG][i][1] = tmp1; /* Fractional part */
+
+               /* Fill sinc3 filter SPS table */
+               st->sps_tbl[AD4170_SINC3][i][0] = tmp0; /* Integer part */
+               st->sps_tbl[AD4170_SINC3][i][1] = tmp1; /* Fractional part */
+       }
+       /* Sinc5 filter ODR doesn't use all FILTER_FS bits */
+       for (i = 0; i < ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); i++) {
+               tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc5_filt_fs_tbl[i],
+                                  &tmp1);
+               tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc5_filt_fs_tbl[i]);
+               /* Fill sinc5 filter SPS table */
+               st->sps_tbl[AD4170_SINC5][i][0] = tmp0; /* Integer part */
+               st->sps_tbl[AD4170_SINC5][i][1] = tmp1; /* Fractional part */
+       }
+}
+
 static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev,
                                     unsigned int reg, unsigned int writeval,
                                     unsigned int *readval)
@@ -620,6 +693,100 @@ static int ad4170_set_channel_enable(struct ad4170_state *st,
        return 0;
 }
 
+static int __ad4170_get_filter_type(unsigned int filter)
+{
+       u16 f_conf = FIELD_GET(AD4170_FILTER_FILTER_TYPE_MSK, filter);
+
+       switch (f_conf) {
+       case AD4170_FILTER_FILTER_TYPE_SINC5_AVG:
+               return AD4170_SINC5_AVG;
+       case AD4170_FILTER_FILTER_TYPE_SINC5:
+               return AD4170_SINC5;
+       case AD4170_FILTER_FILTER_TYPE_SINC3:
+               return AD4170_SINC3;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ad4170_set_filter_type(struct iio_dev *indio_dev,
+                                 struct iio_chan_spec const *chan,
+                                 unsigned int val)
+{
+       struct ad4170_state *st = iio_priv(indio_dev);
+       struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+       struct ad4170_setup *setup = &chan_info->setup;
+       unsigned int filter_type_conf;
+       int ret;
+
+       switch (val) {
+       case AD4170_SINC5_AVG:
+               filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5_AVG;
+               break;
+       case AD4170_SINC5:
+               filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5;
+               break;
+       case AD4170_SINC3:
+               filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC3;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /*
+        * The filters provide the same ODR for a given filter_fs value but
+        * there are different minimum and maximum filter_fs limits for each
+        * filter. The filter_fs value will be adjusted if the current filter_fs
+        * is out of the limits of the just requested filter. Since the
+        * filter_fs value affects the ODR (sampling_frequency), changing the
+        * filter may lead to a change in the sampling frequency.
+        */
+       scoped_guard(mutex, &st->lock) {
+               if (!iio_device_claim_direct(indio_dev))
+                       return -EBUSY;
+
+               if (val == AD4170_SINC5_AVG || val == AD4170_SINC3)
+                       setup->filter_fs = clamp(val, AD4170_SINC3_MIN_FS,
+                                                AD4170_SINC3_MAX_FS);
+               else
+                       setup->filter_fs = clamp(val, AD4170_SINC5_MIN_FS,
+                                                AD4170_SINC5_MAX_FS);
+
+               setup->filter &= ~AD4170_FILTER_FILTER_TYPE_MSK;
+               setup->filter |= FIELD_PREP(AD4170_FILTER_FILTER_TYPE_MSK,
+                                           filter_type_conf);
+
+               ret = ad4170_write_channel_setup(st, chan->address, false);
+               iio_device_release_direct(indio_dev);
+       }
+
+       return ret;
+}
+
+static int ad4170_get_filter_type(struct iio_dev *indio_dev,
+                                 struct iio_chan_spec const *chan)
+{
+       struct ad4170_state *st = iio_priv(indio_dev);
+       struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+       struct ad4170_setup *setup = &chan_info->setup;
+
+       return __ad4170_get_filter_type(setup->filter);
+}
+
+static const struct iio_enum ad4170_filter_type_enum = {
+       .items = ad4170_filt_names,
+       .num_items = ARRAY_SIZE(ad4170_filt_names),
+       .get = ad4170_get_filter_type,
+       .set = ad4170_set_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = {
+       IIO_ENUM("filter_type", IIO_SEPARATE, &ad4170_filter_type_enum),
+       IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+                          &ad4170_filter_type_enum),
+       { }
+};
+
 static const struct iio_chan_spec ad4170_channel_template = {
        .type = IIO_VOLTAGE,
        .indexed = 1,
@@ -628,8 +795,11 @@ static const struct iio_chan_spec ad4170_channel_template = {
                              BIT(IIO_CHAN_INFO_SCALE) |
                              BIT(IIO_CHAN_INFO_CALIBBIAS) |
                              BIT(IIO_CHAN_INFO_CALIBSCALE) |
+                             BIT(IIO_CHAN_INFO_SAMP_FREQ) |
                              BIT(IIO_CHAN_INFO_OFFSET),
-       .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+       .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+                                       BIT(IIO_CHAN_INFO_SAMP_FREQ),
+       .ext_info = ad4170_filter_type_ext_info,
        .scan_type = {
                .realbits = 24,
                .storagebits = 32,
@@ -924,7 +1094,8 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
        struct ad4170_state *st = iio_priv(indio_dev);
        struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
        struct ad4170_setup *setup = &chan_info->setup;
-       unsigned int pga;
+       enum ad4170_filter_type f_type;
+       unsigned int pga, fs_idx;
        int ret;
 
        guard(mutex)(&st->lock);
@@ -950,6 +1121,27 @@ static int ad4170_read_raw(struct iio_dev *indio_dev,
                pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe);
                *val = chan_info->offset_tbl[pga];
                return IIO_VAL_INT;
+       case IIO_CHAN_INFO_SAMP_FREQ:
+               f_type = __ad4170_get_filter_type(setup->filter);
+               switch (f_type) {
+               case AD4170_SINC5_AVG:
+               case AD4170_SINC3:
+                       fs_idx = find_closest(setup->filter_fs,
+                                             ad4170_sinc3_filt_fs_tbl,
+                                             ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl));
+                       *val = st->sps_tbl[f_type][fs_idx][0];
+                       *val2 = st->sps_tbl[f_type][fs_idx][1];
+                       return IIO_VAL_INT_PLUS_MICRO;
+               case AD4170_SINC5:
+                       fs_idx = find_closest(setup->filter_fs,
+                                             ad4170_sinc5_filt_fs_tbl,
+                                             ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl));
+                       *val = st->sps_tbl[f_type][fs_idx][0];
+                       *val2 = st->sps_tbl[f_type][fs_idx][1];
+                       return IIO_VAL_INT_PLUS_MICRO;
+               default:
+                       return -EINVAL;
+               }
        case IIO_CHAN_INFO_CALIBBIAS:
                *val = setup->offset;
                return IIO_VAL_INT;
@@ -1035,6 +1227,7 @@ static int ad4170_read_avail(struct iio_dev *indio_dev,
 {
        struct ad4170_state *st = iio_priv(indio_dev);
        struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+       enum ad4170_filter_type f_type;
 
        switch (info) {
        case IIO_CHAN_INFO_SCALE:
@@ -1042,6 +1235,24 @@ static int ad4170_read_avail(struct iio_dev *indio_dev,
                *length = ARRAY_SIZE(chan_info->scale_tbl) * 2;
                *type = IIO_VAL_INT_PLUS_NANO;
                return IIO_AVAIL_LIST;
+       case IIO_CHAN_INFO_SAMP_FREQ:
+               *type = IIO_VAL_INT_PLUS_MICRO;
+               f_type = ad4170_get_filter_type(indio_dev, chan);
+               switch (f_type) {
+               case AD4170_SINC5_AVG:
+               case AD4170_SINC3:
+                       /* Read sps_tbl here to ensure in bounds array access */
+                       *vals = (int *)st->sps_tbl[f_type];
+                       *length = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) * 2;
+                       return IIO_AVAIL_LIST;
+               case AD4170_SINC5:
+                       /* Read sps_tbl here to ensure in bounds array access */
+                       *vals = (int *)st->sps_tbl[f_type];
+                       *length = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl) * 2;
+                       return IIO_AVAIL_LIST;
+               default:
+                       return -EINVAL;
+               }
        default:
                return -EINVAL;
        }
@@ -1070,6 +1281,42 @@ static int ad4170_set_pga(struct ad4170_state *st,
        return ad4170_write_channel_setup(st, chan->address, false);
 }
 
+static int ad4170_set_channel_freq(struct ad4170_state *st,
+                                  struct iio_chan_spec const *chan, int val,
+                                  int val2)
+{
+       struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address];
+       struct ad4170_setup *setup = &chan_info->setup;
+       enum ad4170_filter_type f_type = __ad4170_get_filter_type(setup->filter);
+       unsigned int filt_fs_tbl_size, i;
+
+       switch (f_type) {
+       case AD4170_SINC5_AVG:
+       case AD4170_SINC3:
+               filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl);
+               break;
+       case AD4170_SINC5:
+               filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl);
+               break;
+       }
+
+       for (i = 0; i < filt_fs_tbl_size; i++) {
+               if (st->sps_tbl[f_type][i][0] == val &&
+                   st->sps_tbl[f_type][i][1] == val2)
+                       break;
+       }
+       if (i == filt_fs_tbl_size)
+               return -EINVAL;
+
+       guard(mutex)(&st->lock);
+       if (f_type == AD4170_SINC5)
+               setup->filter_fs = ad4170_sinc5_filt_fs_tbl[i];
+       else
+               setup->filter_fs = ad4170_sinc3_filt_fs_tbl[i];
+
+       return ad4170_write_channel_setup(st, chan->address, false);
+}
+
 static int ad4170_set_calib_offset(struct ad4170_state *st,
                                   struct iio_chan_spec const *chan, int val)
 {
@@ -1103,6 +1350,8 @@ static int __ad4170_write_raw(struct iio_dev *indio_dev,
        switch (info) {
        case IIO_CHAN_INFO_SCALE:
                return ad4170_set_pga(st, chan, val, val2);
+       case IIO_CHAN_INFO_SAMP_FREQ:
+               return ad4170_set_channel_freq(st, chan, val, val2);
        case IIO_CHAN_INFO_CALIBBIAS:
                return ad4170_set_calib_offset(st, chan, val);
        case IIO_CHAN_INFO_CALIBSCALE:
@@ -1133,6 +1382,8 @@ static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev,
        switch (info) {
        case IIO_CHAN_INFO_SCALE:
                return IIO_VAL_INT_PLUS_NANO;
+       case IIO_CHAN_INFO_SAMP_FREQ:
+               return IIO_VAL_INT_PLUS_MICRO;
        case IIO_CHAN_INFO_CALIBBIAS:
        case IIO_CHAN_INFO_CALIBSCALE:
                return IIO_VAL_INT;
@@ -1387,6 +1638,8 @@ static int ad4170_initial_config(struct iio_dev *indio_dev)
        unsigned int i;
        int ret;
 
+       ad4170_fill_sps_tbl(st);
+
        ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG,
                                 AD4170_ADC_CTRL_MODE_MSK,
                                 FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK,
@@ -1419,6 +1672,12 @@ static int ad4170_initial_config(struct iio_dev *indio_dev)
                        return dev_err_probe(dev, ret,
                                             "Failed to write CHAN_MAP_REG\n");
 
+               ret = ad4170_set_channel_freq(st, chan,
+                                             AD4170_DEFAULT_SAMP_RATE, 0);
+               if (ret)
+                       return dev_err_probe(dev, ret,
+                                            "Failed to set channel freq\n");
+
                ret = ad4170_fill_scale_tbl(indio_dev, chan);
                if (ret)
                        return dev_err_probe(dev, ret,