[PATCH v6 17/17] iio: dac: ad5791: Add offload support

From: David Lechner
Date: Wed Dec 11 2024 - 15:59:35 EST


From: Axel Haslam <ahaslam@xxxxxxxxxxxx>

Add SPI offload support to stream TX buffers using DMA.
This allows loading samples to the DAC with a rate of 1 MSPS.

Signed-off-by: Axel Haslam <ahaslam@xxxxxxxxxxxx>
Signed-off-by: David Lechner <dlechner@xxxxxxxxxxxx>
---

v6 changes: new patch in v6
---
drivers/iio/dac/Kconfig | 3 +
drivers/iio/dac/ad5791.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 153 insertions(+)

diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 4cde34e8c8e3356aa41bcd2cba38d67d5c6f8049..f6c5cb632acbdc2432f60b163452bb0c5f89fa72 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -296,6 +296,9 @@ config AD5770R
config AD5791
tristate "Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC SPI driver"
depends on SPI
+ select SPI_OFFLOAD
+ select IIO_BUFFER
+ select IIO_BUFFER_DMAENGINE
help
Say yes here to build support for Analog Devices AD5760, AD5780,
AD5781, AD5790, AD5791 High Resolution Voltage Output Digital to
diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
index 24462cb020e19e8e2c6faa13109ac047cf423c37..a2953a9a4e5d5bc17c9c4a8281be4b41b1af5de8 100644
--- a/drivers/iio/dac/ad5791.c
+++ b/drivers/iio/dac/ad5791.c
@@ -15,9 +15,12 @@
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <linux/spi/offload/consumer.h>
#include <linux/spi/spi.h>
#include <linux/sysfs.h>
+#include <linux/units.h>

+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/dac/ad5791.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -64,11 +67,13 @@
* struct ad5791_chip_info - chip specific information
* @name: name of the dac chip
* @channel: channel specification
+ * @channel_offload: channel specification for offload
* @get_lin_comp: function pointer to the device specific function
*/
struct ad5791_chip_info {
const char *name;
const struct iio_chan_spec channel;
+ const struct iio_chan_spec channel_offload;
int (*get_lin_comp)(unsigned int span);
};

@@ -81,6 +86,11 @@ struct ad5791_chip_info {
* @gpio_clear: clear gpio
* @gpio_ldac: load dac gpio
* @chip_info: chip model specific constants
+ * @offload_msg: spi message used for offload
+ * @offload_xfer: spi transfer used for offload
+ * @offload: offload device
+ * @offload_trigger: offload trigger
+ * @offload_trigger_hz: offload sample rate
* @vref_mv: actual reference voltage used
* @vref_neg_mv: voltage of the negative supply
* @ctrl: control register cache
@@ -96,6 +106,11 @@ struct ad5791_state {
struct gpio_desc *gpio_clear;
struct gpio_desc *gpio_ldac;
const struct ad5791_chip_info *chip_info;
+ struct spi_message offload_msg;
+ struct spi_transfer offload_xfer;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
+ unsigned int offload_trigger_hz;
unsigned short vref_mv;
unsigned int vref_neg_mv;
unsigned ctrl;
@@ -232,6 +247,25 @@ static int ad5780_get_lin_comp(unsigned int span)
return AD5780_LINCOMP_10_20;
}

+static int ad5791_set_sample_freq(struct ad5791_state *st, int val)
+{
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = val,
+ },
+ };
+ int ret;
+
+ ret = spi_offload_trigger_validate(st->offload_trigger, &config);
+ if (ret)
+ return ret;
+
+ st->offload_trigger_hz = config.periodic.frequency_hz;
+
+ return 0;
+}
+
static int ad5791_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
@@ -259,6 +293,9 @@ static int ad5791_read_raw(struct iio_dev *indio_dev,
do_div(val64, st->vref_mv);
*val = -val64;
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->offload_trigger_hz;
+ return IIO_VAL_INT;
default:
return -EINVAL;
}
@@ -299,6 +336,24 @@ static const struct ad5791_chip_info _name##_chip_info = { \
}, \
.ext_info = ad5791_ext_info, \
}, \
+ .channel_offload = { \
+ .type = IIO_VOLTAGE, \
+ .output = 1, \
+ .indexed = 1, \
+ .address = AD5791_ADDR_DAC0, \
+ .channel = 0, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = (bits), \
+ .storagebits = 32, \
+ .shift = (_shift), \
+ }, \
+ .ext_info = ad5791_ext_info, \
+ }, \
}

