* [PATCH v6 00/17] spi: axi-spi-engine: add offload support
@ 2024-12-11 20:54 David Lechner
2024-12-11 20:54 ` [PATCH v6 01/17] spi: add basic support for SPI offloading David Lechner
` (16 more replies)
0 siblings, 17 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner,
Axel Haslam
Not much to say for this revision. The main changes were making the
trigger-sources/#trigger-source-cells properties more generic,
splitting up the spi-offload.h header into consumer/provider/types.h
and adding a DAC driver patch to make use of the TX DMA stream API.
If we think this is good enough to go in, the SPI patches should be
applying fine since this is based on a recent linux-next. But the IIO
patches will need some care. There are dependencies on both the
iio/fixes-togreg and the iio/testing branches as well as a couple of
patches that haven't been applied yet because they are waiting for other
dependencies [1]. So best would be to let Mark pick up the SPI stuff and
create an immutable branch and let Jonathan work out the IIO stuff.
[1]: https://lore.kernel.org/all/20241124125206.1ffd6e6c@jic23-huawei/
---
Changes in v6:
- Dropped the "spi: dt-bindings: add trigger-source.yaml" patch. It was
reworked and merged into dt-schema in
https://github.com/devicetree-org/dt-schema/pull/147
- Adjusted other dt-bindings patches to account for above change.
- Dropped one iio patch that was already applied to iio tree.
- Added a DAC patch to make use of the TX DMA stream API.
- Minor fixes and improvements to other patches based on feedback.
- Link to v5: https://lore.kernel.org/r/20241115-dlech-mainline-spi-engine-offload-2-v5-0-bea815bd5ea5@baylibre.com
Changes in v5:
- Dropped pwm patch. A variant of this patch has been picked up in the
pwm tree.
- Addressed review comments (see details in individual patches).
- Added some polish, like MAINTAINERS entries and updating ADC docs.
- Link to v4: https://lore.kernel.org/r/20241023-dlech-mainline-spi-engine-offload-2-v4-0-f8125b99f5a1@baylibre.com
Changes in v4:
- Dropped #spi-offload-cells and spi-offload properties from DT bindings.
- Made an attempt at a more generic trigger interface instead of using
clk framework. This also includes a new driver for a generic PWM
trigger.
- Addressed IIO review comments.
- Added new patches for iio/adc/ad4695 as 2nd user of SPI offload.
- Link to v3: https://lore.kernel.org/r/20240722-dlech-mainline-spi-engine-offload-2-v3-0-7420e45df69b@baylibre.com
Changes in v3:
- Reworked DT bindings to have things physically connected to the SPI
controller be properties of the SPI controller and use more
conventional provider/consumer properties.
- Added more SPI APIs for peripheral drivers to use to get auxillary
offload resources, like triggers.
- Link to v2: https://lore.kernel.org/r/20240510-dlech-mainline-spi-engine-offload-2-v2-0-8707a870c435@baylibre.com
Individual patches have more details on these changes and earlier revisions too.
---
As a recap, here is the background and end goal of this series:
The AXI SPI Engine is a SPI controller that has the ability to record a
series of SPI transactions and then play them back using a hardware
trigger. This allows operations to be performed, repeating many times,
without any CPU intervention. This is needed for achieving high data
rates (millions of samples per second) from ADCs and DACs that are
connected via a SPI bus.
The offload hardware interface consists of a trigger input and a data
output for the RX data. These are connected to other hardware external
to the SPI controller.
To record one or more transactions, commands and TX data are written
to memories in the controller (RX buffer is not used since RX data gets
streamed to an external sink). This sequence of transactions can then be
played back when the trigger input is asserted.
This series includes core SPI support along with the first SPI
controller (AXI SPI Engine) and SPI peripheral (AD7944 ADC) that use
them. This enables capturing analog data at 2 million samples per
second.
The hardware setup looks like this:
+-------------------------------+ +------------------+
| | | |
| SOC/FPGA | | AD7944 ADC |
| +---------------------+ | | |
| | AXI SPI Engine | | | |
| | SPI Bus ============ SPI Bus |
| | | | | |
| | +---------------+ | | | |
| | | Offload 0 | | | +------------------+
| | | RX DATA OUT > > > > |
| | | TRIGGER IN < < < v |
| | +---------------+ | ^ v |
| +---------------------+ ^ v |
| | AXI PWM | ^ v |
| | CH0 > ^ v |
| +---------------------+ v |
| | AXI DMA | v |
| | CH0 < < < |
| +---------------------+ |
| |
+-------------------------------+
---
Axel Haslam (1):
iio: dac: ad5791: Add offload support
David Lechner (16):
spi: add basic support for SPI offloading
spi: offload: add support for hardware triggers
dt-bindings: trigger-source: add generic PWM trigger source
spi: offload-trigger: add PWM trigger driver
spi: add offload TX/RX streaming APIs
spi: dt-bindings: axi-spi-engine: add SPI offload properties
spi: axi-spi-engine: implement offload support
iio: buffer-dmaengine: split requesting DMA channel from allocating buffer
iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle()
iio: adc: ad7944: don't use storagebits for sizing
iio: adc: ad7944: add support for SPI offload
doc: iio: ad7944: describe offload support
dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
iio: adc: ad4695: Add support for SPI offload
doc: iio: ad4695: add SPI offload support
iio: dac: ad5791: sort include directives
.../devicetree/bindings/iio/adc/adi,ad4695.yaml | 13 +
.../bindings/spi/adi,axi-spi-engine.yaml | 24 ++
.../bindings/trigger-source/pwm-trigger.yaml | 37 ++
Documentation/iio/ad4695.rst | 68 +++
Documentation/iio/ad7944.rst | 24 +-
MAINTAINERS | 12 +
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ad4695.c | 445 +++++++++++++++++++-
drivers/iio/adc/ad7944.c | 307 +++++++++++++-
drivers/iio/adc/adi-axi-adc.c | 2 +-
drivers/iio/buffer/industrialio-buffer-dmaengine.c | 144 +++++--
drivers/iio/dac/Kconfig | 3 +
drivers/iio/dac/ad5791.c | 166 +++++++-
drivers/iio/dac/adi-axi-dac.c | 2 +-
drivers/spi/Kconfig | 16 +
drivers/spi/Makefile | 4 +
drivers/spi/spi-axi-spi-engine.c | 314 +++++++++++++-
drivers/spi/spi-offload-trigger-pwm.c | 162 +++++++
drivers/spi/spi-offload.c | 465 +++++++++++++++++++++
drivers/spi/spi.c | 10 +
include/dt-bindings/iio/adc/adi,ad4695.h | 7 +
include/linux/iio/buffer-dmaengine.h | 7 +-
include/linux/spi/offload/consumer.h | 39 ++
include/linux/spi/offload/provider.h | 47 +++
include/linux/spi/offload/types.h | 99 +++++
include/linux/spi/spi.h | 20 +
26 files changed, 2336 insertions(+), 103 deletions(-)
---
base-commit: 8ca8e57217e8263bc6dda6b77ef6c9051c2b6241
change-id: 20240510-dlech-mainline-spi-engine-offload-2-afce3790b5ab
prerequisite-patch-id: 7e6d36bfc262e562cb74d524e96db64694064326
prerequisite-patch-id: d864ef9f8a7303822d50d580a9ebbd8d304c8aa6
Best regards,
--
David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v6 01/17] spi: add basic support for SPI offloading
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 11:21 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 02/17] spi: offload: add support for hardware triggers David Lechner
` (15 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Add the basic infrastructure to support SPI offload providers and
consumers.
SPI offloading is a feature that allows the SPI controller to perform
transfers without any CPU intervention. This is useful, e.g. for
high-speed data acquisition.
SPI controllers with offload support need to implement the get_offload
and put_offload callbacks and can use the devm_spi_offload_alloc() to
allocate offload instances.
SPI peripheral drivers will call devm_spi_offload_get() to get a
reference to the matching offload instance. This offload instance can
then be attached to a SPI message to request offloading that message.
It is expected that SPI controllers with offload support will check for
the offload instance in the SPI message in the ctlr->optimize_message()
callback and handle it accordingly.
CONFIG_SPI_OFFLOAD is intended to be a select-only option. Both
consumer and provider drivers should `select SPI_OFFLOAD` in their
Kconfig to ensure that the SPI core is built with offload support.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Drop use of PTR_ERR_OR_ZERO().
* Split header into types.h/provider.h/consumer.h.
* Remove unused spi_controller_offload_ops forward declaration.
v5 changes:
* Don't include linux/property.h (moved to later patch).
* Only allocate single offload instance instead of array.
* Allocate *priv separately to avoid alignment issues.
* Add put_offload() callback instead of assuming devm semantics.
* Drop struct spi_offload::spi. It was only being used as a flag.
* Don't get/put struct spi_offload::provider_dev.
* Add MAINTAINERS entry for me as reviewer for anything related to
SPI offload.
v4 changes:
* SPI offload functions moved to a separate file instead of spi.c
(spi.c is already too long).
* struct spi_offload and devm_spi_offload_get() are back, similar to
but improved over v1. This avoids having to pass the function ID
string to every function call and re-lookup the offload instance.
* offload message prepare/unprepare functions are removed. Instead the
existing optimize/unoptimize functions should be used. Setting
spi_message::offload pointer is used as a flag to differentiate
between an offloaded message and a regular message.
v3 changes:
* Minor changes to doc comments.
* Changed to use phandle array for spi-offloads.
* Changed id to string to make use of spi-offload-names.
v2 changes:
* This is a rework of "spi: add core support for controllers with offload
capabilities" from v1.
* The spi_offload_get() function that Nuno didn't like is gone. Instead,
there is now a mapping callback that uses the new generic devicetree
binding to request resources automatically when a SPI device is probed.
* The spi_offload_enable/disable() functions for dealing with hardware
triggers are deferred to a separate patch.
* This leaves adding spi_offload_prepare/unprepare() which have been
reworked to be a bit more robust.
---
MAINTAINERS | 6 ++
drivers/spi/Kconfig | 3 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-offload.c | 114 +++++++++++++++++++++++++++++++++++
include/linux/spi/offload/consumer.h | 22 +++++++
include/linux/spi/offload/provider.h | 19 ++++++
include/linux/spi/offload/types.h | 43 +++++++++++++
include/linux/spi/spi.h | 17 ++++++
8 files changed, 225 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index bf2dcd4e0261785add520b5eac747ceac523e112..9284a257607a740ab7f6fd960c2bcdc34ead7586 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22129,6 +22129,12 @@ F: Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
F: drivers/mtd/spi-nor/
F: include/linux/mtd/spi-nor.h
+SPI OFFLOAD
+R: David Lechner <dlechner@baylibre.com>
+F: drivers/spi/spi-offload.c
+F: include/linux/spi/spi-offload.h
+K: spi_offload
+
SPI SUBSYSTEM
M: Mark Brown <broonie@kernel.org>
L: linux-spi@vger.kernel.org
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index ea8a310329274bb2701e265cd152a56fb4e0f3a7..02064a4e292815ec0213e2e446b4f90ed8855a52 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -55,6 +55,9 @@ config SPI_MEM
This extension is meant to simplify interaction with SPI memories
by providing a high-level interface to send memory-like commands.
+config SPI_OFFLOAD
+ bool
+
comment "SPI Master Controller Drivers"
config SPI_AIROHA_SNFI
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 9db7554c1864bf9b37dcf59c16eb76f5af03a7e8..bb5fc20df21332232533c2e70c0cc230f6bcf27f 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -10,6 +10,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o
obj-$(CONFIG_SPI_MEM) += spi-mem.o
obj-$(CONFIG_SPI_MUX) += spi-mux.o
+obj-$(CONFIG_SPI_OFFLOAD) += spi-offload.o
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
new file mode 100644
index 0000000000000000000000000000000000000000..3a40ef30debf09c6fd7b2c14526f3e5976e2b21f
--- /dev/null
+++ b/drivers/spi/spi-offload.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ */
+
+/*
+ * SPI Offloading support.
+ *
+ * Some SPI controllers support offloading of SPI transfers. Essentially, this
+ * is the ability for a SPI controller to perform SPI transfers with minimal
+ * or even no CPU intervention, e.g. via a specialized SPI controller with a
+ * hardware trigger or via a conventional SPI controller using a non-Linux MCU
+ * processor core to offload the work.
+ */
+
+#define DEFAULT_SYMBOL_NAMESPACE "SPI_OFFLOAD"
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
+#include <linux/spi/offload/types.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+struct spi_controller_and_offload {
+ struct spi_controller *controller;
+ struct spi_offload *offload;
+};
+
+/**
+ * devm_spi_offload_alloc() - Allocate offload instance
+ * @dev: Device for devm purposes and assigned to &struct spi_offload.provider_dev
+ * @priv_size: Size of private data to allocate
+ *
+ * Offload providers should use this to allocate offload instances.
+ *
+ * Return: Pointer to new offload instance or error on failure.
+ */
+struct spi_offload *devm_spi_offload_alloc(struct device *dev,
+ size_t priv_size)
+{
+ struct spi_offload *offload;
+ void *priv;
+
+ offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
+ if (!offload)
+ return ERR_PTR(-ENOMEM);
+
+ priv = devm_kzalloc(dev, priv_size, GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ offload->provider_dev = dev;
+ offload->priv = priv;
+
+ return offload;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_alloc);
+
+static void spi_offload_put(void *data)
+{
+ struct spi_controller_and_offload *resource = data;
+
+ resource->controller->put_offload(resource->offload);
+ kfree(resource);
+}
+
+/**
+ * devm_spi_offload_get() - Get an offload instance
+ * @dev: Device for devm purposes
+ * @spi: SPI device to use for the transfers
+ * @config: Offload configuration
+ *
+ * Peripheral drivers call this function to get an offload instance that meets
+ * the requirements specified in @config. If no suitable offload instance is
+ * available, -ENODEV is returned.
+ *
+ * Return: Offload instance or error on failure.
+ */
+struct spi_offload *devm_spi_offload_get(struct device *dev,
+ struct spi_device *spi,
+ const struct spi_offload_config *config)
+{
+ struct spi_controller_and_offload *resource;
+ int ret;
+
+ if (!spi || !config)
+ return ERR_PTR(-EINVAL);
+
+ if (!spi->controller->get_offload)
+ return ERR_PTR(-ENODEV);
+
+ resource = kzalloc(sizeof(*resource), GFP_KERNEL);
+ if (!resource)
+ return ERR_PTR(-ENOMEM);
+
+ resource->controller = spi->controller;
+ resource->offload = spi->controller->get_offload(spi, config);
+ if (IS_ERR(resource->offload)) {
+ kfree(resource);
+ return resource->offload;
+ }
+
+ ret = devm_add_action_or_reset(dev, spi_offload_put, resource);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return resource->offload;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_get);
diff --git a/include/linux/spi/offload/consumer.h b/include/linux/spi/offload/consumer.h
new file mode 100644
index 0000000000000000000000000000000000000000..05543dbedf3086fb4befcd149cff3c8c70a88825
--- /dev/null
+++ b/include/linux/spi/offload/consumer.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ */
+
+#ifndef __LINUX_SPI_OFFLOAD_CONSUMER_H
+#define __LINUX_SPI_OFFLOAD_CONSUMER_H
+
+#include <linux/module.h>
+#include <linux/spi/offload/types.h>
+#include <linux/types.h>
+
+MODULE_IMPORT_NS("SPI_OFFLOAD");
+
+struct device;
+struct spi_device;
+
+struct spi_offload *devm_spi_offload_get(struct device *dev, struct spi_device *spi,
+ const struct spi_offload_config *config);
+
+#endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
diff --git a/include/linux/spi/offload/provider.h b/include/linux/spi/offload/provider.h
new file mode 100644
index 0000000000000000000000000000000000000000..278c4edfcdb7b1f43870ca99d2ba252bf2820576
--- /dev/null
+++ b/include/linux/spi/offload/provider.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ */
+
+#ifndef __LINUX_SPI_OFFLOAD_PROVIDER_H
+#define __LINUX_SPI_OFFLOAD_PROVIDER_H
+
+#include <linux/module.h>
+#include <linux/types.h>
+
+MODULE_IMPORT_NS("SPI_OFFLOAD");
+
+struct device;
+
+struct spi_offload *devm_spi_offload_alloc(struct device *dev, size_t priv_size);
+
+#endif /* __LINUX_SPI_OFFLOAD_PROVIDER_H */
diff --git a/include/linux/spi/offload/types.h b/include/linux/spi/offload/types.h
new file mode 100644
index 0000000000000000000000000000000000000000..a74f8d84541b10062353e81a638f05628b696394
--- /dev/null
+++ b/include/linux/spi/offload/types.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ */
+
+#ifndef __LINUX_SPI_OFFLOAD_TYPES_H
+#define __LINUX_SPI_OFFLOAD_TYPES_H
+
+#include <linux/types.h>
+
+struct device;
+
+/* Offload can be triggered by external hardware event. */
+#define SPI_OFFLOAD_CAP_TRIGGER BIT(0)
+/* Offload can record and then play back TX data when triggered. */
+#define SPI_OFFLOAD_CAP_TX_STATIC_DATA BIT(1)
+/* Offload can get TX data from an external stream source. */
+#define SPI_OFFLOAD_CAP_TX_STREAM_DMA BIT(2)
+/* Offload can send RX data to an external stream sink. */
+#define SPI_OFFLOAD_CAP_RX_STREAM_DMA BIT(3)
+
+/**
+ * struct spi_offload_config - offload configuration
+ *
+ * This is used to request an offload with specific configuration.
+ */
+struct spi_offload_config {
+ /** @capability_flags: required capabilities. See %SPI_OFFLOAD_CAP_* */
+ u32 capability_flags;
+};
+
+/**
+ * struct spi_offload - offload instance
+ */
+struct spi_offload {
+ /** @provider_dev: for get/put reference counting */
+ struct device *provider_dev;
+ /** @priv: provider driver private data */
+ void *priv;
+};
+
+#endif /* __LINUX_SPI_OFFLOAD_TYPES_H */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 8497f4747e24d4ecd85b74f49609ac1c82c73535..98bdc8c16c20521c0a94e5f72f5e71c4f6d7d11e 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -31,6 +31,8 @@ struct spi_transfer;
struct spi_controller_mem_ops;
struct spi_controller_mem_caps;
struct spi_message;
+struct spi_offload;
+struct spi_offload_config;
/*
* INTERFACES between SPI master-side drivers and SPI slave protocol handlers,
@@ -496,6 +498,10 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
* @mem_ops: optimized/dedicated operations for interactions with SPI memory.
* This field is optional and should only be implemented if the
* controller has native support for memory like operations.
+ * @get_offload: callback for controllers with offload support to get matching
+ * offload instance. Implementations should return -ENODEV if no match is
+ * found.
+ * @put_offload: release the offload instance acquired by @get_offload.
* @mem_caps: controller capabilities for the handling of memory operations.
* @unprepare_message: undo any work done by prepare_message().
* @target_abort: abort the ongoing transfer request on an SPI target controller
@@ -740,6 +746,10 @@ struct spi_controller {
const struct spi_controller_mem_ops *mem_ops;
const struct spi_controller_mem_caps *mem_caps;
+ struct spi_offload *(*get_offload)(struct spi_device *spi,
+ const struct spi_offload_config *config);
+ void (*put_offload)(struct spi_offload *offload);
+
/* GPIO chip select */
struct gpio_desc **cs_gpiods;
bool use_gpio_descriptors;
@@ -1108,6 +1118,7 @@ struct spi_transfer {
* @state: for use by whichever driver currently owns the message
* @opt_state: for use by whichever driver currently owns the message
* @resources: for resource management when the SPI message is processed
+ * @offload: (optional) offload instance used by this message
*
* A @spi_message is used to execute an atomic sequence of data transfers,
* each represented by a struct spi_transfer. The sequence is "atomic"
@@ -1168,6 +1179,12 @@ struct spi_message {
*/
void *opt_state;
+ /*
+ * Optional offload instance used by this message. This must be set
+ * by the peripheral driver before calling spi_optimize_message().
+ */
+ struct spi_offload *offload;
+
/* List of spi_res resources when the SPI message is processed */
struct list_head resources;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 02/17] spi: offload: add support for hardware triggers
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
2024-12-11 20:54 ` [PATCH v6 01/17] spi: add basic support for SPI offloading David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 11:30 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source David Lechner
` (14 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Extend SPI offloading to support hardware triggers.
This allows an arbitrary hardware trigger to be used to start a SPI
transfer that was previously set up with spi_optimize_message().
A new struct spi_offload_trigger is introduced that can be used to
configure any type of trigger. It has a type discriminator and a union
to allow it to be extended in the future. Two trigger types are defined
to start with. One is a trigger that indicates that the SPI peripheral
is ready to read or write data. The other is a periodic trigger to
repeat a SPI message at a fixed rate.
There is also a spi_offload_hw_trigger_validate() function that works
similar to clk_round_rate(). It basically asks the question of if we
enabled the hardware trigger what would the actual parameters be. This
can be used to test if the requested trigger type is actually supported
by the hardware and for periodic triggers, it can be used to find the
actual rate that the hardware is capable of.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Updated for header file split.
v5 changes:
* Use struct kref instead of struct dev for trigger lifetime management.
* Don't use __free() for args.fwnode.
* Pass *trigger instead of *priv to all callbacks.
* Add new *spi_offload_trigger_get_priv() function.
* Use ops instead of priv for "provider is gone" flag.
* Combine devm_spi_offload_trigger_alloc() and
devm_spi_offload_trigger_register() into one function.
* Add kernel-doc comments for public functions.
v4 changes:
* Added new struct spi_offload_trigger that is a generic struct for any
hardware trigger rather than returning a struct clk.
* Added new spi_offload_hw_trigger_validate() function.
* Dropped extra locking since it was too restrictive.
v3 changes:
* renamed enable/disable functions to spi_offload_hw_trigger_*mode*_...
* added spi_offload_hw_trigger_get_clk() function
* fixed missing EXPORT_SYMBOL_GPL
v2 changes:
* This is split out from "spi: add core support for controllers with
offload capabilities".
* Added locking for offload trigger to claim exclusive use of the SPI
bus.
---
drivers/spi/spi-offload.c | 281 +++++++++++++++++++++++++++++++++++
include/linux/spi/offload/consumer.h | 12 ++
include/linux/spi/offload/provider.h | 28 ++++
include/linux/spi/offload/types.h | 37 +++++
4 files changed, 358 insertions(+)
diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
index 3a40ef30debf09c6fd7b2c14526f3e5976e2b21f..43582e50e279c4b1b958765fec556aaa91180e55 100644
--- a/drivers/spi/spi-offload.c
+++ b/drivers/spi/spi-offload.c
@@ -19,7 +19,11 @@
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/export.h>
+#include <linux/kref.h>
+#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/property.h>
#include <linux/spi/offload/consumer.h>
#include <linux/spi/offload/provider.h>
#include <linux/spi/offload/types.h>
@@ -31,6 +35,23 @@ struct spi_controller_and_offload {
struct spi_offload *offload;
};
+struct spi_offload_trigger {
+ struct list_head list;
+ struct kref ref;
+ struct fwnode_handle *fwnode;
+ /* synchronizes calling ops and driver registration */
+ struct mutex lock;
+ /*
+ * If the provider goes away while the consumer still has a reference,
+ * ops and priv will be set to NULL and all calls will fail with -ENODEV.
+ */
+ const struct spi_offload_trigger_ops *ops;
+ void *priv;
+};
+
+static LIST_HEAD(spi_offload_triggers);
+static DEFINE_MUTEX(spi_offload_triggers_lock);
+
/**
* devm_spi_offload_alloc() - Allocate offload instance
* @dev: Device for devm purposes and assigned to &struct spi_offload.provider_dev
@@ -112,3 +133,263 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
return resource->offload;
}
EXPORT_SYMBOL_GPL(devm_spi_offload_get);
+
+static void spi_offload_trigger_free(struct kref *ref)
+{
+ struct spi_offload_trigger *trigger =
+ container_of(ref, struct spi_offload_trigger, ref);
+
+ mutex_destroy(&trigger->lock);
+ fwnode_handle_put(trigger->fwnode);
+ kfree(trigger);
+}
+
+static void spi_offload_trigger_put(void *data)
+{
+ struct spi_offload_trigger *trigger = data;
+
+ scoped_guard(mutex, &trigger->lock)
+ if (trigger->ops && trigger->ops->release)
+ trigger->ops->release(trigger);
+
+ kref_put(&trigger->ref, spi_offload_trigger_free);
+}
+
+static struct spi_offload_trigger
+*spi_offload_trigger_get(enum spi_offload_trigger_type type,
+ struct fwnode_reference_args *args)
+{
+ struct spi_offload_trigger *trigger;
+ bool match = false;
+ int ret;
+
+ guard(mutex)(&spi_offload_triggers_lock);
+
+ list_for_each_entry(trigger, &spi_offload_triggers, list) {
+ if (trigger->fwnode != args->fwnode)
+ continue;
+
+ match = trigger->ops->match(trigger, type, args->args, args->nargs);
+ if (match)
+ break;
+ }
+
+ if (!match)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ guard(mutex)(&trigger->lock);
+
+ if (!trigger->ops)
+ return ERR_PTR(-ENODEV);
+
+ if (trigger->ops->request) {
+ ret = trigger->ops->request(trigger, type, args->args, args->nargs);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ kref_get(&trigger->ref);
+
+ return trigger;
+}
+
+/**
+ * devm_spi_offload_trigger_get() - Get an offload trigger instance
+ * @dev: Device for devm purposes.
+ * @offload: Offload instance connected to a trigger.
+ * @type: Trigger type to get.
+ *
+ * Return: Offload trigger instance or error on failure.
+ */
+struct spi_offload_trigger
+*devm_spi_offload_trigger_get(struct device *dev,
+ struct spi_offload *offload,
+ enum spi_offload_trigger_type type)
+{
+ struct spi_offload_trigger *trigger;
+ struct fwnode_reference_args args;
+ int ret;
+
+ ret = fwnode_property_get_reference_args(dev_fwnode(offload->provider_dev),
+ "trigger-sources",
+ "#trigger-source-cells", 0, 0,
+ &args);
+ if (ret)
+ return ERR_PTR(ret);
+
+ trigger = spi_offload_trigger_get(type, &args);
+ fwnode_handle_put(args.fwnode);
+ if (IS_ERR(trigger))
+ return trigger;
+
+ ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return trigger;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_get);
+
+/**
+ * spi_offload_trigger_validate - Validate the requested trigger
+ * @trigger: Offload trigger instance
+ * @config: Trigger config to validate
+ *
+ * On success, @config may be modifed to reflect what the hardware can do.
+ * For example, the frequency of a periodic trigger may be adjusted to the
+ * nearest supported value.
+ *
+ * Callers will likely need to do additional validation of the modified trigger
+ * parameters.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int spi_offload_trigger_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ guard(mutex)(&trigger->lock);
+
+ if (!trigger->ops)
+ return -ENODEV;
+
+ if (!trigger->ops->validate)
+ return -EOPNOTSUPP;
+
+ return trigger->ops->validate(trigger, config);
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_validate);
+
+/**
+ * spi_offload_trigger_enable - enables trigger for offload
+ * @offload: Offload instance
+ * @trigger: Offload trigger instance
+ * @config: Trigger config to validate
+ *
+ * There must be a prepared offload instance with the specified ID (i.e.
+ * spi_optimize_message() was called with the same offload assigned to the
+ * message). This will also reserve the bus for exclusive use by the offload
+ * instance until the trigger is disabled. Any other attempts to send a
+ * transfer or lock the bus will fail with -EBUSY during this time.
+ *
+ * Calls must be balanced with spi_offload_trigger_disable().
+ *
+ * Context: can sleep
+ * Return: 0 on success, else a negative error code.
+ */
+int spi_offload_trigger_enable(struct spi_offload *offload,
+ struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ int ret;
+
+ guard(mutex)(&trigger->lock);
+
+ if (!trigger->ops)
+ return -ENODEV;
+
+ if (offload->ops && offload->ops->trigger_enable) {
+ ret = offload->ops->trigger_enable(offload);
+ if (ret)
+ return ret;
+ }
+
+ if (trigger->ops->enable) {
+ ret = trigger->ops->enable(trigger, config);
+ if (ret) {
+ if (offload->ops->trigger_disable)
+ offload->ops->trigger_disable(offload);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_enable);
+
+/**
+ * spi_offload_trigger_disable - disables hardware trigger for offload
+ * @offload: Offload instance
+ * @trigger: Offload trigger instance
+ *
+ * Disables the hardware trigger for the offload instance with the specified ID
+ * and releases the bus for use by other clients.
+ *
+ * Context: can sleep
+ */
+void spi_offload_trigger_disable(struct spi_offload *offload,
+ struct spi_offload_trigger *trigger)
+{
+ if (offload->ops && offload->ops->trigger_disable)
+ offload->ops->trigger_disable(offload);
+
+ guard(mutex)(&trigger->lock);
+
+ if (!trigger->ops)
+ return;
+
+ if (trigger->ops->disable)
+ trigger->ops->disable(trigger);
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_disable);
+
+/* Triggers providers */
+
+static void spi_offload_trigger_unregister(void *data)
+{
+ struct spi_offload_trigger *trigger = data;
+
+ scoped_guard(mutex, &spi_offload_triggers_lock)
+ list_del(&trigger->list);
+
+ scoped_guard(mutex, &trigger->lock) {
+ trigger->priv = NULL;
+ trigger->ops = NULL;
+ }
+
+ kref_put(&trigger->ref, spi_offload_trigger_free);
+}
+
+/**
+ * devm_spi_offload_trigger_register() - Allocate and register an offload trigger
+ * @dev: Device for devm purposes.
+ * @info: Provider-specific trigger info.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int devm_spi_offload_trigger_register(struct device *dev,
+ struct spi_offload_trigger_info *info)
+{
+ struct spi_offload_trigger *trigger;
+
+ if (!info->fwnode || !info->ops)
+ return -EINVAL;
+
+ trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);
+ if (!trigger)
+ return -ENOMEM;
+
+ kref_init(&trigger->ref);
+ mutex_init(&trigger->lock);
+ trigger->fwnode = fwnode_handle_get(info->fwnode);
+ trigger->ops = info->ops;
+ trigger->priv = info->priv;
+
+ scoped_guard(mutex, &spi_offload_triggers_lock)
+ list_add_tail(&trigger->list, &spi_offload_triggers);
+
+ return devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger);
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register);
+
+/**
+ * spi_offload_trigger_get_priv() - Get the private data for the trigger
+ *
+ * @trigger: Offload trigger instance.
+ *
+ * Return: Private data for the trigger.
+ */
+void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger)
+{
+ return trigger->priv;
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_get_priv);
diff --git a/include/linux/spi/offload/consumer.h b/include/linux/spi/offload/consumer.h
index 05543dbedf3086fb4befcd149cff3c8c70a88825..5a0ec5303d600728959594bcdbd0cb2baeba7c77 100644
--- a/include/linux/spi/offload/consumer.h
+++ b/include/linux/spi/offload/consumer.h
@@ -19,4 +19,16 @@ struct spi_device;
struct spi_offload *devm_spi_offload_get(struct device *dev, struct spi_device *spi,
const struct spi_offload_config *config);
+struct spi_offload_trigger
+*devm_spi_offload_trigger_get(struct device *dev,
+ struct spi_offload *offload,
+ enum spi_offload_trigger_type type);
+int spi_offload_trigger_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config);
+int spi_offload_trigger_enable(struct spi_offload *offload,
+ struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config);
+void spi_offload_trigger_disable(struct spi_offload *offload,
+ struct spi_offload_trigger *trigger);
+
#endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
diff --git a/include/linux/spi/offload/provider.h b/include/linux/spi/offload/provider.h
index 278c4edfcdb7b1f43870ca99d2ba252bf2820576..76c7cf65109241f39c6ab72f0e15454a874b6c24 100644
--- a/include/linux/spi/offload/provider.h
+++ b/include/linux/spi/offload/provider.h
@@ -8,12 +8,40 @@
#define __LINUX_SPI_OFFLOAD_PROVIDER_H
#include <linux/module.h>
+#include <linux/spi/offload/types.h>
#include <linux/types.h>
MODULE_IMPORT_NS("SPI_OFFLOAD");
struct device;
+struct spi_offload_trigger;
struct spi_offload *devm_spi_offload_alloc(struct device *dev, size_t priv_size);
+struct spi_offload_trigger_ops {
+ bool (*match)(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type, u64 *args, u32 nargs);
+ int (*request)(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type, u64 *args, u32 nargs);
+ void (*release)(struct spi_offload_trigger *trigger);
+ int (*validate)(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config);
+ int (*enable)(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config);
+ void (*disable)(struct spi_offload_trigger *trigger);
+};
+
+struct spi_offload_trigger_info {
+ /** @fwnode: Provider fwnode, used to match to consumer. */
+ struct fwnode_handle *fwnode;
+ /** @ops: Provider-specific callbacks. */
+ const struct spi_offload_trigger_ops *ops;
+ /** Provider-specific state to be used in callbacks. */
+ void *priv;
+};
+
+int devm_spi_offload_trigger_register(struct device *dev,
+ struct spi_offload_trigger_info *info);
+void *spi_offload_trigger_get_priv(struct spi_offload_trigger *trigger);
+
#endif /* __LINUX_SPI_OFFLOAD_PROVIDER_H */
diff --git a/include/linux/spi/offload/types.h b/include/linux/spi/offload/types.h
index a74f8d84541b10062353e81a638f05628b696394..7476f2073b02ee0f9edd3ae75e587b075746fa92 100644
--- a/include/linux/spi/offload/types.h
+++ b/include/linux/spi/offload/types.h
@@ -38,6 +38,43 @@ struct spi_offload {
struct device *provider_dev;
/** @priv: provider driver private data */
void *priv;
+ /** @ops: callbacks for offload support */
+ const struct spi_offload_ops *ops;
+};
+
+enum spi_offload_trigger_type {
+ /* Indication from SPI peripheral that data is read to read. */
+ SPI_OFFLOAD_TRIGGER_DATA_READY,
+ /* Trigger comes from a periodic source such as a clock. */
+ SPI_OFFLOAD_TRIGGER_PERIODIC,
+};
+
+struct spi_offload_trigger_periodic {
+ u64 frequency_hz;
+};
+
+struct spi_offload_trigger_config {
+ /** @type: type discriminator for union */
+ enum spi_offload_trigger_type type;
+ union {
+ struct spi_offload_trigger_periodic periodic;
+ };
+};
+
+/**
+ * struct spi_offload_ops - callbacks implemented by offload providers
+ */
+struct spi_offload_ops {
+ /**
+ * @trigger_enable: Optional callback to enable the trigger for the
+ * given offload instance.
+ */
+ int (*trigger_enable)(struct spi_offload *offload);
+ /**
+ * @trigger_disable: Optional callback to disable the trigger for the
+ * given offload instance.
+ */
+ void (*trigger_disable)(struct spi_offload *offload);
};
#endif /* __LINUX_SPI_OFFLOAD_TYPES_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
2024-12-11 20:54 ` [PATCH v6 01/17] spi: add basic support for SPI offloading David Lechner
2024-12-11 20:54 ` [PATCH v6 02/17] spi: offload: add support for hardware triggers David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 14:25 ` Jonathan Cameron
2024-12-17 14:32 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver David Lechner
` (13 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Add a new binding for using a PWM signal as a trigger source.
The idea here is similar to e.g. "pwm-clock" to allow a trigger source
consumer to use a PWM provider as a trigger source.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Moved file from bindings/spi/ to bindings/trigger-source/
* Updated description to not mention SPI
* Dropped $ref: /schemas/spi/trigger-source.yaml#
* Swapped order in name to be consistent with "pwm-clock"
v5 changes:
* Add MAINTAINERS entry
v4 changes: new patch in v4
---
.../bindings/trigger-source/pwm-trigger.yaml | 37 ++++++++++++++++++++++
MAINTAINERS | 5 +++
2 files changed, 42 insertions(+)
diff --git a/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml b/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1eac20031dc3cf921aafb8aa37f4e4eca1075835
--- /dev/null
+++ b/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/trigger-source/pwm-trigger.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic trigger source using PWM
+
+description: Remaps a PWM channel as a trigger source.
+
+maintainers:
+ - David Lechner <dlechner@baylibre.com>
+
+properties:
+ compatible:
+ const: pwm-trigger
+
+ '#trigger-source-cells':
+ const: 0
+
+ pwms:
+ maxItems: 1
+
+required:
+ - compatible
+ - '#trigger-source-cells'
+ - pwms
+
+additionalProperties: false
+
+examples:
+ - |
+ trigger {
+ compatible = "pwm-trigger";
+ #trigger-source-cells = <0>;
+ pwms = <&pwm 0 1000000 0>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 9284a257607a740ab7f6fd960c2bcdc34ead7586..b2aa6f37743e48353c60e5973ea8126590c7f6d5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23879,6 +23879,11 @@ W: https://github.com/srcres258/linux-doc
T: git git://github.com/srcres258/linux-doc.git doc-zh-tw
F: Documentation/translations/zh_TW/
+TRIGGER SOURCE - PWM
+M: David Lechner <dlechner@baylibre.com>
+S: Maintained
+F: Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
+
TRUSTED SECURITY MODULE (TSM) ATTESTATION REPORTS
M: Dan Williams <dan.j.williams@intel.com>
L: linux-coco@lists.linux.dev
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (2 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 11:36 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 05/17] spi: add offload TX/RX streaming APIs David Lechner
` (12 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Add a new driver for a generic PWM trigger for SPI offloads.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Use dev instead of &pdev->dev
* Swap order of "pwm" and "trigger" in name to follow "pwm-clock"
precedent.
v5 changes:
* Updated to accommodate changes in other patches in this series.
* Add MAINTAINERS entry.
v4 changes: new patch in v4
---
MAINTAINERS | 1 +
drivers/spi/Kconfig | 12 +++
drivers/spi/Makefile | 3 +
drivers/spi/spi-offload-trigger-pwm.c | 162 ++++++++++++++++++++++++++++++++++
4 files changed, 178 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index b2aa6f37743e48353c60e5973ea8126590c7f6d5..d8d72da5ac4bcab817a515774eb8db37a7e94f25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22131,6 +22131,7 @@ F: include/linux/mtd/spi-nor.h
SPI OFFLOAD
R: David Lechner <dlechner@baylibre.com>
+F: drivers/spi/spi-offload-trigger-pwm.c
F: drivers/spi/spi-offload.c
F: include/linux/spi/spi-offload.h
K: spi_offload
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 02064a4e292815ec0213e2e446b4f90ed8855a52..2cfc14be869790f5226130428bb7cb40aadfb031 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1320,4 +1320,16 @@ endif # SPI_SLAVE
config SPI_DYNAMIC
def_bool ACPI || OF_DYNAMIC || SPI_SLAVE
+if SPI_OFFLOAD
+
+comment "SPI Offload triggers"
+
+config SPI_OFFLOAD_TRIGGER_PWM
+ tristate "SPI offload trigger using PWM"
+ depends on PWM
+ help
+ Generic SPI offload trigger implemented using PWM output.
+
+endif # SPI_OFFLOAD
+
endif # SPI
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index bb5fc20df21332232533c2e70c0cc230f6bcf27f..0068d170bc99c750c13376c4013991d927bbac63 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -164,3 +164,6 @@ obj-$(CONFIG_SPI_AMD) += spi-amd.o
# SPI slave protocol handlers
obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o
+
+# SPI offload triggers
+obj-$(CONFIG_SPI_OFFLOAD_TRIGGER_PWM) += spi-offload-trigger-pwm.o
diff --git a/drivers/spi/spi-offload-trigger-pwm.c b/drivers/spi/spi-offload-trigger-pwm.c
new file mode 100644
index 0000000000000000000000000000000000000000..b26d4437c589052709a8206f8314ffd08355870e
--- /dev/null
+++ b/drivers/spi/spi-offload-trigger-pwm.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ *
+ * Generic PWM trigger for SPI offload.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/mod_devicetable.h>
+#include <linux/spi/offload/provider.h>
+#include <linux/types.h>
+
+struct spi_offload_trigger_pwm_state {
+ struct device *dev;
+ struct pwm_device *pwm;
+};
+
+static bool spi_offload_trigger_pwm_match(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ if (nargs)
+ return false;
+
+ return type == SPI_OFFLOAD_TRIGGER_PERIODIC;
+}
+
+static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
+ struct spi_offload_trigger_periodic *periodic = &config->periodic;
+ struct pwm_waveform wf = { };
+ int ret;
+
+ if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
+ return -EINVAL;
+
+ if (!periodic->frequency_hz)
+ return -EINVAL;
+
+ wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
+ /* REVISIT: 50% duty-cycle for now - may add config parameter later */
+ wf.duty_length_ns = wf.period_length_ns / 2;
+
+ ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
+ if (ret < 0)
+ return ret;
+
+ periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns);
+
+ return 0;
+}
+
+static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
+ struct spi_offload_trigger_periodic *periodic = &config->periodic;
+ struct pwm_waveform wf = { };
+
+ if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
+ return -EINVAL;
+
+ if (!periodic->frequency_hz)
+ return -EINVAL;
+
+ wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
+ /* REVISIT: 50% duty-cycle for now - may add config parameter later */
+ wf.duty_length_ns = wf.period_length_ns / 2;
+
+ return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
+}
+
+static void spi_offload_trigger_pwm_disable(struct spi_offload_trigger *trigger)
+{
+ struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger);
+ struct pwm_waveform wf;
+ int ret;
+
+ ret = pwm_get_waveform_might_sleep(st->pwm, &wf);
+ if (ret < 0) {
+ dev_err(st->dev, "failed to get waveform: %d\n", ret);
+ return;
+ }
+
+ wf.duty_length_ns = 0;
+
+ ret = pwm_set_waveform_might_sleep(st->pwm, &wf, false);
+ if (ret < 0)
+ dev_err(st->dev, "failed to disable PWM: %d\n", ret);
+}
+
+static const struct spi_offload_trigger_ops spi_offload_trigger_pwm_ops = {
+ .match = spi_offload_trigger_pwm_match,
+ .validate = spi_offload_trigger_pwm_validate,
+ .enable = spi_offload_trigger_pwm_enable,
+ .disable = spi_offload_trigger_pwm_disable,
+};
+
+static void spi_offload_trigger_pwm_release(void *data)
+{
+ pwm_disable(data);
+}
+
+static int spi_offload_trigger_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spi_offload_trigger_info info = {
+ .fwnode = dev_fwnode(dev),
+ .ops = &spi_offload_trigger_pwm_ops,
+ };
+ struct spi_offload_trigger_pwm_state *st;
+ struct pwm_state state;
+ int ret;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ info.priv = st;
+ st->dev = dev;
+
+ st->pwm = devm_pwm_get(dev, NULL);
+ if (IS_ERR(st->pwm))
+ return dev_err_probe(dev, PTR_ERR(st->pwm), "failed to get PWM\n");
+
+ /* init with duty_cycle = 0, output enabled to ensure trigger off */
+ pwm_init_state(st->pwm, &state);
+ state.enabled = true;
+
+ ret = pwm_apply_might_sleep(st->pwm, &state);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to apply PWM state\n");
+
+ ret = devm_add_action_or_reset(dev, spi_offload_trigger_pwm_release, st->pwm);
+ if (ret)
+ return ret;
+
+ return devm_spi_offload_trigger_register(dev, &info);
+}
+
+static const struct of_device_id spi_offload_trigger_pwm_of_match_table[] = {
+ { .compatible = "pwm-trigger" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, spi_offload_trigger_pwm_of_match_table);
+
+static struct platform_driver spi_offload_trigger_pwm_driver = {
+ .driver = {
+ .name = "pwm-trigger",
+ .of_match_table = spi_offload_trigger_pwm_of_match_table,
+ },
+ .probe = spi_offload_trigger_pwm_probe,
+};
+module_platform_driver(spi_offload_trigger_pwm_driver);
+
+MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
+MODULE_DESCRIPTION("Generic PWM trigger");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 05/17] spi: add offload TX/RX streaming APIs
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (3 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 14:28 ` Jonathan Cameron
2024-12-17 11:43 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
` (11 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Most configuration of SPI offloads is handled opaquely using the offload
pointer that is passed to the various offload functions. However, there
are some offload features that need to be controlled on a per transfer
basis.
This patch adds a flag field to struct spi_transfer to allow specifying
such features. The first feature to be added is the ability to stream
data to/from a hardware sink/source rather than using a tx or rx buffer.
Additional flags can be added in the future as needed.
A flags field is also added to the offload struct for providers to
indicate which flags are supported. This allows for generic checking of
offload capabilities during __spi_validate() so that each offload
provider doesn't have to implement their own validation.
As a first users of this streaming capability, getter functions are
added to get a DMA channel that is directly connected to the offload.
Peripheral drivers will use this to get a DMA channel and configure it
to suit their needs.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Update for header file split.
* Fix wrong kernel-doc comments.
v5 change:
* Remove incorrect comment about caller needing to release DMA channels.
v4 changes:
* DMA API's now automatically release DMA channels instead of leaving
it up to the caller.
v3 changes:
* Added spi_offload_{tx,rx}_stream_get_dma_chan() functions.
v2 changes:
* This is also split out from "spi: add core support for controllers with
offload capabilities".
* In the previous version, we were using (void *)-1 as a sentinel value
that could be assigned, e.g. to rx_buf. But this was naive since there
is core code that would try to dereference this pointer. So instead,
we've added a new flags field to the spi_transfer structure for this
sort of thing. This also has the advantage of being able to be used in
the future for other arbitrary features.
---
drivers/spi/spi-offload.c | 70 ++++++++++++++++++++++++++++++++++++
drivers/spi/spi.c | 10 ++++++
include/linux/spi/offload/consumer.h | 5 +++
include/linux/spi/offload/types.h | 19 ++++++++++
include/linux/spi/spi.h | 3 ++
5 files changed, 107 insertions(+)
diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
index 43582e50e279c4b1b958765fec556aaa91180e55..df5e963d5ee29d37833559595536a460c530bc81 100644
--- a/drivers/spi/spi-offload.c
+++ b/drivers/spi/spi-offload.c
@@ -18,6 +18,7 @@
#include <linux/cleanup.h>
#include <linux/device.h>
+#include <linux/dmaengine.h>
#include <linux/export.h>
#include <linux/kref.h>
#include <linux/list.h>
@@ -332,6 +333,75 @@ void spi_offload_trigger_disable(struct spi_offload *offload,
}
EXPORT_SYMBOL_GPL(spi_offload_trigger_disable);
+static void spi_offload_release_dma_chan(void *chan)
+{
+ dma_release_channel(chan);
+}
+
+/**
+ * devm_spi_offload_tx_stream_request_dma_chan - Get the DMA channel info for the TX stream
+ * @dev: Device for devm purposes.
+ * @offload: Offload instance
+ *
+ * This is the DMA channel that will provide data to transfers that use the
+ * %SPI_OFFLOAD_XFER_TX_STREAM offload flag.
+ *
+ * Return: Pointer to DMA channel info, or negative error code
+ */
+struct dma_chan
+*devm_spi_offload_tx_stream_request_dma_chan(struct device *dev,
+ struct spi_offload *offload)
+{
+ struct dma_chan *chan;
+ int ret;
+
+ if (!offload->ops || !offload->ops->tx_stream_request_dma_chan)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ chan = offload->ops->tx_stream_request_dma_chan(offload);
+ if (IS_ERR(chan))
+ return chan;
+
+ ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return chan;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_tx_stream_request_dma_chan);
+
+/**
+ * devm_spi_offload_rx_stream_request_dma_chan - Get the DMA channel info for the RX stream
+ * @dev: Device for devm purposes.
+ * @offload: Offload instance
+ *
+ * This is the DMA channel that will receive data from transfers that use the
+ * %SPI_OFFLOAD_XFER_RX_STREAM offload flag.
+ *
+ * Return: Pointer to DMA channel info, or negative error code
+ */
+struct dma_chan
+*devm_spi_offload_rx_stream_request_dma_chan(struct device *dev,
+ struct spi_offload *offload)
+{
+ struct dma_chan *chan;
+ int ret;
+
+ if (!offload->ops || !offload->ops->rx_stream_request_dma_chan)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ chan = offload->ops->rx_stream_request_dma_chan(offload);
+ if (IS_ERR(chan))
+ return chan;
+
+ ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan, chan);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return chan;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_rx_stream_request_dma_chan);
+
/* Triggers providers */
static void spi_offload_trigger_unregister(void *data)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index ff1add2ecb91f18cf82e6f1e9595584c11adf9d8..4a871db9ee636aba64c866ebdd8bb1dbf82e0f42 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -31,6 +31,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/sched/rt.h>
#include <linux/slab.h>
+#include <linux/spi/offload/types.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <uapi/linux/sched/types.h>
@@ -4163,6 +4164,15 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
if (_spi_xfer_word_delay_update(xfer, spi))
return -EINVAL;
+
+ /* make sure controller supports required offload features */
+ if (xfer->offload_flags) {
+ if (!message->offload)
+ return -EINVAL;
+
+ if (xfer->offload_flags & ~message->offload->xfer_flags)
+ return -EINVAL;
+ }
}
message->status = -EINPROGRESS;
diff --git a/include/linux/spi/offload/consumer.h b/include/linux/spi/offload/consumer.h
index 5a0ec5303d600728959594bcdbd0cb2baeba7c77..cd7d5daa21e69b61c16eba6c10c855345a4f3297 100644
--- a/include/linux/spi/offload/consumer.h
+++ b/include/linux/spi/offload/consumer.h
@@ -31,4 +31,9 @@ int spi_offload_trigger_enable(struct spi_offload *offload,
void spi_offload_trigger_disable(struct spi_offload *offload,
struct spi_offload_trigger *trigger);
+struct dma_chan *devm_spi_offload_tx_stream_request_dma_chan(struct device *dev,
+ struct spi_offload *offload);
+struct dma_chan *devm_spi_offload_rx_stream_request_dma_chan(struct device *dev,
+ struct spi_offload *offload);
+
#endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
diff --git a/include/linux/spi/offload/types.h b/include/linux/spi/offload/types.h
index 7476f2073b02ee0f9edd3ae75e587b075746fa92..86d0e8cb9495bb43e177378b2041067de8ea8786 100644
--- a/include/linux/spi/offload/types.h
+++ b/include/linux/spi/offload/types.h
@@ -11,6 +11,11 @@
struct device;
+/* This is write xfer but TX uses external data stream rather than tx_buf. */
+#define SPI_OFFLOAD_XFER_TX_STREAM BIT(0)
+/* This is read xfer but RX uses external data stream rather than rx_buf. */
+#define SPI_OFFLOAD_XFER_RX_STREAM BIT(1)
+
/* Offload can be triggered by external hardware event. */
#define SPI_OFFLOAD_CAP_TRIGGER BIT(0)
/* Offload can record and then play back TX data when triggered. */
@@ -40,6 +45,8 @@ struct spi_offload {
void *priv;
/** @ops: callbacks for offload support */
const struct spi_offload_ops *ops;
+ /** @xfer_flags: %SPI_OFFLOAD_XFER_* flags supported by provider */
+ u32 xfer_flags;
};
enum spi_offload_trigger_type {
@@ -75,6 +82,18 @@ struct spi_offload_ops {
* given offload instance.
*/
void (*trigger_disable)(struct spi_offload *offload);
+ /**
+ * @tx_stream_request_dma_chan: Optional callback for controllers that
+ * have an offload where the TX data stream is connected directly to a
+ * DMA channel.
+ */
+ struct dma_chan *(*tx_stream_request_dma_chan)(struct spi_offload *offload);
+ /**
+ * @rx_stream_request_dma_chan: Optional callback for controllers that
+ * have an offload where the RX data stream is connected directly to a
+ * DMA channel.
+ */
+ struct dma_chan *(*rx_stream_request_dma_chan)(struct spi_offload *offload);
};
#endif /* __LINUX_SPI_OFFLOAD_TYPES_H */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 98bdc8c16c20521c0a94e5f72f5e71c4f6d7d11e..4c087009cf974595f23036b1b7a030a45913420c 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -1093,6 +1093,9 @@ struct spi_transfer {
u32 effective_speed_hz;
+ /* Use %SPI_OFFLOAD_XFER_* from spi-offload.h */
+ unsigned int offload_flags;
+
unsigned int ptp_sts_word_pre;
unsigned int ptp_sts_word_post;
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (4 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 05/17] spi: add offload TX/RX streaming APIs David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 14:30 ` Jonathan Cameron
2024-12-17 14:33 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 07/17] spi: axi-spi-engine: implement offload support David Lechner
` (10 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
The AXI SPI Engine has support for hardware offloading capabilities.
This includes a connection to a DMA controller for streaming RX or TX
data and a trigger input for starting execution of the SPI message
programmed in the offload. It is designed to support up to 32 offload
instances.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Drop type $ref for trigger-sources property since it is defined
elsewhere now
* Undo v5 changes that limited the number of offloads to 1
v5 changes:
* Also document offload0-tx DMA names since the hardware can support
that now.
* Limit the number of offloads to 1 for now since it would require
significant hardware changes to actually support more than that.
v4 changes:
* Dropped #spi-offload-cells property.
* Changed subject line.
v3 changes:
* Added #spi-offload-cells property.
* Added properties for triggers and RX data stream connected to DMA.
v2 changes:
* This is basically a new patch. It partially replaces "dt-bindings:
iio: offload: add binding for PWM/DMA triggered buffer".
* The controller no longer has an offloads object node and the
spi-offloads property is now a standard SPI peripheral property.
---
.../bindings/spi/adi,axi-spi-engine.yaml | 24 ++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
index d48faa42d025b07d72baa61f8946f42acbaf47dc..4b3828eda6cb4c5524570f00033b081a6e027b09 100644
--- a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
+++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
@@ -41,6 +41,26 @@ properties:
- const: s_axi_aclk
- const: spi_clk
+ trigger-sources:
+ description:
+ An array of trigger source phandles for offload instances. The index in
+ the array corresponds to the offload instance number.
+ minItems: 1
+ maxItems: 32
+
+ dmas:
+ description:
+ DMA channels connected to the input or output stream interface of an
+ offload instance.
+ minItems: 1
+ maxItems: 32
+
+ dma-names:
+ items:
+ pattern: "^offload(?:[12]?[0-9]|3[01])-[tr]x$"
+ minItems: 1
+ maxItems: 32
+
required:
- compatible
- reg
@@ -59,6 +79,10 @@ examples:
clocks = <&clkc 15>, <&clkc 15>;
clock-names = "s_axi_aclk", "spi_clk";
+ trigger-sources = <&trigger_clock>;
+ dmas = <&dma 0>;
+ dma-names = "offload0-rx";
+
#address-cells = <1>;
#size-cells = <0>;
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 07/17] spi: axi-spi-engine: implement offload support
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (5 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 11:48 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer David Lechner
` (9 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Implement SPI offload support for the AXI SPI Engine. Currently, the
hardware only supports triggering offload transfers with a hardware
trigger so attempting to use an offload message in the regular SPI
message queue will fail. Also, only allows streaming rx data to an
external sink, so attempts to use a rx_buf in the offload message will
fail.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Update for split spi/offload headers.
v5 changes:
* Set offload capability flags based on DT properties.
* Add support for TX DMA since the hardware supports that now.
* Update for changes in other patches in the series.
v4 changes:
* Adapted to changes in other patches in the series.
* Moved trigger enable/disable to same function as offload
enable/disable.
v3 changes:
* Added clk and dma_chan getter callbacks.
* Fixed some bugs.
v2 changes:
This patch has been reworked to accommodate the changes described in all
of the other patches.
---
drivers/spi/Kconfig | 1 +
drivers/spi/spi-axi-spi-engine.c | 314 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 308 insertions(+), 7 deletions(-)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2cfc14be869790f5226130428bb7cb40aadfb031..f496ab127ef011d092f66063e05772725ab89771 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -179,6 +179,7 @@ config SPI_AU1550
config SPI_AXI_SPI_ENGINE
tristate "Analog Devices AXI SPI Engine controller"
depends on HAS_IOMEM
+ select SPI_OFFLOAD
help
This enables support for the Analog Devices AXI SPI Engine SPI controller.
It is part of the SPI Engine framework that is used in some Analog Devices
diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
index 7c252126b33ea83fe6a6e80c6cb87499243069f5..dd6077d3ff7b8d29b0ca2e803a5930c4cedf2e93 100644
--- a/drivers/spi/spi-axi-spi-engine.c
+++ b/drivers/spi/spi-axi-spi-engine.c
@@ -2,11 +2,14 @@
/*
* SPI-Engine SPI controller driver
* Copyright 2015 Analog Devices Inc.
+ * Copyright 2024 BayLibre, SAS
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
+#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/completion.h>
+#include <linux/dmaengine.h>
#include <linux/fpga/adi-axi-common.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -14,9 +17,11 @@
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
+#include <linux/spi/offload/provider.h>
#include <linux/spi/spi.h>
#include <trace/events/spi.h>
+#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH 0x10
#define SPI_ENGINE_REG_RESET 0x40
#define SPI_ENGINE_REG_INT_ENABLE 0x80
@@ -24,6 +29,7 @@
#define SPI_ENGINE_REG_INT_SOURCE 0x88
#define SPI_ENGINE_REG_SYNC_ID 0xc0
+#define SPI_ENGINE_REG_OFFLOAD_SYNC_ID 0xc4
#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0
#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4
@@ -34,10 +40,24 @@
#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8
#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec
+#define SPI_ENGINE_MAX_NUM_OFFLOADS 32
+
+#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
+#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
+#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
+#define SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(x) (0x110 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
+#define SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(x) (0x114 + SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
+
+#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO GENMASK(15, 8)
+#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD GENMASK(7, 0)
+
#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0)
#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1)
#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2)
#define SPI_ENGINE_INT_SYNC BIT(3)
+#define SPI_ENGINE_INT_OFFLOAD_SYNC BIT(4)
+
+#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0)
#define SPI_ENGINE_CONFIG_CPHA BIT(0)
#define SPI_ENGINE_CONFIG_CPOL BIT(1)
@@ -79,6 +99,10 @@
#define SPI_ENGINE_CMD_CS_INV(flags) \
SPI_ENGINE_CMD(SPI_ENGINE_INST_CS_INV, 0, (flags))
+/* default sizes - can be changed when SPI Engine firmware is compiled */
+#define SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE 16
+#define SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE 16
+
struct spi_engine_program {
unsigned int length;
uint16_t instructions[] __counted_by(length);
@@ -106,6 +130,17 @@ struct spi_engine_message_state {
uint8_t *rx_buf;
};
+enum {
+ SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED,
+ SPI_ENGINE_OFFLOAD_FLAG_PREPARED,
+};
+
+struct spi_engine_offload {
+ struct spi_engine *spi_engine;
+ unsigned long flags;
+ unsigned int offload_num;
+};
+
struct spi_engine {
struct clk *clk;
struct clk *ref_clk;
@@ -118,6 +153,11 @@ struct spi_engine {
unsigned int int_enable;
/* shadows hardware CS inversion flag state */
u8 cs_inv;
+
+ unsigned int offload_ctrl_mem_size;
+ unsigned int offload_sdo_mem_size;
+ struct spi_offload *offload;
+ u32 offload_caps;
};
static void spi_engine_program_add_cmd(struct spi_engine_program *p,
@@ -163,9 +203,9 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
unsigned int n = min(len, 256U);
unsigned int flags = 0;
- if (xfer->tx_buf)
+ if (xfer->tx_buf || (xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM))
flags |= SPI_ENGINE_TRANSFER_WRITE;
- if (xfer->rx_buf)
+ if (xfer->rx_buf || (xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM))
flags |= SPI_ENGINE_TRANSFER_READ;
spi_engine_program_add_cmd(p, dry,
@@ -217,16 +257,24 @@ static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
*
* NB: This is separate from spi_engine_compile_message() because the latter
* is called twice and would otherwise result in double-evaluation.
+ *
+ * Returns 0 on success, -EINVAL on failure.
*/
-static void spi_engine_precompile_message(struct spi_message *msg)
+static int spi_engine_precompile_message(struct spi_message *msg)
{
unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz;
struct spi_transfer *xfer;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ /* If we have an offload transfer, we can't rx to buffer */
+ if (msg->offload && xfer->rx_buf)
+ return -EINVAL;
+
clk_div = DIV_ROUND_UP(max_hz, xfer->speed_hz);
xfer->effective_speed_hz = max_hz / min(clk_div, 256U);
}
+
+ return 0;
}
static void spi_engine_compile_message(struct spi_message *msg, bool dry,
@@ -521,11 +569,105 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
return IRQ_HANDLED;
}
+static int spi_engine_offload_prepare(struct spi_message *msg)
+{
+ struct spi_controller *host = msg->spi->controller;
+ struct spi_engine *spi_engine = spi_controller_get_devdata(host);
+ struct spi_engine_program *p = msg->opt_state;
+ struct spi_engine_offload *priv = msg->offload->priv;
+ struct spi_transfer *xfer;
+ void __iomem *cmd_addr;
+ void __iomem *sdo_addr;
+ size_t tx_word_count = 0;
+ unsigned int i;
+
+ if (p->length > spi_engine->offload_ctrl_mem_size)
+ return -EINVAL;
+
+ /* count total number of tx words in message */
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ /* no support for reading to rx_buf */
+ if (xfer->rx_buf)
+ return -EINVAL;
+
+ if (!xfer->tx_buf)
+ continue;
+
+ if (xfer->bits_per_word <= 8)
+ tx_word_count += xfer->len;
+ else if (xfer->bits_per_word <= 16)
+ tx_word_count += xfer->len / 2;
+ else
+ tx_word_count += xfer->len / 4;
+ }
+
+ if (tx_word_count && !(spi_engine->offload_caps & SPI_OFFLOAD_CAP_TX_STATIC_DATA))
+ return -EINVAL;
+
+ if (tx_word_count > spi_engine->offload_sdo_mem_size)
+ return -EINVAL;
+
+ /*
+ * This protects against calling spi_optimize_message() with an offload
+ * that has already been prepared with a different message.
+ */
+ if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags))
+ return -EBUSY;
+
+ cmd_addr = spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(priv->offload_num);
+ sdo_addr = spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(priv->offload_num);
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ if (!xfer->tx_buf)
+ continue;
+
+ if (xfer->bits_per_word <= 8) {
+ const u8 *buf = xfer->tx_buf;
+
+ for (i = 0; i < xfer->len; i++)
+ writel_relaxed(buf[i], sdo_addr);
+ } else if (xfer->bits_per_word <= 16) {
+ const u16 *buf = xfer->tx_buf;
+
+ for (i = 0; i < xfer->len / 2; i++)
+ writel_relaxed(buf[i], sdo_addr);
+ } else {
+ const u32 *buf = xfer->tx_buf;
+
+ for (i = 0; i < xfer->len / 4; i++)
+ writel_relaxed(buf[i], sdo_addr);
+ }
+ }
+
+ for (i = 0; i < p->length; i++)
+ writel_relaxed(p->instructions[i], cmd_addr);
+
+ return 0;
+}
+
+static void spi_engine_offload_unprepare(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+ struct spi_engine *spi_engine = priv->spi_engine;
+
+ writel_relaxed(1, spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
+ writel_relaxed(0, spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
+
+ clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags);
+}
+
static int spi_engine_optimize_message(struct spi_message *msg)
{
struct spi_engine_program p_dry, *p;
+ int ret;
- spi_engine_precompile_message(msg);
+ ret = spi_engine_precompile_message(msg);
+ if (ret)
+ return ret;
p_dry.length = 0;
spi_engine_compile_message(msg, true, &p_dry);
@@ -537,20 +679,61 @@ static int spi_engine_optimize_message(struct spi_message *msg)
spi_engine_compile_message(msg, false, p);
spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(
- AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
+ msg->offload ? 0 : AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
msg->opt_state = p;
+ if (msg->offload) {
+ ret = spi_engine_offload_prepare(msg);
+ if (ret) {
+ msg->opt_state = NULL;
+ kfree(p);
+ return ret;
+ }
+ }
+
return 0;
}
static int spi_engine_unoptimize_message(struct spi_message *msg)
{
+ if (msg->offload)
+ spi_engine_offload_unprepare(msg->offload);
+
kfree(msg->opt_state);
return 0;
}
+static struct spi_offload
+*spi_engine_get_offload(struct spi_device *spi,
+ const struct spi_offload_config *config)
+{
+ struct spi_controller *host = spi->controller;
+ struct spi_engine *spi_engine = spi_controller_get_devdata(host);
+ struct spi_engine_offload *priv;
+
+ if (!spi_engine->offload)
+ return ERR_PTR(-ENODEV);
+
+ if (config->capability_flags & ~spi_engine->offload_caps)
+ return ERR_PTR(-EINVAL);
+
+ priv = spi_engine->offload->priv;
+
+ if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv->flags))
+ return ERR_PTR(-EBUSY);
+
+ return spi_engine->offload;
+}
+
+static void spi_engine_put_offload(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+
+ clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv->flags);
+}
+
static int spi_engine_setup(struct spi_device *device)
{
struct spi_controller *host = device->controller;
@@ -583,6 +766,12 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
unsigned int int_enable = 0;
unsigned long flags;
+ if (msg->offload) {
+ dev_err(&host->dev, "Single transfer offload not supported\n");
+ msg->status = -EOPNOTSUPP;
+ goto out;
+ }
+
/* reinitialize message state for this transfer */
memset(st, 0, sizeof(*st));
st->cmd_buf = p->instructions;
@@ -632,11 +821,68 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
trace_spi_transfer_stop(msg, xfer);
}
+out:
spi_finalize_current_message(host);
return msg->status;
}
+static int spi_engine_trigger_enable(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+ struct spi_engine *spi_engine = priv->spi_engine;
+ unsigned int reg;
+
+ reg = readl_relaxed(spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
+ reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
+ writel_relaxed(reg, spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
+ return 0;
+}
+
+static void spi_engine_trigger_disable(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+ struct spi_engine *spi_engine = priv->spi_engine;
+ unsigned int reg;
+
+ reg = readl_relaxed(spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
+ reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
+ writel_relaxed(reg, spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
+}
+
+static struct dma_chan
+*spi_engine_tx_stream_request_dma_chan(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+ char name[16];
+
+ snprintf(name, sizeof(name), "offload%u-tx", priv->offload_num);
+
+ return dma_request_chan(offload->provider_dev, name);
+}
+
+static struct dma_chan
+*spi_engine_rx_stream_request_dma_chan(struct spi_offload *offload)
+{
+ struct spi_engine_offload *priv = offload->priv;
+ char name[16];
+
+ snprintf(name, sizeof(name), "offload%u-rx", priv->offload_num);
+
+ return dma_request_chan(offload->provider_dev, name);
+}
+
+static const struct spi_offload_ops spi_engine_offload_ops = {
+ .trigger_enable = spi_engine_trigger_enable,
+ .trigger_disable = spi_engine_trigger_disable,
+ .tx_stream_request_dma_chan = spi_engine_tx_stream_request_dma_chan,
+ .rx_stream_request_dma_chan = spi_engine_rx_stream_request_dma_chan,
+};
+
static void spi_engine_release_hw(void *p)
{
struct spi_engine *spi_engine = p;
@@ -651,8 +897,7 @@ static int spi_engine_probe(struct platform_device *pdev)
struct spi_engine *spi_engine;
struct spi_controller *host;
unsigned int version;
- int irq;
- int ret;
+ int irq, ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
@@ -667,6 +912,46 @@ static int spi_engine_probe(struct platform_device *pdev)
spin_lock_init(&spi_engine->lock);
init_completion(&spi_engine->msg_complete);
+ /*
+ * REVISIT: for now, all SPI Engines only have one offload. In the
+ * future, this should be read from a memory mapped register to
+ * determine the number of offloads enabled at HDL compile time. For
+ * now, we can tell if an offload is present if there is a trigger
+ * source wired up to it.
+ */
+ if (device_property_present(&pdev->dev, "trigger-sources")) {
+ struct spi_engine_offload *priv;
+
+ spi_engine->offload =
+ devm_spi_offload_alloc(&pdev->dev,
+ sizeof(struct spi_engine_offload));
+ if (IS_ERR(spi_engine->offload))
+ return PTR_ERR(spi_engine->offload);
+
+ priv = spi_engine->offload->priv;
+ priv->spi_engine = spi_engine;
+ priv->offload_num = 0;
+
+ spi_engine->offload->ops = &spi_engine_offload_ops;
+ spi_engine->offload_caps = SPI_OFFLOAD_CAP_TRIGGER;
+
+ if (device_property_match_string(&pdev->dev, "dma-names", "offload0-rx") >= 0) {
+ spi_engine->offload_caps |= SPI_OFFLOAD_CAP_RX_STREAM_DMA;
+ spi_engine->offload->xfer_flags |= SPI_OFFLOAD_XFER_RX_STREAM;
+ }
+
+ if (device_property_match_string(&pdev->dev, "dma-names", "offload0-tx") >= 0) {
+ spi_engine->offload_caps |= SPI_OFFLOAD_CAP_TX_STREAM_DMA;
+ spi_engine->offload->xfer_flags |= SPI_OFFLOAD_XFER_TX_STREAM;
+ } else {
+ /*
+ * HDL compile option to enable TX DMA stream also disables
+ * the SDO memory, so can't do both at the same time.
+ */
+ spi_engine->offload_caps |= SPI_OFFLOAD_CAP_TX_STATIC_DATA;
+ }
+ }
+
spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
if (IS_ERR(spi_engine->clk))
return PTR_ERR(spi_engine->clk);
@@ -688,6 +973,19 @@ static int spi_engine_probe(struct platform_device *pdev)
return -ENODEV;
}
+ if (ADI_AXI_PCORE_VER_MINOR(version) >= 1) {
+ unsigned int sizes = readl(spi_engine->base +
+ SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH);
+
+ spi_engine->offload_ctrl_mem_size = 1 <<
+ FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD, sizes);
+ spi_engine->offload_sdo_mem_size = 1 <<
+ FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO, sizes);
+ } else {
+ spi_engine->offload_ctrl_mem_size = SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE;
+ spi_engine->offload_sdo_mem_size = SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE;
+ }
+
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET);
writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
@@ -709,6 +1007,8 @@ static int spi_engine_probe(struct platform_device *pdev)
host->transfer_one_message = spi_engine_transfer_one_message;
host->optimize_message = spi_engine_optimize_message;
host->unoptimize_message = spi_engine_unoptimize_message;
+ host->get_offload = spi_engine_get_offload;
+ host->put_offload = spi_engine_put_offload;
host->num_chipselect = 8;
/* Some features depend of the IP core version. */
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (6 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 07/17] spi: axi-spi-engine: implement offload support David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 14:37 ` Jonathan Cameron
2024-12-17 11:50 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle() David Lechner
` (8 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Refactor the IIO dmaengine buffer code to split requesting the DMA
channel from allocating the buffer. We want to be able to add a new
function where the IIO device driver manages the DMA channel, so these
two actions need to be separate.
To do this, calling dma_request_chan() is moved from
iio_dmaengine_buffer_alloc() to iio_dmaengine_buffer_setup_ext(). A new
__iio_dmaengine_buffer_setup_ext() helper function is added to simplify
error unwinding and will also be used by a new function in a later
patch.
iio_dmaengine_buffer_free() now only frees the buffer and does not
release the DMA channel. A new iio_dmaengine_buffer_teardown() function
is added to unwind everything done in iio_dmaengine_buffer_setup_ext().
This keeps things more symmetrical with obvious pairs alloc/free and
setup/teardown.
Calling dma_get_slave_caps() in iio_dmaengine_buffer_alloc() is moved so
that we can avoid any gotos for error unwinding.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Split out from patch that adds the new function
* Dropped owns_chan flag
* Introduced iio_dmaengine_buffer_teardown() so that
iio_dmaengine_buffer_free() doesn't have to manage the DMA channel
---
drivers/iio/adc/adi-axi-adc.c | 2 +-
drivers/iio/buffer/industrialio-buffer-dmaengine.c | 106 ++++++++++++---------
drivers/iio/dac/adi-axi-dac.c | 2 +-
include/linux/iio/buffer-dmaengine.h | 2 +-
4 files changed, 65 insertions(+), 47 deletions(-)
diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index c7357601f0f869e57636f00bb1e26c059c3ab15c..a55db308baabf7b26ea98431cab1e6af7fe2a5f3 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -305,7 +305,7 @@ static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back,
static void axi_adc_free_buffer(struct iio_backend *back,
struct iio_buffer *buffer)
{
- iio_dmaengine_buffer_free(buffer);
+ iio_dmaengine_buffer_teardown(buffer);
}
static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg,
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 614e1c4189a9cdd5a8d9d8c5ef91566983032951..02847d3962fcbb43ec76167db6482ab951f20942 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -206,39 +206,29 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
/**
* iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
- * @dev: DMA channel consumer device
- * @channel: DMA channel name, typically "rx".
+ * @chan: DMA channel.
*
* This allocates a new IIO buffer which internally uses the DMAengine framework
- * to perform its transfers. The parent device will be used to request the DMA
- * channel.
+ * to perform its transfers.
*
* Once done using the buffer iio_dmaengine_buffer_free() should be used to
* release it.
*/
-static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
- const char *channel)
+static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan)
{
struct dmaengine_buffer *dmaengine_buffer;
unsigned int width, src_width, dest_width;
struct dma_slave_caps caps;
- struct dma_chan *chan;
int ret;
+ ret = dma_get_slave_caps(chan, &caps);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
dmaengine_buffer = kzalloc(sizeof(*dmaengine_buffer), GFP_KERNEL);
if (!dmaengine_buffer)
return ERR_PTR(-ENOMEM);
- chan = dma_request_chan(dev, channel);
- if (IS_ERR(chan)) {
- ret = PTR_ERR(chan);
- goto err_free;
- }
-
- ret = dma_get_slave_caps(chan, &caps);
- if (ret < 0)
- goto err_release;
-
/* Needs to be aligned to the maximum of the minimums */
if (caps.src_addr_widths)
src_width = __ffs(caps.src_addr_widths);
@@ -262,12 +252,6 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops;
return &dmaengine_buffer->queue.buffer;
-
-err_release:
- dma_release_channel(chan);
-err_free:
- kfree(dmaengine_buffer);
- return ERR_PTR(ret);
}
/**
@@ -276,17 +260,57 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
*
* Frees a buffer previously allocated with iio_dmaengine_buffer_alloc().
*/
-void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
+static void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
{
struct dmaengine_buffer *dmaengine_buffer =
iio_buffer_to_dmaengine_buffer(buffer);
iio_dma_buffer_exit(&dmaengine_buffer->queue);
- dma_release_channel(dmaengine_buffer->chan);
-
iio_buffer_put(buffer);
}
-EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, "IIO_DMAENGINE_BUFFER");
+
+/**
+ * iio_dmaengine_buffer_teardown() - Releases DMA channel and frees buffer
+ * @buffer: Buffer to free
+ *
+ * Releases the DMA channel and frees the buffer previously setup with
+ * iio_dmaengine_buffer_setup_ext().
+ */
+void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer)
+{
+ struct dmaengine_buffer *dmaengine_buffer =
+ iio_buffer_to_dmaengine_buffer(buffer);
+ struct dma_chan *chan = dmaengine_buffer->chan;
+
+ iio_dmaengine_buffer_free(buffer);
+ dma_release_channel(chan);
+}
+EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_teardown, "IIO_DMAENGINE_BUFFER");
+
+static struct iio_buffer
+*__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev,
+ struct dma_chan *chan,
+ enum iio_buffer_direction dir)
+{
+ struct iio_buffer *buffer;
+ int ret;
+
+ buffer = iio_dmaengine_buffer_alloc(chan);
+ if (IS_ERR(buffer))
+ return ERR_CAST(buffer);
+
+ indio_dev->modes |= INDIO_BUFFER_HARDWARE;
+
+ buffer->direction = dir;
+
+ ret = iio_device_attach_buffer(indio_dev, buffer);
+ if (ret) {
+ iio_dmaengine_buffer_free(buffer);
+ return ERR_PTR(ret);
+ }
+
+ return buffer;
+}
/**
* iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
@@ -300,7 +324,7 @@ EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, "IIO_DMAENGINE_BUFFER");
* It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the
* IIO device.
*
- * Once done using the buffer iio_dmaengine_buffer_free() should be used to
+ * Once done using the buffer iio_dmaengine_buffer_teardown() should be used to
* release it.
*/
struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
@@ -308,30 +332,24 @@ struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
const char *channel,
enum iio_buffer_direction dir)
{
+ struct dma_chan *chan;
struct iio_buffer *buffer;
- int ret;
-
- buffer = iio_dmaengine_buffer_alloc(dev, channel);
- if (IS_ERR(buffer))
- return ERR_CAST(buffer);
-
- indio_dev->modes |= INDIO_BUFFER_HARDWARE;
- buffer->direction = dir;
+ chan = dma_request_chan(dev, channel);
+ if (IS_ERR(chan))
+ return ERR_CAST(chan);
- ret = iio_device_attach_buffer(indio_dev, buffer);
- if (ret) {
- iio_dmaengine_buffer_free(buffer);
- return ERR_PTR(ret);
- }
+ buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
+ if (IS_ERR(buffer))
+ dma_release_channel(chan);
return buffer;
}
EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER");
-static void __devm_iio_dmaengine_buffer_free(void *buffer)
+static void devm_iio_dmaengine_buffer_teardown(void *buffer)
{
- iio_dmaengine_buffer_free(buffer);
+ iio_dmaengine_buffer_teardown(buffer);
}
/**
@@ -357,7 +375,7 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
if (IS_ERR(buffer))
return PTR_ERR(buffer);
- return devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free,
+ return devm_add_action_or_reset(dev, devm_iio_dmaengine_buffer_teardown,
buffer);
}
EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER");
diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c
index b143f7ed6847277aeb49094627d90e5d95eed71c..5d5157af0a233143daff906b699bdae10f368867 100644
--- a/drivers/iio/dac/adi-axi-dac.c
+++ b/drivers/iio/dac/adi-axi-dac.c
@@ -168,7 +168,7 @@ static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back,
static void axi_dac_free_buffer(struct iio_backend *back,
struct iio_buffer *buffer)
{
- iio_dmaengine_buffer_free(buffer);
+ iio_dmaengine_buffer_teardown(buffer);
}
enum {
diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h
index 81d9a19aeb9199dd58bb9d35a91f0ec4b00846df..72a2e3fd8a5bf5e8f27ee226ddd92979d233754b 100644
--- a/include/linux/iio/buffer-dmaengine.h
+++ b/include/linux/iio/buffer-dmaengine.h
@@ -12,7 +12,7 @@
struct iio_dev;
struct device;
-void iio_dmaengine_buffer_free(struct iio_buffer *buffer);
+void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer);
struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle()
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (7 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 14:39 ` Jonathan Cameron
2024-12-17 11:51 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing David Lechner
` (7 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Add a new devm_iio_dmaengine_buffer_setup_with_handle() function to
handle cases where the DMA channel is managed by the caller rather than
being requested and released by the iio_dmaengine module.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Rename from devm_iio_dmaengine_buffer_setup_ext2()
* This patch now just adds the new function - prep work was split out
to a separate patch
v5 changes: none
v4 changes:
* This replaces "iio: buffer-dmaengine: generalize requesting DMA channel"
---
drivers/iio/buffer/industrialio-buffer-dmaengine.c | 38 ++++++++++++++++++++++
include/linux/iio/buffer-dmaengine.h | 5 +++
2 files changed, 43 insertions(+)
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 02847d3962fcbb43ec76167db6482ab951f20942..e9d9a7d39fe191c2b6e8c196a08cdd26cd3a8d4b 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -380,6 +380,44 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
}
EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER");
+static void devm_iio_dmaengine_buffer_free(void *buffer)
+{
+ iio_dmaengine_buffer_free(buffer);
+}
+
+/**
+ * devm_iio_dmaengine_buffer_setup_with_handle() - Setup a DMA buffer for an
+ * IIO device
+ * @dev: Device for devm ownership
+ * @indio_dev: IIO device to which to attach this buffer.
+ * @chan: DMA channel
+ * @dir: Direction of buffer (in or out)
+ *
+ * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc()
+ * and attaches it to an IIO device with iio_device_attach_buffer().
+ * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the
+ * IIO device.
+ *
+ * This is the same as devm_iio_dmaengine_buffer_setup_ext() except that the
+ * caller manages requesting and releasing the DMA channel handle.
+ */
+int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
+ struct iio_dev *indio_dev,
+ struct dma_chan *chan,
+ enum iio_buffer_direction dir)
+{
+ struct iio_buffer *buffer;
+
+ buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
+ if (IS_ERR(buffer))
+ return PTR_ERR(buffer);
+
+ return devm_add_action_or_reset(dev, devm_iio_dmaengine_buffer_free,
+ buffer);
+}
+EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_with_handle,
+ "IIO_DMAENGINE_BUFFER");
+
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("DMA buffer for the IIO framework");
MODULE_LICENSE("GPL");
diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h
index 72a2e3fd8a5bf5e8f27ee226ddd92979d233754b..37f27545f69f761c3327c307cc6311b02a751096 100644
--- a/include/linux/iio/buffer-dmaengine.h
+++ b/include/linux/iio/buffer-dmaengine.h
@@ -11,6 +11,7 @@
struct iio_dev;
struct device;
+struct dma_chan;
void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer);
struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
@@ -26,6 +27,10 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
enum iio_buffer_direction dir);
+int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
+ struct iio_dev *indio_dev,
+ struct dma_chan *chan,
+ enum iio_buffer_direction dir);
#define devm_iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (8 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle() David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 16:56 ` Jonathan Cameron
2024-12-17 11:52 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload David Lechner
` (6 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Replace use of storagebits with realbits for determining the number of
bytes needed for SPI transfers.
When adding SPI offload support, storagebits will no longer be
guaranteed to be the "best fit" for 16-bit chips so we can no longer
rely on storagebits being the correct size expected by the SPI
framework. Instead, derive the correct size from realbits since it will
always be correct even when SPI offloads are used.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes: none
v5 changes: none
v4 changes: new patch in v4
---
drivers/iio/adc/ad7944.c | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
index a5aea4e9f1a7bdd8ca10f9f4580ad3216ddcdfcb..6d1202bd55a013b092ff803f2065fd128dfa9bdd 100644
--- a/drivers/iio/adc/ad7944.c
+++ b/drivers/iio/adc/ad7944.c
@@ -98,6 +98,9 @@ struct ad7944_chip_info {
const struct iio_chan_spec channels[2];
};
+/* get number of bytes for SPI xfer */
+#define AD7944_SPI_BYTES(scan_type) ((scan_type).realbits > 16 ? 4 : 2)
+
/*
* AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip
* @_name: The name of the chip
@@ -164,7 +167,7 @@ static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *
/* Then we can read the data during the acquisition phase */
xfers[2].rx_buf = &adc->sample.raw;
- xfers[2].len = BITS_TO_BYTES(chan->scan_type.storagebits);
+ xfers[2].len = AD7944_SPI_BYTES(chan->scan_type);
xfers[2].bits_per_word = chan->scan_type.realbits;
spi_message_init_with_transfers(&adc->msg, xfers, 3);
@@ -193,7 +196,7 @@ static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc
xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
xfers[1].rx_buf = &adc->sample.raw;
- xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits);
+ xfers[1].len = AD7944_SPI_BYTES(chan->scan_type);
xfers[1].bits_per_word = chan->scan_type.realbits;
spi_message_init_with_transfers(&adc->msg, xfers, 2);
@@ -228,7 +231,7 @@ static int ad7944_chain_mode_init_msg(struct device *dev, struct ad7944_adc *adc
xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
xfers[1].rx_buf = adc->chain_mode_buf;
- xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits) * n_chain_dev;
+ xfers[1].len = AD7944_SPI_BYTES(chan->scan_type) * n_chain_dev;
xfers[1].bits_per_word = chan->scan_type.realbits;
spi_message_init_with_transfers(&adc->msg, xfers, 2);
@@ -274,12 +277,12 @@ static int ad7944_single_conversion(struct ad7944_adc *adc,
return ret;
if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
- if (chan->scan_type.storagebits > 16)
+ if (chan->scan_type.realbits > 16)
*val = ((u32 *)adc->chain_mode_buf)[chan->scan_index];
else
*val = ((u16 *)adc->chain_mode_buf)[chan->scan_index];
} else {
- if (chan->scan_type.storagebits > 16)
+ if (chan->scan_type.realbits > 16)
*val = adc->sample.raw.u32;
else
*val = adc->sample.raw.u16;
@@ -409,8 +412,7 @@ static int ad7944_chain_mode_alloc(struct device *dev,
/* 1 word for each voltage channel + aligned u64 for timestamp */
chain_mode_buf_size = ALIGN(n_chain_dev *
- BITS_TO_BYTES(chan[0].scan_type.storagebits), sizeof(u64))
- + sizeof(u64);
+ AD7944_SPI_BYTES(chan[0].scan_type), sizeof(u64)) + sizeof(u64);
buf = devm_kzalloc(dev, chain_mode_buf_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (9 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 12:02 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 12/17] doc: iio: ad7944: describe offload support David Lechner
` (5 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Add support for SPI offload to the ad7944 driver. This allows reading
data at the max sample rate of 2.5 MSPS.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* More detailed comments on offload channels info struct. (I didn't
put them inside the macro because the multiline comments don't play
well with the line continuation "\".)
v5 changes:
* Remove dev_info() message.
* Implement sampling_frequency_available attribute.
* Limit max sample rate to chip-specific value.
v4 changes:
* Adapted to changes in other patches.
* Add new separate channel spec for when using SPI offload.
* Fixed some nitpicks.
v3 changes:
* Finished TODOs.
* Adapted to changes in other patches.
v2 changes:
In the previous version, there was a new separate driver for the PWM
trigger and DMA hardware buffer. This was deemed too complex so they
are moved into the ad7944 driver.
It has also been reworked to accommodate for the changes described in
the other patches.
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad7944.c | 291 ++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 276 insertions(+), 16 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a3e8ac569ce4c6b6b30b48acb265d530aa98e89c..995b9cacbaa964d26424346120c139858f93cdcd 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -360,6 +360,7 @@ config AD7923
config AD7944
tristate "Analog Devices AD7944 and similar ADCs driver"
depends on SPI
+ select SPI_OFFLOAD
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
index 6d1202bd55a013b092ff803f2065fd128dfa9bdd..07984eb450e82fc06d67fa0a157e3ae4e7555678 100644
--- a/drivers/iio/adc/ad7944.c
+++ b/drivers/iio/adc/ad7944.c
@@ -16,11 +16,14 @@
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>
+#include <linux/spi/offload/consumer.h>
#include <linux/spi/spi.h>
#include <linux/string_helpers.h>
+#include <linux/units.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
@@ -54,6 +57,12 @@ struct ad7944_adc {
enum ad7944_spi_mode spi_mode;
struct spi_transfer xfers[3];
struct spi_message msg;
+ struct spi_transfer offload_xfers[2];
+ struct spi_message offload_msg;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
+ unsigned long offload_trigger_hz;
+ int sample_freq_range[3];
void *chain_mode_buf;
/* Chip-specific timing specifications. */
const struct ad7944_timing_spec *timing_spec;
@@ -81,6 +90,8 @@ struct ad7944_adc {
/* quite time before CNV rising edge */
#define AD7944_T_QUIET_NS 20
+/* minimum CNV high time to trigger conversion */
+#define AD7944_T_CNVH_NS 10
static const struct ad7944_timing_spec ad7944_timing_spec = {
.conv_ns = 420,
@@ -95,7 +106,9 @@ static const struct ad7944_timing_spec ad7986_timing_spec = {
struct ad7944_chip_info {
const char *name;
const struct ad7944_timing_spec *timing_spec;
+ u32 max_sample_rate_hz;
const struct iio_chan_spec channels[2];
+ const struct iio_chan_spec offload_channels[1];
};
/* get number of bytes for SPI xfer */
@@ -105,13 +118,15 @@ struct ad7944_chip_info {
* AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip
* @_name: The name of the chip
* @_ts: The timing specification for the chip
+ * @_max: The maximum sample rate in Hz
* @_bits: The number of bits in the conversion result
* @_diff: Whether the chip is true differential or not
*/
-#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _bits, _diff) \
+#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _max, _bits, _diff) \
static const struct ad7944_chip_info _name##_chip_info = { \
.name = #_name, \
.timing_spec = &_ts##_timing_spec, \
+ .max_sample_rate_hz = _max, \
.channels = { \
{ \
.type = IIO_VOLTAGE, \
@@ -129,13 +144,43 @@ static const struct ad7944_chip_info _name##_chip_info = { \
}, \
IIO_CHAN_SOFT_TIMESTAMP(1), \
}, \
+ .offload_channels = { \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .differential = _diff, \
+ .channel = 0, \
+ .channel2 = _diff ? 1 : 0, \
+ .scan_index = 0, \
+ .scan_type.sign = _diff ? 's' : 'u', \
+ .scan_type.realbits = _bits, \
+ .scan_type.storagebits = 32, \
+ .scan_type.endianness = IIO_CPU, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SCALE) \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ }, \
+ }, \
}
+/*
+ * Notes on the offload channels:
+ * - There is no soft timestamp since everything is done in hardware.
+ * - There is a sampling frequency attribute added. This controls the SPI
+ * offload trigger.
+ * - The storagebits value depends on the SPI offload provider. Currently there
+ * is only one supported provider, namely the ADI PULSAR ADC HDL project,
+ * which always uses 32-bit words for data values, even for <= 16-bit ADCs.
+ * So the value is just hardcoded to 32 for now.
+ */
+
/* pseudo-differential with ground sense */
-AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 14, 0);
-AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0);
+AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 2.5 * MEGA, 14, 0);
+AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 2.5 * MEGA, 16, 0);
/* fully differential */
-AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1);
+AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 2 * MEGA, 18, 1);
static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *adc,
const struct iio_chan_spec *chan)
@@ -239,6 +284,48 @@ static int ad7944_chain_mode_init_msg(struct device *dev, struct ad7944_adc *adc
return devm_spi_optimize_message(dev, adc->spi, &adc->msg);
}
+/*
+ * Unlike ad7944_3wire_cs_mode_init_msg(), this creates a message that reads
+ * during the conversion phase instead of the acquisition phase when reading
+ * a sample from the ADC. This is needed to be able to read at the maximum
+ * sample rate. It requires the SPI controller to have offload support and a
+ * high enough SCLK rate to read the sample during the conversion phase.
+ */
+static int ad7944_3wire_cs_mode_init_offload_msg(struct device *dev,
+ struct ad7944_adc *adc,
+ const struct iio_chan_spec *chan)
+{
+ struct spi_transfer *xfers = adc->offload_xfers;
+ int ret;
+
+ /*
+ * CS is tied to CNV and we need a low to high transition to start the
+ * conversion, so place CNV low for t_QUIET to prepare for this.
+ */
+ xfers[0].delay.value = AD7944_T_QUIET_NS;
+ xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
+ /* CNV has to be high for a minimum time to trigger conversion. */
+ xfers[0].cs_change = 1;
+ xfers[0].cs_change_delay.value = AD7944_T_CNVH_NS;
+ xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+
+ /* Then we can read the previous sample during the conversion phase */
+ xfers[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ xfers[1].len = AD7944_SPI_BYTES(chan->scan_type);
+ xfers[1].bits_per_word = chan->scan_type.realbits;
+
+ spi_message_init_with_transfers(&adc->offload_msg, xfers,
+ ARRAY_SIZE(adc->offload_xfers));
+
+ adc->offload_msg.offload = adc->offload;
+
+ ret = devm_spi_optimize_message(dev, adc->spi, &adc->offload_msg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to prepare offload msg\n");
+
+ return 0;
+}
+
/**
* ad7944_convert_and_acquire - Perform a single conversion and acquisition
* @adc: The ADC device structure
@@ -294,6 +381,23 @@ static int ad7944_single_conversion(struct ad7944_adc *adc,
return IIO_VAL_INT;
}
+static int ad7944_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = adc->sample_freq_range;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ default:
+ return -EINVAL;
+ }
+}
+
static int ad7944_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long info)
@@ -326,13 +430,104 @@ static int ad7944_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = adc->offload_trigger_hz;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad7944_set_sample_freq(struct ad7944_adc *adc, int val)
+{
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = val,
+ },
+ };
+ int ret;
+
+ ret = spi_offload_trigger_validate(adc->offload_trigger, &config);
+ if (ret)
+ return ret;
+
+ adc->offload_trigger_hz = config.periodic.frequency_hz;
+
+ return 0;
+}
+
+static int ad7944_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long info)
+{
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val < 1 || val > adc->sample_freq_range[2])
+ return -EINVAL;
+
+ return ad7944_set_sample_freq(adc, val);
default:
return -EINVAL;
}
}
+static int ad7944_write_raw_get_fmt(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static const struct iio_info ad7944_iio_info = {
+ .read_avail = &ad7944_read_avail,
.read_raw = &ad7944_read_raw,
+ .write_raw = &ad7944_write_raw,
+ .write_raw_get_fmt = &ad7944_write_raw_get_fmt,
+};
+
+static int ad7944_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = adc->offload_trigger_hz,
+ },
+ };
+ int ret;
+
+ gpiod_set_value_cansleep(adc->turbo, 1);
+
+ ret = spi_offload_trigger_enable(adc->offload, adc->offload_trigger,
+ &config);
+ if (ret)
+ gpiod_set_value_cansleep(adc->turbo, 0);
+
+ return ret;
+}
+
+static int ad7944_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+
+ spi_offload_trigger_disable(adc->offload, adc->offload_trigger);
+ gpiod_set_value_cansleep(adc->turbo, 0);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ad7944_offload_buffer_setup_ops = {
+ .postenable = &ad7944_offload_buffer_postenable,
+ .predisable = &ad7944_offload_buffer_predisable,
};
static irqreturn_t ad7944_trigger_handler(int irq, void *p)
@@ -446,6 +641,11 @@ static const char * const ad7944_power_supplies[] = {
"avdd", "dvdd", "bvdd", "vio"
};
+static const struct spi_offload_config ad7944_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
static int ad7944_probe(struct spi_device *spi)
{
const struct ad7944_chip_info *chip_info;
@@ -471,6 +671,10 @@ static int ad7944_probe(struct spi_device *spi)
adc->timing_spec = chip_info->timing_spec;
+ adc->sample_freq_range[0] = 1; /* min */
+ adc->sample_freq_range[1] = 1; /* step */
+ adc->sample_freq_range[2] = chip_info->max_sample_rate_hz; /* max */
+
ret = device_property_match_property_string(dev, "adi,spi-mode",
ad7944_spi_modes,
ARRAY_SIZE(ad7944_spi_modes));
@@ -590,20 +794,74 @@ static int ad7944_probe(struct spi_device *spi)
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &ad7944_iio_info;
- if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
- indio_dev->available_scan_masks = chain_scan_masks;
- indio_dev->channels = chain_chan;
- indio_dev->num_channels = n_chain_dev + 1;
+ adc->offload = devm_spi_offload_get(dev, spi, &ad7944_offload_config);
+ ret = PTR_ERR_OR_ZERO(adc->offload);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get offload\n");
+
+ /* Fall back to low speed usage when no SPI offload available. */
+ if (ret == -ENODEV) {
+ if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
+ indio_dev->available_scan_masks = chain_scan_masks;
+ indio_dev->channels = chain_chan;
+ indio_dev->num_channels = n_chain_dev + 1;
+ } else {
+ indio_dev->channels = chip_info->channels;
+ indio_dev->num_channels = ARRAY_SIZE(chip_info->channels);
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ad7944_trigger_handler,
+ NULL);
+ if (ret)
+ return ret;
} else {
- indio_dev->channels = chip_info->channels;
- indio_dev->num_channels = ARRAY_SIZE(chip_info->channels);
- }
+ struct dma_chan *rx_dma;
- ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
- iio_pollfunc_store_time,
- ad7944_trigger_handler, NULL);
- if (ret)
- return ret;
+ if (adc->spi_mode != AD7944_SPI_MODE_SINGLE)
+ return dev_err_probe(dev, -EINVAL,
+ "offload only supported in single mode\n");
+
+ indio_dev->setup_ops = &ad7944_offload_buffer_setup_ops;
+ indio_dev->channels = chip_info->offload_channels;
+ indio_dev->num_channels = ARRAY_SIZE(chip_info->offload_channels);
+
+ adc->offload_trigger = devm_spi_offload_trigger_get(dev,
+ adc->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(adc->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(adc->offload_trigger),
+ "failed to get offload trigger\n");
+
+ ret = ad7944_set_sample_freq(adc, 2 * MEGA);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to init sample rate\n");
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev,
+ adc->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma),
+ "failed to get offload RX DMA\n");
+
+ /*
+ * REVISIT: ideally, we would confirm that the offload RX DMA
+ * buffer layout is the same as what is hard-coded in
+ * offload_channels. Right now, the only supported offload
+ * is the pulsar_adc project which always uses 32-bit word
+ * size for data values, regardless of the SPI bits per word.
+ */
+
+ ret = devm_iio_dmaengine_buffer_setup_with_handle(dev,
+ indio_dev, rx_dma, IIO_BUFFER_DIRECTION_IN);
+ if (ret)
+ return ret;
+
+ ret = ad7944_3wire_cs_mode_init_offload_msg(dev, adc,
+ &chip_info->offload_channels[0]);
+ if (ret)
+ return ret;
+ }
return devm_iio_device_register(dev, indio_dev);
}
@@ -638,3 +896,4 @@ module_spi_driver(ad7944_driver);
MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 12/17] doc: iio: ad7944: describe offload support
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (10 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-11 20:54 ` [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
` (4 subsequent siblings)
16 siblings, 0 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Add a section to the ad7944 documentation describing how to use the
driver with SPI offloading.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes: none
v5 changes: new patch in v5
---
Documentation/iio/ad7944.rst | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/Documentation/iio/ad7944.rst b/Documentation/iio/ad7944.rst
index 0d26e56aba8862a8d2ff541012bb4681d05c7bb5..e6dbe4d7f58cffd9f700f931ad2641d336526129 100644
--- a/Documentation/iio/ad7944.rst
+++ b/Documentation/iio/ad7944.rst
@@ -46,6 +46,8 @@ CS mode, 3-wire, without busy indicator
To select this mode in the device tree, set the ``adi,spi-mode`` property to
``"single"`` and omit the ``cnv-gpios`` property.
+This is the only wiring configuration supported when using `SPI offload support`_.
+
CS mode, 4-wire, without busy indicator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -106,7 +108,6 @@ Unimplemented features
----------------------
- ``BUSY`` indication
-- ``TURBO`` mode
Device attributes
@@ -147,6 +148,27 @@ AD7986 is a fully-differential ADC and has the following attributes:
In "chain" mode, additional chips will appear as additional voltage input
channels, e.g. ``in_voltage2-voltage3_raw``.
+SPI offload support
+===================
+
+To be able to achieve the maximum sample rate, the driver can be used with the
+`AXI SPI Engine`_ to provide SPI offload support.
+
+.. _AXI SPI Engine: http://analogdevicesinc.github.io/hdl/projects/pulsar_adc/index.html
+
+When SPI offload is being used, some attributes will be different.
+
+* ``trigger`` directory is removed.
+* ``in_voltage0_sampling_frequency`` attribute is added for setting the sample
+ rate.
+* ``in_voltage0_sampling_frequency_available`` attribute is added for querying
+ the max sample rate.
+* ``timestamp`` channel is removed.
+* Buffer data format may be different compared to when offload is not used,
+ e.g. the ``in_voltage0_type`` attribute.
+
+If the ``turbo-gpios`` property is present in the device tree, the driver will
+turn on TURBO during buffered reads and turn it off otherwise.
Device buffers
==============
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (11 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 12/17] doc: iio: ad7944: describe offload support David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 16:59 ` Jonathan Cameron
2024-12-17 14:36 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload David Lechner
` (3 subsequent siblings)
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Add a pwms property to the adi,ad4695 binding to specify an optional PWM
output connected to the CNV pin on the ADC.
Also add #trigger-source-cells property to allow the BUSY output to be
used as a SPI offload trigger source to indicate when a sample is ready
to be read.
Macros are added to adi,ad4695.h for the cell values to help with
readability since they are arbitrary values.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Drop $ref to trigger-source.yaml
* Add maxItems to pwms property
v5 changes:
* Added macros for cell values
v4 changes: new patch in v4
---
Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml | 13 +++++++++++++
include/dt-bindings/iio/adc/adi,ad4695.h | 7 +++++++
2 files changed, 20 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
index 7d2229dee4441e20a7bafc9165fe309ac2e9eada..cbde7a0505d2b5df22c54ca4556878bf22e9b4b1 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
@@ -84,6 +84,10 @@ properties:
description: The Reset Input (RESET). Should be configured GPIO_ACTIVE_LOW.
maxItems: 1
+ pwms:
+ description: PWM signal connected to the CNV pin.
+ maxItems: 1
+
interrupts:
minItems: 1
items:
@@ -106,6 +110,15 @@ properties:
The first cell is the GPn number: 0 to 3.
The second cell takes standard GPIO flags.
+ '#trigger-source-cells':
+ description: |
+ First cell indicates the output signal: 0 = BUSY, 1 = ALERT.
+ Second cell indicates which GPn pin is used: 0, 2 or 3.
+
+ For convenience, macros for these values are available in
+ dt-bindings/iio/adc/adi,ad4695.h.
+ const: 2
+
"#address-cells":
const: 1
diff --git a/include/dt-bindings/iio/adc/adi,ad4695.h b/include/dt-bindings/iio/adc/adi,ad4695.h
index 9fbef542bf670015c5b34bfbe1336e7e295bf8ab..fea4525d2710cbf58cd4236d3276bad4cb318df9 100644
--- a/include/dt-bindings/iio/adc/adi,ad4695.h
+++ b/include/dt-bindings/iio/adc/adi,ad4695.h
@@ -6,4 +6,11 @@
#define AD4695_COMMON_MODE_REFGND 0xFF
#define AD4695_COMMON_MODE_COM 0xFE
+#define AD4695_TRIGGER_EVENT_BUSY 0
+#define AD4695_TRIGGER_EVENT_ALERT 1
+
+#define AD4695_TRIGGER_PIN_GP0 0
+#define AD4695_TRIGGER_PIN_GP2 2
+#define AD4695_TRIGGER_PIN_GP3 3
+
#endif /* _DT_BINDINGS_ADI_AD4695_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (12 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 12:15 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 15/17] doc: iio: ad4695: add SPI offload support David Lechner
` (2 subsequent siblings)
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron, David Lechner
Add support for SPI offload to the ad4695 driver. SPI offload allows
sampling data at the max sample rate (500kSPS or 1MSPS).
This is developed and tested against the ADI example FPGA design for
this family of ADCs [1].
[1]: http://analogdevicesinc.github.io/hdl/projects/ad469x_fmc/index.html
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Fixed use of c++ style comments
* Moved static const struct definition out of probe function
* Changes bits_per_word to always be 19 for future oversampling
compatibility (Trevor is working on implementing oversampling support
on top of this patch, so we have high confidence this is the correct
thing to do)
* Fixed wrong xfer->len
v5 changes:
* Register SCLK speed handling has been split out into a separate series.
* Add sampling_frequency_available attribute.
* Limit max allowed sampling frequency based on chip info.
* Expand explanations of offload enable/disable ordering requirements.
* Finish TODO to use macros for phandle arg values.
* Don't use dev_info() when falling back to non-offload operation.
* Update to accommodate changes in other patches in this series.
v4 changes: new patch in v4
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4695.c | 445 +++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 429 insertions(+), 17 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 995b9cacbaa964d26424346120c139858f93cdcd..ec60b64c46e187e2be18ab1f8ca9e6f4f03299f9 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -52,6 +52,7 @@ config AD4695
tristate "Analog Device AD4695 ADC Driver"
depends on SPI
select IIO_BUFFER
+ select IIO_BUFFER_DMAENGINE
select IIO_TRIGGERED_BUFFER
select REGMAP
help
diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
index 13cf01d35301be40369571e7dd2aeac1a8148d15..c8cd73d19e869f11999608f61df5724d329b4427 100644
--- a/drivers/iio/adc/ad4695.c
+++ b/drivers/iio/adc/ad4695.c
@@ -19,14 +19,19 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/minmax.h>
+#include <linux/mutex.h>
#include <linux/property.h>
+#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
#include <linux/spi/spi.h>
#include <linux/units.h>
@@ -66,6 +71,8 @@
#define AD4695_REG_STD_SEQ_CONFIG 0x0024
#define AD4695_REG_GPIO_CTRL 0x0026
#define AD4695_REG_GP_MODE 0x0027
+#define AD4695_REG_GP_MODE_BUSY_GP_SEL BIT(5)
+#define AD4695_REG_GP_MODE_BUSY_GP_EN BIT(1)
#define AD4695_REG_TEMP_CTRL 0x0029
#define AD4695_REG_TEMP_CTRL_TEMP_EN BIT(0)
#define AD4695_REG_CONFIG_IN(n) (0x0030 | (n))
@@ -124,13 +131,22 @@ struct ad4695_channel_config {
struct ad4695_state {
struct spi_device *spi;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
struct regmap *regmap;
struct regmap *regmap16;
struct gpio_desc *reset_gpio;
+ /* currently PWM CNV only supported with SPI offload use */
+ struct pwm_device *cnv_pwm;
+ /* protects against concurrent use of cnv_pwm */
+ struct mutex cnv_pwm_lock;
+ /* offload also requires separate gpio to manually control CNV */
+ struct gpio_desc *cnv_gpio;
/* voltages channels plus temperature and timestamp */
struct iio_chan_spec iio_chan[AD4695_MAX_CHANNELS + 2];
struct ad4695_channel_config channels_cfg[AD4695_MAX_CHANNELS];
const struct ad4695_chip_info *chip_info;
+ int sample_freq_range[3];
/* Reference voltage. */
unsigned int vref_mv;
/* Common mode input pin voltage. */
@@ -355,6 +371,13 @@ static const struct ad4695_chip_info ad4698_chip_info = {
.num_voltage_inputs = 8,
};
+static void ad4695_cnv_manual_trigger(struct ad4695_state *st)
+{
+ gpiod_set_value_cansleep(st->cnv_gpio, 1);
+ ndelay(10);
+ gpiod_set_value_cansleep(st->cnv_gpio, 0);
+}
+
/**
* ad4695_set_single_cycle_mode - Set the device in single cycle mode
* @st: The AD4695 state
@@ -460,6 +483,17 @@ static int ad4695_exit_conversion_mode(struct ad4695_state *st)
*/
st->cnv_cmd2 = AD4695_CMD_EXIT_CNV_MODE << 3;
+ if (st->cnv_gpio) {
+ ad4695_cnv_manual_trigger(st);
+
+ /*
+ * In this case, CNV is not connected to CS, so we don't need
+ * the extra CS toggle to trigger the conversion and toggling
+ * CS would have no effect.
+ */
+ return spi_sync_transfer(st->spi, &xfers[1], 1);
+ }
+
return spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
}
@@ -687,6 +721,160 @@ static irqreturn_t ad4695_trigger_handler(int irq, void *p)
return IRQ_HANDLED;
}
+static int ad4695_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4695_state *st = iio_priv(indio_dev);
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
+ };
+ struct spi_transfer *xfer = &st->buf_read_xfer[0];
+ struct pwm_state state;
+ u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
+ u8 num_slots = 0;
+ u8 temp_en = 0;
+ unsigned int bit;
+ int ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ if (bit == temp_chan_bit) {
+ temp_en = 1;
+ continue;
+ }
+
+ ret = regmap_write(st->regmap, AD4695_REG_AS_SLOT(num_slots),
+ FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit));
+ if (ret)
+ return ret;
+
+ num_slots++;
+ }
+
+ /*
+ * For non-offload, we could discard data to work around this
+ * restriction, but with offload, that is not possible.
+ */
+ if (num_slots < 2) {
+ dev_err(&st->spi->dev,
+ "At least two voltage channels must be enabled.\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(st->regmap, AD4695_REG_TEMP_CTRL,
+ AD4695_REG_TEMP_CTRL_TEMP_EN,
+ FIELD_PREP(AD4695_REG_TEMP_CTRL_TEMP_EN,
+ temp_en));
+ if (ret)
+ return ret;
+
+ /* Each BUSY event means just one sample for one channel is ready. */
+ memset(xfer, 0, sizeof(*xfer));
+ xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ /* Using 19 bits per word to allow for possible oversampling */
+ xfer->bits_per_word = 19;
+ xfer->len = 4;
+
+ spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
+ st->buf_read_msg.offload = st->offload;
+
+ ret = spi_optimize_message(st->spi, &st->buf_read_msg);
+ if (ret)
+ return ret;
+
+ /*
+ * NB: technically, this is part the SPI offload trigger enable, but it
+ * doesn't work to call it from the offload trigger enable callback
+ * because it requires accessing the SPI bus. Calling it from the
+ * trigger enable callback could cause a deadlock.
+ */
+ ret = regmap_set_bits(st->regmap, AD4695_REG_GP_MODE,
+ AD4695_REG_GP_MODE_BUSY_GP_EN);
+ if (ret)
+ goto err_unoptimize_message;
+
+ ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
+ &config);
+ if (ret)
+ goto err_disable_busy_output;
+
+ ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
+ if (ret)
+ goto err_offload_trigger_disable;
+
+ guard(mutex)(&st->cnv_pwm_lock);
+ pwm_get_state(st->cnv_pwm, &state);
+ /*
+ * PWM subsystem generally rounds down, so requesting 2x minimum high
+ * time ensures that we meet the minimum high time in any case.
+ */
+ state.duty_cycle = AD4695_T_CNVH_NS * 2;
+ ret = pwm_apply_might_sleep(st->cnv_pwm, &state);
+ if (ret)
+ goto err_offload_exit_conversion_mode;
+
+ return 0;
+
+err_offload_exit_conversion_mode:
+ /*
+ * We have to unwind in a different order to avoid triggering offload.
+ * ad4695_exit_conversion_mode() triggers a conversion, so it has to be
+ * done after spi_offload_trigger_disable().
+ */
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+ ad4695_exit_conversion_mode(st);
+ goto err_disable_busy_output;
+
+err_offload_trigger_disable:
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+
+err_disable_busy_output:
+ regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE,
+ AD4695_REG_GP_MODE_BUSY_GP_EN);
+
+err_unoptimize_message:
+ spi_unoptimize_message(&st->buf_read_msg);
+
+ return ret;
+}
+
+static int ad4695_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4695_state *st = iio_priv(indio_dev);
+ struct pwm_state state;
+ int ret;
+
+ scoped_guard(mutex, &st->cnv_pwm_lock) {
+ pwm_get_state(st->cnv_pwm, &state);
+ state.duty_cycle = 0;
+ ret = pwm_apply_might_sleep(st->cnv_pwm, &state);
+ if (ret)
+ return ret;
+ }
+
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+
+ /*
+ * ad4695_exit_conversion_mode() triggers a conversion, so it has to be
+ * done after spi_offload_trigger_disable().
+ */
+ ret = ad4695_exit_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE,
+ AD4695_REG_GP_MODE_BUSY_GP_EN);
+ if (ret)
+ return ret;
+
+ spi_unoptimize_message(&st->buf_read_msg);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ad4695_offload_buffer_setup_ops = {
+ .postenable = ad4695_offload_buffer_postenable,
+ .predisable = ad4695_offload_buffer_predisable,
+};
+
/**
* ad4695_read_one_sample - Read a single sample using single-cycle mode
* @st: The AD4695 state
@@ -718,6 +906,13 @@ static int ad4695_read_one_sample(struct ad4695_state *st, unsigned int address)
if (ret)
return ret;
+ /*
+ * If CNV is connected to CS, the previous function will have triggered
+ * the conversion, otherwise, we do it manually.
+ */
+ if (st->cnv_gpio)
+ ad4695_cnv_manual_trigger(st);
+
/*
* Setting the first channel to the temperature channel isn't supported
* in single-cycle mode, so we have to do an extra conversion to read
@@ -729,6 +924,13 @@ static int ad4695_read_one_sample(struct ad4695_state *st, unsigned int address)
ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
if (ret)
return ret;
+
+ /*
+ * If CNV is connected to CS, the previous function will have
+ * triggered the conversion, otherwise, we do it manually.
+ */
+ if (st->cnv_gpio)
+ ad4695_cnv_manual_trigger(st);
}
/* Then read the result and exit conversion mode. */
@@ -842,11 +1044,34 @@ static int ad4695_read_raw(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct pwm_state state;
+
+ ret = pwm_get_state_hw(st->cnv_pwm, &state);
+ if (ret)
+ return ret;
+
+ *val = DIV_ROUND_UP_ULL(NSEC_PER_SEC, state.period);
+
+ return IIO_VAL_INT;
+ }
default:
return -EINVAL;
}
}
+static int ad4695_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static int ad4695_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
@@ -900,6 +1125,17 @@ static int ad4695_write_raw(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct pwm_state state;
+
+ if (val <= 0 || val > st->chip_info->max_sample_rate)
+ return -EINVAL;
+
+ guard(mutex)(&st->cnv_pwm_lock);
+ pwm_get_state(st->cnv_pwm, &state);
+ state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val);
+ return pwm_apply_might_sleep(st->cnv_pwm, &state);
+ }
default:
return -EINVAL;
}
@@ -923,6 +1159,7 @@ static int ad4695_read_avail(struct iio_dev *indio_dev,
*/
S16_MIN / 4, 0, 0, MICRO / 4, S16_MAX / 4, S16_MAX % 4 * MICRO / 4
};
+ struct ad4695_state *st = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_CALIBSCALE:
@@ -943,6 +1180,10 @@ static int ad4695_read_avail(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = st->sample_freq_range;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
default:
return -EINVAL;
}
@@ -978,6 +1219,7 @@ static int ad4695_debugfs_reg_access(struct iio_dev *indio_dev,
static const struct iio_info ad4695_info = {
.read_raw = &ad4695_read_raw,
+ .write_raw_get_fmt = &ad4695_write_raw_get_fmt,
.write_raw = &ad4695_write_raw,
.read_avail = &ad4695_read_avail,
.debugfs_reg_access = &ad4695_debugfs_reg_access,
@@ -1091,26 +1333,166 @@ static int ad4695_parse_channel_cfg(struct ad4695_state *st)
return 0;
}
+static bool ad4695_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;
+
+ /*
+ * Requires 2 args:
+ * args[0] is the trigger event.
+ * args[1] is the GPIO pin number.
+ */
+ if (nargs != 2 || args[0] != AD4695_TRIGGER_EVENT_BUSY)
+ return false;
+
+ return true;
+}
+
+static int ad4695_offload_trigger_request(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ struct ad4695_state *st = spi_offload_trigger_get_priv(trigger);
+
+ /* Should already be validated by match, but just in case. */
+ if (nargs != 2)
+ return -EINVAL;
+
+ /* DT tells us if BUSY event uses GP0 or GP3. */
+ if (args[1] == AD4695_TRIGGER_PIN_GP3)
+ return regmap_set_bits(st->regmap, AD4695_REG_GP_MODE,
+ AD4695_REG_GP_MODE_BUSY_GP_SEL);
+
+ return regmap_clear_bits(st->regmap, AD4695_REG_GPIO_CTRL,
+ AD4695_REG_GP_MODE_BUSY_GP_SEL);
+}
+
+static int
+ad4695_offload_trigger_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * NB: There are no enable/disable callbacks here due to requiring a SPI
+ * message to enable or disable the BUSY output on the ADC.
+ */
+static const struct spi_offload_trigger_ops ad4695_offload_trigger_ops = {
+ .match = ad4695_offload_trigger_match,
+ .request = ad4695_offload_trigger_request,
+ .validate = ad4695_offload_trigger_validate,
+};
+
+static void ad4695_pwm_disable(void *pwm)
+{
+ pwm_disable(pwm);
+}
+
+static int ad4695_probe_spi_offload(struct iio_dev *indio_dev,
+ struct ad4695_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct spi_offload_trigger_info trigger_info = {
+ .fwnode = dev_fwnode(dev),
+ .ops = &ad4695_offload_trigger_ops,
+ .priv = st,
+ };
+ struct pwm_state pwm_state;
+ struct dma_chan *rx_dma;
+ int ret, i;
+
+ indio_dev->num_channels = st->chip_info->num_voltage_inputs + 1;
+ indio_dev->setup_ops = &ad4695_offload_buffer_setup_ops;
+
+ if (!st->cnv_gpio)
+ return dev_err_probe(dev, -ENODEV,
+ "CNV GPIO is required for SPI offload\n");
+
+ 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");
+
+ ret = devm_mutex_init(dev, &st->cnv_pwm_lock);
+ if (ret)
+ return ret;
+
+ st->cnv_pwm = devm_pwm_get(dev, NULL);
+ if (IS_ERR(st->cnv_pwm))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_pwm),
+ "failed to get CNV PWM\n");
+
+ pwm_init_state(st->cnv_pwm, &pwm_state);
+
+ /* If firmware didn't provide default rate, use 10kHz (arbitrary). */
+ if (pwm_state.period == 0)
+ pwm_state.period = 100 * MILLI;
+
+ pwm_state.enabled = true;
+
+ ret = pwm_apply_might_sleep(st->cnv_pwm, &pwm_state);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to apply CNV PWM\n");
+
+ ret = devm_add_action_or_reset(dev, ad4695_pwm_disable, st->cnv_pwm);
+ if (ret)
+ return ret;
+
+ 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");
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ struct iio_chan_spec *chan = &st->iio_chan[i];
+
+ /*
+ * NB: When using offload support, all channels need to have the
+ * same bits_per_word because they all use the same SPI message
+ * for reading one sample. In order to prevent breaking
+ * userspace in the future when oversampling support is added,
+ * all channels are set read 19 bits with a shift of 3 to mask
+ * out the extra bits even though we currently only support 16
+ * bit samples (oversampling ratio == 1).
+ */
+ chan->scan_type.shift = 3;
+ chan->scan_type.storagebits = 32;
+ /* add sample frequency for PWM CNV trigger */
+ chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ }
+
+ return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
+ rx_dma, IIO_BUFFER_DIRECTION_IN);
+}
+
+static const struct spi_offload_config ad4695_spi_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
static int ad4695_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct ad4695_state *st;
struct iio_dev *indio_dev;
- struct gpio_desc *cnv_gpio;
bool use_internal_ldo_supply;
bool use_internal_ref_buffer;
int ret;
- cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
- if (IS_ERR(cnv_gpio))
- return dev_err_probe(dev, PTR_ERR(cnv_gpio),
- "Failed to get CNV GPIO\n");
-
- /* Driver currently requires CNV pin to be connected to SPI CS */
- if (cnv_gpio)
- return dev_err_probe(dev, -ENODEV,
- "CNV GPIO is not supported\n");
-
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
@@ -1122,6 +1504,10 @@ static int ad4695_probe(struct spi_device *spi)
if (!st->chip_info)
return -EINVAL;
+ st->sample_freq_range[0] = 1; /* min */
+ st->sample_freq_range[1] = 1; /* step */
+ st->sample_freq_range[2] = st->chip_info->max_sample_rate; /* max */
+
st->regmap = devm_regmap_init(dev, &ad4695_regmap_bus, st,
&ad4695_regmap_config);
if (IS_ERR(st->regmap))
@@ -1134,6 +1520,11 @@ static int ad4695_probe(struct spi_device *spi)
return dev_err_probe(dev, PTR_ERR(st->regmap16),
"Failed to initialize regmap16\n");
+ st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
+ if (IS_ERR(st->cnv_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
+ "Failed to get CNV GPIO\n");
+
ret = devm_regulator_bulk_get_enable(dev,
ARRAY_SIZE(ad4695_power_supplies),
ad4695_power_supplies);
@@ -1261,12 +1652,31 @@ static int ad4695_probe(struct spi_device *spi)
indio_dev->channels = st->iio_chan;
indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2;
- ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
- iio_pollfunc_store_time,
- ad4695_trigger_handler,
- &ad4695_buffer_setup_ops);
- if (ret)
- return ret;
+ st->offload = devm_spi_offload_get(dev, spi, &ad4695_spi_offload_config);
+ ret = PTR_ERR_OR_ZERO(st->offload);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get SPI offload\n");
+
+ /* If no SPI offload, fall back to low speed usage. */
+ if (ret == -ENODEV) {
+ /* Driver currently requires CNV pin to be connected to SPI CS */
+ if (st->cnv_gpio)
+ return dev_err_probe(dev, -EINVAL,
+ "CNV GPIO is not supported\n");
+
+ indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ad4695_trigger_handler,
+ &ad4695_buffer_setup_ops);
+ if (ret)
+ return ret;
+ } else {
+ ret = ad4695_probe_spi_offload(indio_dev, st);
+ if (ret)
+ return ret;
+ }
return devm_iio_device_register(dev, indio_dev);
}
@@ -1303,3 +1713,4 @@ MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
MODULE_DESCRIPTION("Analog Devices AD4695 ADC driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 15/17] doc: iio: ad4695: add SPI offload support
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (13 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-11 20:54 ` [PATCH v6 16/17] iio: dac: ad5791: sort include directives David Lechner
2024-12-11 20:54 ` [PATCH v6 17/17] iio: dac: ad5791: Add offload support David Lechner
16 siblings, 0 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Document SPI offload support for the ad4695 driver.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes:
* Fixed double "all"
* Don't abbreviate "sampling_frequency" attribute names for clarity.
v5 changes: new patch in v5
---
Documentation/iio/ad4695.rst | 68 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/Documentation/iio/ad4695.rst b/Documentation/iio/ad4695.rst
index 9ec8bf466c15bf94cbae2ebdb61875f66af1264f..ead0faadff4b5aede2a34bac52e103e375da6f21 100644
--- a/Documentation/iio/ad4695.rst
+++ b/Documentation/iio/ad4695.rst
@@ -47,6 +47,36 @@ In this mode, CNV and CS are tied together and there is a single SDO line.
To use this mode, in the device tree, omit the ``cnv-gpios`` and
``spi-rx-bus-width`` properties.
+SPI offload wiring
+^^^^^^^^^^^^^^^^^^
+
+When used with a SPI offload, the supported wiring configuration is:
+
+.. code-block::
+
+ +-------------+ +-------------+
+ | GP0/BUSY |-------->| TRIGGER |
+ | CS |<--------| CS |
+ | | | |
+ | ADC | | SPI |
+ | | | |
+ | SDI |<--------| SDO |
+ | SDO |-------->| SDI |
+ | SCLK |<--------| SCLK |
+ | | | |
+ | | +-------------+
+ | CNV |<-----+--| PWM |
+ | | +--| GPIO |
+ +-------------+ +-------------+
+
+In this case, both the ``cnv-gpios`` and ``pwms`` properties are required.
+The ``#trigger-source-cells = <2>`` property is also required to connect back
+to the SPI offload. The SPI offload will have ``trigger-sources`` property
+with cells to indicate the busy signal and which GPx pin is used, e.g
+``<&ad4695 AD4695_TRIGGER_EVENT_BUSY AD4695_TRIGGER_PIN_GP0>``.
+
+.. seealso:: `SPI offload support`_
+
Channel configuration
---------------------
@@ -158,6 +188,27 @@ Unimplemented features
- GPIO support
- CRC support
+SPI offload support
+===================
+
+To be able to achieve the maximum sample rate, the driver can be used with the
+`AXI SPI Engine`_ to provide SPI offload support.
+
+.. _AXI SPI Engine: http://analogdevicesinc.github.io/hdl/projects/ad469x_fmc/index.html
+
+.. seealso:: `SPI offload wiring`_
+
+When SPI offload is being used, some attributes will be different.
+
+* ``trigger`` directory is removed.
+* ``in_voltage0_sampling_frequency`` attributes are added for setting the sample
+ rate.
+* ``in_voltage0_sampling_frequency_available`` attributes are added for querying
+ the max sample rate.
+* ``timestamp`` channel is removed.
+* Buffer data format may be different compared to when offload is not used,
+ e.g. the ``buffer0/in_voltage0_type`` attribute.
+
Device buffers
==============
@@ -165,3 +216,20 @@ This driver supports hardware triggered buffers. This uses the "advanced
sequencer" feature of the chip to trigger a burst of conversions.
Also see :doc:`iio_devbuf` for more general information.
+
+Effective sample rate for buffered reads
+----------------------------------------
+
+When SPI offload is not used, the sample rate is determined by the trigger that
+is manually configured in userspace. All enabled channels will be read in a
+burst when the trigger is received.
+
+When SPI offload is used, the sample rate is configured per channel. All
+channels will have the same rate, so only one ``in_voltageY_sampling_frequency``
+attribute needs to be set. Since this rate determines the delay between each
+individual conversion, the effective sample rate for each sample is actually
+the sum of the periods of each enabled channel in a buffered read. In other
+words, it is the value of the ``in_voltageY_sampling_frequency`` attribute
+divided by the number of enabled channels. So if 4 channels are enabled, with
+the ``in_voltageY_sampling_frequency`` attributes set to 1 MHz, the effective
+sample rate is 250 kHz.
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 16/17] iio: dac: ad5791: sort include directives
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (14 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 15/17] doc: iio: ad4695: add SPI offload support David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-17 12:15 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 17/17] iio: dac: ad5791: Add offload support David Lechner
16 siblings, 1 reply; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, David Lechner
Sort includes alphabetically before we add more in a later patch.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v6 changes: new patch in v6
---
drivers/iio/dac/ad5791.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
index 57374f78f6b885e1d4f2fb452ac0563b85fc222e..24462cb020e19e8e2c6faa13109ac047cf423c37 100644
--- a/drivers/iio/dac/ad5791.c
+++ b/drivers/iio/dac/ad5791.c
@@ -6,21 +6,21 @@
* Copyright 2011 Analog Devices Inc.
*/
-#include <linux/interrupt.h>
-#include <linux/fs.h>
-#include <linux/device.h>
+#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
#include <linux/kernel.h>
-#include <linux/spi/spi.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <linux/spi/spi.h>
#include <linux/sysfs.h>
-#include <linux/regulator/consumer.h>
-#include <linux/module.h>
-#include <linux/bitops.h>
+#include <linux/iio/dac/ad5791.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#include <linux/iio/dac/ad5791.h>
#define AD5791_DAC_MASK GENMASK(19, 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [PATCH v6 17/17] iio: dac: ad5791: Add offload support
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
` (15 preceding siblings ...)
2024-12-11 20:54 ` [PATCH v6 16/17] iio: dac: ad5791: sort include directives David Lechner
@ 2024-12-11 20:54 ` David Lechner
2024-12-14 17:12 ` Jonathan Cameron
2024-12-17 12:18 ` Nuno Sá
16 siblings, 2 replies; 42+ messages in thread
From: David Lechner @ 2024-12-11 20:54 UTC (permalink / raw)
To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Axel Haslam, David Lechner
From: Axel Haslam <ahaslam@baylibre.com>
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@baylibre.com>
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
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@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 42+ messages in thread
* Re: [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source
2024-12-11 20:54 ` [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source David Lechner
@ 2024-12-14 14:25 ` Jonathan Cameron
2024-12-17 14:32 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 14:25 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:40 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Add a new binding for using a PWM signal as a trigger source.
>
> The idea here is similar to e.g. "pwm-clock" to allow a trigger source
> consumer to use a PWM provider as a trigger source.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
LGTM. But obviously this is really one for the dt-binding maintainers to look
at. With that in mind.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> ---
>
> v6 changes:
> * Moved file from bindings/spi/ to bindings/trigger-source/
> * Updated description to not mention SPI
> * Dropped $ref: /schemas/spi/trigger-source.yaml#
> * Swapped order in name to be consistent with "pwm-clock"
>
> v5 changes:
> * Add MAINTAINERS entry
>
> v4 changes: new patch in v4
> ---
> .../bindings/trigger-source/pwm-trigger.yaml | 37 ++++++++++++++++++++++
> MAINTAINERS | 5 +++
> 2 files changed, 42 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml b/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..1eac20031dc3cf921aafb8aa37f4e4eca1075835
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
> @@ -0,0 +1,37 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/trigger-source/pwm-trigger.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Generic trigger source using PWM
> +
> +description: Remaps a PWM channel as a trigger source.
> +
> +maintainers:
> + - David Lechner <dlechner@baylibre.com>
> +
> +properties:
> + compatible:
> + const: pwm-trigger
> +
> + '#trigger-source-cells':
> + const: 0
> +
> + pwms:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - '#trigger-source-cells'
> + - pwms
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + trigger {
> + compatible = "pwm-trigger";
> + #trigger-source-cells = <0>;
> + pwms = <&pwm 0 1000000 0>;
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9284a257607a740ab7f6fd960c2bcdc34ead7586..b2aa6f37743e48353c60e5973ea8126590c7f6d5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23879,6 +23879,11 @@ W: https://github.com/srcres258/linux-doc
> T: git git://github.com/srcres258/linux-doc.git doc-zh-tw
> F: Documentation/translations/zh_TW/
>
> +TRIGGER SOURCE - PWM
> +M: David Lechner <dlechner@baylibre.com>
> +S: Maintained
> +F: Documentation/devicetree/bindings/trigger-source/pwm-trigger.yaml
> +
> TRUSTED SECURITY MODULE (TSM) ATTESTATION REPORTS
> M: Dan Williams <dan.j.williams@intel.com>
> L: linux-coco@lists.linux.dev
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 05/17] spi: add offload TX/RX streaming APIs
2024-12-11 20:54 ` [PATCH v6 05/17] spi: add offload TX/RX streaming APIs David Lechner
@ 2024-12-14 14:28 ` Jonathan Cameron
2024-12-17 11:43 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 14:28 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:42 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Most configuration of SPI offloads is handled opaquely using the offload
> pointer that is passed to the various offload functions. However, there
> are some offload features that need to be controlled on a per transfer
> basis.
>
> This patch adds a flag field to struct spi_transfer to allow specifying
> such features. The first feature to be added is the ability to stream
> data to/from a hardware sink/source rather than using a tx or rx buffer.
> Additional flags can be added in the future as needed.
>
> A flags field is also added to the offload struct for providers to
> indicate which flags are supported. This allows for generic checking of
> offload capabilities during __spi_validate() so that each offload
> provider doesn't have to implement their own validation.
>
> As a first users of this streaming capability, getter functions are
> added to get a DMA channel that is directly connected to the offload.
> Peripheral drivers will use this to get a DMA channel and configure it
> to suit their needs.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
Really really minor comment inline.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index ff1add2ecb91f18cf82e6f1e9595584c11adf9d8..4a871db9ee636aba64c866ebdd8bb1dbf82e0f42 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -31,6 +31,7 @@
> #include <linux/ptp_clock_kernel.h>
> #include <linux/sched/rt.h>
> #include <linux/slab.h>
> +#include <linux/spi/offload/types.h>
> #include <linux/spi/spi.h>
> #include <linux/spi/spi-mem.h>
> #include <uapi/linux/sched/types.h>
> @@ -4163,6 +4164,15 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
>
> if (_spi_xfer_word_delay_update(xfer, spi))
> return -EINVAL;
> +
> + /* make sure controller supports required offload features */
Comment syntax seems inconsistent with local code.
/* Make sure controller supports required offload features. */
> + if (xfer->offload_flags) {
> + if (!message->offload)
> + return -EINVAL;
> +
> + if (xfer->offload_flags & ~message->offload->xfer_flags)
> + return -EINVAL;
> + }
> }
>
> message->status = -EINPROGRESS;
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties
2024-12-11 20:54 ` [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
@ 2024-12-14 14:30 ` Jonathan Cameron
2024-12-17 14:33 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 14:30 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:43 -0600
David Lechner <dlechner@baylibre.com> wrote:
> The AXI SPI Engine has support for hardware offloading capabilities.
> This includes a connection to a DMA controller for streaming RX or TX
> data and a trigger input for starting execution of the SPI message
> programmed in the offload. It is designed to support up to 32 offload
> instances.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
I briefly thought about suggesting some docs for the dma channel naming
but meh, that's just the pattern stuff confusing me. The actual strings
are pretty self documenting.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer
2024-12-11 20:54 ` [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer David Lechner
@ 2024-12-14 14:37 ` Jonathan Cameron
2024-12-17 11:50 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 14:37 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:45 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Refactor the IIO dmaengine buffer code to split requesting the DMA
> channel from allocating the buffer. We want to be able to add a new
> function where the IIO device driver manages the DMA channel, so these
> two actions need to be separate.
>
> To do this, calling dma_request_chan() is moved from
> iio_dmaengine_buffer_alloc() to iio_dmaengine_buffer_setup_ext(). A new
> __iio_dmaengine_buffer_setup_ext() helper function is added to simplify
> error unwinding and will also be used by a new function in a later
> patch.
>
> iio_dmaengine_buffer_free() now only frees the buffer and does not
> release the DMA channel. A new iio_dmaengine_buffer_teardown() function
> is added to unwind everything done in iio_dmaengine_buffer_setup_ext().
> This keeps things more symmetrical with obvious pairs alloc/free and
> setup/teardown.
>
> Calling dma_get_slave_caps() in iio_dmaengine_buffer_alloc() is moved so
> that we can avoid any gotos for error unwinding.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
> v6 changes:
> * Split out from patch that adds the new function
> * Dropped owns_chan flag
> * Introduced iio_dmaengine_buffer_teardown() so that
> iio_dmaengine_buffer_free() doesn't have to manage the DMA channel
Ouch this is a fiddly refactor to unwind from the diff.
I 'think' it's correct, but am keen to get a few more eyes on this
if possible. Not 100% sure what route this series will take, so on off
chance this patch doesn't go through IIO or a immutable branch I
create.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle()
2024-12-11 20:54 ` [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle() David Lechner
@ 2024-12-14 14:39 ` Jonathan Cameron
2024-12-17 11:51 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 14:39 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:46 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Add a new devm_iio_dmaengine_buffer_setup_with_handle() function to
> handle cases where the DMA channel is managed by the caller rather than
> being requested and released by the iio_dmaengine module.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing
2024-12-11 20:54 ` [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing David Lechner
@ 2024-12-14 16:56 ` Jonathan Cameron
2024-12-17 11:52 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 16:56 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:47 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Replace use of storagebits with realbits for determining the number of
> bytes needed for SPI transfers.
>
> When adding SPI offload support, storagebits will no longer be
> guaranteed to be the "best fit" for 16-bit chips so we can no longer
> rely on storagebits being the correct size expected by the SPI
> framework. Instead, derive the correct size from realbits since it will
> always be correct even when SPI offloads are used.
A more specific example with widths etc might be useful addition to this
commit message.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
2024-12-11 20:54 ` [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
@ 2024-12-14 16:59 ` Jonathan Cameron
2024-12-17 14:36 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 16:59 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm
On Wed, 11 Dec 2024 14:54:50 -0600
David Lechner <dlechner@baylibre.com> wrote:
> Add a pwms property to the adi,ad4695 binding to specify an optional PWM
> output connected to the CNV pin on the ADC.
>
> Also add #trigger-source-cells property to allow the BUSY output to be
> used as a SPI offload trigger source to indicate when a sample is ready
> to be read.
>
> Macros are added to adi,ad4695.h for the cell values to help with
> readability since they are arbitrary values.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 17/17] iio: dac: ad5791: Add offload support
2024-12-11 20:54 ` [PATCH v6 17/17] iio: dac: ad5791: Add offload support David Lechner
@ 2024-12-14 17:12 ` Jonathan Cameron
2024-12-17 12:18 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Jonathan Cameron @ 2024-12-14 17:12 UTC (permalink / raw)
To: David Lechner
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Nuno Sá, Uwe Kleine-König, Michael Hennerich,
Lars-Peter Clausen, David Jander, Martin Sperl, linux-spi,
devicetree, linux-kernel, linux-iio, linux-pwm, Axel Haslam
On Wed, 11 Dec 2024 14:54:54 -0600
David Lechner <dlechner@baylibre.com> wrote:
> From: Axel Haslam <ahaslam@baylibre.com>
>
> 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@baylibre.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
> v6 changes: new patch in v6
Nice. A few formatting micro comments inline.
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
For merging this ultimately I'm kind of assuming Mark Brown (or I) will
spin an immutable branch with the SPI parts and then I'll pull that into the
IIO tree and apply patch 8 onwards on top.
Before that point we need DT folk and Mark to be happy of course.
Thanks,
Jonathan
> @@ -299,6 +336,24 @@ static const struct ad5791_chip_info _name##_chip_info = { \
> }, \
> .ext_info = ad5791_ext_info, \
> }, \
> + .channel_offload = { \
> + .type = IIO_VOLTAGE, \
Reduce indent by 1 tab for these.
> + .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)
Given you ignore val2, is val2 != 0 more appropriate?
> + return -EINVAL;
> + return ad5791_set_sample_freq(st, val);
> default:
> return -EINVAL;
> }
> }
> 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);
bonus space after =
> + 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@analog.com>");
> MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 01/17] spi: add basic support for SPI offloading
2024-12-11 20:54 ` [PATCH v6 01/17] spi: add basic support for SPI offloading David Lechner
@ 2024-12-17 11:21 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:21 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Add the basic infrastructure to support SPI offload providers and
> consumers.
>
> SPI offloading is a feature that allows the SPI controller to perform
> transfers without any CPU intervention. This is useful, e.g. for
> high-speed data acquisition.
>
> SPI controllers with offload support need to implement the get_offload
> and put_offload callbacks and can use the devm_spi_offload_alloc() to
> allocate offload instances.
>
> SPI peripheral drivers will call devm_spi_offload_get() to get a
> reference to the matching offload instance. This offload instance can
> then be attached to a SPI message to request offloading that message.
>
> It is expected that SPI controllers with offload support will check for
> the offload instance in the SPI message in the ctlr->optimize_message()
> callback and handle it accordingly.
>
> CONFIG_SPI_OFFLOAD is intended to be a select-only option. Both
> consumer and provider drivers should `select SPI_OFFLOAD` in their
> Kconfig to ensure that the SPI core is built with offload support.
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
LGTM,
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes:
> * Drop use of PTR_ERR_OR_ZERO().
> * Split header into types.h/provider.h/consumer.h.
> * Remove unused spi_controller_offload_ops forward declaration.
>
> v5 changes:
> * Don't include linux/property.h (moved to later patch).
> * Only allocate single offload instance instead of array.
> * Allocate *priv separately to avoid alignment issues.
> * Add put_offload() callback instead of assuming devm semantics.
> * Drop struct spi_offload::spi. It was only being used as a flag.
> * Don't get/put struct spi_offload::provider_dev.
> * Add MAINTAINERS entry for me as reviewer for anything related to
> SPI offload.
>
> v4 changes:
> * SPI offload functions moved to a separate file instead of spi.c
> (spi.c is already too long).
> * struct spi_offload and devm_spi_offload_get() are back, similar to
> but improved over v1. This avoids having to pass the function ID
> string to every function call and re-lookup the offload instance.
> * offload message prepare/unprepare functions are removed. Instead the
> existing optimize/unoptimize functions should be used. Setting
> spi_message::offload pointer is used as a flag to differentiate
> between an offloaded message and a regular message.
>
> v3 changes:
> * Minor changes to doc comments.
> * Changed to use phandle array for spi-offloads.
> * Changed id to string to make use of spi-offload-names.
>
> v2 changes:
> * This is a rework of "spi: add core support for controllers with offload
> capabilities" from v1.
> * The spi_offload_get() function that Nuno didn't like is gone. Instead,
> there is now a mapping callback that uses the new generic devicetree
> binding to request resources automatically when a SPI device is probed.
> * The spi_offload_enable/disable() functions for dealing with hardware
> triggers are deferred to a separate patch.
> * This leaves adding spi_offload_prepare/unprepare() which have been
> reworked to be a bit more robust.
> ---
> MAINTAINERS | 6 ++
> drivers/spi/Kconfig | 3 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-offload.c | 114
> +++++++++++++++++++++++++++++++++++
> include/linux/spi/offload/consumer.h | 22 +++++++
> include/linux/spi/offload/provider.h | 19 ++++++
> include/linux/spi/offload/types.h | 43 +++++++++++++
> include/linux/spi/spi.h | 17 ++++++
> 8 files changed, 225 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index
> bf2dcd4e0261785add520b5eac747ceac523e112..9284a257607a740ab7f6fd960c2bcdc34ead
> 7586 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22129,6 +22129,12 @@
> F: Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> F: drivers/mtd/spi-nor/
> F: include/linux/mtd/spi-nor.h
>
> +SPI OFFLOAD
> +R: David Lechner <dlechner@baylibre.com>
> +F: drivers/spi/spi-offload.c
> +F: include/linux/spi/spi-offload.h
> +K: spi_offload
> +
> SPI SUBSYSTEM
> M: Mark Brown <broonie@kernel.org>
> L: linux-spi@vger.kernel.org
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index
> ea8a310329274bb2701e265cd152a56fb4e0f3a7..02064a4e292815ec0213e2e446b4f90ed885
> 5a52 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -55,6 +55,9 @@ config SPI_MEM
> This extension is meant to simplify interaction with SPI memories
> by providing a high-level interface to send memory-like commands.
>
> +config SPI_OFFLOAD
> + bool
> +
> comment "SPI Master Controller Drivers"
>
> config SPI_AIROHA_SNFI
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index
> 9db7554c1864bf9b37dcf59c16eb76f5af03a7e8..bb5fc20df21332232533c2e70c0cc230f6bc
> f27f 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -10,6 +10,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
> obj-$(CONFIG_SPI_MASTER) += spi.o
> obj-$(CONFIG_SPI_MEM) += spi-mem.o
> obj-$(CONFIG_SPI_MUX) += spi-mux.o
> +obj-$(CONFIG_SPI_OFFLOAD) += spi-offload.o
> obj-$(CONFIG_SPI_SPIDEV) += spidev.o
> obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
>
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..3a40ef30debf09c6fd7b2c14526f3e5976e2
> b21f
> --- /dev/null
> +++ b/drivers/spi/spi-offload.c
> @@ -0,0 +1,114 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +/*
> + * SPI Offloading support.
> + *
> + * Some SPI controllers support offloading of SPI transfers. Essentially,
> this
> + * is the ability for a SPI controller to perform SPI transfers with minimal
> + * or even no CPU intervention, e.g. via a specialized SPI controller with a
> + * hardware trigger or via a conventional SPI controller using a non-Linux
> MCU
> + * processor core to offload the work.
> + */
> +
> +#define DEFAULT_SYMBOL_NAMESPACE "SPI_OFFLOAD"
> +
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/mutex.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.h>
> +#include <linux/spi/offload/types.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +struct spi_controller_and_offload {
> + struct spi_controller *controller;
> + struct spi_offload *offload;
> +};
> +
> +/**
> + * devm_spi_offload_alloc() - Allocate offload instance
> + * @dev: Device for devm purposes and assigned to &struct
> spi_offload.provider_dev
> + * @priv_size: Size of private data to allocate
> + *
> + * Offload providers should use this to allocate offload instances.
> + *
> + * Return: Pointer to new offload instance or error on failure.
> + */
> +struct spi_offload *devm_spi_offload_alloc(struct device *dev,
> + size_t priv_size)
> +{
> + struct spi_offload *offload;
> + void *priv;
> +
> + offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
> + if (!offload)
> + return ERR_PTR(-ENOMEM);
> +
> + priv = devm_kzalloc(dev, priv_size, GFP_KERNEL);
> + if (!priv)
> + return ERR_PTR(-ENOMEM);
> +
> + offload->provider_dev = dev;
> + offload->priv = priv;
> +
> + return offload;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_alloc);
> +
> +static void spi_offload_put(void *data)
> +{
> + struct spi_controller_and_offload *resource = data;
> +
> + resource->controller->put_offload(resource->offload);
> + kfree(resource);
> +}
> +
> +/**
> + * devm_spi_offload_get() - Get an offload instance
> + * @dev: Device for devm purposes
> + * @spi: SPI device to use for the transfers
> + * @config: Offload configuration
> + *
> + * Peripheral drivers call this function to get an offload instance that
> meets
> + * the requirements specified in @config. If no suitable offload instance is
> + * available, -ENODEV is returned.
> + *
> + * Return: Offload instance or error on failure.
> + */
> +struct spi_offload *devm_spi_offload_get(struct device *dev,
> + struct spi_device *spi,
> + const struct spi_offload_config
> *config)
> +{
> + struct spi_controller_and_offload *resource;
> + int ret;
> +
> + if (!spi || !config)
> + return ERR_PTR(-EINVAL);
> +
> + if (!spi->controller->get_offload)
> + return ERR_PTR(-ENODEV);
> +
> + resource = kzalloc(sizeof(*resource), GFP_KERNEL);
> + if (!resource)
> + return ERR_PTR(-ENOMEM);
> +
> + resource->controller = spi->controller;
> + resource->offload = spi->controller->get_offload(spi, config);
> + if (IS_ERR(resource->offload)) {
> + kfree(resource);
> + return resource->offload;
> + }
> +
> + ret = devm_add_action_or_reset(dev, spi_offload_put, resource);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return resource->offload;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> diff --git a/include/linux/spi/offload/consumer.h
> b/include/linux/spi/offload/consumer.h
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..05543dbedf3086fb4befcd149cff3c8c70a8
> 8825
> --- /dev/null
> +++ b/include/linux/spi/offload/consumer.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#ifndef __LINUX_SPI_OFFLOAD_CONSUMER_H
> +#define __LINUX_SPI_OFFLOAD_CONSUMER_H
> +
> +#include <linux/module.h>
> +#include <linux/spi/offload/types.h>
> +#include <linux/types.h>
> +
> +MODULE_IMPORT_NS("SPI_OFFLOAD");
> +
> +struct device;
> +struct spi_device;
> +
> +struct spi_offload *devm_spi_offload_get(struct device *dev, struct
> spi_device *spi,
> + const struct spi_offload_config
> *config);
> +
> +#endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
> diff --git a/include/linux/spi/offload/provider.h
> b/include/linux/spi/offload/provider.h
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..278c4edfcdb7b1f43870ca99d2ba252bf282
> 0576
> --- /dev/null
> +++ b/include/linux/spi/offload/provider.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#ifndef __LINUX_SPI_OFFLOAD_PROVIDER_H
> +#define __LINUX_SPI_OFFLOAD_PROVIDER_H
> +
> +#include <linux/module.h>
> +#include <linux/types.h>
> +
> +MODULE_IMPORT_NS("SPI_OFFLOAD");
> +
> +struct device;
> +
> +struct spi_offload *devm_spi_offload_alloc(struct device *dev, size_t
> priv_size);
> +
> +#endif /* __LINUX_SPI_OFFLOAD_PROVIDER_H */
> diff --git a/include/linux/spi/offload/types.h
> b/include/linux/spi/offload/types.h
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..a74f8d84541b10062353e81a638f05628b69
> 6394
> --- /dev/null
> +++ b/include/linux/spi/offload/types.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#ifndef __LINUX_SPI_OFFLOAD_TYPES_H
> +#define __LINUX_SPI_OFFLOAD_TYPES_H
> +
> +#include <linux/types.h>
> +
> +struct device;
> +
> +/* Offload can be triggered by external hardware event. */
> +#define SPI_OFFLOAD_CAP_TRIGGER BIT(0)
> +/* Offload can record and then play back TX data when triggered. */
> +#define SPI_OFFLOAD_CAP_TX_STATIC_DATA BIT(1)
> +/* Offload can get TX data from an external stream source. */
> +#define SPI_OFFLOAD_CAP_TX_STREAM_DMA BIT(2)
> +/* Offload can send RX data to an external stream sink. */
> +#define SPI_OFFLOAD_CAP_RX_STREAM_DMA BIT(3)
> +
> +/**
> + * struct spi_offload_config - offload configuration
> + *
> + * This is used to request an offload with specific configuration.
> + */
> +struct spi_offload_config {
> + /** @capability_flags: required capabilities. See %SPI_OFFLOAD_CAP_*
> */
> + u32 capability_flags;
> +};
> +
> +/**
> + * struct spi_offload - offload instance
> + */
> +struct spi_offload {
> + /** @provider_dev: for get/put reference counting */
> + struct device *provider_dev;
> + /** @priv: provider driver private data */
> + void *priv;
> +};
> +
> +#endif /* __LINUX_SPI_OFFLOAD_TYPES_H */
> diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
> index
> 8497f4747e24d4ecd85b74f49609ac1c82c73535..98bdc8c16c20521c0a94e5f72f5e71c4f6d7
> d11e 100644
> --- a/include/linux/spi/spi.h
> +++ b/include/linux/spi/spi.h
> @@ -31,6 +31,8 @@ struct spi_transfer;
> struct spi_controller_mem_ops;
> struct spi_controller_mem_caps;
> struct spi_message;
> +struct spi_offload;
> +struct spi_offload_config;
>
> /*
> * INTERFACES between SPI master-side drivers and SPI slave protocol
> handlers,
> @@ -496,6 +498,10 @@ extern struct spi_device *spi_new_ancillary_device(struct
> spi_device *spi, u8 ch
> * @mem_ops: optimized/dedicated operations for interactions with SPI memory.
> * This field is optional and should only be implemented if the
> * controller has native support for memory like operations.
> + * @get_offload: callback for controllers with offload support to get
> matching
> + * offload instance. Implementations should return -ENODEV if no match
> is
> + * found.
> + * @put_offload: release the offload instance acquired by @get_offload.
> * @mem_caps: controller capabilities for the handling of memory operations.
> * @unprepare_message: undo any work done by prepare_message().
> * @target_abort: abort the ongoing transfer request on an SPI target
> controller
> @@ -740,6 +746,10 @@ struct spi_controller {
> const struct spi_controller_mem_ops *mem_ops;
> const struct spi_controller_mem_caps *mem_caps;
>
> + struct spi_offload *(*get_offload)(struct spi_device *spi,
> + const struct spi_offload_config
> *config);
> + void (*put_offload)(struct spi_offload *offload);
> +
> /* GPIO chip select */
> struct gpio_desc **cs_gpiods;
> bool use_gpio_descriptors;
> @@ -1108,6 +1118,7 @@ struct spi_transfer {
> * @state: for use by whichever driver currently owns the message
> * @opt_state: for use by whichever driver currently owns the message
> * @resources: for resource management when the SPI message is processed
> + * @offload: (optional) offload instance used by this message
> *
> * A @spi_message is used to execute an atomic sequence of data transfers,
> * each represented by a struct spi_transfer. The sequence is "atomic"
> @@ -1168,6 +1179,12 @@ struct spi_message {
> */
> void *opt_state;
>
> + /*
> + * Optional offload instance used by this message. This must be set
> + * by the peripheral driver before calling spi_optimize_message().
> + */
> + struct spi_offload *offload;
> +
> /* List of spi_res resources when the SPI message is processed */
> struct list_head resources;
> };
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 02/17] spi: offload: add support for hardware triggers
2024-12-11 20:54 ` [PATCH v6 02/17] spi: offload: add support for hardware triggers David Lechner
@ 2024-12-17 11:30 ` Nuno Sá
2024-12-17 15:35 ` David Lechner
0 siblings, 1 reply; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:30 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Extend SPI offloading to support hardware triggers.
>
> This allows an arbitrary hardware trigger to be used to start a SPI
> transfer that was previously set up with spi_optimize_message().
>
> A new struct spi_offload_trigger is introduced that can be used to
> configure any type of trigger. It has a type discriminator and a union
> to allow it to be extended in the future. Two trigger types are defined
> to start with. One is a trigger that indicates that the SPI peripheral
> is ready to read or write data. The other is a periodic trigger to
> repeat a SPI message at a fixed rate.
>
> There is also a spi_offload_hw_trigger_validate() function that works
> similar to clk_round_rate(). It basically asks the question of if we
> enabled the hardware trigger what would the actual parameters be. This
> can be used to test if the requested trigger type is actually supported
> by the hardware and for periodic triggers, it can be used to find the
> actual rate that the hardware is capable of.
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
One minor comment (and another suggestion) inline...
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
> v6 changes:
> * Updated for header file split.
>
> v5 changes:
> * Use struct kref instead of struct dev for trigger lifetime management.
> * Don't use __free() for args.fwnode.
> * Pass *trigger instead of *priv to all callbacks.
> * Add new *spi_offload_trigger_get_priv() function.
> * Use ops instead of priv for "provider is gone" flag.
> * Combine devm_spi_offload_trigger_alloc() and
> devm_spi_offload_trigger_register() into one function.
> * Add kernel-doc comments for public functions.
>
> v4 changes:
> * Added new struct spi_offload_trigger that is a generic struct for any
> hardware trigger rather than returning a struct clk.
> * Added new spi_offload_hw_trigger_validate() function.
> * Dropped extra locking since it was too restrictive.
>
> v3 changes:
> * renamed enable/disable functions to spi_offload_hw_trigger_*mode*_...
> * added spi_offload_hw_trigger_get_clk() function
> * fixed missing EXPORT_SYMBOL_GPL
>
> v2 changes:
> * This is split out from "spi: add core support for controllers with
> offload capabilities".
> * Added locking for offload trigger to claim exclusive use of the SPI
> bus.
> ---
> drivers/spi/spi-offload.c | 281
> +++++++++++++++++++++++++++++++++++
> include/linux/spi/offload/consumer.h | 12 ++
> include/linux/spi/offload/provider.h | 28 ++++
> include/linux/spi/offload/types.h | 37 +++++
> 4 files changed, 358 insertions(+)
>
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index
> 3a40ef30debf09c6fd7b2c14526f3e5976e2b21f..43582e50e279c4b1b958765fec556aaa9118
> 0e55 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -19,7 +19,11 @@
> #include <linux/cleanup.h>
> #include <linux/device.h>
> #include <linux/export.h>
> +#include <linux/kref.h>
> +#include <linux/list.h>
> #include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/property.h>
> #include <linux/spi/offload/consumer.h>
> #include <linux/spi/offload/provider.h>
> #include <linux/spi/offload/types.h>
> @@ -31,6 +35,23 @@ struct spi_controller_and_offload {
> struct spi_offload *offload;
> };
>
> +struct spi_offload_trigger {
> + struct list_head list;
> + struct kref ref;
> + struct fwnode_handle *fwnode;
> + /* synchronizes calling ops and driver registration */
> + struct mutex lock;
> + /*
> + * If the provider goes away while the consumer still has a
> reference,
> + * ops and priv will be set to NULL and all calls will fail with -
> ENODEV.
> + */
> + const struct spi_offload_trigger_ops *ops;
> + void *priv;
> +};
> +
> +static LIST_HEAD(spi_offload_triggers);
> +static DEFINE_MUTEX(spi_offload_triggers_lock);
> +
> /**
> * devm_spi_offload_alloc() - Allocate offload instance
> * @dev: Device for devm purposes and assigned to &struct
> spi_offload.provider_dev
> @@ -112,3 +133,263 @@ struct spi_offload *devm_spi_offload_get(struct device
> *dev,
> return resource->offload;
> }
> EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> +
> +static void spi_offload_trigger_free(struct kref *ref)
> +{
> + struct spi_offload_trigger *trigger =
> + container_of(ref, struct spi_offload_trigger, ref);
> +
> + mutex_destroy(&trigger->lock);
> + fwnode_handle_put(trigger->fwnode);
> + kfree(trigger);
> +}
> +
> +static void spi_offload_trigger_put(void *data)
> +{
> + struct spi_offload_trigger *trigger = data;
> +
> + scoped_guard(mutex, &trigger->lock)
> + if (trigger->ops && trigger->ops->release)
> + trigger->ops->release(trigger);
> +
> + kref_put(&trigger->ref, spi_offload_trigger_free);
> +}
> +
> +static struct spi_offload_trigger
> +*spi_offload_trigger_get(enum spi_offload_trigger_type type,
> + struct fwnode_reference_args *args)
> +{
> + struct spi_offload_trigger *trigger;
> + bool match = false;
> + int ret;
> +
> + guard(mutex)(&spi_offload_triggers_lock);
> +
> + list_for_each_entry(trigger, &spi_offload_triggers, list) {
> + if (trigger->fwnode != args->fwnode)
> + continue;
> +
> + match = trigger->ops->match(trigger, type, args->args, args-
> >nargs);
> + if (match)
> + break;
> + }
> +
> + if (!match)
> + return ERR_PTR(-EPROBE_DEFER);
> +
> + guard(mutex)(&trigger->lock);
> +
> + if (!trigger->ops)
> + return ERR_PTR(-ENODEV);
> +
> + if (trigger->ops->request) {
> + ret = trigger->ops->request(trigger, type, args->args, args-
> >nargs);
> + if (ret)
> + return ERR_PTR(ret);
> + }
> +
> + kref_get(&trigger->ref);
maybe try_module_get() would also make sense...
> +
> + return trigger;
> +}
> +
> +/**
> + * devm_spi_offload_trigger_get() - Get an offload trigger instance
> + * @dev: Device for devm purposes.
> + * @offload: Offload instance connected to a trigger.
> + * @type: Trigger type to get.
> + *
> + * Return: Offload trigger instance or error on failure.
> + */
> +struct spi_offload_trigger
> +*devm_spi_offload_trigger_get(struct device *dev,
> + struct spi_offload *offload,
> + enum spi_offload_trigger_type type)
> +{
> + struct spi_offload_trigger *trigger;
> + struct fwnode_reference_args args;
> + int ret;
> +
> + ret = fwnode_property_get_reference_args(dev_fwnode(offload-
> >provider_dev),
> + "trigger-sources",
> + "#trigger-source-cells", 0,
> 0,
> + &args);
I guess at some point we can add these to fwlinks?
- Nuno Sá
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver
2024-12-11 20:54 ` [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver David Lechner
@ 2024-12-17 11:36 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:36 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Add a new driver for a generic PWM trigger for SPI offloads.
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Still don't really agree with always checking against
SPI_OFFLOAD_TRIGGER_PERIODIC. But yeah...
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes:
> * Use dev instead of &pdev->dev
> * Swap order of "pwm" and "trigger" in name to follow "pwm-clock"
> precedent.
>
> v5 changes:
> * Updated to accommodate changes in other patches in this series.
> * Add MAINTAINERS entry.
>
> v4 changes: new patch in v4
> ---
> MAINTAINERS | 1 +
> drivers/spi/Kconfig | 12 +++
> drivers/spi/Makefile | 3 +
> drivers/spi/spi-offload-trigger-pwm.c | 162
> ++++++++++++++++++++++++++++++++++
> 4 files changed, 178 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index
> b2aa6f37743e48353c60e5973ea8126590c7f6d5..d8d72da5ac4bcab817a515774eb8db37a7e9
> 4f25 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22131,6 +22131,7 @@ F: include/linux/mtd/spi-nor.h
>
> SPI OFFLOAD
> R: David Lechner <dlechner@baylibre.com>
> +F: drivers/spi/spi-offload-trigger-pwm.c
> F: drivers/spi/spi-offload.c
> F: include/linux/spi/spi-offload.h
> K: spi_offload
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index
> 02064a4e292815ec0213e2e446b4f90ed8855a52..2cfc14be869790f5226130428bb7cb40aadf
> b031 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -1320,4 +1320,16 @@ endif # SPI_SLAVE
> config SPI_DYNAMIC
> def_bool ACPI || OF_DYNAMIC || SPI_SLAVE
>
> +if SPI_OFFLOAD
> +
> +comment "SPI Offload triggers"
> +
> +config SPI_OFFLOAD_TRIGGER_PWM
> + tristate "SPI offload trigger using PWM"
> + depends on PWM
> + help
> + Generic SPI offload trigger implemented using PWM output.
> +
> +endif # SPI_OFFLOAD
> +
> endif # SPI
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index
> bb5fc20df21332232533c2e70c0cc230f6bcf27f..0068d170bc99c750c13376c4013991d927bb
> ac63 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -164,3 +164,6 @@ obj-$(CONFIG_SPI_AMD) += spi-amd.o
> # SPI slave protocol handlers
> obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o
> obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o
> +
> +# SPI offload triggers
> +obj-$(CONFIG_SPI_OFFLOAD_TRIGGER_PWM) += spi-offload-trigger-pwm.o
> diff --git a/drivers/spi/spi-offload-trigger-pwm.c b/drivers/spi/spi-offload-
> trigger-pwm.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..b26d4437c589052709a8206f8314ffd08355
> 870e
> --- /dev/null
> +++ b/drivers/spi/spi-offload-trigger-pwm.c
> @@ -0,0 +1,162 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + *
> + * Generic PWM trigger for SPI offload.
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/spi/offload/provider.h>
> +#include <linux/types.h>
> +
> +struct spi_offload_trigger_pwm_state {
> + struct device *dev;
> + struct pwm_device *pwm;
> +};
> +
> +static bool spi_offload_trigger_pwm_match(struct spi_offload_trigger
> *trigger,
> + enum spi_offload_trigger_type type,
> + u64 *args, u32 nargs)
> +{
> + if (nargs)
> + return false;
> +
> + return type == SPI_OFFLOAD_TRIGGER_PERIODIC;
> +}
> +
> +static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger
> *trigger,
> + struct spi_offload_trigger_config
> *config)
> +{
> + struct spi_offload_trigger_pwm_state *st =
> spi_offload_trigger_get_priv(trigger);
> + struct spi_offload_trigger_periodic *periodic = &config->periodic;
> + struct pwm_waveform wf = { };
> + int ret;
> +
> + if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
> + return -EINVAL;
> +
> + if (!periodic->frequency_hz)
> + return -EINVAL;
> +
> + wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic-
> >frequency_hz);
> + /* REVISIT: 50% duty-cycle for now - may add config parameter later
> */
> + wf.duty_length_ns = wf.period_length_ns / 2;
> +
> + ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
> + if (ret < 0)
> + return ret;
> +
> + periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC,
> wf.period_length_ns);
> +
> + return 0;
> +}
> +
> +static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger
> *trigger,
> + struct spi_offload_trigger_config
> *config)
> +{
> + struct spi_offload_trigger_pwm_state *st =
> spi_offload_trigger_get_priv(trigger);
> + struct spi_offload_trigger_periodic *periodic = &config->periodic;
> + struct pwm_waveform wf = { };
> +
> + if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
> + return -EINVAL;
> +
> + if (!periodic->frequency_hz)
> + return -EINVAL;
> +
> + wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic-
> >frequency_hz);
> + /* REVISIT: 50% duty-cycle for now - may add config parameter later
> */
> + wf.duty_length_ns = wf.period_length_ns / 2;
> +
> + return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
> +}
> +
> +static void spi_offload_trigger_pwm_disable(struct spi_offload_trigger
> *trigger)
> +{
> + struct spi_offload_trigger_pwm_state *st =
> spi_offload_trigger_get_priv(trigger);
> + struct pwm_waveform wf;
> + int ret;
> +
> + ret = pwm_get_waveform_might_sleep(st->pwm, &wf);
> + if (ret < 0) {
> + dev_err(st->dev, "failed to get waveform: %d\n", ret);
> + return;
> + }
> +
> + wf.duty_length_ns = 0;
> +
> + ret = pwm_set_waveform_might_sleep(st->pwm, &wf, false);
> + if (ret < 0)
> + dev_err(st->dev, "failed to disable PWM: %d\n", ret);
> +}
> +
> +static const struct spi_offload_trigger_ops spi_offload_trigger_pwm_ops = {
> + .match = spi_offload_trigger_pwm_match,
> + .validate = spi_offload_trigger_pwm_validate,
> + .enable = spi_offload_trigger_pwm_enable,
> + .disable = spi_offload_trigger_pwm_disable,
> +};
> +
> +static void spi_offload_trigger_pwm_release(void *data)
> +{
> + pwm_disable(data);
> +}
> +
> +static int spi_offload_trigger_pwm_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct spi_offload_trigger_info info = {
> + .fwnode = dev_fwnode(dev),
> + .ops = &spi_offload_trigger_pwm_ops,
> + };
> + struct spi_offload_trigger_pwm_state *st;
> + struct pwm_state state;
> + int ret;
> +
> + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> + if (!st)
> + return -ENOMEM;
> +
> + info.priv = st;
> + st->dev = dev;
> +
> + st->pwm = devm_pwm_get(dev, NULL);
> + if (IS_ERR(st->pwm))
> + return dev_err_probe(dev, PTR_ERR(st->pwm), "failed to get
> PWM\n");
> +
> + /* init with duty_cycle = 0, output enabled to ensure trigger off */
> + pwm_init_state(st->pwm, &state);
> + state.enabled = true;
> +
> + ret = pwm_apply_might_sleep(st->pwm, &state);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to apply PWM
> state\n");
> +
> + ret = devm_add_action_or_reset(dev, spi_offload_trigger_pwm_release,
> st->pwm);
> + if (ret)
> + return ret;
> +
> + return devm_spi_offload_trigger_register(dev, &info);
> +}
> +
> +static const struct of_device_id spi_offload_trigger_pwm_of_match_table[] = {
> + { .compatible = "pwm-trigger" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, spi_offload_trigger_pwm_of_match_table);
> +
> +static struct platform_driver spi_offload_trigger_pwm_driver = {
> + .driver = {
> + .name = "pwm-trigger",
> + .of_match_table = spi_offload_trigger_pwm_of_match_table,
> + },
> + .probe = spi_offload_trigger_pwm_probe,
> +};
> +module_platform_driver(spi_offload_trigger_pwm_driver);
> +
> +MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
> +MODULE_DESCRIPTION("Generic PWM trigger");
> +MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 05/17] spi: add offload TX/RX streaming APIs
2024-12-11 20:54 ` [PATCH v6 05/17] spi: add offload TX/RX streaming APIs David Lechner
2024-12-14 14:28 ` Jonathan Cameron
@ 2024-12-17 11:43 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:43 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Most configuration of SPI offloads is handled opaquely using the offload
> pointer that is passed to the various offload functions. However, there
> are some offload features that need to be controlled on a per transfer
> basis.
>
> This patch adds a flag field to struct spi_transfer to allow specifying
> such features. The first feature to be added is the ability to stream
> data to/from a hardware sink/source rather than using a tx or rx buffer.
> Additional flags can be added in the future as needed.
>
> A flags field is also added to the offload struct for providers to
> indicate which flags are supported. This allows for generic checking of
> offload capabilities during __spi_validate() so that each offload
> provider doesn't have to implement their own validation.
>
> As a first users of this streaming capability, getter functions are
> added to get a DMA channel that is directly connected to the offload.
> Peripheral drivers will use this to get a DMA channel and configure it
> to suit their needs.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Still not sure about releasing the channel. But I guess this might be also a
problem today with "plain" IIO DMA buffering. So...
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
> v6 changes:
> * Update for header file split.
> * Fix wrong kernel-doc comments.
>
> v5 change:
> * Remove incorrect comment about caller needing to release DMA channels.
>
> v4 changes:
> * DMA API's now automatically release DMA channels instead of leaving
> it up to the caller.
>
> v3 changes:
> * Added spi_offload_{tx,rx}_stream_get_dma_chan() functions.
>
> v2 changes:
> * This is also split out from "spi: add core support for controllers with
> offload capabilities".
> * In the previous version, we were using (void *)-1 as a sentinel value
> that could be assigned, e.g. to rx_buf. But this was naive since there
> is core code that would try to dereference this pointer. So instead,
> we've added a new flags field to the spi_transfer structure for this
> sort of thing. This also has the advantage of being able to be used in
> the future for other arbitrary features.
> ---
> drivers/spi/spi-offload.c | 70
> ++++++++++++++++++++++++++++++++++++
> drivers/spi/spi.c | 10 ++++++
> include/linux/spi/offload/consumer.h | 5 +++
> include/linux/spi/offload/types.h | 19 ++++++++++
> include/linux/spi/spi.h | 3 ++
> 5 files changed, 107 insertions(+)
>
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index
> 43582e50e279c4b1b958765fec556aaa91180e55..df5e963d5ee29d37833559595536a460c530
> bc81 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -18,6 +18,7 @@
>
> #include <linux/cleanup.h>
> #include <linux/device.h>
> +#include <linux/dmaengine.h>
> #include <linux/export.h>
> #include <linux/kref.h>
> #include <linux/list.h>
> @@ -332,6 +333,75 @@ void spi_offload_trigger_disable(struct spi_offload
> *offload,
> }
> EXPORT_SYMBOL_GPL(spi_offload_trigger_disable);
>
> +static void spi_offload_release_dma_chan(void *chan)
> +{
> + dma_release_channel(chan);
> +}
> +
> +/**
> + * devm_spi_offload_tx_stream_request_dma_chan - Get the DMA channel info for
> the TX stream
> + * @dev: Device for devm purposes.
> + * @offload: Offload instance
> + *
> + * This is the DMA channel that will provide data to transfers that use the
> + * %SPI_OFFLOAD_XFER_TX_STREAM offload flag.
> + *
> + * Return: Pointer to DMA channel info, or negative error code
> + */
> +struct dma_chan
> +*devm_spi_offload_tx_stream_request_dma_chan(struct device *dev,
> + struct spi_offload *offload)
> +{
> + struct dma_chan *chan;
> + int ret;
> +
> + if (!offload->ops || !offload->ops->tx_stream_request_dma_chan)
> + return ERR_PTR(-EOPNOTSUPP);
> +
> + chan = offload->ops->tx_stream_request_dma_chan(offload);
> + if (IS_ERR(chan))
> + return chan;
> +
> + ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan,
> chan);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return chan;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_tx_stream_request_dma_chan);
> +
> +/**
> + * devm_spi_offload_rx_stream_request_dma_chan - Get the DMA channel info for
> the RX stream
> + * @dev: Device for devm purposes.
> + * @offload: Offload instance
> + *
> + * This is the DMA channel that will receive data from transfers that use the
> + * %SPI_OFFLOAD_XFER_RX_STREAM offload flag.
> + *
> + * Return: Pointer to DMA channel info, or negative error code
> + */
> +struct dma_chan
> +*devm_spi_offload_rx_stream_request_dma_chan(struct device *dev,
> + struct spi_offload *offload)
> +{
> + struct dma_chan *chan;
> + int ret;
> +
> + if (!offload->ops || !offload->ops->rx_stream_request_dma_chan)
> + return ERR_PTR(-EOPNOTSUPP);
> +
> + chan = offload->ops->rx_stream_request_dma_chan(offload);
> + if (IS_ERR(chan))
> + return chan;
> +
> + ret = devm_add_action_or_reset(dev, spi_offload_release_dma_chan,
> chan);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return chan;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_rx_stream_request_dma_chan);
> +
> /* Triggers providers */
>
> static void spi_offload_trigger_unregister(void *data)
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index
> ff1add2ecb91f18cf82e6f1e9595584c11adf9d8..4a871db9ee636aba64c866ebdd8bb1dbf82e
> 0f42 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -31,6 +31,7 @@
> #include <linux/ptp_clock_kernel.h>
> #include <linux/sched/rt.h>
> #include <linux/slab.h>
> +#include <linux/spi/offload/types.h>
> #include <linux/spi/spi.h>
> #include <linux/spi/spi-mem.h>
> #include <uapi/linux/sched/types.h>
> @@ -4163,6 +4164,15 @@ static int __spi_validate(struct spi_device *spi,
> struct spi_message *message)
>
> if (_spi_xfer_word_delay_update(xfer, spi))
> return -EINVAL;
> +
> + /* make sure controller supports required offload features */
> + if (xfer->offload_flags) {
> + if (!message->offload)
> + return -EINVAL;
> +
> + if (xfer->offload_flags & ~message->offload-
> >xfer_flags)
> + return -EINVAL;
> + }
> }
>
> message->status = -EINPROGRESS;
> diff --git a/include/linux/spi/offload/consumer.h
> b/include/linux/spi/offload/consumer.h
> index
> 5a0ec5303d600728959594bcdbd0cb2baeba7c77..cd7d5daa21e69b61c16eba6c10c855345a4f
> 3297 100644
> --- a/include/linux/spi/offload/consumer.h
> +++ b/include/linux/spi/offload/consumer.h
> @@ -31,4 +31,9 @@ int spi_offload_trigger_enable(struct spi_offload *offload,
> void spi_offload_trigger_disable(struct spi_offload *offload,
> struct spi_offload_trigger *trigger);
>
> +struct dma_chan *devm_spi_offload_tx_stream_request_dma_chan(struct device
> *dev,
> + struct
> spi_offload *offload);
> +struct dma_chan *devm_spi_offload_rx_stream_request_dma_chan(struct device
> *dev,
> + struct
> spi_offload *offload);
> +
> #endif /* __LINUX_SPI_OFFLOAD_CONSUMER_H */
> diff --git a/include/linux/spi/offload/types.h
> b/include/linux/spi/offload/types.h
> index
> 7476f2073b02ee0f9edd3ae75e587b075746fa92..86d0e8cb9495bb43e177378b2041067de8ea
> 8786 100644
> --- a/include/linux/spi/offload/types.h
> +++ b/include/linux/spi/offload/types.h
> @@ -11,6 +11,11 @@
>
> struct device;
>
> +/* This is write xfer but TX uses external data stream rather than tx_buf. */
> +#define SPI_OFFLOAD_XFER_TX_STREAM BIT(0)
> +/* This is read xfer but RX uses external data stream rather than rx_buf. */
> +#define SPI_OFFLOAD_XFER_RX_STREAM BIT(1)
> +
> /* Offload can be triggered by external hardware event. */
> #define SPI_OFFLOAD_CAP_TRIGGER BIT(0)
> /* Offload can record and then play back TX data when triggered. */
> @@ -40,6 +45,8 @@ struct spi_offload {
> void *priv;
> /** @ops: callbacks for offload support */
> const struct spi_offload_ops *ops;
> + /** @xfer_flags: %SPI_OFFLOAD_XFER_* flags supported by provider */
> + u32 xfer_flags;
> };
>
> enum spi_offload_trigger_type {
> @@ -75,6 +82,18 @@ struct spi_offload_ops {
> * given offload instance.
> */
> void (*trigger_disable)(struct spi_offload *offload);
> + /**
> + * @tx_stream_request_dma_chan: Optional callback for controllers
> that
> + * have an offload where the TX data stream is connected directly to
> a
> + * DMA channel.
> + */
> + struct dma_chan *(*tx_stream_request_dma_chan)(struct spi_offload
> *offload);
> + /**
> + * @rx_stream_request_dma_chan: Optional callback for controllers
> that
> + * have an offload where the RX data stream is connected directly to
> a
> + * DMA channel.
> + */
> + struct dma_chan *(*rx_stream_request_dma_chan)(struct spi_offload
> *offload);
> };
>
> #endif /* __LINUX_SPI_OFFLOAD_TYPES_H */
> diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
> index
> 98bdc8c16c20521c0a94e5f72f5e71c4f6d7d11e..4c087009cf974595f23036b1b7a030a45913
> 420c 100644
> --- a/include/linux/spi/spi.h
> +++ b/include/linux/spi/spi.h
> @@ -1093,6 +1093,9 @@ struct spi_transfer {
>
> u32 effective_speed_hz;
>
> + /* Use %SPI_OFFLOAD_XFER_* from spi-offload.h */
> + unsigned int offload_flags;
> +
> unsigned int ptp_sts_word_pre;
> unsigned int ptp_sts_word_post;
>
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 07/17] spi: axi-spi-engine: implement offload support
2024-12-11 20:54 ` [PATCH v6 07/17] spi: axi-spi-engine: implement offload support David Lechner
@ 2024-12-17 11:48 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:48 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Implement SPI offload support for the AXI SPI Engine. Currently, the
> hardware only supports triggering offload transfers with a hardware
> trigger so attempting to use an offload message in the regular SPI
> message queue will fail. Also, only allows streaming rx data to an
> external sink, so attempts to use a rx_buf in the offload message will
> fail.
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
LGTM,
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes:
> * Update for split spi/offload headers.
>
> v5 changes:
> * Set offload capability flags based on DT properties.
> * Add support for TX DMA since the hardware supports that now.
> * Update for changes in other patches in the series.
>
> v4 changes:
> * Adapted to changes in other patches in the series.
> * Moved trigger enable/disable to same function as offload
> enable/disable.
>
> v3 changes:
> * Added clk and dma_chan getter callbacks.
> * Fixed some bugs.
>
> v2 changes:
>
> This patch has been reworked to accommodate the changes described in all
> of the other patches.
> ---
> drivers/spi/Kconfig | 1 +
> drivers/spi/spi-axi-spi-engine.c | 314
> ++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 308 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index
> 2cfc14be869790f5226130428bb7cb40aadfb031..f496ab127ef011d092f66063e05772725ab8
> 9771 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -179,6 +179,7 @@ config SPI_AU1550
> config SPI_AXI_SPI_ENGINE
> tristate "Analog Devices AXI SPI Engine controller"
> depends on HAS_IOMEM
> + select SPI_OFFLOAD
> help
> This enables support for the Analog Devices AXI SPI Engine SPI
> controller.
> It is part of the SPI Engine framework that is used in some Analog
> Devices
> diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-
> engine.c
> index
> 7c252126b33ea83fe6a6e80c6cb87499243069f5..dd6077d3ff7b8d29b0ca2e803a5930c4cedf
> 2e93 100644
> --- a/drivers/spi/spi-axi-spi-engine.c
> +++ b/drivers/spi/spi-axi-spi-engine.c
> @@ -2,11 +2,14 @@
> /*
> * SPI-Engine SPI controller driver
> * Copyright 2015 Analog Devices Inc.
> + * Copyright 2024 BayLibre, SAS
> * Author: Lars-Peter Clausen <lars@metafoo.de>
> */
>
> +#include <linux/bitops.h>
> #include <linux/clk.h>
> #include <linux/completion.h>
> +#include <linux/dmaengine.h>
> #include <linux/fpga/adi-axi-common.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> @@ -14,9 +17,11 @@
> #include <linux/module.h>
> #include <linux/overflow.h>
> #include <linux/platform_device.h>
> +#include <linux/spi/offload/provider.h>
> #include <linux/spi/spi.h>
> #include <trace/events/spi.h>
>
> +#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH 0x10
> #define SPI_ENGINE_REG_RESET 0x40
>
> #define SPI_ENGINE_REG_INT_ENABLE 0x80
> @@ -24,6 +29,7 @@
> #define SPI_ENGINE_REG_INT_SOURCE 0x88
>
> #define SPI_ENGINE_REG_SYNC_ID 0xc0
> +#define SPI_ENGINE_REG_OFFLOAD_SYNC_ID 0xc4
>
> #define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0
> #define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4
> @@ -34,10 +40,24 @@
> #define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8
> #define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec
>
> +#define SPI_ENGINE_MAX_NUM_OFFLOADS 32
> +
> +#define SPI_ENGINE_REG_OFFLOAD_CTRL(x) (0x100 +
> SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
> +#define SPI_ENGINE_REG_OFFLOAD_STATUS(x) (0x104 +
> SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
> +#define SPI_ENGINE_REG_OFFLOAD_RESET(x) (0x108 +
> SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
> +#define SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(x) (0x110 +
> SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
> +#define SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(x) (0x114 +
> SPI_ENGINE_MAX_NUM_OFFLOADS * (x))
> +
> +#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO GENMASK(15, 8)
> +#define SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD GENMASK(7, 0)
> +
> #define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0)
> #define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1)
> #define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2)
> #define SPI_ENGINE_INT_SYNC BIT(3)
> +#define SPI_ENGINE_INT_OFFLOAD_SYNC BIT(4)
> +
> +#define SPI_ENGINE_OFFLOAD_CTRL_ENABLE BIT(0)
>
> #define SPI_ENGINE_CONFIG_CPHA BIT(0)
> #define SPI_ENGINE_CONFIG_CPOL BIT(1)
> @@ -79,6 +99,10 @@
> #define SPI_ENGINE_CMD_CS_INV(flags) \
> SPI_ENGINE_CMD(SPI_ENGINE_INST_CS_INV, 0, (flags))
>
> +/* default sizes - can be changed when SPI Engine firmware is compiled */
> +#define SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE 16
> +#define SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE 16
> +
> struct spi_engine_program {
> unsigned int length;
> uint16_t instructions[] __counted_by(length);
> @@ -106,6 +130,17 @@ struct spi_engine_message_state {
> uint8_t *rx_buf;
> };
>
> +enum {
> + SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED,
> + SPI_ENGINE_OFFLOAD_FLAG_PREPARED,
> +};
> +
> +struct spi_engine_offload {
> + struct spi_engine *spi_engine;
> + unsigned long flags;
> + unsigned int offload_num;
> +};
> +
> struct spi_engine {
> struct clk *clk;
> struct clk *ref_clk;
> @@ -118,6 +153,11 @@ struct spi_engine {
> unsigned int int_enable;
> /* shadows hardware CS inversion flag state */
> u8 cs_inv;
> +
> + unsigned int offload_ctrl_mem_size;
> + unsigned int offload_sdo_mem_size;
> + struct spi_offload *offload;
> + u32 offload_caps;
> };
>
> static void spi_engine_program_add_cmd(struct spi_engine_program *p,
> @@ -163,9 +203,9 @@ static void spi_engine_gen_xfer(struct spi_engine_program
> *p, bool dry,
> unsigned int n = min(len, 256U);
> unsigned int flags = 0;
>
> - if (xfer->tx_buf)
> + if (xfer->tx_buf || (xfer->offload_flags &
> SPI_OFFLOAD_XFER_TX_STREAM))
> flags |= SPI_ENGINE_TRANSFER_WRITE;
> - if (xfer->rx_buf)
> + if (xfer->rx_buf || (xfer->offload_flags &
> SPI_OFFLOAD_XFER_RX_STREAM))
> flags |= SPI_ENGINE_TRANSFER_READ;
>
> spi_engine_program_add_cmd(p, dry,
> @@ -217,16 +257,24 @@ static void spi_engine_gen_cs(struct spi_engine_program
> *p, bool dry,
> *
> * NB: This is separate from spi_engine_compile_message() because the latter
> * is called twice and would otherwise result in double-evaluation.
> + *
> + * Returns 0 on success, -EINVAL on failure.
> */
> -static void spi_engine_precompile_message(struct spi_message *msg)
> +static int spi_engine_precompile_message(struct spi_message *msg)
> {
> unsigned int clk_div, max_hz = msg->spi->controller->max_speed_hz;
> struct spi_transfer *xfer;
>
> list_for_each_entry(xfer, &msg->transfers, transfer_list) {
> + /* If we have an offload transfer, we can't rx to buffer */
> + if (msg->offload && xfer->rx_buf)
> + return -EINVAL;
> +
> clk_div = DIV_ROUND_UP(max_hz, xfer->speed_hz);
> xfer->effective_speed_hz = max_hz / min(clk_div, 256U);
> }
> +
> + return 0;
> }
>
> static void spi_engine_compile_message(struct spi_message *msg, bool dry,
> @@ -521,11 +569,105 @@ static irqreturn_t spi_engine_irq(int irq, void *devid)
> return IRQ_HANDLED;
> }
>
> +static int spi_engine_offload_prepare(struct spi_message *msg)
> +{
> + struct spi_controller *host = msg->spi->controller;
> + struct spi_engine *spi_engine = spi_controller_get_devdata(host);
> + struct spi_engine_program *p = msg->opt_state;
> + struct spi_engine_offload *priv = msg->offload->priv;
> + struct spi_transfer *xfer;
> + void __iomem *cmd_addr;
> + void __iomem *sdo_addr;
> + size_t tx_word_count = 0;
> + unsigned int i;
> +
> + if (p->length > spi_engine->offload_ctrl_mem_size)
> + return -EINVAL;
> +
> + /* count total number of tx words in message */
> + list_for_each_entry(xfer, &msg->transfers, transfer_list) {
> + /* no support for reading to rx_buf */
> + if (xfer->rx_buf)
> + return -EINVAL;
> +
> + if (!xfer->tx_buf)
> + continue;
> +
> + if (xfer->bits_per_word <= 8)
> + tx_word_count += xfer->len;
> + else if (xfer->bits_per_word <= 16)
> + tx_word_count += xfer->len / 2;
> + else
> + tx_word_count += xfer->len / 4;
> + }
> +
> + if (tx_word_count && !(spi_engine->offload_caps &
> SPI_OFFLOAD_CAP_TX_STATIC_DATA))
> + return -EINVAL;
> +
> + if (tx_word_count > spi_engine->offload_sdo_mem_size)
> + return -EINVAL;
> +
> + /*
> + * This protects against calling spi_optimize_message() with an
> offload
> + * that has already been prepared with a different message.
> + */
> + if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv-
> >flags))
> + return -EBUSY;
> +
> + cmd_addr = spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_CMD_FIFO(priv->offload_num);
> + sdo_addr = spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_SDO_FIFO(priv->offload_num);
> +
> + list_for_each_entry(xfer, &msg->transfers, transfer_list) {
> + if (!xfer->tx_buf)
> + continue;
> +
> + if (xfer->bits_per_word <= 8) {
> + const u8 *buf = xfer->tx_buf;
> +
> + for (i = 0; i < xfer->len; i++)
> + writel_relaxed(buf[i], sdo_addr);
> + } else if (xfer->bits_per_word <= 16) {
> + const u16 *buf = xfer->tx_buf;
> +
> + for (i = 0; i < xfer->len / 2; i++)
> + writel_relaxed(buf[i], sdo_addr);
> + } else {
> + const u32 *buf = xfer->tx_buf;
> +
> + for (i = 0; i < xfer->len / 4; i++)
> + writel_relaxed(buf[i], sdo_addr);
> + }
> + }
> +
> + for (i = 0; i < p->length; i++)
> + writel_relaxed(p->instructions[i], cmd_addr);
> +
> + return 0;
> +}
> +
> +static void spi_engine_offload_unprepare(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> + struct spi_engine *spi_engine = priv->spi_engine;
> +
> + writel_relaxed(1, spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
> + writel_relaxed(0, spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_RESET(priv->offload_num));
> +
> + clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags);
> +}
> +
> static int spi_engine_optimize_message(struct spi_message *msg)
> {
> struct spi_engine_program p_dry, *p;
> + int ret;
>
> - spi_engine_precompile_message(msg);
> + ret = spi_engine_precompile_message(msg);
> + if (ret)
> + return ret;
>
> p_dry.length = 0;
> spi_engine_compile_message(msg, true, &p_dry);
> @@ -537,20 +679,61 @@ static int spi_engine_optimize_message(struct
> spi_message *msg)
> spi_engine_compile_message(msg, false, p);
>
> spi_engine_program_add_cmd(p, false, SPI_ENGINE_CMD_SYNC(
> -
> AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
> + msg->offload ? 0 : AXI_SPI_ENGINE_CUR_MSG_SYNC_ID));
>
> msg->opt_state = p;
>
> + if (msg->offload) {
> + ret = spi_engine_offload_prepare(msg);
> + if (ret) {
> + msg->opt_state = NULL;
> + kfree(p);
> + return ret;
> + }
> + }
> +
> return 0;
> }
>
> static int spi_engine_unoptimize_message(struct spi_message *msg)
> {
> + if (msg->offload)
> + spi_engine_offload_unprepare(msg->offload);
> +
> kfree(msg->opt_state);
>
> return 0;
> }
>
> +static struct spi_offload
> +*spi_engine_get_offload(struct spi_device *spi,
> + const struct spi_offload_config *config)
> +{
> + struct spi_controller *host = spi->controller;
> + struct spi_engine *spi_engine = spi_controller_get_devdata(host);
> + struct spi_engine_offload *priv;
> +
> + if (!spi_engine->offload)
> + return ERR_PTR(-ENODEV);
> +
> + if (config->capability_flags & ~spi_engine->offload_caps)
> + return ERR_PTR(-EINVAL);
> +
> + priv = spi_engine->offload->priv;
> +
> + if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv-
> >flags))
> + return ERR_PTR(-EBUSY);
> +
> + return spi_engine->offload;
> +}
> +
> +static void spi_engine_put_offload(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> +
> + clear_bit_unlock(SPI_ENGINE_OFFLOAD_FLAG_ASSIGNED, &priv->flags);
> +}
> +
> static int spi_engine_setup(struct spi_device *device)
> {
> struct spi_controller *host = device->controller;
> @@ -583,6 +766,12 @@ static int spi_engine_transfer_one_message(struct
> spi_controller *host,
> unsigned int int_enable = 0;
> unsigned long flags;
>
> + if (msg->offload) {
> + dev_err(&host->dev, "Single transfer offload not
> supported\n");
> + msg->status = -EOPNOTSUPP;
> + goto out;
> + }
> +
> /* reinitialize message state for this transfer */
> memset(st, 0, sizeof(*st));
> st->cmd_buf = p->instructions;
> @@ -632,11 +821,68 @@ static int spi_engine_transfer_one_message(struct
> spi_controller *host,
> trace_spi_transfer_stop(msg, xfer);
> }
>
> +out:
> spi_finalize_current_message(host);
>
> return msg->status;
> }
>
> +static int spi_engine_trigger_enable(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> + struct spi_engine *spi_engine = priv->spi_engine;
> + unsigned int reg;
> +
> + reg = readl_relaxed(spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
> + reg |= SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
> + writel_relaxed(reg, spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
> + return 0;
> +}
> +
> +static void spi_engine_trigger_disable(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> + struct spi_engine *spi_engine = priv->spi_engine;
> + unsigned int reg;
> +
> + reg = readl_relaxed(spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
> + reg &= ~SPI_ENGINE_OFFLOAD_CTRL_ENABLE;
> + writel_relaxed(reg, spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_CTRL(priv->offload_num));
> +}
> +
> +static struct dma_chan
> +*spi_engine_tx_stream_request_dma_chan(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> + char name[16];
> +
> + snprintf(name, sizeof(name), "offload%u-tx", priv->offload_num);
> +
> + return dma_request_chan(offload->provider_dev, name);
> +}
> +
> +static struct dma_chan
> +*spi_engine_rx_stream_request_dma_chan(struct spi_offload *offload)
> +{
> + struct spi_engine_offload *priv = offload->priv;
> + char name[16];
> +
> + snprintf(name, sizeof(name), "offload%u-rx", priv->offload_num);
> +
> + return dma_request_chan(offload->provider_dev, name);
> +}
> +
> +static const struct spi_offload_ops spi_engine_offload_ops = {
> + .trigger_enable = spi_engine_trigger_enable,
> + .trigger_disable = spi_engine_trigger_disable,
> + .tx_stream_request_dma_chan = spi_engine_tx_stream_request_dma_chan,
> + .rx_stream_request_dma_chan = spi_engine_rx_stream_request_dma_chan,
> +};
> +
> static void spi_engine_release_hw(void *p)
> {
> struct spi_engine *spi_engine = p;
> @@ -651,8 +897,7 @@ static int spi_engine_probe(struct platform_device *pdev)
> struct spi_engine *spi_engine;
> struct spi_controller *host;
> unsigned int version;
> - int irq;
> - int ret;
> + int irq, ret;
>
> irq = platform_get_irq(pdev, 0);
> if (irq < 0)
> @@ -667,6 +912,46 @@ static int spi_engine_probe(struct platform_device *pdev)
> spin_lock_init(&spi_engine->lock);
> init_completion(&spi_engine->msg_complete);
>
> + /*
> + * REVISIT: for now, all SPI Engines only have one offload. In the
> + * future, this should be read from a memory mapped register to
> + * determine the number of offloads enabled at HDL compile time. For
> + * now, we can tell if an offload is present if there is a trigger
> + * source wired up to it.
> + */
> + if (device_property_present(&pdev->dev, "trigger-sources")) {
> + struct spi_engine_offload *priv;
> +
> + spi_engine->offload =
> + devm_spi_offload_alloc(&pdev->dev,
> + sizeof(struct
> spi_engine_offload));
> + if (IS_ERR(spi_engine->offload))
> + return PTR_ERR(spi_engine->offload);
> +
> + priv = spi_engine->offload->priv;
> + priv->spi_engine = spi_engine;
> + priv->offload_num = 0;
> +
> + spi_engine->offload->ops = &spi_engine_offload_ops;
> + spi_engine->offload_caps = SPI_OFFLOAD_CAP_TRIGGER;
> +
> + if (device_property_match_string(&pdev->dev, "dma-names",
> "offload0-rx") >= 0) {
> + spi_engine->offload_caps |=
> SPI_OFFLOAD_CAP_RX_STREAM_DMA;
> + spi_engine->offload->xfer_flags |=
> SPI_OFFLOAD_XFER_RX_STREAM;
> + }
> +
> + if (device_property_match_string(&pdev->dev, "dma-names",
> "offload0-tx") >= 0) {
> + spi_engine->offload_caps |=
> SPI_OFFLOAD_CAP_TX_STREAM_DMA;
> + spi_engine->offload->xfer_flags |=
> SPI_OFFLOAD_XFER_TX_STREAM;
> + } else {
> + /*
> + * HDL compile option to enable TX DMA stream also
> disables
> + * the SDO memory, so can't do both at the same time.
> + */
> + spi_engine->offload_caps |=
> SPI_OFFLOAD_CAP_TX_STATIC_DATA;
> + }
> + }
> +
> spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
> if (IS_ERR(spi_engine->clk))
> return PTR_ERR(spi_engine->clk);
> @@ -688,6 +973,19 @@ static int spi_engine_probe(struct platform_device *pdev)
> return -ENODEV;
> }
>
> + if (ADI_AXI_PCORE_VER_MINOR(version) >= 1) {
> + unsigned int sizes = readl(spi_engine->base +
> + SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH);
> +
> + spi_engine->offload_ctrl_mem_size = 1 <<
> + FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_CMD,
> sizes);
> + spi_engine->offload_sdo_mem_size = 1 <<
> + FIELD_GET(SPI_ENGINE_SPI_OFFLOAD_MEM_WIDTH_SDO,
> sizes);
> + } else {
> + spi_engine->offload_ctrl_mem_size =
> SPI_ENGINE_OFFLOAD_CMD_FIFO_SIZE;
> + spi_engine->offload_sdo_mem_size =
> SPI_ENGINE_OFFLOAD_SDO_FIFO_SIZE;
> + }
> +
> writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET);
> writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
> writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
> @@ -709,6 +1007,8 @@ static int spi_engine_probe(struct platform_device *pdev)
> host->transfer_one_message = spi_engine_transfer_one_message;
> host->optimize_message = spi_engine_optimize_message;
> host->unoptimize_message = spi_engine_unoptimize_message;
> + host->get_offload = spi_engine_get_offload;
> + host->put_offload = spi_engine_put_offload;
> host->num_chipselect = 8;
>
> /* Some features depend of the IP core version. */
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer
2024-12-11 20:54 ` [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer David Lechner
2024-12-14 14:37 ` Jonathan Cameron
@ 2024-12-17 11:50 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:50 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Refactor the IIO dmaengine buffer code to split requesting the DMA
> channel from allocating the buffer. We want to be able to add a new
> function where the IIO device driver manages the DMA channel, so these
> two actions need to be separate.
>
> To do this, calling dma_request_chan() is moved from
> iio_dmaengine_buffer_alloc() to iio_dmaengine_buffer_setup_ext(). A new
> __iio_dmaengine_buffer_setup_ext() helper function is added to simplify
> error unwinding and will also be used by a new function in a later
> patch.
>
> iio_dmaengine_buffer_free() now only frees the buffer and does not
> release the DMA channel. A new iio_dmaengine_buffer_teardown() function
> is added to unwind everything done in iio_dmaengine_buffer_setup_ext().
> This keeps things more symmetrical with obvious pairs alloc/free and
> setup/teardown.
>
> Calling dma_get_slave_caps() in iio_dmaengine_buffer_alloc() is moved so
> that we can avoid any gotos for error unwinding.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes:
> * Split out from patch that adds the new function
> * Dropped owns_chan flag
> * Introduced iio_dmaengine_buffer_teardown() so that
> iio_dmaengine_buffer_free() doesn't have to manage the DMA channel
> ---
> drivers/iio/adc/adi-axi-adc.c | 2 +-
> drivers/iio/buffer/industrialio-buffer-dmaengine.c | 106 ++++++++++++--------
> -
> drivers/iio/dac/adi-axi-dac.c | 2 +-
> include/linux/iio/buffer-dmaengine.h | 2 +-
> 4 files changed, 65 insertions(+), 47 deletions(-)
>
> diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
> index
> c7357601f0f869e57636f00bb1e26c059c3ab15c..a55db308baabf7b26ea98431cab1e6af7fe2
> a5f3 100644
> --- a/drivers/iio/adc/adi-axi-adc.c
> +++ b/drivers/iio/adc/adi-axi-adc.c
> @@ -305,7 +305,7 @@ static struct iio_buffer *axi_adc_request_buffer(struct
> iio_backend *back,
> static void axi_adc_free_buffer(struct iio_backend *back,
> struct iio_buffer *buffer)
> {
> - iio_dmaengine_buffer_free(buffer);
> + iio_dmaengine_buffer_teardown(buffer);
> }
>
> static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg,
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> index
> 614e1c4189a9cdd5a8d9d8c5ef91566983032951..02847d3962fcbb43ec76167db6482ab951f2
> 0942 100644
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -206,39 +206,29 @@ static const struct iio_dev_attr
> *iio_dmaengine_buffer_attrs[] = {
>
> /**
> * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
> - * @dev: DMA channel consumer device
> - * @channel: DMA channel name, typically "rx".
> + * @chan: DMA channel.
> *
> * This allocates a new IIO buffer which internally uses the DMAengine
> framework
> - * to perform its transfers. The parent device will be used to request the
> DMA
> - * channel.
> + * to perform its transfers.
> *
> * Once done using the buffer iio_dmaengine_buffer_free() should be used to
> * release it.
> */
> -static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
> - const char *channel)
> +static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan)
> {
> struct dmaengine_buffer *dmaengine_buffer;
> unsigned int width, src_width, dest_width;
> struct dma_slave_caps caps;
> - struct dma_chan *chan;
> int ret;
>
> + ret = dma_get_slave_caps(chan, &caps);
> + if (ret < 0)
> + return ERR_PTR(ret);
> +
> dmaengine_buffer = kzalloc(sizeof(*dmaengine_buffer), GFP_KERNEL);
> if (!dmaengine_buffer)
> return ERR_PTR(-ENOMEM);
>
> - chan = dma_request_chan(dev, channel);
> - if (IS_ERR(chan)) {
> - ret = PTR_ERR(chan);
> - goto err_free;
> - }
> -
> - ret = dma_get_slave_caps(chan, &caps);
> - if (ret < 0)
> - goto err_release;
> -
> /* Needs to be aligned to the maximum of the minimums */
> if (caps.src_addr_widths)
> src_width = __ffs(caps.src_addr_widths);
> @@ -262,12 +252,6 @@ static struct iio_buffer
> *iio_dmaengine_buffer_alloc(struct device *dev,
> dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops;
>
> return &dmaengine_buffer->queue.buffer;
> -
> -err_release:
> - dma_release_channel(chan);
> -err_free:
> - kfree(dmaengine_buffer);
> - return ERR_PTR(ret);
> }
>
> /**
> @@ -276,17 +260,57 @@ static struct iio_buffer
> *iio_dmaengine_buffer_alloc(struct device *dev,
> *
> * Frees a buffer previously allocated with iio_dmaengine_buffer_alloc().
> */
> -void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
> +static void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
> {
> struct dmaengine_buffer *dmaengine_buffer =
> iio_buffer_to_dmaengine_buffer(buffer);
>
> iio_dma_buffer_exit(&dmaengine_buffer->queue);
> - dma_release_channel(dmaengine_buffer->chan);
> -
> iio_buffer_put(buffer);
> }
> -EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, "IIO_DMAENGINE_BUFFER");
> +
> +/**
> + * iio_dmaengine_buffer_teardown() - Releases DMA channel and frees buffer
> + * @buffer: Buffer to free
> + *
> + * Releases the DMA channel and frees the buffer previously setup with
> + * iio_dmaengine_buffer_setup_ext().
> + */
> +void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer)
> +{
> + struct dmaengine_buffer *dmaengine_buffer =
> + iio_buffer_to_dmaengine_buffer(buffer);
> + struct dma_chan *chan = dmaengine_buffer->chan;
> +
> + iio_dmaengine_buffer_free(buffer);
> + dma_release_channel(chan);
> +}
> +EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_teardown, "IIO_DMAENGINE_BUFFER");
> +
> +static struct iio_buffer
> +*__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev,
> + struct dma_chan *chan,
> + enum iio_buffer_direction dir)
> +{
> + struct iio_buffer *buffer;
> + int ret;
> +
> + buffer = iio_dmaengine_buffer_alloc(chan);
> + if (IS_ERR(buffer))
> + return ERR_CAST(buffer);
> +
> + indio_dev->modes |= INDIO_BUFFER_HARDWARE;
> +
> + buffer->direction = dir;
> +
> + ret = iio_device_attach_buffer(indio_dev, buffer);
> + if (ret) {
> + iio_dmaengine_buffer_free(buffer);
> + return ERR_PTR(ret);
> + }
> +
> + return buffer;
> +}
>
> /**
> * iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
> @@ -300,7 +324,7 @@ EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free,
> "IIO_DMAENGINE_BUFFER");
> * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of
> the
> * IIO device.
> *
> - * Once done using the buffer iio_dmaengine_buffer_free() should be used to
> + * Once done using the buffer iio_dmaengine_buffer_teardown() should be used
> to
> * release it.
> */
> struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
> @@ -308,30 +332,24 @@ struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct
> device *dev,
> const char *channel,
> enum iio_buffer_direction
> dir)
> {
> + struct dma_chan *chan;
> struct iio_buffer *buffer;
> - int ret;
> -
> - buffer = iio_dmaengine_buffer_alloc(dev, channel);
> - if (IS_ERR(buffer))
> - return ERR_CAST(buffer);
> -
> - indio_dev->modes |= INDIO_BUFFER_HARDWARE;
>
> - buffer->direction = dir;
> + chan = dma_request_chan(dev, channel);
> + if (IS_ERR(chan))
> + return ERR_CAST(chan);
>
> - ret = iio_device_attach_buffer(indio_dev, buffer);
> - if (ret) {
> - iio_dmaengine_buffer_free(buffer);
> - return ERR_PTR(ret);
> - }
> + buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
> + if (IS_ERR(buffer))
> + dma_release_channel(chan);
>
> return buffer;
> }
> EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER");
>
> -static void __devm_iio_dmaengine_buffer_free(void *buffer)
> +static void devm_iio_dmaengine_buffer_teardown(void *buffer)
> {
> - iio_dmaengine_buffer_free(buffer);
> + iio_dmaengine_buffer_teardown(buffer);
> }
>
> /**
> @@ -357,7 +375,7 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device
> *dev,
> if (IS_ERR(buffer))
> return PTR_ERR(buffer);
>
> - return devm_add_action_or_reset(dev,
> __devm_iio_dmaengine_buffer_free,
> + return devm_add_action_or_reset(dev,
> devm_iio_dmaengine_buffer_teardown,
> buffer);
> }
> EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext,
> "IIO_DMAENGINE_BUFFER");
> diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c
> index
> b143f7ed6847277aeb49094627d90e5d95eed71c..5d5157af0a233143daff906b699bdae10f36
> 8867 100644
> --- a/drivers/iio/dac/adi-axi-dac.c
> +++ b/drivers/iio/dac/adi-axi-dac.c
> @@ -168,7 +168,7 @@ static struct iio_buffer *axi_dac_request_buffer(struct
> iio_backend *back,
> static void axi_dac_free_buffer(struct iio_backend *back,
> struct iio_buffer *buffer)
> {
> - iio_dmaengine_buffer_free(buffer);
> + iio_dmaengine_buffer_teardown(buffer);
> }
>
> enum {
> diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-
> dmaengine.h
> index
> 81d9a19aeb9199dd58bb9d35a91f0ec4b00846df..72a2e3fd8a5bf5e8f27ee226ddd92979d233
> 754b 100644
> --- a/include/linux/iio/buffer-dmaengine.h
> +++ b/include/linux/iio/buffer-dmaengine.h
> @@ -12,7 +12,7 @@
> struct iio_dev;
> struct device;
>
> -void iio_dmaengine_buffer_free(struct iio_buffer *buffer);
> +void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer);
> struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
> struct iio_dev *indio_dev,
> const char *channel,
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle()
2024-12-11 20:54 ` [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle() David Lechner
2024-12-14 14:39 ` Jonathan Cameron
@ 2024-12-17 11:51 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:51 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Add a new devm_iio_dmaengine_buffer_setup_with_handle() function to
> handle cases where the DMA channel is managed by the caller rather than
> being requested and released by the iio_dmaengine module.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
> v6 changes:
> * Rename from devm_iio_dmaengine_buffer_setup_ext2()
> * This patch now just adds the new function - prep work was split out
> to a separate patch
>
> v5 changes: none
>
> v4 changes:
> * This replaces "iio: buffer-dmaengine: generalize requesting DMA channel"
> ---
> drivers/iio/buffer/industrialio-buffer-dmaengine.c | 38
> ++++++++++++++++++++++
> include/linux/iio/buffer-dmaengine.h | 5 +++
> 2 files changed, 43 insertions(+)
>
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> index
> 02847d3962fcbb43ec76167db6482ab951f20942..e9d9a7d39fe191c2b6e8c196a08cdd26cd3a
> 8d4b 100644
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -380,6 +380,44 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device
> *dev,
> }
> EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext,
> "IIO_DMAENGINE_BUFFER");
>
> +static void devm_iio_dmaengine_buffer_free(void *buffer)
> +{
> + iio_dmaengine_buffer_free(buffer);
> +}
> +
> +/**
> + * devm_iio_dmaengine_buffer_setup_with_handle() - Setup a DMA buffer for an
> + * IIO device
> + * @dev: Device for devm ownership
> + * @indio_dev: IIO device to which to attach this buffer.
> + * @chan: DMA channel
> + * @dir: Direction of buffer (in or out)
> + *
> + * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc()
> + * and attaches it to an IIO device with iio_device_attach_buffer().
> + * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of
> the
> + * IIO device.
> + *
> + * This is the same as devm_iio_dmaengine_buffer_setup_ext() except that the
> + * caller manages requesting and releasing the DMA channel handle.
> + */
> +int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
> + struct iio_dev *indio_dev,
> + struct dma_chan *chan,
> + enum iio_buffer_direction
> dir)
> +{
> + struct iio_buffer *buffer;
> +
> + buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
> + if (IS_ERR(buffer))
> + return PTR_ERR(buffer);
> +
> + return devm_add_action_or_reset(dev, devm_iio_dmaengine_buffer_free,
> + buffer);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_with_handle,
> + "IIO_DMAENGINE_BUFFER");
> +
> MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
> MODULE_DESCRIPTION("DMA buffer for the IIO framework");
> MODULE_LICENSE("GPL");
> diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-
> dmaengine.h
> index
> 72a2e3fd8a5bf5e8f27ee226ddd92979d233754b..37f27545f69f761c3327c307cc6311b02a75
> 1096 100644
> --- a/include/linux/iio/buffer-dmaengine.h
> +++ b/include/linux/iio/buffer-dmaengine.h
> @@ -11,6 +11,7 @@
>
> struct iio_dev;
> struct device;
> +struct dma_chan;
>
> void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer);
> struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
> @@ -26,6 +27,10 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
> struct iio_dev *indio_dev,
> const char *channel,
> enum iio_buffer_direction dir);
> +int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
> + struct iio_dev *indio_dev,
> + struct dma_chan *chan,
> + enum iio_buffer_direction
> dir);
>
> #define devm_iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
> devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing
2024-12-11 20:54 ` [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing David Lechner
2024-12-14 16:56 ` Jonathan Cameron
@ 2024-12-17 11:52 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 11:52 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Replace use of storagebits with realbits for determining the number of
> bytes needed for SPI transfers.
>
> When adding SPI offload support, storagebits will no longer be
> guaranteed to be the "best fit" for 16-bit chips so we can no longer
> rely on storagebits being the correct size expected by the SPI
> framework. Instead, derive the correct size from realbits since it will
> always be correct even when SPI offloads are used.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Reviewed-vy: Nuno Sa <nuno.sa@analog.com>
> v6 changes: none
>
> v5 changes: none
>
> v4 changes: new patch in v4
> ---
> drivers/iio/adc/ad7944.c | 16 +++++++++-------
> 1 file changed, 9 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
> index
> a5aea4e9f1a7bdd8ca10f9f4580ad3216ddcdfcb..6d1202bd55a013b092ff803f2065fd128dfa
> 9bdd 100644
> --- a/drivers/iio/adc/ad7944.c
> +++ b/drivers/iio/adc/ad7944.c
> @@ -98,6 +98,9 @@ struct ad7944_chip_info {
> const struct iio_chan_spec channels[2];
> };
>
> +/* get number of bytes for SPI xfer */
> +#define AD7944_SPI_BYTES(scan_type) ((scan_type).realbits > 16 ? 4 : 2)
> +
> /*
> * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip
> * @_name: The name of the chip
> @@ -164,7 +167,7 @@ static int ad7944_3wire_cs_mode_init_msg(struct device
> *dev, struct ad7944_adc *
>
> /* Then we can read the data during the acquisition phase */
> xfers[2].rx_buf = &adc->sample.raw;
> - xfers[2].len = BITS_TO_BYTES(chan->scan_type.storagebits);
> + xfers[2].len = AD7944_SPI_BYTES(chan->scan_type);
> xfers[2].bits_per_word = chan->scan_type.realbits;
>
> spi_message_init_with_transfers(&adc->msg, xfers, 3);
> @@ -193,7 +196,7 @@ static int ad7944_4wire_mode_init_msg(struct device *dev,
> struct ad7944_adc *adc
> xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
>
> xfers[1].rx_buf = &adc->sample.raw;
> - xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits);
> + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type);
> xfers[1].bits_per_word = chan->scan_type.realbits;
>
> spi_message_init_with_transfers(&adc->msg, xfers, 2);
> @@ -228,7 +231,7 @@ static int ad7944_chain_mode_init_msg(struct device *dev,
> struct ad7944_adc *adc
> xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
>
> xfers[1].rx_buf = adc->chain_mode_buf;
> - xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits) *
> n_chain_dev;
> + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type) * n_chain_dev;
> xfers[1].bits_per_word = chan->scan_type.realbits;
>
> spi_message_init_with_transfers(&adc->msg, xfers, 2);
> @@ -274,12 +277,12 @@ static int ad7944_single_conversion(struct ad7944_adc
> *adc,
> return ret;
>
> if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
> - if (chan->scan_type.storagebits > 16)
> + if (chan->scan_type.realbits > 16)
> *val = ((u32 *)adc->chain_mode_buf)[chan-
> >scan_index];
> else
> *val = ((u16 *)adc->chain_mode_buf)[chan-
> >scan_index];
> } else {
> - if (chan->scan_type.storagebits > 16)
> + if (chan->scan_type.realbits > 16)
> *val = adc->sample.raw.u32;
> else
> *val = adc->sample.raw.u16;
> @@ -409,8 +412,7 @@ static int ad7944_chain_mode_alloc(struct device *dev,
> /* 1 word for each voltage channel + aligned u64 for timestamp */
>
> chain_mode_buf_size = ALIGN(n_chain_dev *
> - BITS_TO_BYTES(chan[0].scan_type.storagebits), sizeof(u64))
> - + sizeof(u64);
> + AD7944_SPI_BYTES(chan[0].scan_type), sizeof(u64)) +
> sizeof(u64);
> buf = devm_kzalloc(dev, chain_mode_buf_size, GFP_KERNEL);
> if (!buf)
> return -ENOMEM;
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload
2024-12-11 20:54 ` [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload David Lechner
@ 2024-12-17 12:02 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 12:02 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Add support for SPI offload to the ad7944 driver. This allows reading
> data at the max sample rate of 2.5 MSPS.
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes:
> * More detailed comments on offload channels info struct. (I didn't
> put them inside the macro because the multiline comments don't play
> well with the line continuation "\".)
>
> v5 changes:
> * Remove dev_info() message.
> * Implement sampling_frequency_available attribute.
> * Limit max sample rate to chip-specific value.
>
> v4 changes:
> * Adapted to changes in other patches.
> * Add new separate channel spec for when using SPI offload.
> * Fixed some nitpicks.
>
> v3 changes:
> * Finished TODOs.
> * Adapted to changes in other patches.
>
> v2 changes:
>
> In the previous version, there was a new separate driver for the PWM
> trigger and DMA hardware buffer. This was deemed too complex so they
> are moved into the ad7944 driver.
>
> It has also been reworked to accommodate for the changes described in
> the other patches.
> ---
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad7944.c | 291 ++++++++++++++++++++++++++++++++++++++++++++--
> -
> 2 files changed, 276 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index
> a3e8ac569ce4c6b6b30b48acb265d530aa98e89c..995b9cacbaa964d26424346120c139858f93
> cdcd 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -360,6 +360,7 @@ config AD7923
> config AD7944
> tristate "Analog Devices AD7944 and similar ADCs driver"
> depends on SPI
> + select SPI_OFFLOAD
> select IIO_BUFFER
> select IIO_TRIGGERED_BUFFER
> help
> diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
> index
> 6d1202bd55a013b092ff803f2065fd128dfa9bdd..07984eb450e82fc06d67fa0a157e3ae4e755
> 5678 100644
> --- a/drivers/iio/adc/ad7944.c
> +++ b/drivers/iio/adc/ad7944.c
> @@ -16,11 +16,14 @@
> #include <linux/module.h>
> #include <linux/property.h>
> #include <linux/regulator/consumer.h>
> +#include <linux/spi/offload/consumer.h>
> #include <linux/spi/spi.h>
> #include <linux/string_helpers.h>
> +#include <linux/units.h>
>
> #include <linux/iio/iio.h>
> #include <linux/iio/sysfs.h>
> +#include <linux/iio/buffer-dmaengine.h>
> #include <linux/iio/trigger_consumer.h>
> #include <linux/iio/triggered_buffer.h>
>
> @@ -54,6 +57,12 @@ struct ad7944_adc {
> enum ad7944_spi_mode spi_mode;
> struct spi_transfer xfers[3];
> struct spi_message msg;
> + struct spi_transfer offload_xfers[2];
> + struct spi_message offload_msg;
> + struct spi_offload *offload;
> + struct spi_offload_trigger *offload_trigger;
> + unsigned long offload_trigger_hz;
> + int sample_freq_range[3];
> void *chain_mode_buf;
> /* Chip-specific timing specifications. */
> const struct ad7944_timing_spec *timing_spec;
> @@ -81,6 +90,8 @@ struct ad7944_adc {
>
> /* quite time before CNV rising edge */
> #define AD7944_T_QUIET_NS 20
> +/* minimum CNV high time to trigger conversion */
> +#define AD7944_T_CNVH_NS 10
>
> static const struct ad7944_timing_spec ad7944_timing_spec = {
> .conv_ns = 420,
> @@ -95,7 +106,9 @@ static const struct ad7944_timing_spec ad7986_timing_spec =
> {
> struct ad7944_chip_info {
> const char *name;
> const struct ad7944_timing_spec *timing_spec;
> + u32 max_sample_rate_hz;
> const struct iio_chan_spec channels[2];
> + const struct iio_chan_spec offload_channels[1];
> };
>
> /* get number of bytes for SPI xfer */
> @@ -105,13 +118,15 @@ struct ad7944_chip_info {
> * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip
> * @_name: The name of the chip
> * @_ts: The timing specification for the chip
> + * @_max: The maximum sample rate in Hz
> * @_bits: The number of bits in the conversion result
> * @_diff: Whether the chip is true differential or not
> */
> -#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _bits, _diff) \
> +#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _max, _bits,
> _diff) \
> static const struct ad7944_chip_info _name##_chip_info = { \
> .name =
> #_name, \
> .timing_spec = &_ts##_timing_spec, \
> + .max_sample_rate_hz = _max, \
> .channels = { \
> { \
> .type = IIO_VOLTAGE, \
> @@ -129,13 +144,43 @@ static const struct ad7944_chip_info _name##_chip_info =
> { \
> }, \
> IIO_CHAN_SOFT_TIMESTAMP(1), \
> }, \
> + .offload_channels = { \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .differential = _diff, \
> + .channel = 0, \
> + .channel2 = _diff ? 1 : 0, \
> + .scan_index = 0, \
> + .scan_type.sign = _diff ? 's' : 'u', \
> + .scan_type.realbits = _bits, \
> + .scan_type.storagebits = 32, \
> + .scan_type.endianness = IIO_CPU, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
> + | BIT(IIO_CHAN_INFO_SCALE) \
> + |
> BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .info_mask_separate_available
> = \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + }, \
> + }, \
> }
>
> +/*
> + * Notes on the offload channels:
> + * - There is no soft timestamp since everything is done in hardware.
> + * - There is a sampling frequency attribute added. This controls the SPI
> + * offload trigger.
> + * - The storagebits value depends on the SPI offload provider. Currently
> there
> + * is only one supported provider, namely the ADI PULSAR ADC HDL project,
> + * which always uses 32-bit words for data values, even for <= 16-bit ADCs.
> + * So the value is just hardcoded to 32 for now.
> + */
> +
> /* pseudo-differential with ground sense */
> -AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 14, 0);
> -AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0);
> +AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 2.5 * MEGA, 14, 0);
> +AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 2.5 * MEGA, 16, 0);
> /* fully differential */
> -AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1);
> +AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 2 * MEGA, 18, 1);
>
> static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct
> ad7944_adc *adc,
> const struct iio_chan_spec *chan)
> @@ -239,6 +284,48 @@ static int ad7944_chain_mode_init_msg(struct device *dev,
> struct ad7944_adc *adc
> return devm_spi_optimize_message(dev, adc->spi, &adc->msg);
> }
>
> +/*
> + * Unlike ad7944_3wire_cs_mode_init_msg(), this creates a message that reads
> + * during the conversion phase instead of the acquisition phase when reading
> + * a sample from the ADC. This is needed to be able to read at the maximum
> + * sample rate. It requires the SPI controller to have offload support and a
> + * high enough SCLK rate to read the sample during the conversion phase.
> + */
> +static int ad7944_3wire_cs_mode_init_offload_msg(struct device *dev,
> + struct ad7944_adc *adc,
> + const struct iio_chan_spec
> *chan)
> +{
> + struct spi_transfer *xfers = adc->offload_xfers;
> + int ret;
> +
> + /*
> + * CS is tied to CNV and we need a low to high transition to start
> the
> + * conversion, so place CNV low for t_QUIET to prepare for this.
> + */
> + xfers[0].delay.value = AD7944_T_QUIET_NS;
> + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
> + /* CNV has to be high for a minimum time to trigger conversion. */
> + xfers[0].cs_change = 1;
> + xfers[0].cs_change_delay.value = AD7944_T_CNVH_NS;
> + xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> +
> + /* Then we can read the previous sample during the conversion phase
> */
> + xfers[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type);
> + xfers[1].bits_per_word = chan->scan_type.realbits;
> +
> + spi_message_init_with_transfers(&adc->offload_msg, xfers,
> + ARRAY_SIZE(adc->offload_xfers));
> +
> + adc->offload_msg.offload = adc->offload;
> +
> + ret = devm_spi_optimize_message(dev, adc->spi, &adc->offload_msg);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to prepare offload
> msg\n");
> +
> + return 0;
> +}
> +
> /**
> * ad7944_convert_and_acquire - Perform a single conversion and acquisition
> * @adc: The ADC device structure
> @@ -294,6 +381,23 @@ static int ad7944_single_conversion(struct ad7944_adc
> *adc,
> return IIO_VAL_INT;
> }
>
> +static int ad7944_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type, int *length,
> + long mask)
> +{
> + struct ad7944_adc *adc = iio_priv(indio_dev);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = adc->sample_freq_range;
> + *type = IIO_VAL_INT;
> + return IIO_AVAIL_RANGE;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> static int ad7944_read_raw(struct iio_dev *indio_dev,
> const struct iio_chan_spec *chan,
> int *val, int *val2, long info)
> @@ -326,13 +430,104 @@ static int ad7944_read_raw(struct iio_dev *indio_dev,
> return -EINVAL;
> }
>
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = adc->offload_trigger_hz;
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad7944_set_sample_freq(struct ad7944_adc *adc, int val)
> +{
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> + .periodic = {
> + .frequency_hz = val,
> + },
> + };
> + int ret;
> +
> + ret = spi_offload_trigger_validate(adc->offload_trigger, &config);
> + if (ret)
> + return ret;
> +
> + adc->offload_trigger_hz = config.periodic.frequency_hz;
> +
> + return 0;
> +}
> +
> +static int ad7944_write_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int val, int val2, long info)
> +{
> + struct ad7944_adc *adc = iio_priv(indio_dev);
> +
> + switch (info) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + if (val < 1 || val > adc->sample_freq_range[2])
> + return -EINVAL;
> +
> + return ad7944_set_sample_freq(adc, val);
> default:
> return -EINVAL;
> }
> }
>
> +static int ad7944_write_raw_get_fmt(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return IIO_VAL_INT;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +}
> +
> static const struct iio_info ad7944_iio_info = {
> + .read_avail = &ad7944_read_avail,
> .read_raw = &ad7944_read_raw,
> + .write_raw = &ad7944_write_raw,
> + .write_raw_get_fmt = &ad7944_write_raw_get_fmt,
> +};
> +
> +static int ad7944_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad7944_adc *adc = iio_priv(indio_dev);
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> + .periodic = {
> + .frequency_hz = adc->offload_trigger_hz,
> + },
> + };
> + int ret;
> +
> + gpiod_set_value_cansleep(adc->turbo, 1);
> +
> + ret = spi_offload_trigger_enable(adc->offload, adc->offload_trigger,
> + &config);
> + if (ret)
> + gpiod_set_value_cansleep(adc->turbo, 0);
> +
> + return ret;
> +}
> +
> +static int ad7944_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct ad7944_adc *adc = iio_priv(indio_dev);
> +
> + spi_offload_trigger_disable(adc->offload, adc->offload_trigger);
> + gpiod_set_value_cansleep(adc->turbo, 0);
> +
> + return 0;
> +}
> +
> +static const struct iio_buffer_setup_ops ad7944_offload_buffer_setup_ops = {
> + .postenable = &ad7944_offload_buffer_postenable,
> + .predisable = &ad7944_offload_buffer_predisable,
> };
>
> static irqreturn_t ad7944_trigger_handler(int irq, void *p)
> @@ -446,6 +641,11 @@ static const char * const ad7944_power_supplies[] = {
> "avdd", "dvdd", "bvdd", "vio"
> };
>
> +static const struct spi_offload_config ad7944_offload_config = {
> + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
> + SPI_OFFLOAD_CAP_RX_STREAM_DMA,
> +};
> +
> static int ad7944_probe(struct spi_device *spi)
> {
> const struct ad7944_chip_info *chip_info;
> @@ -471,6 +671,10 @@ static int ad7944_probe(struct spi_device *spi)
>
> adc->timing_spec = chip_info->timing_spec;
>
> + adc->sample_freq_range[0] = 1; /* min */
> + adc->sample_freq_range[1] = 1; /* step */
> + adc->sample_freq_range[2] = chip_info->max_sample_rate_hz; /* max */
> +
> ret = device_property_match_property_string(dev, "adi,spi-mode",
> ad7944_spi_modes,
>
> ARRAY_SIZE(ad7944_spi_modes));
> @@ -590,20 +794,74 @@ static int ad7944_probe(struct spi_device *spi)
> indio_dev->modes = INDIO_DIRECT_MODE;
> indio_dev->info = &ad7944_iio_info;
>
> - if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
> - indio_dev->available_scan_masks = chain_scan_masks;
> - indio_dev->channels = chain_chan;
> - indio_dev->num_channels = n_chain_dev + 1;
> + adc->offload = devm_spi_offload_get(dev, spi,
> &ad7944_offload_config);
> + ret = PTR_ERR_OR_ZERO(adc->offload);
> + if (ret && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to get offload\n");
> +
> + /* Fall back to low speed usage when no SPI offload available. */
> + if (ret == -ENODEV) {
> + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) {
> + indio_dev->available_scan_masks = chain_scan_masks;
> + indio_dev->channels = chain_chan;
> + indio_dev->num_channels = n_chain_dev + 1;
> + } else {
> + indio_dev->channels = chip_info->channels;
> + indio_dev->num_channels = ARRAY_SIZE(chip_info-
> >channels);
> + }
> +
> + ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> +
> iio_pollfunc_store_time,
> + ad7944_trigger_handler,
> + NULL);
> + if (ret)
> + return ret;
> } else {
> - indio_dev->channels = chip_info->channels;
> - indio_dev->num_channels = ARRAY_SIZE(chip_info->channels);
> - }
> + struct dma_chan *rx_dma;
>
> - ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> - iio_pollfunc_store_time,
> - ad7944_trigger_handler, NULL);
> - if (ret)
> - return ret;
> + if (adc->spi_mode != AD7944_SPI_MODE_SINGLE)
> + return dev_err_probe(dev, -EINVAL,
> + "offload only supported in single mode\n");
> +
> + indio_dev->setup_ops = &ad7944_offload_buffer_setup_ops;
> + indio_dev->channels = chip_info->offload_channels;
> + indio_dev->num_channels = ARRAY_SIZE(chip_info-
> >offload_channels);
> +
> + adc->offload_trigger = devm_spi_offload_trigger_get(dev,
> + adc->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
> + if (IS_ERR(adc->offload_trigger))
> + return dev_err_probe(dev, PTR_ERR(adc-
> >offload_trigger),
> + "failed to get offload
> trigger\n");
> +
> + ret = ad7944_set_sample_freq(adc, 2 * MEGA);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to init sample rate\n");
> +
> + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev,
> + adc-
> >offload);
> + if (IS_ERR(rx_dma))
> + return dev_err_probe(dev, PTR_ERR(rx_dma),
> + "failed to get offload RX
> DMA\n");
> +
> + /*
> + * REVISIT: ideally, we would confirm that the offload RX DMA
> + * buffer layout is the same as what is hard-coded in
> + * offload_channels. Right now, the only supported offload
> + * is the pulsar_adc project which always uses 32-bit word
> + * size for data values, regardless of the SPI bits per word.
> + */
> +
> + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev,
> + indio_dev, rx_dma, IIO_BUFFER_DIRECTION_IN);
> + if (ret)
> + return ret;
> +
> + ret = ad7944_3wire_cs_mode_init_offload_msg(dev, adc,
> + &chip_info->offload_channels[0]);
> + if (ret)
> + return ret;
> + }
>
> return devm_iio_device_register(dev, indio_dev);
> }
> @@ -638,3 +896,4 @@ module_spi_driver(ad7944_driver);
> MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
> MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload
2024-12-11 20:54 ` [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload David Lechner
@ 2024-12-17 12:15 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 12:15 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Add support for SPI offload to the ad4695 driver. SPI offload allows
> sampling data at the max sample rate (500kSPS or 1MSPS).
>
> This is developed and tested against the ADI example FPGA design for
> this family of ADCs [1].
>
> [1]: http://analogdevicesinc.github.io/hdl/projects/ad469x_fmc/index.html
>
> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
LGTM,
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
> v6 changes:
> * Fixed use of c++ style comments
> * Moved static const struct definition out of probe function
> * Changes bits_per_word to always be 19 for future oversampling
> compatibility (Trevor is working on implementing oversampling support
> on top of this patch, so we have high confidence this is the correct
> thing to do)
> * Fixed wrong xfer->len
>
> v5 changes:
> * Register SCLK speed handling has been split out into a separate series.
> * Add sampling_frequency_available attribute.
> * Limit max allowed sampling frequency based on chip info.
> * Expand explanations of offload enable/disable ordering requirements.
> * Finish TODO to use macros for phandle arg values.
> * Don't use dev_info() when falling back to non-offload operation.
> * Update to accommodate changes in other patches in this series.
>
> v4 changes: new patch in v4
> ---
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad4695.c | 445 +++++++++++++++++++++++++++++++++++++++++++++-
> -
> 2 files changed, 429 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index
> 995b9cacbaa964d26424346120c139858f93cdcd..ec60b64c46e187e2be18ab1f8ca9e6f4f032
> 99f9 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -52,6 +52,7 @@ config AD4695
> tristate "Analog Device AD4695 ADC Driver"
> depends on SPI
> select IIO_BUFFER
> + select IIO_BUFFER_DMAENGINE
> select IIO_TRIGGERED_BUFFER
> select REGMAP
> help
> diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
> index
> 13cf01d35301be40369571e7dd2aeac1a8148d15..c8cd73d19e869f11999608f61df5724d329b
> 4427 100644
> --- a/drivers/iio/adc/ad4695.c
> +++ b/drivers/iio/adc/ad4695.c
> @@ -19,14 +19,19 @@
> #include <linux/device.h>
> #include <linux/err.h>
> #include <linux/gpio/consumer.h>
> +#include <linux/iio/buffer-dmaengine.h>
> #include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
> #include <linux/iio/triggered_buffer.h>
> #include <linux/iio/trigger_consumer.h>
> #include <linux/minmax.h>
> +#include <linux/mutex.h>
> #include <linux/property.h>
> +#include <linux/pwm.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.h>
> #include <linux/spi/spi.h>
> #include <linux/units.h>
>
> @@ -66,6 +71,8 @@
> #define AD4695_REG_STD_SEQ_CONFIG 0x0024
> #define AD4695_REG_GPIO_CTRL 0x0026
> #define AD4695_REG_GP_MODE 0x0027
> +#define AD4695_REG_GP_MODE_BUSY_GP_SEL BIT(5)
> +#define AD4695_REG_GP_MODE_BUSY_GP_EN BIT(1)
> #define AD4695_REG_TEMP_CTRL 0x0029
> #define AD4695_REG_TEMP_CTRL_TEMP_EN BIT(0)
> #define AD4695_REG_CONFIG_IN(n) (0x0030 |
> (n))
> @@ -124,13 +131,22 @@ struct ad4695_channel_config {
>
> struct ad4695_state {
> struct spi_device *spi;
> + struct spi_offload *offload;
> + struct spi_offload_trigger *offload_trigger;
> struct regmap *regmap;
> struct regmap *regmap16;
> struct gpio_desc *reset_gpio;
> + /* currently PWM CNV only supported with SPI offload use */
> + struct pwm_device *cnv_pwm;
> + /* protects against concurrent use of cnv_pwm */
> + struct mutex cnv_pwm_lock;
> + /* offload also requires separate gpio to manually control CNV */
> + struct gpio_desc *cnv_gpio;
> /* voltages channels plus temperature and timestamp */
> struct iio_chan_spec iio_chan[AD4695_MAX_CHANNELS + 2];
> struct ad4695_channel_config channels_cfg[AD4695_MAX_CHANNELS];
> const struct ad4695_chip_info *chip_info;
> + int sample_freq_range[3];
> /* Reference voltage. */
> unsigned int vref_mv;
> /* Common mode input pin voltage. */
> @@ -355,6 +371,13 @@ static const struct ad4695_chip_info ad4698_chip_info = {
> .num_voltage_inputs = 8,
> };
>
> +static void ad4695_cnv_manual_trigger(struct ad4695_state *st)
> +{
> + gpiod_set_value_cansleep(st->cnv_gpio, 1);
> + ndelay(10);
> + gpiod_set_value_cansleep(st->cnv_gpio, 0);
> +}
> +
> /**
> * ad4695_set_single_cycle_mode - Set the device in single cycle mode
> * @st: The AD4695 state
> @@ -460,6 +483,17 @@ static int ad4695_exit_conversion_mode(struct
> ad4695_state *st)
> */
> st->cnv_cmd2 = AD4695_CMD_EXIT_CNV_MODE << 3;
>
> + if (st->cnv_gpio) {
> + ad4695_cnv_manual_trigger(st);
> +
> + /*
> + * In this case, CNV is not connected to CS, so we don't need
> + * the extra CS toggle to trigger the conversion and toggling
> + * CS would have no effect.
> + */
> + return spi_sync_transfer(st->spi, &xfers[1], 1);
> + }
> +
> return spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
> }
>
> @@ -687,6 +721,160 @@ static irqreturn_t ad4695_trigger_handler(int irq, void
> *p)
> return IRQ_HANDLED;
> }
>
> +static int ad4695_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4695_state *st = iio_priv(indio_dev);
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
> + };
> + struct spi_transfer *xfer = &st->buf_read_xfer[0];
> + struct pwm_state state;
> + u8 temp_chan_bit = st->chip_info->num_voltage_inputs;
> + u8 num_slots = 0;
> + u8 temp_en = 0;
> + unsigned int bit;
> + int ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + if (bit == temp_chan_bit) {
> + temp_en = 1;
> + continue;
> + }
> +
> + ret = regmap_write(st->regmap, AD4695_REG_AS_SLOT(num_slots),
> + FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit));
> + if (ret)
> + return ret;
> +
> + num_slots++;
> + }
> +
> + /*
> + * For non-offload, we could discard data to work around this
> + * restriction, but with offload, that is not possible.
> + */
> + if (num_slots < 2) {
> + dev_err(&st->spi->dev,
> + "At least two voltage channels must be enabled.\n");
> + return -EINVAL;
> + }
> +
> + ret = regmap_update_bits(st->regmap, AD4695_REG_TEMP_CTRL,
> + AD4695_REG_TEMP_CTRL_TEMP_EN,
> + FIELD_PREP(AD4695_REG_TEMP_CTRL_TEMP_EN,
> + temp_en));
> + if (ret)
> + return ret;
> +
> + /* Each BUSY event means just one sample for one channel is ready. */
> + memset(xfer, 0, sizeof(*xfer));
> + xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + /* Using 19 bits per word to allow for possible oversampling */
> + xfer->bits_per_word = 19;
> + xfer->len = 4;
> +
> + spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
> + st->buf_read_msg.offload = st->offload;
> +
> + ret = spi_optimize_message(st->spi, &st->buf_read_msg);
> + if (ret)
> + return ret;
> +
> + /*
> + * NB: technically, this is part the SPI offload trigger enable, but
> it
> + * doesn't work to call it from the offload trigger enable callback
> + * because it requires accessing the SPI bus. Calling it from the
> + * trigger enable callback could cause a deadlock.
> + */
> + ret = regmap_set_bits(st->regmap, AD4695_REG_GP_MODE,
> + AD4695_REG_GP_MODE_BUSY_GP_EN);
> + if (ret)
> + goto err_unoptimize_message;
> +
> + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> + &config);
> + if (ret)
> + goto err_disable_busy_output;
> +
> + ret = ad4695_enter_advanced_sequencer_mode(st, num_slots);
> + if (ret)
> + goto err_offload_trigger_disable;
> +
> + guard(mutex)(&st->cnv_pwm_lock);
> + pwm_get_state(st->cnv_pwm, &state);
> + /*
> + * PWM subsystem generally rounds down, so requesting 2x minimum high
> + * time ensures that we meet the minimum high time in any case.
> + */
> + state.duty_cycle = AD4695_T_CNVH_NS * 2;
> + ret = pwm_apply_might_sleep(st->cnv_pwm, &state);
> + if (ret)
> + goto err_offload_exit_conversion_mode;
> +
> + return 0;
> +
> +err_offload_exit_conversion_mode:
> + /*
> + * We have to unwind in a different order to avoid triggering
> offload.
> + * ad4695_exit_conversion_mode() triggers a conversion, so it has to
> be
> + * done after spi_offload_trigger_disable().
> + */
> + spi_offload_trigger_disable(st->offload, st->offload_trigger);
> + ad4695_exit_conversion_mode(st);
> + goto err_disable_busy_output;
> +
> +err_offload_trigger_disable:
> + spi_offload_trigger_disable(st->offload, st->offload_trigger);
> +
> +err_disable_busy_output:
> + regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE,
> + AD4695_REG_GP_MODE_BUSY_GP_EN);
> +
> +err_unoptimize_message:
> + spi_unoptimize_message(&st->buf_read_msg);
> +
> + return ret;
> +}
> +
> +static int ad4695_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct ad4695_state *st = iio_priv(indio_dev);
> + struct pwm_state state;
> + int ret;
> +
> + scoped_guard(mutex, &st->cnv_pwm_lock) {
> + pwm_get_state(st->cnv_pwm, &state);
> + state.duty_cycle = 0;
> + ret = pwm_apply_might_sleep(st->cnv_pwm, &state);
> + if (ret)
> + return ret;
> + }
> +
> + spi_offload_trigger_disable(st->offload, st->offload_trigger);
> +
> + /*
> + * ad4695_exit_conversion_mode() triggers a conversion, so it has to
> be
> + * done after spi_offload_trigger_disable().
> + */
> + ret = ad4695_exit_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + ret = regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE,
> + AD4695_REG_GP_MODE_BUSY_GP_EN);
> + if (ret)
> + return ret;
> +
> + spi_unoptimize_message(&st->buf_read_msg);
> +
> + return 0;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4695_offload_buffer_setup_ops = {
> + .postenable = ad4695_offload_buffer_postenable,
> + .predisable = ad4695_offload_buffer_predisable,
> +};
> +
> /**
> * ad4695_read_one_sample - Read a single sample using single-cycle mode
> * @st: The AD4695 state
> @@ -718,6 +906,13 @@ static int ad4695_read_one_sample(struct ad4695_state
> *st, unsigned int address)
> if (ret)
> return ret;
>
> + /*
> + * If CNV is connected to CS, the previous function will have
> triggered
> + * the conversion, otherwise, we do it manually.
> + */
> + if (st->cnv_gpio)
> + ad4695_cnv_manual_trigger(st);
> +
> /*
> * Setting the first channel to the temperature channel isn't
> supported
> * in single-cycle mode, so we have to do an extra conversion to read
> @@ -729,6 +924,13 @@ static int ad4695_read_one_sample(struct ad4695_state
> *st, unsigned int address)
> ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
> if (ret)
> return ret;
> +
> + /*
> + * If CNV is connected to CS, the previous function will have
> + * triggered the conversion, otherwise, we do it manually.
> + */
> + if (st->cnv_gpio)
> + ad4695_cnv_manual_trigger(st);
> }
>
> /* Then read the result and exit conversion mode. */
> @@ -842,11 +1044,34 @@ static int ad4695_read_raw(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + struct pwm_state state;
> +
> + ret = pwm_get_state_hw(st->cnv_pwm, &state);
> + if (ret)
> + return ret;
> +
> + *val = DIV_ROUND_UP_ULL(NSEC_PER_SEC, state.period);
> +
> + return IIO_VAL_INT;
> + }
> default:
> return -EINVAL;
> }
> }
>
> +static int ad4695_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return IIO_VAL_INT;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +}
> +
> static int ad4695_write_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int val, int val2, long mask)
> @@ -900,6 +1125,17 @@ static int ad4695_write_raw(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + struct pwm_state state;
> +
> + if (val <= 0 || val > st->chip_info->max_sample_rate)
> + return -EINVAL;
> +
> + guard(mutex)(&st->cnv_pwm_lock);
> + pwm_get_state(st->cnv_pwm, &state);
> + state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val);
> + return pwm_apply_might_sleep(st->cnv_pwm, &state);
> + }
> default:
> return -EINVAL;
> }
> @@ -923,6 +1159,7 @@ static int ad4695_read_avail(struct iio_dev *indio_dev,
> */
> S16_MIN / 4, 0, 0, MICRO / 4, S16_MAX / 4, S16_MAX % 4 *
> MICRO / 4
> };
> + struct ad4695_state *st = iio_priv(indio_dev);
>
> switch (mask) {
> case IIO_CHAN_INFO_CALIBSCALE:
> @@ -943,6 +1180,10 @@ static int ad4695_read_avail(struct iio_dev *indio_dev,
> default:
> return -EINVAL;
> }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = st->sample_freq_range;
> + *type = IIO_VAL_INT;
> + return IIO_AVAIL_RANGE;
> default:
> return -EINVAL;
> }
> @@ -978,6 +1219,7 @@ static int ad4695_debugfs_reg_access(struct iio_dev
> *indio_dev,
>
> static const struct iio_info ad4695_info = {
> .read_raw = &ad4695_read_raw,
> + .write_raw_get_fmt = &ad4695_write_raw_get_fmt,
> .write_raw = &ad4695_write_raw,
> .read_avail = &ad4695_read_avail,
> .debugfs_reg_access = &ad4695_debugfs_reg_access,
> @@ -1091,26 +1333,166 @@ static int ad4695_parse_channel_cfg(struct
> ad4695_state *st)
> return 0;
> }
>
> +static bool ad4695_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;
> +
> + /*
> + * Requires 2 args:
> + * args[0] is the trigger event.
> + * args[1] is the GPIO pin number.
> + */
> + if (nargs != 2 || args[0] != AD4695_TRIGGER_EVENT_BUSY)
> + return false;
> +
> + return true;
> +}
> +
> +static int ad4695_offload_trigger_request(struct spi_offload_trigger
> *trigger,
> + enum spi_offload_trigger_type type,
> + u64 *args, u32 nargs)
> +{
> + struct ad4695_state *st = spi_offload_trigger_get_priv(trigger);
> +
> + /* Should already be validated by match, but just in case. */
> + if (nargs != 2)
> + return -EINVAL;
> +
> + /* DT tells us if BUSY event uses GP0 or GP3. */
> + if (args[1] == AD4695_TRIGGER_PIN_GP3)
> + return regmap_set_bits(st->regmap, AD4695_REG_GP_MODE,
> + AD4695_REG_GP_MODE_BUSY_GP_SEL);
> +
> + return regmap_clear_bits(st->regmap, AD4695_REG_GPIO_CTRL,
> + AD4695_REG_GP_MODE_BUSY_GP_SEL);
> +}
> +
> +static int
> +ad4695_offload_trigger_validate(struct spi_offload_trigger *trigger,
> + struct spi_offload_trigger_config *config)
> +{
> + if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +/*
> + * NB: There are no enable/disable callbacks here due to requiring a SPI
> + * message to enable or disable the BUSY output on the ADC.
> + */
> +static const struct spi_offload_trigger_ops ad4695_offload_trigger_ops = {
> + .match = ad4695_offload_trigger_match,
> + .request = ad4695_offload_trigger_request,
> + .validate = ad4695_offload_trigger_validate,
> +};
> +
> +static void ad4695_pwm_disable(void *pwm)
> +{
> + pwm_disable(pwm);
> +}
> +
> +static int ad4695_probe_spi_offload(struct iio_dev *indio_dev,
> + struct ad4695_state *st)
> +{
> + struct device *dev = &st->spi->dev;
> + struct spi_offload_trigger_info trigger_info = {
> + .fwnode = dev_fwnode(dev),
> + .ops = &ad4695_offload_trigger_ops,
> + .priv = st,
> + };
> + struct pwm_state pwm_state;
> + struct dma_chan *rx_dma;
> + int ret, i;
> +
> + indio_dev->num_channels = st->chip_info->num_voltage_inputs + 1;
> + indio_dev->setup_ops = &ad4695_offload_buffer_setup_ops;
> +
> + if (!st->cnv_gpio)
> + return dev_err_probe(dev, -ENODEV,
> + "CNV GPIO is required for SPI
> offload\n");
> +
> + 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");
> +
> + ret = devm_mutex_init(dev, &st->cnv_pwm_lock);
> + if (ret)
> + return ret;
> +
> + st->cnv_pwm = devm_pwm_get(dev, NULL);
> + if (IS_ERR(st->cnv_pwm))
> + return dev_err_probe(dev, PTR_ERR(st->cnv_pwm),
> + "failed to get CNV PWM\n");
> +
> + pwm_init_state(st->cnv_pwm, &pwm_state);
> +
> + /* If firmware didn't provide default rate, use 10kHz (arbitrary). */
> + if (pwm_state.period == 0)
> + pwm_state.period = 100 * MILLI;
> +
> + pwm_state.enabled = true;
> +
> + ret = pwm_apply_might_sleep(st->cnv_pwm, &pwm_state);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to apply CNV PWM\n");
> +
> + ret = devm_add_action_or_reset(dev, ad4695_pwm_disable, st->cnv_pwm);
> + if (ret)
> + return ret;
> +
> + 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");
> +
> + for (i = 0; i < indio_dev->num_channels; i++) {
> + struct iio_chan_spec *chan = &st->iio_chan[i];
> +
> + /*
> + * NB: When using offload support, all channels need to have
> the
> + * same bits_per_word because they all use the same SPI
> message
> + * for reading one sample. In order to prevent breaking
> + * userspace in the future when oversampling support is
> added,
> + * all channels are set read 19 bits with a shift of 3 to
> mask
> + * out the extra bits even though we currently only support
> 16
> + * bit samples (oversampling ratio == 1).
> + */
> + chan->scan_type.shift = 3;
> + chan->scan_type.storagebits = 32;
> + /* add sample frequency for PWM CNV trigger */
> + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
> + chan->info_mask_separate_available |=
> BIT(IIO_CHAN_INFO_SAMP_FREQ);
> + }
> +
> + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
> + rx_dma, IIO_BUFFER_DIRECTION_IN);
> +}
> +
> +static const struct spi_offload_config ad4695_spi_offload_config = {
> + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
> + SPI_OFFLOAD_CAP_RX_STREAM_DMA,
> +};
> +
> static int ad4695_probe(struct spi_device *spi)
> {
> struct device *dev = &spi->dev;
> struct ad4695_state *st;
> struct iio_dev *indio_dev;
> - struct gpio_desc *cnv_gpio;
> bool use_internal_ldo_supply;
> bool use_internal_ref_buffer;
> int ret;
>
> - cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
> - if (IS_ERR(cnv_gpio))
> - return dev_err_probe(dev, PTR_ERR(cnv_gpio),
> - "Failed to get CNV GPIO\n");
> -
> - /* Driver currently requires CNV pin to be connected to SPI CS */
> - if (cnv_gpio)
> - return dev_err_probe(dev, -ENODEV,
> - "CNV GPIO is not supported\n");
> -
> indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> if (!indio_dev)
> return -ENOMEM;
> @@ -1122,6 +1504,10 @@ static int ad4695_probe(struct spi_device *spi)
> if (!st->chip_info)
> return -EINVAL;
>
> + st->sample_freq_range[0] = 1; /* min */
> + st->sample_freq_range[1] = 1; /* step */
> + st->sample_freq_range[2] = st->chip_info->max_sample_rate; /* max */
> +
> st->regmap = devm_regmap_init(dev, &ad4695_regmap_bus, st,
> &ad4695_regmap_config);
> if (IS_ERR(st->regmap))
> @@ -1134,6 +1520,11 @@ static int ad4695_probe(struct spi_device *spi)
> return dev_err_probe(dev, PTR_ERR(st->regmap16),
> "Failed to initialize regmap16\n");
>
> + st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
> + if (IS_ERR(st->cnv_gpio))
> + return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
> + "Failed to get CNV GPIO\n");
> +
> ret = devm_regulator_bulk_get_enable(dev,
>
> ARRAY_SIZE(ad4695_power_supplies),
> ad4695_power_supplies);
> @@ -1261,12 +1652,31 @@ static int ad4695_probe(struct spi_device *spi)
> indio_dev->channels = st->iio_chan;
> indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2;
>
> - ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> - iio_pollfunc_store_time,
> - ad4695_trigger_handler,
> - &ad4695_buffer_setup_ops);
> - if (ret)
> - return ret;
> + st->offload = devm_spi_offload_get(dev, spi,
> &ad4695_spi_offload_config);
> + ret = PTR_ERR_OR_ZERO(st->offload);
> + if (ret && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to get SPI
> offload\n");
> +
> + /* If no SPI offload, fall back to low speed usage. */
> + if (ret == -ENODEV) {
> + /* Driver currently requires CNV pin to be connected to SPI
> CS */
> + if (st->cnv_gpio)
> + return dev_err_probe(dev, -EINVAL,
> + "CNV GPIO is not supported\n");
> +
> + indio_dev->num_channels = st->chip_info->num_voltage_inputs +
> 2;
> +
> + ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> +
> iio_pollfunc_store_time,
> + ad4695_trigger_handler,
> +
> &ad4695_buffer_setup_ops);
> + if (ret)
> + return ret;
> + } else {
> + ret = ad4695_probe_spi_offload(indio_dev, st);
> + if (ret)
> + return ret;
> + }
>
> return devm_iio_device_register(dev, indio_dev);
> }
> @@ -1303,3 +1713,4 @@ MODULE_AUTHOR("Ramona Gradinariu
> <ramona.gradinariu@analog.com>");
> MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
> MODULE_DESCRIPTION("Analog Devices AD4695 ADC driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 16/17] iio: dac: ad5791: sort include directives
2024-12-11 20:54 ` [PATCH v6 16/17] iio: dac: ad5791: sort include directives David Lechner
@ 2024-12-17 12:15 ` Nuno Sá
0 siblings, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 12:15 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> Sort includes alphabetically before we add more in a later patch.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> v6 changes: new patch in v6
> ---
> drivers/iio/dac/ad5791.c | 16 ++++++++--------
> 1 file changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
> index
> 57374f78f6b885e1d4f2fb452ac0563b85fc222e..24462cb020e19e8e2c6faa13109ac047cf42
> 3c37 100644
> --- a/drivers/iio/dac/ad5791.c
> +++ b/drivers/iio/dac/ad5791.c
> @@ -6,21 +6,21 @@
> * Copyright 2011 Analog Devices Inc.
> */
>
> -#include <linux/interrupt.h>
> -#include <linux/fs.h>
> -#include <linux/device.h>
> +#include <linux/bitops.h>
> #include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/fs.h>
> +#include <linux/interrupt.h>
> #include <linux/kernel.h>
> -#include <linux/spi/spi.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> #include <linux/slab.h>
> +#include <linux/spi/spi.h>
> #include <linux/sysfs.h>
> -#include <linux/regulator/consumer.h>
> -#include <linux/module.h>
> -#include <linux/bitops.h>
>
> +#include <linux/iio/dac/ad5791.h>
> #include <linux/iio/iio.h>
> #include <linux/iio/sysfs.h>
> -#include <linux/iio/dac/ad5791.h>
>
> #define AD5791_DAC_MASK GENMASK(19, 0)
>
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 17/17] iio: dac: ad5791: Add offload support
2024-12-11 20:54 ` [PATCH v6 17/17] iio: dac: ad5791: Add offload support David Lechner
2024-12-14 17:12 ` Jonathan Cameron
@ 2024-12-17 12:18 ` Nuno Sá
1 sibling, 0 replies; 42+ messages in thread
From: Nuno Sá @ 2024-12-17 12:18 UTC (permalink / raw)
To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Axel Haslam
On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
> From: Axel Haslam <ahaslam@baylibre.com>
>
> 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@baylibre.com>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
Reviewed-by: Nuno Sa <nuno.sa@analog.com>
>
> 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..f6c5cb632acbdc2432f60b163452bb0c5f89
> fa72 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..a2953a9a4e5d5bc17c9c4a8281be4b41b1af
> 5de8 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@analog.com>");
> MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source
2024-12-11 20:54 ` [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source David Lechner
2024-12-14 14:25 ` Jonathan Cameron
@ 2024-12-17 14:32 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Rob Herring (Arm) @ 2024-12-17 14:32 UTC (permalink / raw)
To: David Lechner
Cc: linux-spi, Michael Hennerich, Martin Sperl, Uwe Kleine-König,
linux-kernel, Nuno Sá, devicetree, linux-pwm,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
Lars-Peter Clausen, David Jander, linux-iio, Mark Brown
On Wed, 11 Dec 2024 14:54:40 -0600, David Lechner wrote:
> Add a new binding for using a PWM signal as a trigger source.
>
> The idea here is similar to e.g. "pwm-clock" to allow a trigger source
> consumer to use a PWM provider as a trigger source.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
> v6 changes:
> * Moved file from bindings/spi/ to bindings/trigger-source/
> * Updated description to not mention SPI
> * Dropped $ref: /schemas/spi/trigger-source.yaml#
> * Swapped order in name to be consistent with "pwm-clock"
>
> v5 changes:
> * Add MAINTAINERS entry
>
> v4 changes: new patch in v4
> ---
> .../bindings/trigger-source/pwm-trigger.yaml | 37 ++++++++++++++++++++++
> MAINTAINERS | 5 +++
> 2 files changed, 42 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties
2024-12-11 20:54 ` [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
2024-12-14 14:30 ` Jonathan Cameron
@ 2024-12-17 14:33 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Rob Herring (Arm) @ 2024-12-17 14:33 UTC (permalink / raw)
To: David Lechner
Cc: devicetree, Mark Brown, Uwe Kleine-König, linux-spi,
linux-kernel, Jonathan Cameron, Lars-Peter Clausen,
Krzysztof Kozlowski, Conor Dooley, Michael Hennerich,
Martin Sperl, linux-iio, linux-pwm, Nuno Sá, David Jander
On Wed, 11 Dec 2024 14:54:43 -0600, David Lechner wrote:
> The AXI SPI Engine has support for hardware offloading capabilities.
> This includes a connection to a DMA controller for streaming RX or TX
> data and a trigger input for starting execution of the SPI message
> programmed in the offload. It is designed to support up to 32 offload
> instances.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
> v6 changes:
> * Drop type $ref for trigger-sources property since it is defined
> elsewhere now
> * Undo v5 changes that limited the number of offloads to 1
>
> v5 changes:
> * Also document offload0-tx DMA names since the hardware can support
> that now.
> * Limit the number of offloads to 1 for now since it would require
> significant hardware changes to actually support more than that.
>
> v4 changes:
> * Dropped #spi-offload-cells property.
> * Changed subject line.
>
> v3 changes:
> * Added #spi-offload-cells property.
> * Added properties for triggers and RX data stream connected to DMA.
>
> v2 changes:
> * This is basically a new patch. It partially replaces "dt-bindings:
> iio: offload: add binding for PWM/DMA triggered buffer".
> * The controller no longer has an offloads object node and the
> spi-offloads property is now a standard SPI peripheral property.
> ---
> .../bindings/spi/adi,axi-spi-engine.yaml | 24 ++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
2024-12-11 20:54 ` [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
2024-12-14 16:59 ` Jonathan Cameron
@ 2024-12-17 14:36 ` Rob Herring (Arm)
1 sibling, 0 replies; 42+ messages in thread
From: Rob Herring (Arm) @ 2024-12-17 14:36 UTC (permalink / raw)
To: David Lechner
Cc: linux-pwm, Nuno Sá, Mark Brown, linux-spi, linux-iio,
Martin Sperl, David Jander, Conor Dooley, Jonathan Cameron,
Uwe Kleine-König, Lars-Peter Clausen, devicetree,
Krzysztof Kozlowski, linux-kernel, Michael Hennerich
On Wed, 11 Dec 2024 14:54:50 -0600, David Lechner wrote:
> Add a pwms property to the adi,ad4695 binding to specify an optional PWM
> output connected to the CNV pin on the ADC.
>
> Also add #trigger-source-cells property to allow the BUSY output to be
> used as a SPI offload trigger source to indicate when a sample is ready
> to be read.
>
> Macros are added to adi,ad4695.h for the cell values to help with
> readability since they are arbitrary values.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
> v6 changes:
> * Drop $ref to trigger-source.yaml
> * Add maxItems to pwms property
>
> v5 changes:
> * Added macros for cell values
>
> v4 changes: new patch in v4
> ---
> Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml | 13 +++++++++++++
> include/dt-bindings/iio/adc/adi,ad4695.h | 7 +++++++
> 2 files changed, 20 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v6 02/17] spi: offload: add support for hardware triggers
2024-12-17 11:30 ` Nuno Sá
@ 2024-12-17 15:35 ` David Lechner
0 siblings, 0 replies; 42+ messages in thread
From: David Lechner @ 2024-12-17 15:35 UTC (permalink / raw)
To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Nuno Sá
Cc: Uwe Kleine-König, Michael Hennerich, Lars-Peter Clausen,
David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
linux-iio, linux-pwm, Jonathan Cameron
On 12/17/24 5:30 AM, Nuno Sá wrote:
> On Wed, 2024-12-11 at 14:54 -0600, David Lechner wrote:
>> Extend SPI offloading to support hardware triggers.
>>
...
>> +static struct spi_offload_trigger
>> +*spi_offload_trigger_get(enum spi_offload_trigger_type type,
>> + struct fwnode_reference_args *args)
>> +{
>> + struct spi_offload_trigger *trigger;
>> + bool match = false;
>> + int ret;
>> +
>> + guard(mutex)(&spi_offload_triggers_lock);
>> +
>> + list_for_each_entry(trigger, &spi_offload_triggers, list) {
>> + if (trigger->fwnode != args->fwnode)
>> + continue;
>> +
>> + match = trigger->ops->match(trigger, type, args->args, args-
>>> nargs);
>> + if (match)
>> + break;
>> + }
>> +
>> + if (!match)
>> + return ERR_PTR(-EPROBE_DEFER);
>> +
>> + guard(mutex)(&trigger->lock);
>> +
>> + if (!trigger->ops)
>> + return ERR_PTR(-ENODEV);
>> +
>> + if (trigger->ops->request) {
>> + ret = trigger->ops->request(trigger, type, args->args, args-
>>> nargs);
>> + if (ret)
>> + return ERR_PTR(ret);
>> + }
>> +
>> + kref_get(&trigger->ref);
>
> maybe try_module_get() would also make sense...
Even if a module had more than one trigger? Or do you mean
in addition to the kref_get()?
>
>> +
>> + return trigger;
>> +}
>> +
>> +/**
>> + * devm_spi_offload_trigger_get() - Get an offload trigger instance
>> + * @dev: Device for devm purposes.
>> + * @offload: Offload instance connected to a trigger.
>> + * @type: Trigger type to get.
>> + *
>> + * Return: Offload trigger instance or error on failure.
>> + */
>> +struct spi_offload_trigger
>> +*devm_spi_offload_trigger_get(struct device *dev,
>> + struct spi_offload *offload,
>> + enum spi_offload_trigger_type type)
>> +{
>> + struct spi_offload_trigger *trigger;
>> + struct fwnode_reference_args args;
>> + int ret;
>> +
>> + ret = fwnode_property_get_reference_args(dev_fwnode(offload-
>>> provider_dev),
>> + "trigger-sources",
>> + "#trigger-source-cells", 0,
>> 0,
>> + &args);
>
> I guess at some point we can add these to fwlinks?
>
Yes. Although we'll need to investigate how it would affect
leds since they use the same binding. So perhaps we can save
that for later since the rest of this series seems ready now.
^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2024-12-17 15:36 UTC | newest]
Thread overview: 42+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-11 20:54 [PATCH v6 00/17] spi: axi-spi-engine: add offload support David Lechner
2024-12-11 20:54 ` [PATCH v6 01/17] spi: add basic support for SPI offloading David Lechner
2024-12-17 11:21 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 02/17] spi: offload: add support for hardware triggers David Lechner
2024-12-17 11:30 ` Nuno Sá
2024-12-17 15:35 ` David Lechner
2024-12-11 20:54 ` [PATCH v6 03/17] dt-bindings: trigger-source: add generic PWM trigger source David Lechner
2024-12-14 14:25 ` Jonathan Cameron
2024-12-17 14:32 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 04/17] spi: offload-trigger: add PWM trigger driver David Lechner
2024-12-17 11:36 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 05/17] spi: add offload TX/RX streaming APIs David Lechner
2024-12-14 14:28 ` Jonathan Cameron
2024-12-17 11:43 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 06/17] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
2024-12-14 14:30 ` Jonathan Cameron
2024-12-17 14:33 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 07/17] spi: axi-spi-engine: implement offload support David Lechner
2024-12-17 11:48 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 08/17] iio: buffer-dmaengine: split requesting DMA channel from allocating buffer David Lechner
2024-12-14 14:37 ` Jonathan Cameron
2024-12-17 11:50 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 09/17] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_with_handle() David Lechner
2024-12-14 14:39 ` Jonathan Cameron
2024-12-17 11:51 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 10/17] iio: adc: ad7944: don't use storagebits for sizing David Lechner
2024-12-14 16:56 ` Jonathan Cameron
2024-12-17 11:52 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 11/17] iio: adc: ad7944: add support for SPI offload David Lechner
2024-12-17 12:02 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 12/17] doc: iio: ad7944: describe offload support David Lechner
2024-12-11 20:54 ` [PATCH v6 13/17] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
2024-12-14 16:59 ` Jonathan Cameron
2024-12-17 14:36 ` Rob Herring (Arm)
2024-12-11 20:54 ` [PATCH v6 14/17] iio: adc: ad4695: Add support for SPI offload David Lechner
2024-12-17 12:15 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 15/17] doc: iio: ad4695: add SPI offload support David Lechner
2024-12-11 20:54 ` [PATCH v6 16/17] iio: dac: ad5791: sort include directives David Lechner
2024-12-17 12:15 ` Nuno Sá
2024-12-11 20:54 ` [PATCH v6 17/17] iio: dac: ad5791: Add offload support David Lechner
2024-12-14 17:12 ` Jonathan Cameron
2024-12-17 12:18 ` Nuno Sá
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).