iio: adc: ad4691: add oversampling support

Add oversampling ratio (OSR) support for CNV burst mode. The accumulator
depth register (ACC_DEPTH_IN(0)) is programmed with the selected OSR at
buffer enable time and before each single-shot read.

Supported OSR values: 1, 2, 4, 8, 16, 32.

Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do
not expose the oversampling_ratio attribute since OSR is not applicable
in that mode. A separate manual_channels array is added to
struct ad4691_channel_info and selected at probe time.

The OSR is shared across all channels (in_voltage_sampling_frequency
and in_voltage_oversampling_ratio are info_mask_shared_by_all) because
the chip has one internal oscillator and a single accumulator depth
register (ACC_DEPTH_IN(0)) for all channels.

in_voltage_sampling_frequency represents the effective output rate,
defined as osc_freq / osr. Writing it computes needed_osc = freq * osr
and snaps down to the largest oscillator table entry that satisfies both
osc <= needed_osc and osc % osr == 0, guaranteeing an exact integer
read-back. The result is stored in target_osc_freq_Hz and written to
OSC_FREQ_REG at buffer enable and single-shot time, so sampling_frequency
and oversampling_ratio can be set in any order.

in_voltage_sampling_frequency_available is precomputed at probe for
each OSR value, listing only oscillator table entries that divide
evenly by that OSR, expressed as effective rates (osc_freq / osr).
The list becomes sparser as OSR increases, capping at max_rate / osr.
read_avail picks the precomputed list for the current OSR, making the
returned pointer stable and race-free.

Writing oversampling_ratio stores the new shared OSR and snaps
target_osc_freq_Hz to the largest oscillator table entry that is both
<= old_effective_rate * new_osr and evenly divisible by new_osr. This
preserves an integer read-back of in_voltage_sampling_frequency after
the OSR change while keeping the oscillator as close as possible to the
previous effective rate.

OSR defaults to 1 (no accumulation).

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
This commit is contained in:
Radu Sabau
2026-05-29 13:15:04 +03:00
committed by Jonathan Cameron
parent ad4f8513aa
commit 6d9e7161bf

View File

