]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: adc: ad7768-1: add support for SPI offload
authorJonathan Santos <Jonathan.Santos@analog.com>
Mon, 23 Feb 2026 11:59:53 +0000 (08:59 -0300)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sun, 1 Mar 2026 12:10:23 +0000 (12:10 +0000)
The AD7768-1 family supports sampling rates up to 1 MSPS, which exceeds
the capabilities of conventional triggered buffer operations due to SPI
transaction overhead and interrupt latency.

Add SPI offload support to enable hardware-accelerated data acquisition
that bypasses software SPI transactions using continuous data streaming.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/Kconfig
drivers/iio/adc/ad7768-1.c

index c54788ca8d8e2a2fb4735569932f35b446926432..a9dedbb8eb461deb0cb849c8420c178382ec1d12 100644 (file)
@@ -418,8 +418,10 @@ config AD7768_1
        select REGMAP_SPI
        select RATIONAL
        select IIO_BUFFER
+       select IIO_BUFFER_DMAENGINE
        select IIO_TRIGGER
        select IIO_TRIGGERED_BUFFER
+       select SPI_OFFLOAD
        help
          Say yes here to build support for Analog Devices AD7768-1 SPI
          simultaneously sampling sigma-delta analog to digital converter (ADC).
index b77523393a18310a01395835495bbc077b8cf57a..917a3a0380a1b0123af71423477a46ef292b602a 100644 (file)
 #include <linux/regulator/consumer.h>
 #include <linux/regulator/driver.h>
 #include <linux/sysfs.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
 #include <linux/spi/spi.h>
 #include <linux/unaligned.h>
 #include <linux/units.h>
 #include <linux/util_macros.h>
 
 #include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dmaengine.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
 #include <linux/iio/trigger.h>
@@ -161,6 +164,8 @@ enum ad7768_filter_regval {
 enum ad7768_scan_type {
        AD7768_SCAN_TYPE_NORMAL,
        AD7768_SCAN_TYPE_HIGH_SPEED,
+       AD7768_SCAN_TYPE_OFFLOAD_NORMAL,
+       AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED,
 };
 
 enum {
@@ -266,6 +271,18 @@ static const struct iio_scan_type ad7768_scan_type[] = {
                .storagebits = 16,
                .endianness = IIO_BE,
        },
+       [AD7768_SCAN_TYPE_OFFLOAD_NORMAL] = {
+               .sign = 's',
+               .realbits = 24,
+               .storagebits = 32,
+               .endianness = IIO_CPU,
+       },
+       [AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED] = {
+               .sign = 's',
+               .realbits = 16,
+               .storagebits = 32,
+               .endianness = IIO_CPU,
+       },
 };
 
 struct ad7768_chip_info {
@@ -283,6 +300,8 @@ struct ad7768_chip_info {
 
 struct ad7768_state {
        struct spi_device *spi;
+       struct spi_offload *offload;
+       struct spi_offload_trigger *offload_trigger;
        struct regmap *regmap;
        struct regmap *regmap24;
        int vref_uv;
@@ -306,6 +325,8 @@ struct ad7768_state {
        struct gpio_desc *gpio_reset;
        const char *labels[AD7768_MAX_CHANNELS];
        struct gpio_chip gpiochip;
+       struct spi_transfer offload_xfer;
+       struct spi_message offload_msg;
        const struct ad7768_chip_info *chip;
        bool en_spi_sync;
        struct mutex pga_lock; /* protect device internal state (PGA) */
@@ -1119,6 +1140,10 @@ static int ad7768_get_current_scan_type(const struct iio_dev *indio_dev,
 {
        struct ad7768_state *st = iio_priv(indio_dev);
 
+       if (st->offload)
+               return st->oversampling_ratio == 8 ?
+                      AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED : AD7768_SCAN_TYPE_OFFLOAD_NORMAL;
+
        return st->oversampling_ratio == 8 ?
               AD7768_SCAN_TYPE_HIGH_SPEED : AD7768_SCAN_TYPE_NORMAL;
 }
@@ -1341,6 +1366,78 @@ static const struct iio_buffer_setup_ops ad7768_buffer_ops = {
        .predisable = &ad7768_buffer_predisable,
 };
 
+static int ad7768_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+       struct ad7768_state *st = iio_priv(indio_dev);
+       struct spi_offload_trigger_config config = {
+               .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
+       };
+       const struct iio_scan_type *scan_type;
+       unsigned int unused;
+       int ret;
+
+       scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]);
+       if (IS_ERR(scan_type))
+               return PTR_ERR(scan_type);
+
+       st->offload_xfer.len = spi_bpw_to_bytes(scan_type->realbits);
+       st->offload_xfer.bits_per_word = scan_type->realbits;
+       st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+
+       spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
+       st->offload_msg.offload = st->offload;
+
+       ret = spi_optimize_message(st->spi, &st->offload_msg);
+       if (ret) {
+               dev_err(&st->spi->dev, "failed to prepare offload, err: %d\n", ret);
+               return ret;
+       }
+
+       /*
+        * Write a 1 to the LSB of the INTERFACE_FORMAT register to enter
+        * continuous read mode. Subsequent data reads do not require an
+        * initial 8-bit write to query the ADC_DATA register.
+        */
+       ret =  regmap_write(st->regmap, AD7768_REG_INTERFACE_FORMAT, 0x01);
+       if (ret)
+               goto err_unoptimize_message;
+
+       ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
+                                        &config);
+       if (ret)
+               goto err_exit_continuous_read_mode;
+
+       return 0;
+
+err_exit_continuous_read_mode:
+       regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &unused);
+
+err_unoptimize_message:
+       spi_unoptimize_message(&st->offload_msg);
+
+       return ret;
+}
+
+static int ad7768_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+       struct ad7768_state *st = iio_priv(indio_dev);
+       unsigned int unused;
+
+       spi_offload_trigger_disable(st->offload, st->offload_trigger);
+       spi_unoptimize_message(&st->offload_msg);
+
+       /*
+        * To exit continuous read mode, perform a single read of the ADC_DATA
+        * reg (0x2C), which allows further configuration of the device.
+        */
+       return regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &unused);
+}
+
+static const struct iio_buffer_setup_ops ad7768_offload_buffer_ops = {
+       .postenable = ad7768_offload_buffer_postenable,
+       .predisable = ad7768_offload_buffer_predisable,
+};
+
 static int ad7768_set_trigger_state(struct iio_trigger *trig, bool enable)
 {
        struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
@@ -1589,6 +1686,36 @@ static int ad7768_parse_aaf_gain(struct device *dev, struct ad7768_state *st)
        return 0;
 }
 
