Add SPI offload support to the ad_sigma_delta module.
When the SPI controller has SPI offload capabilities, the module will
now use that for buffered reads instead of the RDY interrupt trigger.
Drivers that use the ad_sigma_delta module will have to opt into this
by setting supports_spi_offload since each driver will likely need
additional changes before SPI offload can be used. This will allow us
to gradually enable SPI offload support for each driver.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
drivers/iio/adc/ad_sigma_delta.c | 160 +++++++++++++++++++++++----------
include/linux/iio/adc/ad_sigma_delta.h | 14 +++
2 files changed, 129 insertions(+), 45 deletions(-)
diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c
index a9b97f5d4107a2e1bb74877d30403445e9b04a44..449b0756be96d3f864a6e7f070467ad7311bf7d5 100644
--- a/drivers/iio/adc/ad_sigma_delta.c
+++ b/drivers/iio/adc/ad_sigma_delta.c
@@ -14,11 +14,13 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/spi/offload/consumer.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <linux/iio/adc/ad_sigma_delta.h>
+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -460,8 +462,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type;
struct spi_transfer *xfer = sigma_delta->sample_xfer;
- unsigned int i, slot, samples_buf_size;
- unsigned int channel, scan_size;
+ unsigned int i, slot, channel;
u8 *samples_buf;
int ret;
@@ -489,23 +490,33 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
sigma_delta->active_slots = slot;
sigma_delta->current_slot = 0;
- if (sigma_delta->active_slots > 1) {
- ret = ad_sigma_delta_append_status(sigma_delta, true);
- if (ret)
- return ret;
- }
+ if (ad_sigma_delta_has_spi_offload(sigma_delta)) {
+ xfer[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ xfer[1].bits_per_word = scan_type->realbits;
+ xfer[1].len = spi_bpw_to_bytes(scan_type->realbits);
+ } else {
+ unsigned int samples_buf_size, scan_size;
- samples_buf_size = ALIGN(slot * scan_type->storagebits, 8);
- samples_buf_size += sizeof(int64_t);
- samples_buf = devm_krealloc(&sigma_delta->spi->dev, sigma_delta->samples_buf,
- samples_buf_size, GFP_KERNEL);
- if (!samples_buf)
- return -ENOMEM;
+ if (sigma_delta->active_slots > 1) {
+ ret = ad_sigma_delta_append_status(sigma_delta, true);
+ if (ret)
+ return ret;
+ }
- sigma_delta->samples_buf = samples_buf;
- scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift);
- xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0];
- xfer[1].len = scan_size + (sigma_delta->status_appended ? 1 : 0);
+ samples_buf_size = ALIGN(slot * scan_type->storagebits, 8);
+ samples_buf_size += sizeof(int64_t);
+ samples_buf = devm_krealloc(&sigma_delta->spi->dev,
+ sigma_delta->samples_buf,
+ samples_buf_size, GFP_KERNEL);
+ if (!samples_buf)
+ return -ENOMEM;
+
+ sigma_delta->samples_buf = samples_buf;
+ scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift);
+
+ xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0];
+ xfer[1].len = scan_size + (sigma_delta->status_appended ? 1 : 0);
+ }
xfer[1].cs_change = 1;
if (sigma_delta->info->has_registers) {
@@ -521,6 +532,8 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
&xfer[1], 1);
}
+ sigma_delta->sample_msg.offload = sigma_delta->offload;
+
ret = spi_optimize_message(sigma_delta->spi, &sigma_delta->sample_msg);
if (ret)
return ret;
@@ -537,7 +550,19 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
if (ret)
goto err_unlock;
- ad_sd_enable_irq(sigma_delta);
+ if (ad_sigma_delta_has_spi_offload(sigma_delta)) {
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
+ };
+
+ ret = spi_offload_trigger_enable(sigma_delta->offload,
+ sigma_delta->offload_trigger,
+ &config);
+ if (ret)
+ goto err_unlock;
+ } else {
+ ad_sd_enable_irq(sigma_delta);
+ }
return 0;
@@ -552,10 +577,15 @@ static int ad_sd_buffer_postdisable(struct iio_dev *indio_dev)
{
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
- reinit_completion(&sigma_delta->completion);
- wait_for_completion_timeout(&sigma_delta->completion, HZ);
+ if (ad_sigma_delta_has_spi_offload(sigma_delta)) {
+ spi_offload_trigger_disable(sigma_delta->offload,
+ sigma_delta->offload_trigger);
+ } else {
+ reinit_completion(&sigma_delta->completion);
+ wait_for_completion_timeout(&sigma_delta->completion, HZ);
- ad_sd_disable_irq(sigma_delta);
+ ad_sd_disable_irq(sigma_delta);
+ }
sigma_delta->keep_cs_asserted = false;
ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE);
@@ -670,7 +700,8 @@ static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private)
if ((!sigma_delta->rdy_gpiod || gpiod_get_value(sigma_delta->rdy_gpiod)) &&
ad_sd_disable_irq(sigma_delta)) {
complete(&sigma_delta->completion);
- iio_trigger_poll(sigma_delta->trig);
+ if (sigma_delta->trig)
+ iio_trigger_poll(sigma_delta->trig);
return IRQ_HANDLED;
}
@@ -703,17 +734,6 @@ static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_de
unsigned long irq_flags = irq_get_trigger_type(sigma_delta->irq_line);
int ret;
- if (dev != &sigma_delta->spi->dev) {
- dev_err(dev, "Trigger parent should be '%s', got '%s'\n",
- dev_name(dev), dev_name(&sigma_delta->spi->dev));
- return -EFAULT;
- }
-
- sigma_delta->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
- iio_device_id(indio_dev));
- if (sigma_delta->trig == NULL)
- return -ENOMEM;
-
init_completion(&sigma_delta->completion);
sigma_delta->irq_dis = true;
@@ -733,14 +753,33 @@ static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_de
if (ret)
return ret;
- iio_trigger_set_drvdata(sigma_delta->trig, sigma_delta);
+ if (ad_sigma_delta_has_spi_offload(sigma_delta)) {
+ sigma_delta->offload_trigger =
+ devm_spi_offload_trigger_get(dev, sigma_delta->offload,
+ SPI_OFFLOAD_TRIGGER_DATA_READY);
+ if (IS_ERR(sigma_delta->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(sigma_delta->offload_trigger),
+ "Failed to get SPI offload trigger\n");
+ } else {
+ if (dev != &sigma_delta->spi->dev)
+ return dev_err_probe(dev, -EFAULT,
+ "Trigger parent should be '%s', got '%s'\n",
+ dev_name(dev), dev_name(&sigma_delta->spi->dev));
- ret = devm_iio_trigger_register(dev, sigma_delta->trig);
- if (ret)
- return ret;
+ sigma_delta->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name, iio_device_id(indio_dev));
+ if (!sigma_delta->trig)
+ return -ENOMEM;
- /* select default trigger */
- indio_dev->trig = iio_trigger_get(sigma_delta->trig);
+ iio_trigger_set_drvdata(sigma_delta->trig, sigma_delta);
+
+ ret = devm_iio_trigger_register(dev, sigma_delta->trig);
+ if (ret)
+ return ret;
+
+ /* select default trigger */
+ indio_dev->trig = iio_trigger_get(sigma_delta->trig);
+ }
return 0;
}
@@ -760,12 +799,29 @@ int devm_ad_sd_setup_buffer_and_trigger(struct device *dev, struct iio_dev *indi
if (!sigma_delta->slots)
return -ENOMEM;
- ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
- &iio_pollfunc_store_time,
- &ad_sd_trigger_handler,
- &ad_sd_buffer_setup_ops);
- if (ret)
- return ret;
+ if (ad_sigma_delta_has_spi_offload(sigma_delta)) {
+ struct dma_chan *rx_dma;
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev,
+ sigma_delta->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma),
+ "Failed to get RX DMA channel\n");
+
+ ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
+ rx_dma, IIO_BUFFER_DIRECTION_IN);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot setup DMA buffer\n");
+
+ indio_dev->setup_ops = &ad_sd_buffer_setup_ops;
+ } else {
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad_sd_trigger_handler,
+ &ad_sd_buffer_setup_ops);
+ if (ret)
+ return ret;
+ }
return devm_ad_sd_probe_trigger(dev, indio_dev);
}
@@ -828,6 +884,20 @@ int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev,
return sigma_delta->irq_line;
}
+ if (info->supports_spi_offload) {
+ struct spi_offload_config offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+ };
+ int ret;
+
+ sigma_delta->offload = devm_spi_offload_get(&spi->dev, spi,
+ &offload_config);
+ ret = PTR_ERR_OR_ZERO(sigma_delta->offload);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(&spi->dev, ret, "Failed to get SPI offload\n");
+ }
+
iio_device_set_drvdata(indio_dev, sigma_delta);
return 0;
diff --git a/include/linux/iio/adc/ad_sigma_delta.h b/include/linux/iio/adc/ad_sigma_delta.h
index 2037bb68b44115681ff48f66b580b63f50c2ea9e..6e70a412e218d54bbf9bb6861b1a4cc89be868e8 100644
--- a/include/linux/iio/adc/ad_sigma_delta.h
+++ b/include/linux/iio/adc/ad_sigma_delta.h
@@ -31,6 +31,8 @@ struct ad_sigma_delta;
struct device;
struct gpio_desc;
struct iio_dev;
+struct spi_offload;
+struct spi_offload_trigger;
/**
* struct ad_sigma_delta_info - Sigma Delta driver specific callbacks and options
@@ -47,6 +49,10 @@ struct iio_dev;
* @has_registers: true if the device has writable and readable registers, false
* if there is just one read-only sample data shift register.
* @has_named_irqs: Set to true if there is more than one IRQ line.
+ * @supports_spi_offload: Set to true if the driver supports SPI offload. Often
+ * special considerations are needed for scan_type and other channel
+ * info, so individual drivers have to set this to let the core
+ * code know that it can use SPI offload if it is available.
* @addr_shift: Shift of the register address in the communications register.
* @read_mask: Mask for the communications register having the read bit set.
* @status_ch_mask: Mask for the channel number stored in status register.
@@ -65,6 +71,7 @@ struct ad_sigma_delta_info {
int (*postprocess_sample)(struct ad_sigma_delta *, unsigned int raw_sample);
bool has_registers;
bool has_named_irqs;
+ bool supports_spi_offload;
unsigned int addr_shift;
unsigned int read_mask;
unsigned int status_ch_mask;
@@ -108,6 +115,8 @@ struct ad_sigma_delta {
struct spi_message sample_msg;
struct spi_transfer sample_xfer[2];
u8 *samples_buf;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
/*
* DMA (thus cache coherency maintenance) requires the
@@ -121,6 +130,11 @@ struct ad_sigma_delta {
u8 sample_addr;
};
+static inline bool ad_sigma_delta_has_spi_offload(struct ad_sigma_delta *sd)
+{
+ return sd->offload != NULL;
+}
+
static inline int ad_sigma_delta_set_channel(struct ad_sigma_delta *sd,
unsigned int channel)
{
--
2.43.0
Hi David, kernel test robot noticed the following build errors: [auto build test ERROR on d02f330b0c78bcf76643fbb7d3215a58b181f829] url: https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad_sigma_delta-sort-includes/20250621-063127 base: d02f330b0c78bcf76643fbb7d3215a58b181f829 patch link: https://lore.kernel.org/r/20250620-iio-adc-ad7173-add-spi-offload-support-v1-8-0766f6297430%40baylibre.com patch subject: [PATCH 8/9] iio: adc: ad_sigma_delta: add SPI offload support config: x86_64-buildonly-randconfig-001-20250621 (https://download.01.org/0day-ci/archive/20250623/202506232119.aLbzgQow-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250623/202506232119.aLbzgQow-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202506232119.aLbzgQow-lkp@intel.com/ All errors (new ones prefixed by >>, old ones prefixed by <<): >> ERROR: modpost: module ad_sigma_delta uses symbol devm_iio_dmaengine_buffer_setup_with_handle from namespace IIO_DMAENGINE_BUFFER, but does not import it. -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Hi David, kernel test robot noticed the following build warnings: [auto build test WARNING on d02f330b0c78bcf76643fbb7d3215a58b181f829] url: https://github.com/intel-lab-lkp/linux/commits/David-Lechner/iio-adc-ad_sigma_delta-sort-includes/20250621-063127 base: d02f330b0c78bcf76643fbb7d3215a58b181f829 patch link: https://lore.kernel.org/r/20250620-iio-adc-ad7173-add-spi-offload-support-v1-8-0766f6297430%40baylibre.com patch subject: [PATCH 8/9] iio: adc: ad_sigma_delta: add SPI offload support config: x86_64-buildonly-randconfig-004-20250621 (https://download.01.org/0day-ci/archive/20250623/202506231738.CgeNexV4-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250623/202506231738.CgeNexV4-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202506231738.CgeNexV4-lkp@intel.com/ All warnings (new ones prefixed by >>, old ones prefixed by <<): >> WARNING: modpost: module ad_sigma_delta uses symbol devm_iio_dmaengine_buffer_setup_with_handle from namespace IIO_DMAENGINE_BUFFER, but does not import it. -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
On Fri, 20 Jun 2025 17:20:14 -0500 David Lechner <dlechner@baylibre.com> wrote: > Add SPI offload support to the ad_sigma_delta module. > > When the SPI controller has SPI offload capabilities, the module will > now use that for buffered reads instead of the RDY interrupt trigger. > > Drivers that use the ad_sigma_delta module will have to opt into this > by setting supports_spi_offload since each driver will likely need > additional changes before SPI offload can be used. This will allow us > to gradually enable SPI offload support for each driver. > > Signed-off-by: David Lechner <dlechner@baylibre.com> A few queries inline that again are more about the original code than what you change here. Jonathan > --- > drivers/iio/adc/ad_sigma_delta.c | 160 +++++++++++++++++++++++---------- > include/linux/iio/adc/ad_sigma_delta.h | 14 +++ > 2 files changed, 129 insertions(+), 45 deletions(-) > > diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c > index a9b97f5d4107a2e1bb74877d30403445e9b04a44..449b0756be96d3f864a6e7f070467ad7311bf7d5 100644 > --- a/drivers/iio/adc/ad_sigma_delta.c > +++ b/drivers/iio/adc/ad_sigma_delta.c > @@ -14,11 +14,13 @@ > #include <linux/kernel.h> > #include <linux/module.h> > #include <linux/slab.h> > +#include <linux/spi/offload/consumer.h> > #include <linux/spi/spi.h> > #include <linux/types.h> > #include <linux/unaligned.h> > > #include <linux/iio/adc/ad_sigma_delta.h> > +#include <linux/iio/buffer-dmaengine.h> > #include <linux/iio/buffer.h> > #include <linux/iio/iio.h> > #include <linux/iio/sysfs.h> > @@ -460,8 +462,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) > struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); > const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type; > struct spi_transfer *xfer = sigma_delta->sample_xfer; > - unsigned int i, slot, samples_buf_size; > - unsigned int channel, scan_size; > + unsigned int i, slot, channel; > u8 *samples_buf; > int ret; > > @@ -489,23 +490,33 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) > sigma_delta->active_slots = slot; > sigma_delta->current_slot = 0; > > - if (sigma_delta->active_slots > 1) { > - ret = ad_sigma_delta_append_status(sigma_delta, true); > - if (ret) > - return ret; > - } > + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { > + xfer[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; > + xfer[1].bits_per_word = scan_type->realbits; > + xfer[1].len = spi_bpw_to_bytes(scan_type->realbits); > + } else { > + unsigned int samples_buf_size, scan_size; > > - samples_buf_size = ALIGN(slot * scan_type->storagebits, 8); > - samples_buf_size += sizeof(int64_t); > - samples_buf = devm_krealloc(&sigma_delta->spi->dev, sigma_delta->samples_buf, > - samples_buf_size, GFP_KERNEL); > - if (!samples_buf) > - return -ENOMEM; > + if (sigma_delta->active_slots > 1) { > + ret = ad_sigma_delta_append_status(sigma_delta, true); > + if (ret) > + return ret; > + } > > - sigma_delta->samples_buf = samples_buf; > - scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); > - xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0]; > - xfer[1].len = scan_size + (sigma_delta->status_appended ? 1 : 0); > + samples_buf_size = ALIGN(slot * scan_type->storagebits, 8); The code I queried earlier is moved here, so make sure to carry through any changes if it is indeed wrong! > + samples_buf_size += sizeof(int64_t); > + samples_buf = devm_krealloc(&sigma_delta->spi->dev, > + sigma_delta->samples_buf, > + samples_buf_size, GFP_KERNEL); > + if (!samples_buf) > + return -ENOMEM; > + > + sigma_delta->samples_buf = samples_buf; > + scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); > + > + xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0]; > + xfer[1].len = scan_size + (sigma_delta->status_appended ? 1 : 0); > + } > xfer[1].cs_change = 1; > > if (sigma_delta->info->has_registers) { > @@ -670,7 +700,8 @@ static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private) > if ((!sigma_delta->rdy_gpiod || gpiod_get_value(sigma_delta->rdy_gpiod)) && > ad_sd_disable_irq(sigma_delta)) { > complete(&sigma_delta->completion); > - iio_trigger_poll(sigma_delta->trig); > + if (sigma_delta->trig) Is this defensive or can we actually get here with out a trigger? I would have thought in the offload case (so no trigger here) we'd not call this function at all. Mind you, can't we get here with no trigger when doing a calibration or simple read normally? > + iio_trigger_poll(sigma_delta->trig); > > return IRQ_HANDLED; > }
On 6/22/25 10:00 AM, Jonathan Cameron wrote: > On Fri, 20 Jun 2025 17:20:14 -0500 > David Lechner <dlechner@baylibre.com> wrote: > >> Add SPI offload support to the ad_sigma_delta module. >> ... >> @@ -670,7 +700,8 @@ static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private) >> if ((!sigma_delta->rdy_gpiod || gpiod_get_value(sigma_delta->rdy_gpiod)) && >> ad_sd_disable_irq(sigma_delta)) { >> complete(&sigma_delta->completion); >> - iio_trigger_poll(sigma_delta->trig); >> + if (sigma_delta->trig) > > Is this defensive or can we actually get here with out a trigger? > I would have thought in the offload case (so no trigger here) we'd not call this > function at all. Mind you, can't we get here with no trigger when doing > a calibration or simple read normally? The difference is that with SPI offload, sigma_delta->trig is NULL but without SPI offload, it is never NULL. iio_trigger_poll() doesn't check for NULL and would crash with NULL pointer dereference. During calibration and single conversion the poll function isn't attached to the trigger, so I guess that is why it didn't really hurt to call iio_trigger_poll() in that case. > >> + iio_trigger_poll(sigma_delta->trig); >> >> return IRQ_HANDLED; >> } > >
© 2016 - 2025 Red Hat, Inc.