From: David Lechner <dlechner@baylibre.com>
To: Marcelo Schmitt <marcelo.schmitt@analog.com>,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-doc@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: jic23@kernel.org, ukleinek@kernel.org,
michael.hennerich@analog.com, nuno.sa@analog.com,
eblanc@baylibre.com, andy@kernel.org, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net,
marcelo.schmitt1@gmail.com,
Trevor Gamblin <tgamblin@baylibre.com>,
Axel Haslam <ahaslam@baylibre.com>
Subject: Re: [PATCH v4 6/8] iio: adc: ad4030: Add SPI offload support
Date: Fri, 10 Oct 2025 12:39:25 -0500 [thread overview]
Message-ID: <56f63486-20e7-4c9b-8a39-da904b4600ad@baylibre.com> (raw)
In-Reply-To: <2bde211f1bc730ee147c9540b88339a93b2983e6.1759929814.git.marcelo.schmitt@analog.com>
On 10/8/25 8:51 AM, Marcelo Schmitt wrote:
...
> +static int ad4030_update_conversion_rate(struct ad4030_state *st,
> + unsigned int freq, unsigned int avg_log2)
Always nice to have the units, e.g. freq_hz.
> +{
> + struct spi_offload_trigger_config *config = &st->offload_trigger_config;
> + struct pwm_waveform cnv_wf = { };
> + u64 target = AD4030_TCNVH_NS;
> + u64 offload_period_ns;
> + u64 offload_offset_ns;
> + int ret;
> +
> + /*
> + * When averaging/oversampling over N samples, we fire the offload
> + * trigger once at every N pulses of the CNV signal. Conversely, the CNV
> + * signal needs to be N times faster than the offload trigger. Take that
> + * into account to correctly re-evaluate both the PWM waveform connected
> + * to CNV and the SPI offload trigger.
> + */
> + freq <<= avg_log2;
nit: modifying function arguments throws me off when reading the code. Would prefer
an additional local variable for this, e.g. cnv_rate_hz = freq << avg_log2.
> +
> + cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
> + /*
> + * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
> + * rounded PWM's value is less than 10, increase the target value by 10
> + * and attempt to round the waveform again, until the value is at least
> + * 10 ns. Use a separate variable to represent the target in case the
> + * rounding is severe enough to keep putting the first few results under
> + * the minimum 10ns condition checked by the while loop.
> + */
> + do {
> + cnv_wf.duty_length_ns = target;
> + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
> + if (ret)
> + return ret;
> + target += AD4030_TCNVH_NS;
> + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
> +
> + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
> + return -EINVAL;
> +
> + offload_period_ns = cnv_wf.period_length_ns;
> + /*
> + * Make the offload trigger period be N times longer than the CNV PWM
> + * period when averaging over N samples.
> + */
> + offload_period_ns <<= avg_log2;
Then this could be:
offload_period_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
Which might avoid some rounding issues.
period_length_ns can have up to 0.5 ns differnce from the exact value, so when
multiplying that by a large oversampling rate (up to 64k), that rounding error
would really add up.
> +
> + config->periodic.frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC,
> + offload_period_ns);
> +
> + /*
> + * The hardware does the capture on zone 2 (when SPI trigger PWM
> + * is used). This means that the SPI trigger signal should happen at
> + * tsync + tquiet_con_delay being tsync the conversion signal period
> + * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
> + *
> + * The PWM waveform API only supports nanosecond resolution right now,
> + * so round this setting to the closest available value.
> + */
> + offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
> + do {
> + config->periodic.offset_ns = offload_offset_ns;
> + ret = spi_offload_trigger_validate(st->offload_trigger, config);
> + if (ret)
> + return ret;
> + offload_offset_ns += AD4030_TQUIET_CNV_DELAY_NS;
> + } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
> +
> + st->cnv_wf = cnv_wf;
> +
> + return 0;
> +}
...
> +static int ad4030_set_avg_frame_len(struct iio_dev *dev, unsigned long mask, int avg_val)
> +{
> + struct ad4030_state *st = iio_priv(dev);
> + unsigned int avg_log2 = ilog2(avg_val);
> + unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
> + int freq;
> + int ret;
> +
> + if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
> + return -EINVAL;
> +
> + ret = ad4030_set_mode(st, mask, avg_log2);
I still think setting the mode here is wrong for the reasons given in a previous review.
There is no "correct" mode until we have a request to read a sample.
And if we drop this, then we can return ad4030_set_avg_frame_len() to it's original
location and reduce the diff.
> + if (ret)
> + return ret;
> +
> + if (st->offload_trigger) {
> + /*
> + * The sample averaging and sampling frequency configurations
> + * are mutually dependent one from another. That's because the
> + * effective data sample rate is fCNV / 2^N, where N is the
> + * number of samples being averaged.
> + *
> + * When SPI offload is supported and we have control over the
> + * sample rate, the conversion start signal (CNV) and the SPI
> + * offload trigger frequencies must be re-evaluated so data is
> + * fetched only after 'avg_val' conversions.
> + */
> + ad4030_get_sampling_freq(st, &freq);
> + ret = ad4030_update_conversion_rate(st, freq, avg_log2);
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_write(st->regmap, AD4030_REG_AVG,
> + AD4030_REG_AVG_MASK_AVG_SYNC |
> + FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2));
> + if (ret)
> + return ret;
> +
> + st->avg_log2 = avg_log2;
> + return 0;
> +}
> +
...
> @@ -869,7 +1035,11 @@ static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
> static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
> const unsigned long *scan_mask)
> {
> - return ad4030_set_mode(indio_dev, *scan_mask);
> + struct ad4030_state *st = iio_priv(indio_dev);
> +
> + //return ad4030_set_mode(st, *scan_mask, st->avg_log2);
> + //return ad4030_set_mode(iio_priv(indio_dev), &scan_mask, st->avg_log2);
Oops. :-)
> + return ad4030_set_mode(iio_priv(indio_dev), *scan_mask, st->avg_log2);
> }
>
> static const struct iio_info ad4030_iio_info = {
> @@ -898,6 +1068,108 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
> .validate_scan_mask = ad4030_validate_scan_mask,
> };
>
> +static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> + u8 offload_bpw;
> +
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) {
> + offload_bpw = 32;
> + } else {
> + offload_bpw = st->chip->precision_bits;
> + offload_bpw += (st->mode == AD4030_OUT_DATA_MD_24_DIFF_8_COM ||
> + st->mode == AD4030_OUT_DATA_MD_16_DIFF_8_COM) ? 8 : 0;
The 8-bit common-mode voltage value needs to be in a separate SPI transfer
so that is gets placed in a separate word in the DMA buffer. Otherwise userspace
won't be able to interpret it correctly.
> + }
> +
> + st->offload_xfer.bits_per_word = offload_bpw;
> + st->offload_xfer.len = spi_bpw_to_bytes(offload_bpw);
> + st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
> +}
> +
> +static int ad4030_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> + unsigned int reg_modes;
> + int ret, ret2;
> +
> + /*
> + * When data from 2 analog input channels is output through a single
> + * bus line (interleaved mode (LANE_MD == 0b11)) and gets pushed through
> + * DMA, extra hardware is required to do the de-interleaving. While we
> + * don't support such hardware configurations, disallow interleaved mode
> + * when using SPI offload.
> + */
> + ret = regmap_read(st->regmap, AD4030_REG_MODES, ®_modes);
> + if (ret)
> + return ret;
> +
> + if (st->chip->num_voltage_inputs > 1 &&
> + FIELD_GET(AD4030_REG_MODES_MASK_LANE_MODE, reg_modes) == AD4030_LANE_MD_INTERLEAVED)
> + return -EINVAL;
> +
> + ret = regmap_write(st->regmap, AD4030_REG_EXIT_CFG_MODE, BIT(0));
There is already AD4030_REG_EXIT_CFG_MODE_EXIT_MSK for BIT(0).
Also there is ad4030_exit_config_mode() that could be called instead. Not sure
why we have that though since there aren't any comments to explain it.
> + if (ret)
> + return ret;
> +
> + ad4030_prepare_offload_msg(indio_dev);
> + st->offload_msg.offload = st->offload;
> + ret = spi_optimize_message(st->spi, &st->offload_msg);
> + if (ret)
> + goto out_reset_mode;
> +
> + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, false);
> + if (ret)
> + goto out_unoptimize;
> +
> + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> + &st->offload_trigger_config);
> + if (ret)
> + goto out_pwm_disable;
> +
> + return 0;
> +
> +out_pwm_disable:
> + pwm_disable(st->cnv_trigger);
> +out_unoptimize:
> + spi_unoptimize_message(&st->offload_msg);
> +out_reset_mode:
> + /* reenter register configuration mode */
> + ret2 = ad4030_enter_config_mode(st);
> + if (ret2)
> + dev_err(&st->spi->dev,
> + "couldn't reenter register configuration mode: %d\n",
> + ret2);
> +
> + return ret;
> +}
> +
...
> static const struct ad4030_chip_info ad4030_24_chip_info = {
> .name = "ad4030-24",
> .available_masks = ad4030_channel_masks,
> @@ -1125,10 +1511,15 @@ static const struct ad4030_chip_info ad4030_24_chip_info = {
> AD4030_CHAN_CMO(1, 0),
> IIO_CHAN_SOFT_TIMESTAMP(2),
> },
> + .offload_channels = {
> + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types),
> + AD4030_CHAN_CMO(1, 0),
We will need a new AD4030_OFFLOAD_CHAN_CMO() that has storagebits = 32.
Also applies to similar items below.
> + },
> .grade = AD4030_REG_CHIP_GRADE_AD4030_24_GRADE,
> .precision_bits = 24,
> .num_voltage_inputs = 1,
> .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 2 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4630_16_chip_info = {
> @@ -1141,10 +1532,17 @@ static const struct ad4030_chip_info ad4630_16_chip_info = {
> AD4030_CHAN_CMO(3, 1),
> IIO_CHAN_SOFT_TIMESTAMP(4),
> },
> + .offload_channels = {
> + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_scan_types),
> + AD4030_CHAN_CMO(2, 0),
> + AD4030_CHAN_CMO(3, 1),
> + },
> .grade = AD4030_REG_CHIP_GRADE_AD4630_16_GRADE,
> .precision_bits = 16,
> .num_voltage_inputs = 2,
> .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 2 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4630_24_chip_info = {
> @@ -1157,10 +1555,17 @@ static const struct ad4030_chip_info ad4630_24_chip_info = {
> AD4030_CHAN_CMO(3, 1),
> IIO_CHAN_SOFT_TIMESTAMP(4),
> },
> + .offload_channels = {
> + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_scan_types),
> + AD4030_CHAN_CMO(2, 0),
> + AD4030_CHAN_CMO(3, 1),
> + },
> .grade = AD4030_REG_CHIP_GRADE_AD4630_24_GRADE,
> .precision_bits = 24,
> .num_voltage_inputs = 2,
> .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 2 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4632_16_chip_info = {
> @@ -1173,10 +1578,17 @@ static const struct ad4030_chip_info ad4632_16_chip_info = {
> AD4030_CHAN_CMO(3, 1),
> IIO_CHAN_SOFT_TIMESTAMP(4),
> },
> + .offload_channels = {
> + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_scan_types),
> + AD4030_CHAN_CMO(2, 0),
> + AD4030_CHAN_CMO(3, 1),
> + },
> .grade = AD4030_REG_CHIP_GRADE_AD4632_16_GRADE,
> .precision_bits = 16,
> .num_voltage_inputs = 2,
> .tcyc_ns = AD4632_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 500 * HZ_PER_KHZ,
> };
>
> static const struct ad4030_chip_info ad4632_24_chip_info = {
> @@ -1189,10 +1601,17 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
> AD4030_CHAN_CMO(3, 1),
> IIO_CHAN_SOFT_TIMESTAMP(4),
> },
> + .offload_channels = {
> + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_scan_types),
> + AD4030_CHAN_CMO(2, 0),
> + AD4030_CHAN_CMO(3, 1),
> + },
> .grade = AD4030_REG_CHIP_GRADE_AD4632_24_GRADE,
> .precision_bits = 24,
> .num_voltage_inputs = 2,
> .tcyc_ns = AD4632_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 500 * HZ_PER_KHZ,
> };
>
> static const struct spi_device_id ad4030_id_table[] = {
> @@ -1228,3 +1647,4 @@ module_spi_driver(ad4030_driver);
> MODULE_AUTHOR("Esteban Blanc <eblanc@baylibre.com>");
> MODULE_DESCRIPTION("Analog Devices AD4630 ADC family driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
next prev parent reply other threads:[~2025-10-10 17:39 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-08 13:49 [PATCH v4 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
2025-10-08 13:49 ` [PATCH v4 1/8] pwm: Declare waveform stubs for when PWM is not reachable Marcelo Schmitt
2025-10-09 16:58 ` Uwe Kleine-König
2025-10-10 16:34 ` David Lechner
2025-10-13 8:58 ` Uwe Kleine-König
2025-10-08 13:50 ` [PATCH v4 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
2025-10-08 13:50 ` [PATCH v4 3/8] Docs: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
2025-10-08 13:50 ` [PATCH v4 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
2025-10-08 13:50 ` [PATCH v4 5/8] iio: adc: ad4030: Use BIT macro to improve code readability Marcelo Schmitt
2025-10-08 13:51 ` [PATCH v4 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
2025-10-10 11:19 ` Nuno Sá
2025-10-10 16:18 ` David Lechner
2025-10-10 18:46 ` Nuno Sá
2025-10-10 17:39 ` David Lechner [this message]
2025-10-08 13:51 ` [PATCH v4 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
2025-10-08 21:04 ` Conor Dooley
2025-10-09 16:24 ` Marcelo Schmitt
2025-10-08 21:07 ` Conor Dooley
2025-10-09 22:02 ` Marcelo Schmitt
2025-10-10 14:39 ` Conor Dooley
2025-10-12 17:40 ` Jonathan Cameron
2025-10-08 13:51 ` [PATCH v4 8/8] iio: adc: ad4030: Add support for " Marcelo Schmitt
2025-10-10 18:12 ` David Lechner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=56f63486-20e7-4c9b-8a39-da904b4600ad@baylibre.com \
--to=dlechner@baylibre.com \
--cc=ahaslam@baylibre.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=corbet@lwn.net \
--cc=devicetree@vger.kernel.org \
--cc=eblanc@baylibre.com \
--cc=jic23@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=linux-spi@vger.kernel.org \
--cc=marcelo.schmitt1@gmail.com \
--cc=marcelo.schmitt@analog.com \
--cc=michael.hennerich@analog.com \
--cc=nuno.sa@analog.com \
--cc=robh@kernel.org \
--cc=tgamblin@baylibre.com \
--cc=ukleinek@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox