[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <2157e21a54d0f69fafbf92c0faded5e9f830d9c2.1751289747.git.marcelo.schmitt@analog.com>
Date: Mon, 30 Jun 2025 10:59:53 -0300
From: Marcelo Schmitt <marcelo.schmitt@...log.com>
To: <linux-iio@...r.kernel.org>, <devicetree@...r.kernel.org>,
<linux-gpio@...r.kernel.org>, <linux-kernel@...r.kernel.org>
CC: <jic23@...nel.org>, <lars@...afoo.de>, <Michael.Hennerich@...log.com>,
<dlechner@...libre.com>, <nuno.sa@...log.com>, <andy@...nel.org>,
<robh@...nel.org>, <krzk+dt@...nel.org>, <conor+dt@...nel.org>,
<linus.walleij@...aro.org>, <brgl@...ev.pl>, <broonie@...nel.org>,
<lgirdwood@...il.com>, <marcelo.schmitt1@...il.com>
Subject: [PATCH v7 06/12] iio: adc: ad4170-4: Add digital filter and sample frequency config support
Add support for sinc3, sinc5, and averaged sinc5 digital filters along with
sample frequency configuration.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@...log.com>
---
Change log v6 -> v7
- Now reading SPS table within filter type switch to ensure in bounds array access.
- Squeezed info_mask_separate additions to reduce change diff.
drivers/iio/adc/ad4170-4.c | 263 ++++++++++++++++++++++++++++++++++++-
1 file changed, 261 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/adc/ad4170-4.c b/drivers/iio/adc/ad4170-4.c
index 216dc844cc62..abd8a292625b 100644
--- a/drivers/iio/adc/ad4170-4.c
+++ b/drivers/iio/adc/ad4170-4.c
@@ -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 */
@@ -115,6 +119,11 @@
#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
@@ -124,6 +133,7 @@
#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
@@ -132,6 +142,12 @@
#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,
+};
+
struct ad4170_chip_info {
const char *name;
};
@@ -270,6 +304,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];
@@ -279,6 +319,7 @@ struct ad4170_state {
struct ad4170_chan_info chan_infos[AD4170_MAX_CHANNELS];
struct spi_device *spi;
struct regmap *regmap;
+ int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2];
struct completion completion;
unsigned int pins_fn[AD4170_NUM_ANALOG_PINS];
u32 int_pin_sel;
@@ -289,6 +330,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)
@@ -617,6 +690,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,
@@ -625,8 +792,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,
@@ -921,7 +1091,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);
@@ -942,6 +1113,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;
@@ -1027,6 +1219,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:
@@ -1034,6 +1227,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;
}
@@ -1062,6 +1273,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)
{
@@ -1095,6 +1342,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:
@@ -1125,6 +1374,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;
@@ -1370,6 +1621,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,
@@ -1402,6 +1655,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,
--
2.47.2
Powered by blists - more mailing lists