@@ -122,6 +122,7 @@ enum ad4691_ref_ctrl {
struct ad4691_channel_info {
const struct iio_chan_spec *channels __counted_by_ptr(num_channels);
const struct iio_chan_spec *manual_channels __counted_by_ptr(num_channels);
unsigned int num_channels;
};
@@ -132,7 +133,34 @@ struct ad4691_chip_info {
const struct ad4691_channel_info *offload_info;
};
/* CNV burst mode channel — exposes oversampling ratio. */
#define AD4691_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \
| BIT(IIO_CHAN_INFO_SAMP_FREQ) \
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all_available = \
BIT(IIO_CHAN_INFO_SAMP_FREQ) \
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.channel = ch, \
.scan_index = ch, \
.scan_type = { \
.format = 'u', \
.realbits = 16, \
.storagebits = 16, \
.endianness = IIO_BE, \
}, \
}
/*
* Manual mode channel no oversampling ratio attribute. OSR is not
* supported in manual mode; ACC_DEPTH_IN is not configured during manual
* buffer enable.
*/
#define AD4691_MANUAL_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
@@ -156,8 +184,33 @@ struct ad4691_chip_info {
* bits into native 16-bit words before DMA, so samples are in
* CPU-native byte order (IIO_CPU). storagebits=16 matches the 16-bit
* DMA word size.
*
* CNV burst offload configures ACC_DEPTH_IN per channel, so the
* oversampling_ratio attribute is exposed. Manual offload does not;
* use AD4691_OFFLOAD_MANUAL_CHANNEL for that path.
*/
#define AD4691_OFFLOAD_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \
| BIT(IIO_CHAN_INFO_SAMP_FREQ) \
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all_available = \
BIT(IIO_CHAN_INFO_SAMP_FREQ) \
| BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.channel = ch, \
.scan_index = ch, \
.scan_type = { \
.format = 'u', \
.realbits = 16, \
.storagebits = 16, \
}, \
}
/* Manual offload — same IIO_CPU layout but no oversampling_ratio attribute. */
#define AD4691_OFFLOAD_MANUAL_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
@@ -241,23 +294,91 @@ static const struct iio_chan_spec ad4693_offload_channels[] = {
AD4691_OFFLOAD_CHANNEL(7),
};
static const struct iio_chan_spec ad4691_manual_channels[] = {
AD4691_MANUAL_CHANNEL(0),
AD4691_MANUAL_CHANNEL(1),
AD4691_MANUAL_CHANNEL(2),
AD4691_MANUAL_CHANNEL(3),
AD4691_MANUAL_CHANNEL(4),
AD4691_MANUAL_CHANNEL(5),
AD4691_MANUAL_CHANNEL(6),
AD4691_MANUAL_CHANNEL(7),
AD4691_MANUAL_CHANNEL(8),
AD4691_MANUAL_CHANNEL(9),
AD4691_MANUAL_CHANNEL(10),
AD4691_MANUAL_CHANNEL(11),
AD4691_MANUAL_CHANNEL(12),
AD4691_MANUAL_CHANNEL(13),
AD4691_MANUAL_CHANNEL(14),
AD4691_MANUAL_CHANNEL(15),
IIO_CHAN_SOFT_TIMESTAMP(16),
};
static const struct iio_chan_spec ad4693_manual_channels[] = {
AD4691_MANUAL_CHANNEL(0),
AD4691_MANUAL_CHANNEL(1),
AD4691_MANUAL_CHANNEL(2),
AD4691_MANUAL_CHANNEL(3),
AD4691_MANUAL_CHANNEL(4),
AD4691_MANUAL_CHANNEL(5),
AD4691_MANUAL_CHANNEL(6),
AD4691_MANUAL_CHANNEL(7),
IIO_CHAN_SOFT_TIMESTAMP(8),
};
static const struct iio_chan_spec ad4691_offload_manual_channels[] = {
AD4691_OFFLOAD_MANUAL_CHANNEL(0),
AD4691_OFFLOAD_MANUAL_CHANNEL(1),
AD4691_OFFLOAD_MANUAL_CHANNEL(2),
AD4691_OFFLOAD_MANUAL_CHANNEL(3),
AD4691_OFFLOAD_MANUAL_CHANNEL(4),
AD4691_OFFLOAD_MANUAL_CHANNEL(5),
AD4691_OFFLOAD_MANUAL_CHANNEL(6),
AD4691_OFFLOAD_MANUAL_CHANNEL(7),
AD4691_OFFLOAD_MANUAL_CHANNEL(8),
AD4691_OFFLOAD_MANUAL_CHANNEL(9),
AD4691_OFFLOAD_MANUAL_CHANNEL(10),
AD4691_OFFLOAD_MANUAL_CHANNEL(11),
AD4691_OFFLOAD_MANUAL_CHANNEL(12),
AD4691_OFFLOAD_MANUAL_CHANNEL(13),
AD4691_OFFLOAD_MANUAL_CHANNEL(14),
AD4691_OFFLOAD_MANUAL_CHANNEL(15),
};
static const struct iio_chan_spec ad4693_offload_manual_channels[] = {
AD4691_OFFLOAD_MANUAL_CHANNEL(0),
AD4691_OFFLOAD_MANUAL_CHANNEL(1),
AD4691_OFFLOAD_MANUAL_CHANNEL(2),
AD4691_OFFLOAD_MANUAL_CHANNEL(3),
AD4691_OFFLOAD_MANUAL_CHANNEL(4),
AD4691_OFFLOAD_MANUAL_CHANNEL(5),
AD4691_OFFLOAD_MANUAL_CHANNEL(6),
AD4691_OFFLOAD_MANUAL_CHANNEL(7),
};
static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
static const struct ad4691_channel_info ad4691_sw_info = {
.channels = ad4691_channels,
.manual_channels = ad4691_manual_channels,
.num_channels = ARRAY_SIZE(ad4691_channels),
};
static const struct ad4691_channel_info ad4693_sw_info = {
.channels = ad4693_channels,
.manual_channels = ad4693_manual_channels,
.num_channels = ARRAY_SIZE(ad4693_channels),
};
static const struct ad4691_channel_info ad4691_offload_info = {
.channels = ad4691_offload_channels,
.manual_channels = ad4691_offload_manual_channels,
.num_channels = ARRAY_SIZE(ad4691_offload_channels),
};
static const struct ad4691_channel_info ad4693_offload_info = {
.channels = ad4693_offload_channels,
.manual_channels = ad4693_offload_manual_channels,
.num_channels = ARRAY_SIZE(ad4693_offload_channels),
};
@@ -324,6 +445,25 @@ struct ad4691_state {
int irq;
int vref_uV;
u32 cnv_period_ns;
/*
* Snapped oscillator frequency (Hz) shared by all channels. Set when
* sampling_frequency or oversampling_ratio is written; written to
* OSC_FREQ_REG at buffer enable and single-shot time so both attributes
* can be set in any order. Reading in_voltage_sampling_frequency
* returns target_osc_freq_Hz / osr the effective rate given the
* shared oversampling ratio.
*/
u32 target_osc_freq_Hz;
/* Shared oversampling ratio across all channels; always 1 in manual mode. */
unsigned int osr;
/*
* Precomputed effective-rate lists, one row per entry in
* ad4691_oversampling_ratios[]. Populated at probe; read_avail picks
* the row for the current shared OSR. The tables are stable after
* probe so returning a pointer into them from read_avail is race-free.
*/
int samp_freq_avail[ARRAY_SIZE(ad4691_oversampling_ratios)][ARRAY_SIZE(ad4691_osc_freqs_Hz)];
int samp_freq_avail_len[ARRAY_SIZE(ad4691_oversampling_ratios)];
bool manual_mode;
bool irq_enabled;
@@ -580,35 +720,95 @@ static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info)
return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
}
static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
/*
* Find the largest oscillator table entry that is both <= needed_osc and
* evenly divisible by osr (guaranteeing an integer effective rate on
* read-back). Returns 0 if no such entry exists in the chip's valid range.
*/
static unsigned int ad4691_find_osc_freq(struct ad4691_state *st,
unsigned int needed_osc,
unsigned int osr)
{
unsigned int reg_val;
int ret;
unsigned int start = ad4691_samp_freq_start(st->info);
guard(mutex)(&st->lock);
for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
if ((unsigned int)ad4691_osc_freqs_Hz[i] > needed_osc)
continue;
if (ad4691_osc_freqs_Hz[i] % osr)
continue;
return ad4691_osc_freqs_Hz[i];
}
return 0;
}
ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
if (ret)
return ret;
/* Write target_osc_freq_Hz to OSC_FREQ_REG. Called at use time. */
static int ad4691_write_osc_freq(struct ad4691_state *st)
{
for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
if (ad4691_osc_freqs_Hz[i] == st->target_osc_freq_Hz)
return regmap_write(st->regmap, AD4691_OSC_FREQ_REG, i);
}
return -EINVAL;
}
*val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
return IIO_VAL_INT;
/* Return the index of osr in ad4691_oversampling_ratios[], defaulting to 0. */
static unsigned int ad4691_osr_index(unsigned int osr)
{
for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios) - 1; i++) {
if ((unsigned int)ad4691_oversampling_ratios[i] == osr)
return i;
}
return ARRAY_SIZE(ad4691_oversampling_ratios) - 1;
}
/*
* Precompute samp_freq_avail[][]: for each OSR value, list the oscillator
* table entries that divide evenly by that OSR, expressed as effective rates
* (osc_freq / osr). Called once at probe after st->info is set.
*/
static void ad4691_precompute_samp_freq_avail(struct ad4691_state *st)
{
unsigned int start = ad4691_samp_freq_start(st->info);
for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i++) {
unsigned int osr = ad4691_oversampling_ratios[i];
int n = 0;
for (unsigned int j = start; j < ARRAY_SIZE(ad4691_osc_freqs_Hz); j++) {
if (ad4691_osc_freqs_Hz[j] % osr)
continue;
st->samp_freq_avail[i][n++] = ad4691_osc_freqs_Hz[j] / osr;
}
st->samp_freq_avail_len[i] = n;
}
}
static int ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
{
unsigned int start = ad4691_samp_freq_start(st->info);
unsigned int osr, found;
/*
* Read osr under st->lock: osr and target_osc_freq_Hz are modified
* together under the lock; reading after acquiring it ensures we see
* a consistent snapshot with no concurrent write racing us.
*/
guard(mutex)(&st->lock);
osr = st->osr;
for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
if (ad4691_osc_freqs_Hz[i] != freq)
continue;
return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
AD4691_OSC_FREQ_MASK, i);
}
if (freq <= 0 || (unsigned int)freq > st->info->max_rate / osr)
return -EINVAL;
return -EINVAL;
found = ad4691_find_osc_freq(st, (unsigned int)freq * osr, osr);
if (!found)
return -EINVAL;
/*
* Store the snapped oscillator frequency; OSC_FREQ_REG is written at
* buffer enable and single-shot time so that sampling_frequency and
* oversampling_ratio can be set in any order.
*/
st->target_osc_freq_Hz = found;
return 0;
}
static int ad4691_read_avail(struct iio_dev *indio_dev,
@@ -617,13 +817,27 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
int *length, long mask)
{
struct ad4691_state *st = iio_priv(indio_dev);
unsigned int start = ad4691_samp_freq_start(st->info);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*vals = &ad4691_osc_freqs_Hz[start];
case IIO_CHAN_INFO_SAMP_FREQ: {
unsigned int osr_idx;
/*
* The precomputed tables are stable after probe; only the
* current OSR needs to be read under the lock to pick the
* right row atomically.
*/
guard(mutex)(&st->lock);
osr_idx = ad4691_osr_index(st->osr);
*vals = st->samp_freq_avail[osr_idx];
*type = IIO_VAL_INT;
*length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
*length = st->samp_freq_avail_len[osr_idx];
return IIO_AVAIL_LIST;
}
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
*vals = ad4691_oversampling_ratios;
*type = IIO_VAL_INT;
*length = ARRAY_SIZE(ad4691_oversampling_ratios);
return IIO_AVAIL_LIST;
default:
return -EINVAL;
@@ -634,7 +848,7 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val)
{
struct ad4691_state *st = iio_priv(indio_dev);
unsigned int reg_val, osc_idx, period_us;
unsigned int reg_val, period_us;
int ret;
guard(mutex)(&st->lock);
@@ -654,7 +868,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
if (ret)
return ret;
ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr);
if (ret)
return ret;
ret = ad4691_write_osc_freq(st);
if (ret)
return ret;
@@ -662,9 +880,12 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
if (ret)
return ret;
osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
/* Wait 2 oscillator periods for the conversion to complete. */
period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
/*
* Wait osr + 1 oscillator periods: osr for accumulation, +1 for the
* pipeline margin (one extra period ensures the final result is ready).
*/
period_us = DIV_ROUND_UP((st->osr + 1) * USEC_PER_SEC,
st->target_osc_freq_Hz);
fsleep(period_us);
ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
@@ -698,8 +919,22 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
return ad4691_single_shot_read(indio_dev, chan, val);
}
case IIO_CHAN_INFO_SAMP_FREQ:
return ad4691_get_sampling_freq(st, val);
case IIO_CHAN_INFO_SAMP_FREQ: {
/*
* Read target_osc_freq_Hz and osr under st->lock to get a
* consistent snapshot: write_raw for SAMP_FREQ or OSR modifies
* both fields under the lock, so a concurrent read without the
* lock could observe a new oscillator frequency with the old OSR.
*/
guard(mutex)(&st->lock);
*val = st->target_osc_freq_Hz / st->osr;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
guard(mutex)(&st->lock);
*val = st->osr;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_SCALE:
*val = st->vref_uV / (MICRO / MILLI);
*val2 = chan->scan_type.realbits;
@@ -722,6 +957,33 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
return ad4691_set_sampling_freq(st, val);
case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
unsigned int old_effective, found, osr_idx;
osr_idx = ad4691_osr_index(val);
if (ad4691_oversampling_ratios[osr_idx] != val)
return -EINVAL;
/*
* Hold st->lock while computing the new oscillator frequency
* and updating both target_osc_freq_Hz and osr atomically:
* read_raw for SAMP_FREQ reads both fields under the lock and
* must see a consistent pair (new osc new osr).
*
* Snap target_osc_freq_Hz to the largest table entry that is
* both <= old_effective * new_osr and evenly divisible by
* new_osr, preserving an integer read-back of
* in_voltage_sampling_frequency after the OSR change.
*/
guard(mutex)(&st->lock);
old_effective = st->target_osc_freq_Hz / st->osr;
found = ad4691_find_osc_freq(st, old_effective * (unsigned int)val, val);
if (!found)
return -EINVAL;
st->target_osc_freq_Hz = found;
st->osr = val;
return 0;
}
default:
return -EINVAL;
}
@@ -776,6 +1038,10 @@ static int ad4691_enter_conversion_mode(struct ad4691_state *st)
return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
ret = ad4691_write_osc_freq(st);
if (ret)
return ret;
ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
if (ret)
@@ -943,6 +1209,10 @@ static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
if (ret)
goto err_unoptimize;
ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr);
if (ret)
goto err_unoptimize;
ret = ad4691_enter_conversion_mode(st);
if (ret)
goto err_unoptimize;
@@ -1122,6 +1392,10 @@ static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
if (ret)
return ret;
ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr);
if (ret)
return ret;
ret = ad4691_enter_conversion_mode(st);
if (ret)
return ret;
@@ -1538,11 +1812,15 @@ static int ad4691_config(struct ad4691_state *st)
if (ret)
return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
st->target_osc_freq_Hz = ad4691_osc_freqs_Hz[ad4691_samp_freq_start(st->info)];
ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
if (ret)
return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
ad4691_precompute_samp_freq_avail(st);
return 0;
}
@@ -1554,7 +1832,14 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
unsigned int i;
int irq, ret;
indio_dev->channels = st->info->sw_info->channels;
/*
* Manual mode exposes channels without the oversampling_ratio attribute
* because ACC_DEPTH_IN is not configured in manual mode.
*/
if (st->manual_mode)
indio_dev->channels = st->info->sw_info->manual_channels;
else
indio_dev->channels = st->info->sw_info->channels;
indio_dev->num_channels = st->info->sw_info->num_channels;
indio_dev->info = st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_burst_info;
@@ -1636,7 +1921,18 @@ static int ad4691_setup_offload(struct iio_dev *indio_dev,
st->offload = spi_offload;
indio_dev->channels = st->info->offload_info->channels;
/*
* CNV burst offload exposes oversampling_ratio (ACC_DEPTH_IN is
* configured per channel at buffer enable). Manual offload does not
* configure ACC_DEPTH_IN, so it uses a separate channel array
* without the oversampling_ratio attribute. Both paths use IIO_CPU
* (no .endianness annotation) because bits_per_word=16 causes the
* SPI Engine to produce native 16-bit DMA words.
*/
if (st->manual_mode)
indio_dev->channels = st->info->offload_info->manual_channels;
else
indio_dev->channels = st->info->offload_info->channels;
indio_dev->num_channels = st->info->offload_info->num_channels;
/*
* Offload path uses DMA directly; no IIO trigger is involved, so
@@ -1710,6 +2006,7 @@ static int ad4691_probe(struct spi_device *spi)
st->info = spi_get_device_match_data(spi);
if (!st->info)
return -ENODEV;
st->osr = 1;
ret = devm_mutex_init(dev, &st->lock);
if (ret)