AD5791_DEFINE_CHIP_INFO(ad5760, 16, 4, ad5780_get_lin_comp);
@@ -322,16 +377,95 @@ static int ad5791_write_raw(struct iio_dev *indio_dev,

return ad5791_spi_write(st, chan->address, val);

+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+ return ad5791_set_sample_freq(st, val);
default:
return -EINVAL;
}
}

+static int ad5791_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ad5791_state *st = iio_priv(indio_dev);
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = st->offload_trigger_hz,
+ },
+ };
+
+ if (st->pwr_down)
+ return -EINVAL;
+
+ return spi_offload_trigger_enable(st->offload, st->offload_trigger,
+ &config);
+}
+
+static int ad5791_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad5791_state *st = iio_priv(indio_dev);
+
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ad5791_buffer_setup_ops = {
+ .preenable = &ad5791_buffer_preenable,
+ .postdisable = &ad5791_buffer_postdisable,
+};
+
+static int ad5791_offload_setup(struct iio_dev *indio_dev)
+{
+ struct ad5791_state *st = iio_priv(indio_dev);
+ struct spi_device *spi = st->spi;
+ struct dma_chan *tx_dma;
+ int ret;
+
+ st->offload_trigger = devm_spi_offload_trigger_get(&spi->dev,
+ st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(st->offload_trigger))
+ return dev_err_probe(&spi->dev, PTR_ERR(st->offload_trigger),
+ "failed to get offload trigger\n");
+
+ ret = ad5791_set_sample_freq(st, 1 * MEGA);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "failed to init sample rate\n");
+
+ tx_dma = devm_spi_offload_tx_stream_request_dma_chan(&spi->dev,
+ st->offload);
+ if (IS_ERR(tx_dma))
+ return dev_err_probe(&spi->dev, PTR_ERR(tx_dma),
+ "failed to get offload TX DMA\n");
+
+ ret = devm_iio_dmaengine_buffer_setup_with_handle(&spi->dev,
+ indio_dev, tx_dma, IIO_BUFFER_DIRECTION_OUT);
+ if (ret)
+ return ret;
+
+ st->offload_xfer.len = 4;
+ st->offload_xfer.bits_per_word = 24;
+ st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_TX_STREAM;
+
+ spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
+ st->offload_msg.offload = st->offload;
+
+ return devm_spi_optimize_message(&spi->dev, st->spi, &st->offload_msg);
+}
+
static const struct iio_info ad5791_info = {
.read_raw = &ad5791_read_raw,
.write_raw = &ad5791_write_raw,
};

+static const struct spi_offload_config ad5791_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_TX_STREAM_DMA,
+};
+
static int ad5791_probe(struct spi_device *spi)
{
const struct ad5791_platform_data *pdata = dev_get_platdata(&spi->dev);
@@ -416,6 +550,21 @@ static int ad5791_probe(struct spi_device *spi)
indio_dev->channels = &st->chip_info->channel;
indio_dev->num_channels = 1;
indio_dev->name = st->chip_info->name;
+
+ st->offload = devm_spi_offload_get(&spi->dev, spi, &ad5791_offload_config);
+ ret = PTR_ERR_OR_ZERO(st->offload);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(&spi->dev, ret, "failed to get offload\n");
+
+ if (ret != -ENODEV) {
+ indio_dev->channels = &st->chip_info->channel_offload;
+ indio_dev->setup_ops = &ad5791_buffer_setup_ops;
+ ret = ad5791_offload_setup(indio_dev);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "fail to setup offload\n");
+ }
+
return devm_iio_device_register(&spi->dev, indio_dev);
}

@@ -452,3 +601,4 @@ module_spi_driver(ad5791_driver);
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@xxxxxxxxxx>");
MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");

--
2.43.0