+static bool ad7768_offload_trigger_match(struct spi_offload_trigger *trigger,
+                                        enum spi_offload_trigger_type type,
+                                        u64 *args, u32 nargs)
+{
+       if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+               return false;
+
+       /* Up to 2 args are allowed, but only 1 is used */
+       if (nargs == 0 || nargs > 2 || args[0] != AD7768_TRIGGER_SOURCE_DRDY)
+               return false;
+
+       return true;
+}
+
+static int ad7768_offload_trigger_request(struct spi_offload_trigger *trigger,
+                                         enum spi_offload_trigger_type type,
+                                         u64 *args, u32 nargs)
+{
+       /* Should already be validated by match, but just in case */
+       if (nargs == 0 || nargs > 2)
+               return -EINVAL;
+
+       return 0;
+}
+
+static const struct spi_offload_trigger_ops ad7768_offload_trigger_ops = {
+       .match = ad7768_offload_trigger_match,
+       .request = ad7768_offload_trigger_request,
+};
+
 static const struct ad7768_chip_info ad7768_chip_info = {
        .name = "ad7768-1",
        .channel_spec = ad7768_channels,
@@ -1626,10 +1753,51 @@ static const struct ad7768_chip_info adaq7769_chip_info = {
        .has_variable_aaf = true,
 };
 
+static const struct spi_offload_config ad7768_spi_offload_config = {
+       .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static int ad7768_spi_offload_probe(struct iio_dev *indio_dev,
+                                   struct ad7768_state *st)
+{
+       struct device *dev = &st->spi->dev;
+       struct spi_offload_trigger_info trigger_info = {
+               .fwnode = dev_fwnode(dev),
+               .ops = &ad7768_offload_trigger_ops,
+               .priv = st,
+       };
+       struct dma_chan *rx_dma;
+       int ret;
+
+       ret = devm_spi_offload_trigger_register(dev, &trigger_info);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to register offload trigger\n");
+
+       st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload,
+                                                          SPI_OFFLOAD_TRIGGER_DATA_READY);
+       if (IS_ERR(st->offload_trigger))
+               return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+                                    "failed to get offload trigger\n");
+
+       rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+       if (IS_ERR(rx_dma))
+               return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\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, "failed to setup offload RX DMA\n");
+
+       indio_dev->setup_ops = &ad7768_offload_buffer_ops;
+
+       return 0;
+}
+
 static int ad7768_probe(struct spi_device *spi)
 {
        struct ad7768_state *st;
        struct iio_dev *indio_dev;
+       struct device *dev = &spi->dev;
        int ret;
 
        indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
@@ -1725,9 +1893,20 @@ static int ad7768_probe(struct spi_device *spi)
        if (ret)
                return ret;
 
-       ret = ad7768_triggered_buffer_alloc(indio_dev);
-       if (ret)
-               return ret;
+       st->offload = devm_spi_offload_get(dev, spi, &ad7768_spi_offload_config);
+       ret = PTR_ERR_OR_ZERO(st->offload);
+       if (ret == -ENODEV) {
+               /* If not using SPI offload, fall back to low speed usage */
+               ret = ad7768_triggered_buffer_alloc(indio_dev);
+               if (ret)
+                       return ret;
+       } else if (ret) {
+               return dev_err_probe(dev, ret, "failed to get SPI offload\n");
+       } else {
+               ret = ad7768_spi_offload_probe(indio_dev, st);
+               if (ret)
+                       return ret;
+       }
 
        return devm_iio_device_register(&spi->dev, indio_dev);
 }
@@ -1763,3 +1942,4 @@ module_spi_driver(ad7768_driver);
 MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
 MODULE_DESCRIPTION("Analog Devices AD7768-1 ADC driver");
 MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");