devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support
@ 2024-10-23 20:59 David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw() David Lechner
                   ` (15 more replies)
  0 siblings, 16 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	David Lechner

In this revision, I ended up changing quite a bit more that I was
expecting.

In the DT bindings, I ended up dropping the #spi-offload-cells and
spi-offload properties. A couple of reasons for this:

1. Several people commented that it is odd to have a separate provider/
   consumer binding for something that already has a parent/child
   relationship (both on this series and in another unrelated series
   with io-backends). For now, the only SPI offload provider is the AXI
   SPI Engine, which is a SPI controller.
2. In a discussion unrelated to this series, but related to the idea
   of SPI offloads [1], it became apparent that the proposed use for
   the cells to select triggers and tx/rx streams doesn't actually
   work for that case.
3. In offline review, it was suggested that assigning specific offloads
   to specific peripherals may be too restrictive, e.g. if there are
   controllers that have pools of identical offloads. This idea of
   pools of generic offloads has also come up in previous discussions
   on the mailing list.

[1]: https://lore.kernel.org/linux-iio/e5310b63-9dc4-43af-9fbe-0cc3b604ab8b@baylibre.com/

So the idea is that if we do end up needing to use DT to assign certain
resources (triggers, DMA channels, etc.) to specific peripherals, we
would make a mapping attribute in the controller node rather than using
phandle cells. But we don't need this yet, so it isn't present in The
current patches.

And if we ever end up with a SPI offload provider that is not a SPI
controller, we can bring back the #spi-offload-cells and
spi-offload properties.

Regarding the SPI core changes, there are more details on each
individual patch, but a lot has changed there due to adding a second
ADC consumer that is wired up differently. The AD7944 is as pictured
below, but the AD4695 that has been added has the ADC chip itself as
the SPI offload trigger source, which I found to not be compatible with
many of the assumptions we made in previous revisions. So there isn't
much that is still the same as in previous revisions.

---
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 < < <  |
|  +---------------------+      |
|                               |
+-------------------------------+

---
David Lechner (15):
      pwm: core: export pwm_get_state_hw()
      spi: add basic support for SPI offloading
      spi: offload: add support for hardware triggers
      spi: dt-bindings: add trigger-source.yaml
      spi: dt-bindings: add PWM SPI offload trigger
      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: document iio_dmaengine_buffer_setup_ext
      iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
      iio: adc: ad7944: don't use storagebits for sizing
      iio: adc: ad7944: add support for SPI offload
      dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
      iio: adc: ad4695: Add support for SPI offload

 .../devicetree/bindings/iio/adc/adi,ad4695.yaml    |  13 +-
 .../bindings/spi/adi,axi-spi-engine.yaml           |  22 +
 .../devicetree/bindings/spi/trigger-pwm.yaml       |  39 ++
 .../devicetree/bindings/spi/trigger-source.yaml    |  28 ++
 drivers/iio/adc/Kconfig                            |   2 +
 drivers/iio/adc/ad4695.c                           | 470 +++++++++++++++++++--
 drivers/iio/adc/ad7944.c                           | 249 ++++++++++-
 drivers/iio/buffer/industrialio-buffer-dmaengine.c | 104 ++++-
 drivers/pwm/core.c                                 |  55 ++-
 drivers/spi/Kconfig                                |  16 +
 drivers/spi/Makefile                               |   4 +
 drivers/spi/spi-axi-spi-engine.c                   | 273 +++++++++++-
 drivers/spi/spi-offload-trigger-pwm.c              | 169 ++++++++
 drivers/spi/spi-offload.c                          | 446 +++++++++++++++++++
 drivers/spi/spi.c                                  |  10 +
 include/linux/iio/buffer-dmaengine.h               |   5 +
 include/linux/pwm.h                                |   1 +
 include/linux/spi/spi-offload.h                    | 166 ++++++++
 include/linux/spi/spi.h                            |  19 +
 19 files changed, 1995 insertions(+), 96 deletions(-)
---
base-commit: 6c4b0dd7d0df3a803766d4954dc064dc57aeda17
change-id: 20240510-dlech-mainline-spi-engine-offload-2-afce3790b5ab

Best regards,
-- 
David Lechner <dlechner@baylibre.com>


^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw()
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-29  8:05   ` Uwe Kleine-König
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	David Lechner

Export the pwm_get_state_hw() function. This is useful in cases where
we want to know what the hardware is actually doing, rather than what
what we requested it should do.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes: new patch in v4

And FYI for Uwe and Jonathan, there are a couple of other series
introducing PWM conversion triggers that could make use of this
so that the sampling_frequency attribute can return the actual rate
rather than the requested rate.

Already applied:
https://lore.kernel.org/linux-iio/20241015-ad7606_add_iio_backend_support-v5-4-654faf1ae08c@baylibre.com/

Under review:
https://lore.kernel.org/linux-iio/aea7f92b-3d12-4ced-b1c8-90bcf1d992d3@baylibre.com/T/#m1377d5acd7e996acd1f59038bdd09f0742d3ac35
---
 drivers/pwm/core.c  | 55 +++++++++++++++++++++++++++++++++++++----------------
 include/linux/pwm.h |  1 +
 2 files changed, 40 insertions(+), 16 deletions(-)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 634be56e204b..a214d0165d09 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -718,7 +718,7 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
 }
 EXPORT_SYMBOL_GPL(pwm_apply_atomic);
 
-static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
+static int __pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
 {
 	struct pwm_chip *chip = pwm->chip;
 	const struct pwm_ops *ops = chip->ops;
@@ -730,29 +730,50 @@ static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
 
 		BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
 
-		scoped_guard(pwmchip, chip) {
-
-			ret = __pwm_read_waveform(chip, pwm, &wfhw);
-			if (ret)
-				return ret;
+		ret = __pwm_read_waveform(chip, pwm, &wfhw);
+		if (ret)
+			return ret;
 
-			ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
-			if (ret)
-				return ret;
-		}
+		ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
+		if (ret)
+			return ret;
 
 		pwm_wf2state(&wf, state);
 
 	} else if (ops->get_state) {
-		scoped_guard(pwmchip, chip)
-			ret = ops->get_state(chip, pwm, state);
-
+		ret = ops->get_state(chip, pwm, state);
 		trace_pwm_get(pwm, state, ret);
 	}
 
 	return ret;
 }
 
+/**
+ * pwm_get_state_hw() - get the current PWM state from hardware
+ * @pwm: PWM device
+ * @state: state to fill with the current PWM state
+ *
+ * Similar to pwm_get_state() but reads the current PWM state from hardware
+ * instead of the requested state.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ * Context: May sleep.
+ */
+int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
+{
+	struct pwm_chip *chip = pwm->chip;
+
+	might_sleep();
+
+	guard(pwmchip)(chip);
+
+	if (!chip->operational)
+		return -ENODEV;
+
+	return __pwm_get_state_hw(pwm, state);
+}
+EXPORT_SYMBOL_GPL(pwm_get_state_hw);
+
 /**
  * pwm_adjust_config() - adjust the current PWM config to the PWM arguments
  * @pwm: PWM device
@@ -906,9 +927,11 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
 		 */
 		struct pwm_state state = { 0, };
 
-		err = pwm_get_state_hw(pwm, &state);
-		if (!err)
-			pwm->state = state;
+		scoped_guard(pwmchip, chip) {
+			err = __pwm_get_state_hw(pwm, &state);
+			if (!err)
+				pwm->state = state;
+		}
 
 		if (IS_ENABLED(CONFIG_PWM_DEBUG))
 			pwm->last = pwm->state;
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index f1cb1e5b0a36..5bcbcf2911c3 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -370,6 +370,7 @@ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf
 int pwm_set_waveform_might_sleep(struct pwm_device *pwm, const struct pwm_waveform *wf, bool exact);
 int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
 int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
+int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state);
 int pwm_adjust_config(struct pwm_device *pwm);
 
 /**

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw() David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-24 13:27   ` Nuno Sá
                     ` (3 more replies)
  2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
                   ` (13 subsequent siblings)
  15 siblings, 4 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	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
callback 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 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.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

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.
---
 drivers/spi/Kconfig             |   3 ++
 drivers/spi/Makefile            |   1 +
 drivers/spi/spi-offload.c       | 104 ++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi-offload.h |  64 +++++++++++++++++++++++++
 include/linux/spi/spi.h         |  16 +++++++
 5 files changed, 188 insertions(+)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 823797217404..d65074b85f62 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 a9b1bc259b68..6a470eb475a2 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 000000000000..c344cbf50bdb
--- /dev/null
+++ b/drivers/spi/spi-offload.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Analog Devices Inc.
+ * Copyright (C) 2024 BayLibre, SAS
+ */
+
+#define DEFAULT_SYMBOL_NAMESPACE SPI_OFFLOAD
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/spi/spi-offload.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+/**
+ * devm_spi_offload_alloc() - Allocate offload instances
+ * @dev: Device for devm purposes
+ * @num_offloads: Number of offloads to allocate
+ * @priv_size: Size of private data to allocate for each offload
+ *
+ * Offload providers should use this to allocate offload instances.
+ *
+ * Return: Pointer to array of offloads or error on failure.
+ */
+struct spi_offload *devm_spi_offload_alloc(struct device *dev,
+					   size_t num_offloads,
+					   size_t priv_size)
+{
+	struct spi_offload *offloads;
+	void *privs;
+	size_t i;
+
+	offloads = devm_kcalloc(dev, num_offloads, sizeof(*offloads) + priv_size,
+				GFP_KERNEL);
+	if (!offloads)
+		return ERR_PTR(-ENOMEM);
+
+	privs = (void *)(offloads + num_offloads);
+
+	for (i = 0; i < num_offloads; i++) {
+		struct spi_offload *offload = offloads + i;
+		void *priv = privs + i * priv_size;
+
+		offload->provider_dev = dev;
+		offload->priv = priv;
+	}
+
+	return offloads;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_alloc);
+
+static void spi_offload_put(void *data)
+{
+	struct spi_offload *offload = data;
+
+	offload->spi = NULL;
+	put_device(offload->provider_dev);
+}
+
+/**
+ * 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_offload *offload;
+	int ret;
+
+	if (!spi || !config)
+		return ERR_PTR(-EINVAL);
+
+	if (!spi->controller->get_offload)
+		return ERR_PTR(-ENODEV);
+
+	offload = spi->controller->get_offload(spi, config);
+	if (IS_ERR(offload))
+		return offload;
+
+	if (offload->spi)
+		return ERR_PTR(-EBUSY);
+
+	offload->spi = spi;
+	get_device(offload->provider_dev);
+
+	ret = devm_add_action_or_reset(dev, spi_offload_put, offload);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return offload;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_get);
diff --git a/include/linux/spi/spi-offload.h b/include/linux/spi/spi-offload.h
new file mode 100644
index 000000000000..92a557533b83
--- /dev/null
+++ b/include/linux/spi/spi-offload.h
@@ -0,0 +1,64 @@
+/* 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.
+ */
+
+#ifndef __LINUX_SPI_OFFLOAD_H
+#define __LINUX_SPI_OFFLOAD_H
+
+#include <linux/types.h>
+
+MODULE_IMPORT_NS(SPI_OFFLOAD);
+
+struct device;
+struct spi_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;
+	/** @spi: SPI device that is currently using the offload */
+	struct spi_device *spi;
+	/** @priv: provider driver private data */
+	void *priv;
+};
+
+struct spi_offload *devm_spi_offload_alloc(struct device *dev,
+					   size_t num_offloads,
+					   size_t priv_size);
+struct spi_offload *devm_spi_offload_get(struct device *dev, struct spi_device *spi,
+					 const struct spi_offload_config *config);
+
+#endif /* __LINUX_SPI_OFFLOAD_H */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 8497f4747e24..c230d6a209ee 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -31,6 +31,9 @@ struct spi_transfer;
 struct spi_controller_mem_ops;
 struct spi_controller_mem_caps;
 struct spi_message;
+struct spi_controller_offload_ops;
+struct spi_offload;
+struct spi_offload_config;
 
 /*
  * INTERFACES between SPI master-side drivers and SPI slave protocol handlers,
@@ -496,6 +499,9 @@ 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.
  * @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,9 @@ 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);
+
 	/* GPIO chip select */
 	struct gpio_desc	**cs_gpiods;
 	bool			use_gpio_descriptors;
@@ -1108,6 +1117,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 +1178,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] 59+ messages in thread

* [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw() David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-24 14:04   ` Nuno Sá
                     ` (2 more replies)
  2024-10-23 20:59 ` [PATCH RFC v4 04/15] spi: dt-bindings: add trigger-source.yaml David Lechner
                   ` (12 subsequent siblings)
  15 siblings, 3 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	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.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

In previous versions, we locked the SPI bus when the hardware trigger
was enabled, but we found this to be too restrictive. In one use case,
to avoid a race condition, we need to enable the SPI offload via a
hardware trigger, then write a SPI message to the peripheral to place
it into a mode that will generate the trigger. If we did it the other
way around, we could miss the first trigger.

Another likely use case will be enabling two offloads/triggers at one
time on the same device, e.g. a read trigger and a write trigger. So
the exclusive bus lock for a single trigger would be too restrictive in
this case too.

So for now, I'm going with Nuno's suggestion to leave any locking up to
the individual controller driver. If we do find we need something more
generic in the future, we could add a new spi_bus_lock_exclusive() API
that causes spi_bus_lock() to fail instead of waiting and add "locked"
versions of trigger enable functions. This would allow a peripheral to
claim exclusive use of the bus indefinitely while still being able to
do any SPI messaging that it needs.

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       | 266 ++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi-offload.h |  78 ++++++++++++
 2 files changed, 344 insertions(+)

diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
index c344cbf50bdb..2a1f9587f27a 100644
--- a/drivers/spi/spi-offload.c
+++ b/drivers/spi/spi-offload.c
@@ -9,12 +9,26 @@
 #include <linux/cleanup.h>
 #include <linux/device.h>
 #include <linux/export.h>
+#include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/of.h>
 #include <linux/property.h>
 #include <linux/spi/spi-offload.h>
 #include <linux/spi/spi.h>
 #include <linux/types.h>
 
+struct spi_offload_trigger {
+	struct list_head list;
+	struct device dev;
+	/* synchronizes calling ops and driver registration */
+	struct mutex lock;
+	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 instances
  * @dev: Device for devm purposes
@@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
 	return offload;
 }
 EXPORT_SYMBOL_GPL(devm_spi_offload_get);
+
+static void spi_offload_trigger_release(void *data)
+{
+	struct spi_offload_trigger *trigger = data;
+
+	guard(mutex)(&trigger->lock);
+	if (trigger->priv && trigger->ops->release)
+		trigger->ops->release(trigger->priv);
+
+	put_device(&trigger->dev);
+}
+
+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;
+	bool match = false;
+	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);
+
+	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) = args.fwnode;
+
+	guard(mutex)(&spi_offload_triggers_lock);
+
+	list_for_each_entry(trigger, &spi_offload_triggers, list) {
+		if (trigger->dev.fwnode != args.fwnode)
+			continue;
+
+		match = trigger->ops->match(trigger->priv, type, args.args, args.nargs);
+		if (match)
+			break;
+	}
+
+	if (!match)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	guard(mutex)(&trigger->lock);
+
+	if (!trigger->priv)
+		return ERR_PTR(-ENODEV);
+
+	if (trigger->ops->request) {
+		ret = trigger->ops->request(trigger->priv, type, args.args, args.nargs);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	get_device(&trigger->dev);
+
+	ret = devm_add_action_or_reset(dev, spi_offload_trigger_release, 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->priv)
+		return -ENODEV;
+
+	if (!trigger->ops->validate)
+		return -EOPNOTSUPP;
+
+	return trigger->ops->validate(trigger->priv, config);
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_validate);
+
+/**
+ * spi_offload_trigger_enable - enables trigger for offload
+ * @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->priv)
+		return -ENODEV;
+
+	if (offload->ops->trigger_enable) {
+		ret = offload->ops->trigger_enable(offload);
+		if (ret)
+			return ret;
+	}
+
+	if (trigger->ops->enable) {
+		ret = trigger->ops->enable(trigger->priv, 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
+ *
+ * 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->trigger_disable)
+		offload->ops->trigger_disable(offload);
+
+	guard(mutex)(&trigger->lock);
+
+	if (!trigger->priv)
+		return;
+
+	if (trigger->ops->disable)
+		trigger->ops->disable(trigger->priv);
+}
+EXPORT_SYMBOL_GPL(spi_offload_trigger_disable);
+
+/* Triggers providers */
+
+static void spi_offload_trigger_dev_release(struct device *dev)
+{
+	struct spi_offload_trigger *trigger =
+		container_of(dev, struct spi_offload_trigger, dev);
+
+	mutex_destroy(&trigger->lock);
+	of_node_put(trigger->dev.of_node);
+	kfree(trigger);
+}
+
+static void spi_offload_trigger_put(void *data)
+{
+	struct spi_offload_trigger *trigger = data;
+
+	put_device(&trigger->dev);
+}
+
+struct spi_offload_trigger
+*devm_spi_offload_trigger_alloc(struct device *dev,
+				struct spi_offload_trigger_info *info)
+{
+	struct spi_offload_trigger *trigger;
+	int ret;
+
+	trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);
+	if (!trigger)
+		return ERR_PTR(-ENOMEM);
+
+	device_initialize(&trigger->dev);
+	trigger->dev.parent = info->parent;
+	trigger->dev.fwnode = info->fwnode;
+	trigger->dev.of_node = of_node_get(to_of_node(trigger->dev.fwnode));
+	trigger->dev.of_node_reused = true;
+	trigger->dev.release = spi_offload_trigger_dev_release;
+
+	mutex_init(&trigger->lock);
+	trigger->ops = info->ops;
+
+	ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = dev_set_name(&trigger->dev, "%s-%d", info->name, info->id);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return trigger;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_alloc);
+
+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);
+
+	guard(mutex)(&trigger->lock);
+	trigger->priv = NULL;
+	device_del(&trigger->dev);
+}
+
+int devm_spi_offload_trigger_register(struct device *dev,
+				      struct spi_offload_trigger *trigger,
+				      void *priv)
+{
+	int ret;
+
+	ret = device_add(&trigger->dev);
+	if (ret)
+		return ret;
+
+	trigger->priv = priv;
+
+	guard(mutex)(&spi_offload_triggers_lock);
+	list_add_tail(&trigger->list, &spi_offload_triggers);
+
+	ret = devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register);
diff --git a/include/linux/spi/spi-offload.h b/include/linux/spi/spi-offload.h
index 92a557533b83..561cc1fb6f35 100644
--- a/include/linux/spi/spi-offload.h
+++ b/include/linux/spi/spi-offload.h
@@ -22,6 +22,7 @@
 MODULE_IMPORT_NS(SPI_OFFLOAD);
 
 struct device;
+struct fwnode_handle;
 struct spi_device;
 
 /* Offload can be triggered by external hardware event. */
@@ -53,6 +54,43 @@ struct spi_offload {
 	struct spi_device *spi;
 	/** @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);
 };
 
 struct spi_offload *devm_spi_offload_alloc(struct device *dev,
@@ -61,4 +99,44 @@ struct spi_offload *devm_spi_offload_alloc(struct device *dev,
 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);
+
+/* Trigger providers */
+
+struct spi_offload_trigger;
+
+struct spi_offload_trigger_ops {
+	bool (*match)(void *priv, enum spi_offload_trigger_type type, u64 *args, u32 nargs);
+	int (*request)(void *priv, enum spi_offload_trigger_type type, u64 *args, u32 nargs);
+	void (*release)(void *priv);
+	int (*validate)(void *priv, struct spi_offload_trigger_config *config);
+	int (*enable)(void *priv, struct spi_offload_trigger_config *config);
+	void (*disable)(void *priv);
+};
+
+struct spi_offload_trigger_info {
+	const char *name;
+	int id;
+	struct device *parent;
+	struct fwnode_handle *fwnode;
+	const struct spi_offload_trigger_ops *ops;
+};
+
+struct spi_offload_trigger
+*devm_spi_offload_trigger_alloc(struct device *dev,
+				struct spi_offload_trigger_info *info);
+int devm_spi_offload_trigger_register(struct device *dev,
+				      struct spi_offload_trigger *trigger,
+				      void *priv);
+
 #endif /* __LINUX_SPI_OFFLOAD_H */

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 04/15] spi: dt-bindings: add trigger-source.yaml
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (2 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger David Lechner
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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 SPI offload trigger sources.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes: new patch in v4.

FWIW, this is essentially identical to the leds trigger-source binding.
---
 .../devicetree/bindings/spi/trigger-source.yaml    | 28 ++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/trigger-source.yaml b/Documentation/devicetree/bindings/spi/trigger-source.yaml
new file mode 100644
index 000000000000..d64367726af2
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/trigger-source.yaml
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/trigger-source.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Trigger source providers for SPI offloads
+
+maintainers:
+  - David Lechner <dlechner@baylibre.com>
+
+description:
+  Each trigger source provider should be represented by a device tree node. It
+  may be e.g. a SPI peripheral chip or a clock source.
+
+properties:
+  '#trigger-source-cells':
+    description:
+      Number of cells in a source trigger. Typically 0 for nodes of simple
+      trigger sources. For nodes with more than one output signal, the first
+      cell be used to specify which output signal to use. If the same signal is
+      available on more than one pin, the second cell can be used to specify
+      which pin to use.
+    enum: [ 0, 1, 2 ]
+
+additionalProperties: true
+
+...

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (3 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 04/15] spi: dt-bindings: add trigger-source.yaml David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-26 15:18   ` Jonathan Cameron
  2024-10-23 20:59 ` [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver David Lechner
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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 for SPI offloads.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes: new patch in v4
---
 .../devicetree/bindings/spi/trigger-pwm.yaml       | 39 ++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/trigger-pwm.yaml b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
new file mode 100644
index 000000000000..987638aa4732
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/trigger-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic SPI offload trigger using PWM
+
+description: Remaps a PWM channel as a trigger source.
+
+maintainers:
+  - David Lechner <dlechner@baylibre.com>
+
+$ref: /schemas/spi/trigger-source.yaml#
+
+properties:
+  compatible:
+    const: trigger-pwm
+
+  '#trigger-source-cells':
+    const: 0
+
+  pwms:
+    maxItems: 1
+
+required:
+  - compatible
+  - '#trigger-source-cells'
+  - pwms
+
+additionalProperties: false
+
+examples:
+  - |
+    trigger {
+        compatible = "trigger-pwm";
+        #trigger-source-cells = <0>;
+        pwms = <&pwm 0 1000000 0>;
+    };

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (4 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-25 12:07   ` Nuno Sá
  2024-10-23 20:59 ` [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs David Lechner
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	David Lechner

Add a new driver for a generic PWM trigger for SPI offloads.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes: new patch in v4
---
 drivers/spi/Kconfig                   |  12 +++
 drivers/spi/Makefile                  |   3 +
 drivers/spi/spi-offload-trigger-pwm.c | 169 ++++++++++++++++++++++++++++++++++
 3 files changed, 184 insertions(+)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index d65074b85f62..50d04fa317b7 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1286,4 +1286,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 6a470eb475a2..3a76b9c61486 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -161,3 +161,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 000000000000..ffb0bf75cace
--- /dev/null
+++ b/drivers/spi/spi-offload-trigger-pwm.c
@@ -0,0 +1,169 @@
+// 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/spi-offload.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(void *priv,
+					  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(void *priv,
+					    struct spi_offload_trigger_config *config)
+{
+	struct spi_offload_trigger_pwm_state *st = priv;
+	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(void *priv,
+					  struct spi_offload_trigger_config *config)
+{
+	struct spi_offload_trigger_pwm_state *st = priv;
+	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(void *priv)
+{
+	struct spi_offload_trigger_pwm_state *st = priv;
+	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 = {
+		.name = "trigger",
+		.id = 0,
+		.parent = dev,
+		.fwnode = dev_fwnode(dev),
+		.ops = &spi_offload_trigger_pwm_ops,
+	};
+	struct spi_offload_trigger_pwm_state *st;
+	struct spi_offload_trigger *trigger;
+	struct pwm_state state;
+	int ret;
+
+	st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	st->dev = dev;
+
+	st->pwm = devm_pwm_get(&pdev->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;
+
+	trigger = devm_spi_offload_trigger_alloc(dev, &info);
+	if (IS_ERR(trigger))
+		return dev_err_probe(dev, PTR_ERR(trigger), "failed to allocate trigger\n");
+
+	return devm_spi_offload_trigger_register(dev, trigger, st);
+}
+
+static const struct of_device_id spi_offload_trigger_pwm_of_match_table[] = {
+	{ .compatible = "trigger-pwm" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, spi_offload_trigger_pwm_of_match_table);
+
+static struct platform_driver spi_offload_trigger_pwm_driver = {
+	.driver = {
+		.name = "trigger-pwm",
+		.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] 59+ messages in thread

* [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (5 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-25 12:24   ` Nuno Sá
  2024-10-23 20:59 ` [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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>
---

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       | 76 +++++++++++++++++++++++++++++++++++++++++
 drivers/spi/spi.c               | 10 ++++++
 include/linux/spi/spi-offload.h | 24 +++++++++++++
 include/linux/spi/spi.h         |  3 ++
 4 files changed, 113 insertions(+)

diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
index 2a1f9587f27a..dd4cb3c2e985 100644
--- a/drivers/spi/spi-offload.c
+++ b/drivers/spi/spi-offload.c
@@ -8,6 +8,7 @@
 
 #include <linux/cleanup.h>
 #include <linux/device.h>
+#include <linux/dmaengine.h>
 #include <linux/export.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
@@ -282,6 +283,81 @@ 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);
+}
+
+/**
+ * spi_offload_tx_stream_request_dma_chan_info - Get the DMA channel info for the TX stream
+ * @spi: SPI device
+ * @id: Function ID if SPI device uses more than one offload or NULL.
+ *
+ * This is the DMA channel that will provide data to transfers that use the
+ * %SPI_OFFLOAD_XFER_TX_STREAM offload flag.
+ *
+ * The caller is responsible for calling spi_offload_free_dma_chan_info() on the
+ * returned pointer.
+ *
+ * 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);
+
+/**
+ * spi_offload_rx_stream_request_dma_chan_info - Get the DMA channel info for the RX stream
+ * @spi: SPI device
+ * @id: Function ID if SPI device uses more than one offload or NULL.
+ *
+ * This is the DMA channel that will receive data from transfers that use the
+ * %SPI_OFFLOAD_XFER_RX_STREAM offload flag.
+ *
+ * The caller is responsible for calling spi_offload_free_dma_chan_info() on the
+ * returned pointer.
+ *
+ * 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_dev_release(struct device *dev)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 7c5e76b15421..cb6184c0ae03 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/spi-offload.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
 #include <uapi/linux/sched/types.h>
@@ -4159,6 +4160,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/spi-offload.h b/include/linux/spi/spi-offload.h
index 561cc1fb6f35..5d0e29b25977 100644
--- a/include/linux/spi/spi-offload.h
+++ b/include/linux/spi/spi-offload.h
@@ -25,6 +25,11 @@ struct device;
 struct fwnode_handle;
 struct spi_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. */
@@ -56,6 +61,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 {
@@ -91,6 +98,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);
 };
 
 struct spi_offload *devm_spi_offload_alloc(struct device *dev,
@@ -111,6 +130,11 @@ 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);
+
 /* Trigger providers */
 
 struct spi_offload_trigger;
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index c230d6a209ee..fbb8b2d0b5ea 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -1092,6 +1092,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] 59+ messages in thread

* [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (6 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-25 12:26   ` Nuno Sá
  2024-10-23 20:59 ` [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support David Lechner
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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 data
and a trigger input for starting execution of the SPI message programmed
in the offload.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
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           | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
index d48faa42d025..5281b4871209 100644
--- a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
+++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
@@ -41,6 +41,24 @@ 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.
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+
+  dmas:
+    description:
+      DMA channels connected to the output stream interface of an offload instance.
+    minItems: 1
+    maxItems: 32
+
+  dma-names:
+    minItems: 1
+    maxItems: 32
+    items:
+      pattern: "^offload(?:[12]?[0-9]|3[01])-rx$"
+
 required:
   - compatible
   - reg
@@ -59,6 +77,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] 59+ messages in thread

* [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (7 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-25 13:09   ` Nuno Sá
  2024-10-23 20:59 ` [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext David Lechner
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	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.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

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 | 273 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 268 insertions(+), 6 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 50d04fa317b7..af3143ec5245 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -168,6 +168,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 2d24d762b5bd..1710847d81a1 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,8 +17,10 @@
 #include <linux/module.h>
 #include <linux/overflow.h>
 #include <linux/platform_device.h>
+#include <linux/spi/spi-offload.h>
 #include <linux/spi/spi.h>
 
+#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH	0x10
 #define SPI_ENGINE_REG_RESET			0x40
 
 #define SPI_ENGINE_REG_INT_ENABLE		0x80
@@ -23,6 +28,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
@@ -33,10 +39,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)
@@ -78,6 +98,14 @@
 #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
+
+#define SPI_ENGINE_OFFLOAD_CAPS (SPI_OFFLOAD_CAP_TRIGGER | \
+				 SPI_OFFLOAD_CAP_TX_STATIC_DATA | \
+				 SPI_OFFLOAD_CAP_RX_STREAM_DMA)
+
 struct spi_engine_program {
 	unsigned int length;
 	uint16_t instructions[] __counted_by(length);
@@ -105,6 +133,16 @@ struct spi_engine_message_state {
 	uint8_t *rx_buf;
 };
 
+enum {
+	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;
@@ -117,6 +155,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 *offloads;
+	unsigned int num_offloads;
 };
 
 static void spi_engine_program_add_cmd(struct spi_engine_program *p,
@@ -164,7 +207,7 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
 
 		if (xfer->tx_buf)
 			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,
@@ -220,16 +263,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,
@@ -544,11 +595,94 @@ 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) {
+		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_sdo_mem_size)
+		return -EINVAL;
+
+	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);
@@ -560,20 +694,54 @@ 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);
+	int i;
+
+	if (config->capability_flags & ~SPI_ENGINE_OFFLOAD_CAPS)
+		return ERR_PTR(-EINVAL);
+
+	for (i = 0; i < spi_engine->num_offloads; i++) {
+		struct spi_offload *offload = &spi_engine->offloads[i];
+		struct spi_engine_offload *priv = offload->priv;
+
+		if (priv->offload_num == spi_get_chipselect(spi, 0))
+			return offload;
+	}
+
+	return ERR_PTR(-ENODEV);
+}
+
 static int spi_engine_setup(struct spi_device *device)
 {
 	struct spi_controller *host = device->controller;
@@ -606,6 +774,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;
@@ -641,11 +815,62 @@ static int spi_engine_transfer_one_message(struct spi_controller *host,
 		msg->status = -ETIMEDOUT;
 	}
 
+out:
 	spi_finalize_current_message(host);
 
 	return msg->status;
 }
 
+static int spi_engine_trigger_enable(struct spi_offload *offload)
+{
+	struct spi_controller *host = offload->spi->controller;
+	struct spi_engine_offload *priv = offload->priv;
+	struct spi_engine *spi_engine = priv->spi_engine;
+	unsigned int reg;
+
+	guard(mutex)(&host->io_mutex);
+
+	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_controller *host = offload->spi->controller;
+	struct spi_engine_offload *priv = offload->priv;
+	struct spi_engine *spi_engine = priv->spi_engine;
+	unsigned int reg;
+
+	guard(mutex)(&host->io_mutex);
+
+	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_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,
+	.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;
@@ -660,8 +885,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, i;
 
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0)
@@ -676,6 +900,29 @@ 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.
+	 */
+	spi_engine->num_offloads = 1;
+
+	spi_engine->offloads =
+		devm_spi_offload_alloc(&pdev->dev, spi_engine->num_offloads,
+				       sizeof(struct spi_engine_offload));
+	if (IS_ERR(spi_engine->offloads))
+		return PTR_ERR(spi_engine->offloads);
+
+	for (i = 0; i < spi_engine->num_offloads; i++) {
+		struct spi_offload *offload = &spi_engine->offloads[i];
+		struct spi_engine_offload *offload_priv = offload->priv;
+
+		offload->ops = &spi_engine_offload_ops;
+		offload->xfer_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+		offload_priv->spi_engine = spi_engine;
+		offload_priv->offload_num = i;
+	}
+
 	spi_engine->clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
 	if (IS_ERR(spi_engine->clk))
 		return PTR_ERR(spi_engine->clk);
@@ -697,6 +944,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);
@@ -718,6 +978,7 @@ 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->num_chipselect = 8;
 
 	/* Some features depend of the IP core version. */

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (8 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-26 15:29   ` Jonathan Cameron
  2024-10-23 20:59 ` [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() David Lechner
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	David Lechner

The iio_dmaengine_buffer_setup_ext() function is public and should be
documented. Also, while touching this, fix the description of @dev in
related functions. @dev does not strictly have to be the parent of the
IIO device. It is only passed to dma_request_chan() so strictly
speaking, it can be any device that is a valid DMA channel consumer.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes:
* This patch is new in v4.

Jonathan, I think this patch stands on its own if you want to take it
earlier than the rest of this series.
---
 drivers/iio/buffer/industrialio-buffer-dmaengine.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 19af1caf14cd..054af21dfa65 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -206,7 +206,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
 
 /**
  * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
- * @dev: Parent device for the buffer
+ * @dev: DMA channel consumer device
  * @channel: DMA channel name, typically "rx".
  *
  * This allocates a new IIO buffer which internally uses the DMAengine framework
@@ -288,6 +288,21 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
 }
 EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER);
 
+/**
+ * iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
+ * @dev: DMA channel consumer device
+ * @indio_dev: IIO device to which to attach this buffer.
+ * @channel: DMA channel name, typically "rx".
+ * @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.
+ *
+ * Once done using the buffer iio_dmaengine_buffer_free() should be used to
+ * release it.
+ */
 struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
 						  struct iio_dev *indio_dev,
 						  const char *channel,
@@ -321,7 +336,7 @@ static void __devm_iio_dmaengine_buffer_free(void *buffer)
 
 /**
  * devm_iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
- * @dev: Parent device for the buffer
+ * @dev: Device for devm ownership and DMA channel consumer device
  * @indio_dev: IIO device to which to attach this buffer.
  * @channel: DMA channel name, typically "rx".
  * @dir: Direction of buffer (in or out)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (9 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-25 13:24   ` Nuno Sá
  2024-10-23 20:59 ` [PATCH RFC v4 12/15] iio: adc: ad7944: don't use storagebits for sizing David Lechner
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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_ext2() 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>
---

v4 changes:
* This replaces "iio: buffer-dmaengine: generalize requesting DMA channel"
---
 drivers/iio/buffer/industrialio-buffer-dmaengine.c | 107 +++++++++++++++------
 include/linux/iio/buffer-dmaengine.h               |   5 +
 2 files changed, 81 insertions(+), 31 deletions(-)

diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 054af21dfa65..602cb2e147a6 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -33,6 +33,7 @@ struct dmaengine_buffer {
 	struct iio_dma_buffer_queue queue;
 
 	struct dma_chan *chan;
+	bool owns_chan;
 	struct list_head active;
 
 	size_t align;
@@ -216,28 +217,23 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
  * 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,
+						     bool owns_chan)
 {
 	struct dmaengine_buffer *dmaengine_buffer;
 	unsigned int width, src_width, dest_width;
 	struct dma_slave_caps caps;
-	struct dma_chan *chan;
 	int 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;
+	if (!dmaengine_buffer) {
+		ret = -ENOMEM;
+		goto err_release;
 	}
 
 	ret = dma_get_slave_caps(chan, &caps);
 	if (ret < 0)
-		goto err_release;
+		goto err_free;
 
 	/* Needs to be aligned to the maximum of the minimums */
 	if (caps.src_addr_widths)
@@ -252,6 +248,7 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
 
 	INIT_LIST_HEAD(&dmaengine_buffer->active);
 	dmaengine_buffer->chan = chan;
+	dmaengine_buffer->owns_chan = owns_chan;
 	dmaengine_buffer->align = width;
 	dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev);
 
@@ -263,10 +260,12 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
 
 	return &dmaengine_buffer->queue.buffer;
 
-err_release:
-	dma_release_channel(chan);
 err_free:
 	kfree(dmaengine_buffer);
+err_release:
+	if (owns_chan)
+		dma_release_channel(chan);
+
 	return ERR_PTR(ret);
 }
 
@@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
 		iio_buffer_to_dmaengine_buffer(buffer);
 
 	iio_dma_buffer_exit(&dmaengine_buffer->queue);
-	dma_release_channel(dmaengine_buffer->chan);
-
 	iio_buffer_put(buffer);
+
+	if (dmaengine_buffer->owns_chan)
+		dma_release_channel(dmaengine_buffer->chan);
 }
 EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER);
 
+static struct iio_buffer
+*__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev,
+				  struct dma_chan *chan, bool owns_chan,
+				  enum iio_buffer_direction dir)
+{
+	struct iio_buffer *buffer;
+	int ret;
+
+	buffer = iio_dmaengine_buffer_alloc(chan, owns_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
  * @dev: DMA channel consumer device
@@ -308,24 +333,13 @@ struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
 						  const char *channel,
 						  enum iio_buffer_direction dir)
 {
-	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;
+	struct dma_chan *chan;
 
-	ret = iio_device_attach_buffer(indio_dev, buffer);
-	if (ret) {
-		iio_dmaengine_buffer_free(buffer);
-		return ERR_PTR(ret);
-	}
+	chan = dma_request_chan(dev, channel);
+	if (IS_ERR(chan))
+		return ERR_CAST(chan);
 
-	return buffer;
+	return __iio_dmaengine_buffer_setup_ext(indio_dev, chan, true, dir);
 }
 EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER);
 
@@ -362,6 +376,37 @@ int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
 }
 EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER);
 
+/**
+ * devm_iio_dmaengine_buffer_setup_ext2() - 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.
+ */
+int devm_iio_dmaengine_buffer_setup_ext2(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, false, 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_ext2, 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 81d9a19aeb91..7bdb979b59f2 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_free(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_ext2(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] 59+ messages in thread

* [PATCH RFC v4 12/15] iio: adc: ad7944: don't use storagebits for sizing
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (10 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload David Lechner
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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>
---

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 a5aea4e9f1a7..6d1202bd55a0 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] 59+ messages in thread

* [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (11 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 12/15] iio: adc: ad7944: don't use storagebits for sizing David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-26 15:51   ` Jonathan Cameron
  2024-10-23 20:59 ` [PATCH RFC v4 14/15] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	David Lechner

This adds support for SPI offload to the ad7944 driver. This allows
reading data at the max sample rate of 2.5 MSPS.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

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 | 233 ++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 222 insertions(+), 12 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 179d83aafd8a..92dfb495a8ce 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -346,6 +346,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 6d1202bd55a0..52005bd3174c 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/spi-offload.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,11 @@ 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;
 	void *chain_mode_buf;
 	/* Chip-specific timing specifications. */
 	const struct ad7944_timing_spec *timing_spec;
@@ -81,6 +89,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,
@@ -96,6 +106,7 @@ struct ad7944_chip_info {
 	const char *name;
 	const struct ad7944_timing_spec *timing_spec;
 	const struct iio_chan_spec channels[2];
+	const struct iio_chan_spec offload_channels[1];
 };
 
 /* get number of bytes for SPI xfer */
@@ -129,6 +140,24 @@ static const struct ad7944_chip_info _name##_chip_info = {		\
 		},							\
 		IIO_CHAN_SOFT_TIMESTAMP(1),				\
 	},								\
+	/* basically the same minus soft timestamp plus sampling freq */\
+	.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),	\
+		},							\
+	},								\
 }
 
 /* pseudo-differential with ground sense */
@@ -239,6 +268,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
@@ -326,6 +397,46 @@ 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 < 0 || val2 < 0)
+			return -EINVAL;
+
+		return ad7944_set_sample_freq(adc, val);
 	default:
 		return -EINVAL;
 	}
@@ -333,6 +444,43 @@ static int ad7944_read_raw(struct iio_dev *indio_dev,
 
 static const struct iio_info ad7944_iio_info = {
 	.read_raw = &ad7944_read_raw,
+	.write_raw = &ad7944_write_raw,
+};
+
+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 +594,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;
@@ -590,20 +743,75 @@ 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");
+
+	if (ret == -ENODEV) {
+		dev_info(dev, "SPI offload not available\n");
+
+		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_ext2(
+			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 +846,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] 59+ messages in thread

* [PATCH RFC v4 14/15] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (12 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-23 20:59 ` [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload David Lechner
  2024-10-24 14:12 ` [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support Nuno Sá
  15 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: 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.

The $ref for spi-peripheral-props.yaml is moved to keep similar $refs
grouped together.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---

v4 changes: new patch in v4

For offload support, this doesn't actually tell the whole story. For
some use cases, it would be perfectly reasonable to have a PWM directly
connected to the CNV pin, which is why I have made the bindings like
this. However, in order to work with the the AXI SPI Engine SPI offload
and allow for non-cyclic DMA transfers, the actual signal that is being
connected to the CNV pin is:

    CNV = (PWM_OUT && !DMA_FULL) || CNV_GPIO_OUT

RFC: Is this binding sufficient for this use case? Or should we consider
something that describes it more accurately?

The gory details (mostly of interest for those reviewing the driver
patch that uses this binding):
* The PWM output has to be disabled in hardware by connecting it to an
  AND gate where the 2nd input comes from the DMA controller. This is
  necessary to ensure that we can only trigger conversions as long as
  there is room in the DMA buffer to receive them. If we continue to
  trigger conversions when the DMA is full, then the advanced sequencer
  in the ADC chip keeps advancing and we will end up starting the next
  batch of conversions with a random channel when DMA is no longer full.
* To get out of conversion mode, we also have to toggle the CNV pin
  manually one time. If it wasn't for the DMA mask on the PWM output,
  we could just use 0% and 100% duty cycle to toggle the CNV pin, but
  since the PWM output is masked by the DMA, we have to use a GPIO to
  toggle the CNV pin. The GPIO signal is connected to an input of an OR
  gate along with the output of the AND gate mentioned above.
---
 Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
index 310f046e139f..0d767d8b867a 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4695.yaml
@@ -18,8 +18,6 @@ description: |
   * https://www.analog.com/en/products/ad4697.html
   * https://www.analog.com/en/products/ad4698.html
 
-$ref: /schemas/spi/spi-peripheral-props.yaml#
-
 properties:
   compatible:
     enum:
@@ -84,6 +82,9 @@ properties:
     description: The Reset Input (RESET). Should be configured GPIO_ACTIVE_LOW.
     maxItems: 1
 
+  pwms:
+    description: PWM signal connected to the CNV pin.
+
   interrupts:
     minItems: 1
     items:
@@ -106,6 +107,12 @@ 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 to 3.
+    const: 2
+
   "#address-cells":
     const: 1
 
@@ -165,6 +172,8 @@ required:
   - vio-supply
 
 allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+  - $ref: /schemas/spi/trigger-source.yaml#
   - oneOf:
       - required:
           - ldo-in-supply

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (13 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 14/15] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
@ 2024-10-23 20:59 ` David Lechner
  2024-10-26 16:00   ` Jonathan Cameron
  2024-10-24 14:12 ` [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support Nuno Sá
  15 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-23 20:59 UTC (permalink / raw)
  To: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm,
	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

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/iio/adc/Kconfig  |   1 +
 drivers/iio/adc/ad4695.c | 470 +++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 440 insertions(+), 31 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 92dfb495a8ce..f76a3f62a9ad 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -53,6 +53,7 @@ config AD4695
 	depends on SPI
 	select REGMAP_SPI
 	select IIO_BUFFER
+	select IIO_BUFFER_DMAENGINE
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes here to build support for Analog Devices AD4695 and similar
diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
index 595ec4158e73..c150851d1fb1 100644
--- a/drivers/iio/adc/ad4695.c
+++ b/drivers/iio/adc/ad4695.c
@@ -19,14 +19,18 @@
 #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/spi-offload.h>
 #include <linux/spi/spi.h>
 #include <linux/units.h>
 
@@ -66,6 +70,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))
@@ -87,6 +93,7 @@
 
 /* timing specs */
 #define AD4695_T_CONVERT_NS		415
+#define AD4695_T_CNVH_NS		10
 #define AD4695_T_WAKEUP_HW_MS		3
 #define AD4695_T_WAKEUP_SW_MS		3
 #define AD4695_T_REFBUF_MS		100
@@ -121,9 +128,18 @@ struct ad4695_channel_config {
 
 struct ad4695_state {
 	struct spi_device *spi;
+	struct spi_offload *offload;
+	struct spi_offload_trigger *offload_trigger;
+	u32 spi_max_speed_hz;
 	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];
@@ -571,6 +587,167 @@ static irqreturn_t ad4695_trigger_handler(int irq, void *p)
 	return IRQ_HANDLED;
 }
 
+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);
+}
+
+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;
+	xfer->bits_per_word = 16;
+	xfer->len = 2;
+
+	spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
+	st->buf_read_msg.offload = st->offload;
+
+	st->spi->max_speed_hz = st->spi_max_speed_hz;
+	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
+	st->spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
+	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
+	 * due to issues with ordering with respect to entering/exiting
+	 * conversion mode.
+	 */
+	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:
+	/* have to unwind in a different order to avoid triggering offload */
+	spi_offload_trigger_disable(st->offload, st->offload_trigger);
+	ad4695_cnv_manual_trigger(st);
+	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);
+
+	/*
+	 * We have to trigger on more conversion to ensure that the exit
+	 * conversion mode command works.
+	 */
+	ad4695_cnv_manual_trigger(st);
+
+	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
@@ -583,13 +760,20 @@ static irqreturn_t ad4695_trigger_handler(int irq, void *p)
  */
 static int ad4695_read_one_sample(struct ad4695_state *st, unsigned int address)
 {
-	struct spi_transfer xfer[2] = { };
-	int ret, i = 0;
+	struct spi_transfer xfer = { };
+	int ret;
 
 	ret = ad4695_set_single_cycle_mode(st, 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 xfer to read the
@@ -598,23 +782,29 @@ static int ad4695_read_one_sample(struct ad4695_state *st, unsigned int address)
 	if (address == AD4695_CMD_TEMP_CHAN) {
 		/* We aren't reading, so we can make this a short xfer. */
 		st->cnv_cmd2 = AD4695_CMD_TEMP_CHAN << 3;
-		xfer[0].tx_buf = &st->cnv_cmd2;
-		xfer[0].len = 1;
-		xfer[0].cs_change = 1;
-		xfer[0].cs_change_delay.value = AD4695_T_CONVERT_NS;
-		xfer[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+		xfer.tx_buf = &st->cnv_cmd2;
+		xfer.len = 1;
+
+		ret = spi_sync_transfer(st->spi, &xfer, 1);
+		if (ret)
+			return ret;
 
-		i = 1;
+		/*
+		 * 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. */
 	st->cnv_cmd = AD4695_CMD_EXIT_CNV_MODE << 11;
-	xfer[i].bits_per_word = 16;
-	xfer[i].tx_buf = &st->cnv_cmd;
-	xfer[i].rx_buf = &st->raw_data;
-	xfer[i].len = 2;
+	xfer.bits_per_word = 16;
+	xfer.tx_buf = &st->cnv_cmd;
+	xfer.rx_buf = &st->raw_data;
+	xfer.len = 2;
 
-	return spi_sync_transfer(st->spi, xfer, i + 1);
+	return spi_sync_transfer(st->spi, &xfer, 1);
 }
 
 static int ad4695_read_raw(struct iio_dev *indio_dev,
@@ -721,11 +911,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)
@@ -779,6 +992,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)
+				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;
 		}
@@ -857,6 +1081,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,
@@ -970,26 +1195,173 @@ static int ad4695_parse_channel_cfg(struct ad4695_state *st)
 	return 0;
 }
 
+static bool ad4695_offload_trigger_match(void *priv,
+					 enum spi_offload_trigger_type type,
+					 u64 *args, u32 nargs)
+{
+	if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+		return false;
+
+	// TODO: create macros for args[0] and args[1]
+	// args[0] is the trigger signal, 0 == BUSY
+	// args[1] is the GPIO pin number, 0 == GP0, 3 == GP3
+	if (nargs != 2 || args[0] != 0)
+		return false;
+
+	return true;
+}
+
+static int ad4695_offload_trigger_request(void *priv,
+					  enum spi_offload_trigger_type type,
+					  u64 *args, u32 nargs)
+{
+	struct ad4695_state *st = priv;
+
+	/* Should already be validated by match, but just in case. */
+	if (nargs != 2)
+		return -EINVAL;
+
+	/* DT tells us if BUSY signal uses GP0 or GP3 */
+	if (args[1] == 3)
+		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(void *priv, 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_restore_spi_max_speed_hz(void *data)
+{
+	struct ad4695_state *st = data;
+
+	st->spi->max_speed_hz = st->spi_max_speed_hz;
+}
+
+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 = {
+		.name = "data-ready",
+		.id = 0,
+		.parent = dev,
+		.fwnode = dev_fwnode(dev),
+		.ops = &ad4695_offload_trigger_ops,
+	};
+	struct spi_offload_trigger *trigger;
+	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");
+
+	trigger = devm_spi_offload_trigger_alloc(dev, &trigger_info);
+	if (IS_ERR(trigger))
+		return dev_err_probe(dev, PTR_ERR(trigger),
+				     "failed to allocate offload trigger\n");
+
+	ret = devm_spi_offload_trigger_register(dev, trigger, st);
+	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");
+
+	/* Currently, only self-provided trigger is supported. */
+	if (st->offload_trigger != trigger)
+		return dev_err_probe(dev, -EINVAL, "offload trigger mismatch\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");
+
+	/*
+	 * REVISIT: ideally, we would ask the RX DMA stream what the
+	 * buffer layout is. Right now, the only supported offload is
+	 * the ADI ad469x HDL project which always uses 32-bit word
+	 * size for data values, regardless of the SPI bits per word.
+	 */
+
+	for (i = 0; i < indio_dev->num_channels; i++) {
+		struct iio_chan_spec *chan = &st->iio_chan[i];
+
+		/* update storagebits to match offload capabilities */
+		chan->scan_type.storagebits = 32;
+		/* add sample frequency for PWM CNV trigger */
+		chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ);
+	}
+
+	return devm_iio_dmaengine_buffer_setup_ext2(dev, indio_dev, rx_dma,
+						    IIO_BUFFER_DIRECTION_IN);
+}
+
 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;
@@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
 		return -EINVAL;
 
 	/* Registers cannot be read at the max allowable speed */
+	st->spi_max_speed_hz = spi->max_speed_hz;
 	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
 
+	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);
+	if (ret)
+		return ret;
+
 	st->regmap = devm_regmap_init_spi(spi, &ad4695_regmap_config);
 	if (IS_ERR(st->regmap))
 		return dev_err_probe(dev, PTR_ERR(st->regmap),
@@ -1014,6 +1391,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);
@@ -1139,14 +1521,39 @@ static int ad4695_probe(struct spi_device *spi)
 	indio_dev->info = &ad4695_info;
 	indio_dev->modes = INDIO_DIRECT_MODE;
 	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;
+	static const struct spi_offload_config ad4695_offload_config = {
+		.capability_flags = SPI_OFFLOAD_CAP_TRIGGER
+				  | SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+	};
+
+	st->offload = devm_spi_offload_get(dev, spi, &ad4695_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 (ret == -ENODEV) {
+		/* If no SPI offload, fall back to low speed usage. */
+		dev_info(dev, "SPI offload not available\n");
+
+		/* 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);
 }
@@ -1183,3 +1590,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] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
@ 2024-10-24 13:27   ` Nuno Sá
  2024-10-24 14:49     ` David Lechner
  2024-10-25 12:59   ` Nuno Sá
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-24 13:27 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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
> callback 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 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---

Hi David,

Just one minor comment...

> 
> 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.
> ---
>  drivers/spi/Kconfig             |   3 ++
>  drivers/spi/Makefile            |   1 +
>  drivers/spi/spi-offload.c       | 104 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi-offload.h |  64 +++++++++++++++++++++++++
>  include/linux/spi/spi.h         |  16 +++++++
>  5 files changed, 188 insertions(+)
> 
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 823797217404..d65074b85f62 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 a9b1bc259b68..6a470eb475a2 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 000000000000..c344cbf50bdb
> --- /dev/null
> +++ b/drivers/spi/spi-offload.c
> @@ -0,0 +1,104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#define DEFAULT_SYMBOL_NAMESPACE SPI_OFFLOAD

Cool, was not aware of this :)
> +
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/spi/spi-offload.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +/**
> + * devm_spi_offload_alloc() - Allocate offload instances
> + * @dev: Device for devm purposes
> + * @num_offloads: Number of offloads to allocate
> + * @priv_size: Size of private data to allocate for each offload
> + *
> + * Offload providers should use this to allocate offload instances.
> + *
> + * Return: Pointer to array of offloads or error on failure.
> + */
> +struct spi_offload *devm_spi_offload_alloc(struct device *dev,
> +					   size_t num_offloads,
> +					   size_t priv_size)
> +{
> +	struct spi_offload *offloads;
> +	void *privs;
> +	size_t i;
> +
> +	offloads = devm_kcalloc(dev, num_offloads, sizeof(*offloads) + priv_size,
> +				GFP_KERNEL);
> +	if (!offloads)
> +		return ERR_PTR(-ENOMEM);
> +
> +	privs = (void *)(offloads + num_offloads);
> +
> +	for (i = 0; i < num_offloads; i++) {
> +		struct spi_offload *offload = offloads + i;
> +		void *priv = privs + i * priv_size;
> +
> +		offload->provider_dev = dev;
> +		offload->priv = priv;
> +	}
> +
> +	return offloads;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_alloc);
> +
> +static void spi_offload_put(void *data)
> +{
> +	struct spi_offload *offload = data;
> +
> +	offload->spi = NULL;
> +	put_device(offload->provider_dev);
> +}
> +
> +/**
> + * 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_offload *offload;
> +	int ret;
> +
> +	if (!spi || !config)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (!spi->controller->get_offload)
> +		return ERR_PTR(-ENODEV);
> +
> +	offload = spi->controller->get_offload(spi, config);
> +	if (IS_ERR(offload))
> +		return offload;
> +
> +	if (offload->spi)
> +		return ERR_PTR(-EBUSY);
> +
> +	offload->spi = spi;
> +	get_device(offload->provider_dev);

Isn't this redundant? From what I can tell, we're assuming that the spi controller
(of the spi device) is the offload provider. Therefore, getting an extra reference
for it does not really seems necessary. The device cannot go away without under the
spi_device feet. If that could happen, then we would also need to take care about
callback access and things like that. Going this way, it would also be arguable to
have a try_module_get().

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
@ 2024-10-24 14:04   ` Nuno Sá
  2024-10-24 15:02     ` David Lechner
  2024-10-26 15:14   ` Jonathan Cameron
  2024-10-28 13:53   ` Nuno Sá
  2 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-24 14:04 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> In previous versions, we locked the SPI bus when the hardware trigger
> was enabled, but we found this to be too restrictive. In one use case,
> to avoid a race condition, we need to enable the SPI offload via a
> hardware trigger, then write a SPI message to the peripheral to place
> it into a mode that will generate the trigger. If we did it the other
> way around, we could miss the first trigger.
> 
> Another likely use case will be enabling two offloads/triggers at one
> time on the same device, e.g. a read trigger and a write trigger. So
> the exclusive bus lock for a single trigger would be too restrictive in
> this case too.
> 
> So for now, I'm going with Nuno's suggestion to leave any locking up to
> the individual controller driver. If we do find we need something more
> generic in the future, we could add a new spi_bus_lock_exclusive() API
> that causes spi_bus_lock() to fail instead of waiting and add "locked"
> versions of trigger enable functions. This would allow a peripheral to
> claim exclusive use of the bus indefinitely while still being able to
> do any SPI messaging that it needs.
> 
> 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       | 266 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi-offload.h |  78 ++++++++++++
>  2 files changed, 344 insertions(+)
> 
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index c344cbf50bdb..2a1f9587f27a 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -9,12 +9,26 @@
>  #include <linux/cleanup.h>
>  #include <linux/device.h>
>  #include <linux/export.h>
> +#include <linux/list.h>
>  #include <linux/mutex.h>
> +#include <linux/of.h>
>  #include <linux/property.h>
>  #include <linux/spi/spi-offload.h>
>  #include <linux/spi/spi.h>
>  #include <linux/types.h>
>  
> +struct spi_offload_trigger {
> +	struct list_head list;
> +	struct device dev;
> +	/* synchronizes calling ops and driver registration */
> +	struct mutex lock;
> +	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 instances
>   * @dev: Device for devm purposes
> @@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
>  	return offload;
>  }
>  EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> +
> +static void spi_offload_trigger_release(void *data)
> +{
> +	struct spi_offload_trigger *trigger = data;
> +
> +	guard(mutex)(&trigger->lock);
> +	if (trigger->priv && trigger->ops->release)
> +		trigger->ops->release(trigger->priv);
> +
> +	put_device(&trigger->dev);
> +}
> +
> +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;
> +	bool match = false;
> +	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);
> +
> +	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) = args.fwnode;
> +
> +	guard(mutex)(&spi_offload_triggers_lock);
> +
> +	list_for_each_entry(trigger, &spi_offload_triggers, list) {
> +		if (trigger->dev.fwnode != args.fwnode)
> +			continue;
> +
> +		match = trigger->ops->match(trigger->priv, type, args.args,
> args.nargs);
> +		if (match)
> +			break;
> +	}
> +
> +	if (!match)
> +		return ERR_PTR(-EPROBE_DEFER);
> +
> +	guard(mutex)(&trigger->lock);
> +
> +	if (!trigger->priv)
> +		return ERR_PTR(-ENODEV);

This is a bit odd tbh. Not a real deal breaker for me but the typical pattern I would
expect is for methods of the trigger to get a struct spi_offload_trigger opaque
pointer. Then we provide a get_private kind of API for the private data. I guess you
want to avoid that but IMO it makes for neater API instead of getting void pointers.

Another thing is, can the above actually happen? We have the
spi_offload_triggers_lock grabbed and we got a match so the trigger should not be
able to go away (should block on the same lock).

> +
> +	if (trigger->ops->request) {
> +		ret = trigger->ops->request(trigger->priv, type, args.args,
> args.nargs);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	}
> +
> +	get_device(&trigger->dev);

try_module_get() would also make sense...

...

> 
> +struct spi_offload_trigger
> +*devm_spi_offload_trigger_alloc(struct device *dev,
> +				struct spi_offload_trigger_info *info)
> +{
> +	struct spi_offload_trigger *trigger;
> +	int ret;
> +
> +	trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);
> +	if (!trigger)
> +		return ERR_PTR(-ENOMEM);
> +
> +	device_initialize(&trigger->dev);

Do we really need the full struct device and the overhead of adding it to the driver
core? AFAICT, we're using it only for refcouting so we could use a plain kref for
that matter. It would make things simpler. Or do you envision an future usecase as
this might matter? Like allowing userspace to set some controls on the trigger (I
would expect to be done through consumers though)?

> +	trigger->dev.parent = info->parent;
> +	trigger->dev.fwnode = info->fwnode;
> +	trigger->dev.of_node = of_node_get(to_of_node(trigger->dev.fwnode));
> +	trigger->dev.of_node_reused = true;
> +	trigger->dev.release = spi_offload_trigger_dev_release;
> +
> +	mutex_init(&trigger->lock);
> +	trigger->ops = info->ops;
> +
> +	ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = dev_set_name(&trigger->dev, "%s-%d", info->name, info->id);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return trigger;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_alloc);
> +
> +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);
> +
> +	guard(mutex)(&trigger->lock);
> +	trigger->priv = NULL;

nit: I guess this is a good as anything else but *ops could also be a good fit to
nullify :)

> +	device_del(&trigger->dev);
> +}
> +
> +int devm_spi_offload_trigger_register(struct device *dev,
> +				      struct spi_offload_trigger *trigger,
> +				      void *priv)
> +{
> +	int ret;
> +
> +	ret = device_add(&trigger->dev);
> +	if (ret)
> +		return ret;
> +
> +	trigger->priv = priv;
> +
> +	guard(mutex)(&spi_offload_triggers_lock);
> +	list_add_tail(&trigger->list, &spi_offload_triggers);
> +
> +	ret = devm_add_action_or_reset(dev, spi_offload_trigger_unregister,
> trigger);
> +	if (ret)
> +		return ret;
> 

return devm_add_action_or_reset()?

> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register);
> diff --git a/include/linux/spi/spi-offload.h b/include/linux/spi/spi-offload.h
> index 92a557533b83..561cc1fb6f35 100644
> --- a/include/linux/spi/spi-offload.h
> +++ b/include/linux/spi/spi-offload.h
> @@ -22,6 +22,7 @@
>  MODULE_IMPORT_NS(SPI_OFFLOAD);
>  
>  struct device;
> +struct fwnode_handle;
>  struct spi_device;
>  
>  /* Offload can be triggered by external hardware event. */
> @@ -53,6 +54,43 @@ struct spi_offload {
>  	struct spi_device *spi;
>  	/** @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);
>  };
>  
>  struct spi_offload *devm_spi_offload_alloc(struct device *dev,
> @@ -61,4 +99,44 @@ struct spi_offload *devm_spi_offload_alloc(struct device *dev,
>  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);
> +
> +/* Trigger providers */
> +
> +struct spi_offload_trigger;
> +
> +struct spi_offload_trigger_ops {
> +	bool (*match)(void *priv, enum spi_offload_trigger_type type, u64 *args,
> u32 nargs);
> +	int (*request)(void *priv, enum spi_offload_trigger_type type, u64 *args,
> u32 nargs);
> +	void (*release)(void *priv);
> +	int (*validate)(void *priv, struct spi_offload_trigger_config *config);
> +	int (*enable)(void *priv, struct spi_offload_trigger_config *config);
> +	void (*disable)(void *priv);
> +};

Yeah, as I said... not a fan of the void *priv thing

- Nuno Sá


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support
  2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
                   ` (14 preceding siblings ...)
  2024-10-23 20:59 ` [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload David Lechner
@ 2024-10-24 14:12 ` Nuno Sá
  15 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-24 14:12 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

Hi David,

On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
> In this revision, I ended up changing quite a bit more that I was
> expecting.
> 
> In the DT bindings, I ended up dropping the #spi-offload-cells and
> spi-offload properties. A couple of reasons for this:
> 
> 1. Several people commented that it is odd to have a separate provider/
>    consumer binding for something that already has a parent/child
>    relationship (both on this series and in another unrelated series
>    with io-backends). For now, the only SPI offload provider is the AXI
>    SPI Engine, which is a SPI controller.
> 2. In a discussion unrelated to this series, but related to the idea
>    of SPI offloads [1], it became apparent that the proposed use for
>    the cells to select triggers and tx/rx streams doesn't actually
>    work for that case.
> 3. In offline review, it was suggested that assigning specific offloads
>    to specific peripherals may be too restrictive, e.g. if there are
>    controllers that have pools of identical offloads. This idea of
>    pools of generic offloads has also come up in previous discussions
>    on the mailing list.
> 
> [1]:
> https://lore.kernel.org/linux-iio/e5310b63-9dc4-43af-9fbe-0cc3b604ab8b@baylibre.com/
> 
> So the idea is that if we do end up needing to use DT to assign certain
> resources (triggers, DMA channels, etc.) to specific peripherals, we
> would make a mapping attribute in the controller node rather than using
> phandle cells. But we don't need this yet, so it isn't present in The
> current patches.
> 
> And if we ever end up with a SPI offload provider that is not a SPI
> controller, we can bring back the #spi-offload-cells and
> spi-offload properties.

Well I do like we kind of gave a step back and are more focused in supporting what we
have and know at the moment. And I think (for what I saw so far) things are being
implemented in fairly flexible manner. So yeah, as far as I'm concerned, I liked what
I saw so far. Hopefully everyone can agree on this so we drop the RFC :)

I'll try to look at the remaining patches tomorrow...

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-24 13:27   ` Nuno Sá
@ 2024-10-24 14:49     ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-24 14:49 UTC (permalink / raw)
  To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On 10/24/24 8:27 AM, Nuno Sá wrote:
> On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
>> Add the basic infrastructure to support SPI offload providers and
>> consumers.
>>

...

>> +struct spi_offload *devm_spi_offload_get(struct device *dev,
>> +					 struct spi_device *spi,
>> +					 const struct spi_offload_config *config)
>> +{
>> +	struct spi_offload *offload;
>> +	int ret;
>> +
>> +	if (!spi || !config)
>> +		return ERR_PTR(-EINVAL);
>> +
>> +	if (!spi->controller->get_offload)
>> +		return ERR_PTR(-ENODEV);
>> +
>> +	offload = spi->controller->get_offload(spi, config);
>> +	if (IS_ERR(offload))
>> +		return offload;
>> +
>> +	if (offload->spi)
>> +		return ERR_PTR(-EBUSY);
>> +
>> +	offload->spi = spi;
>> +	get_device(offload->provider_dev);
> 
> Isn't this redundant? From what I can tell, we're assuming that the spi controller
> (of the spi device) is the offload provider. Therefore, getting an extra reference
> for it does not really seems necessary. The device cannot go away without under the
> spi_device feet. If that could happen, then we would also need to take care about
> callback access and things like that. Going this way, it would also be arguable to
> have a try_module_get().
> 
> - Nuno Sá
> 
> 

Yes, you are right that we don't really need to take a reference to the device.
This was left over from when I made an implementation that assumed the offload
provider could be anything, not just a SPI controller.

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-24 14:04   ` Nuno Sá
@ 2024-10-24 15:02     ` David Lechner
  2024-10-25  6:29       ` Nuno Sá
  0 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-24 15:02 UTC (permalink / raw)
  To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On 10/24/24 9:04 AM, Nuno Sá wrote:
> On Wed, 2024-10-23 at 15:59 -0500, 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.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>
>> In previous versions, we locked the SPI bus when the hardware trigger
>> was enabled, but we found this to be too restrictive. In one use case,
>> to avoid a race condition, we need to enable the SPI offload via a
>> hardware trigger, then write a SPI message to the peripheral to place
>> it into a mode that will generate the trigger. If we did it the other
>> way around, we could miss the first trigger.
>>
>> Another likely use case will be enabling two offloads/triggers at one
>> time on the same device, e.g. a read trigger and a write trigger. So
>> the exclusive bus lock for a single trigger would be too restrictive in
>> this case too.
>>
>> So for now, I'm going with Nuno's suggestion to leave any locking up to
>> the individual controller driver. If we do find we need something more
>> generic in the future, we could add a new spi_bus_lock_exclusive() API
>> that causes spi_bus_lock() to fail instead of waiting and add "locked"
>> versions of trigger enable functions. This would allow a peripheral to
>> claim exclusive use of the bus indefinitely while still being able to
>> do any SPI messaging that it needs.
>>
>> 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       | 266 ++++++++++++++++++++++++++++++++++++++++
>>  include/linux/spi/spi-offload.h |  78 ++++++++++++
>>  2 files changed, 344 insertions(+)
>>
>> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
>> index c344cbf50bdb..2a1f9587f27a 100644
>> --- a/drivers/spi/spi-offload.c
>> +++ b/drivers/spi/spi-offload.c
>> @@ -9,12 +9,26 @@
>>  #include <linux/cleanup.h>
>>  #include <linux/device.h>
>>  #include <linux/export.h>
>> +#include <linux/list.h>
>>  #include <linux/mutex.h>
>> +#include <linux/of.h>
>>  #include <linux/property.h>
>>  #include <linux/spi/spi-offload.h>
>>  #include <linux/spi/spi.h>
>>  #include <linux/types.h>
>>  
>> +struct spi_offload_trigger {
>> +	struct list_head list;
>> +	struct device dev;
>> +	/* synchronizes calling ops and driver registration */
>> +	struct mutex lock;
>> +	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 instances
>>   * @dev: Device for devm purposes
>> @@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
>>  	return offload;
>>  }
>>  EXPORT_SYMBOL_GPL(devm_spi_offload_get);
>> +
>> +static void spi_offload_trigger_release(void *data)
>> +{
>> +	struct spi_offload_trigger *trigger = data;
>> +
>> +	guard(mutex)(&trigger->lock);
>> +	if (trigger->priv && trigger->ops->release)
>> +		trigger->ops->release(trigger->priv);
>> +
>> +	put_device(&trigger->dev);
>> +}
>> +
>> +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;
>> +	bool match = false;
>> +	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);
>> +
>> +	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) = args.fwnode;
>> +
>> +	guard(mutex)(&spi_offload_triggers_lock);
>> +
>> +	list_for_each_entry(trigger, &spi_offload_triggers, list) {
>> +		if (trigger->dev.fwnode != args.fwnode)
>> +			continue;
>> +
>> +		match = trigger->ops->match(trigger->priv, type, args.args,
>> args.nargs);
>> +		if (match)
>> +			break;
>> +	}
>> +
>> +	if (!match)
>> +		return ERR_PTR(-EPROBE_DEFER);
>> +
>> +	guard(mutex)(&trigger->lock);
>> +
>> +	if (!trigger->priv)
>> +		return ERR_PTR(-ENODEV);
> 
> This is a bit odd tbh. Not a real deal breaker for me but the typical pattern I would
> expect is for methods of the trigger to get a struct spi_offload_trigger opaque
> pointer. Then we provide a get_private kind of API for the private data. I guess you
> want to avoid that but IMO it makes for neater API instead of getting void pointers.

I was just trying to save a step of an extra call to get *priv
in each callback implementation, but yeah, no problem to change
it to something more "normal" looking.

> 
> Another thing is, can the above actually happen? We have the
> spi_offload_triggers_lock grabbed and we got a match so the trigger should not be
> able to go away (should block on the same lock).

The problem is that it could have gone away before we took the lock.

It could happen like this:

* Trigger driver registers trigger - sets *priv.
* SPI peripheral driver gets reference to trigger.
* Trigger driver unregisters trigger - removes *priv.
* SPI peripheral tries to call trigger function.

> 
>>
>> +struct spi_offload_trigger
>> +*devm_spi_offload_trigger_alloc(struct device *dev,
>> +				struct spi_offload_trigger_info *info)
>> +{
>> +	struct spi_offload_trigger *trigger;
>> +	int ret;
>> +
>> +	trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);
>> +	if (!trigger)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	device_initialize(&trigger->dev);
> 
> Do we really need the full struct device and the overhead of adding it to the driver
> core? AFAICT, we're using it only for refcouting so we could use a plain kref for
> that matter. It would make things simpler. Or do you envision an future usecase as
> this might matter? Like allowing userspace to set some controls on the trigger (I
> would expect to be done through consumers though)?

Agreed. We should not need a device at this point.

> 
>> +	trigger->dev.parent = info->parent;
>> +	trigger->dev.fwnode = info->fwnode;
>> +	trigger->dev.of_node = of_node_get(to_of_node(trigger->dev.fwnode));
>> +	trigger->dev.of_node_reused = true;
>> +	trigger->dev.release = spi_offload_trigger_dev_release;
>> +
>> +	mutex_init(&trigger->lock);
>> +	trigger->ops = info->ops;
>> +
>> +	ret = devm_add_action_or_reset(dev, spi_offload_trigger_put, trigger);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	ret = dev_set_name(&trigger->dev, "%s-%d", info->name, info->id);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	return trigger;
>> +}
>> +EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_alloc);
>> +
>> +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);
>> +
>> +	guard(mutex)(&trigger->lock);
>> +	trigger->priv = NULL;
> 
> nit: I guess this is a good as anything else but *ops could also be a good fit to
> nullify :)

I debated between the two. :-)

But if I change the priv handling like you suggest, I think ops will make
more sense here.


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-24 15:02     ` David Lechner
@ 2024-10-25  6:29       ` Nuno Sá
  0 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-25  6:29 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Thu, 2024-10-24 at 10:02 -0500, David Lechner wrote:
> On 10/24/24 9:04 AM, Nuno Sá wrote:
> > On Wed, 2024-10-23 at 15:59 -0500, 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.
> > > 
> > > Signed-off-by: David Lechner <dlechner@baylibre.com>
> > > ---
> > > 
> > > In previous versions, we locked the SPI bus when the hardware trigger
> > > was enabled, but we found this to be too restrictive. In one use case,
> > > to avoid a race condition, we need to enable the SPI offload via a
> > > hardware trigger, then write a SPI message to the peripheral to place
> > > it into a mode that will generate the trigger. If we did it the other
> > > way around, we could miss the first trigger.
> > > 
> > > Another likely use case will be enabling two offloads/triggers at one
> > > time on the same device, e.g. a read trigger and a write trigger. So
> > > the exclusive bus lock for a single trigger would be too restrictive in
> > > this case too.
> > > 
> > > So for now, I'm going with Nuno's suggestion to leave any locking up to
> > > the individual controller driver. If we do find we need something more
> > > generic in the future, we could add a new spi_bus_lock_exclusive() API
> > > that causes spi_bus_lock() to fail instead of waiting and add "locked"
> > > versions of trigger enable functions. This would allow a peripheral to
> > > claim exclusive use of the bus indefinitely while still being able to
> > > do any SPI messaging that it needs.
> > > 
> > > 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       | 266 ++++++++++++++++++++++++++++++++++++++++
> > >  include/linux/spi/spi-offload.h |  78 ++++++++++++
> > >  2 files changed, 344 insertions(+)
> > > 
> > > diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> > > index c344cbf50bdb..2a1f9587f27a 100644
> > > --- a/drivers/spi/spi-offload.c
> > > +++ b/drivers/spi/spi-offload.c
> > > @@ -9,12 +9,26 @@
> > >  #include <linux/cleanup.h>
> > >  #include <linux/device.h>
> > >  #include <linux/export.h>
> > > +#include <linux/list.h>
> > >  #include <linux/mutex.h>
> > > +#include <linux/of.h>
> > >  #include <linux/property.h>
> > >  #include <linux/spi/spi-offload.h>
> > >  #include <linux/spi/spi.h>
> > >  #include <linux/types.h>
> > >  
> > > +struct spi_offload_trigger {
> > > +	struct list_head list;
> > > +	struct device dev;
> > > +	/* synchronizes calling ops and driver registration */
> > > +	struct mutex lock;
> > > +	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 instances
> > >   * @dev: Device for devm purposes
> > > @@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device
> > > *dev,
> > >  	return offload;
> > >  }
> > >  EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> > > +
> > > +static void spi_offload_trigger_release(void *data)
> > > +{
> > > +	struct spi_offload_trigger *trigger = data;
> > > +
> > > +	guard(mutex)(&trigger->lock);
> > > +	if (trigger->priv && trigger->ops->release)
> > > +		trigger->ops->release(trigger->priv);
> > > +
> > > +	put_device(&trigger->dev);
> > > +}
> > > +
> > > +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;
> > > +	bool match = false;
> > > +	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);
> > > +
> > > +	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) =
> > > args.fwnode;
> > > +
> > > +	guard(mutex)(&spi_offload_triggers_lock);
> > > +
> > > +	list_for_each_entry(trigger, &spi_offload_triggers, list) {
> > > +		if (trigger->dev.fwnode != args.fwnode)
> > > +			continue;
> > > +
> > > +		match = trigger->ops->match(trigger->priv, type, args.args,
> > > args.nargs);
> > > +		if (match)
> > > +			break;
> > > +	}
> > > +
> > > +	if (!match)
> > > +		return ERR_PTR(-EPROBE_DEFER);
> > > +
> > > +	guard(mutex)(&trigger->lock);
> > > +
> > > +	if (!trigger->priv)
> > > +		return ERR_PTR(-ENODEV);
> > 
> > This is a bit odd tbh. Not a real deal breaker for me but the typical pattern I
> > would
> > expect is for methods of the trigger to get a struct spi_offload_trigger opaque
> > pointer. Then we provide a get_private kind of API for the private data. I guess
> > you
> > want to avoid that but IMO it makes for neater API instead of getting void
> > pointers.
> 
> I was just trying to save a step of an extra call to get *priv
> in each callback implementation, but yeah, no problem to change
> it to something more "normal" looking.

Yeah, I figured that but I guess any of these paths are fastpaths anyways... 
> 
> > 
> > Another thing is, can the above actually happen? We have the
> > spi_offload_triggers_lock grabbed and we got a match so the trigger should not be
> > able to go away (should block on the same lock).
> 
> The problem is that it could have gone away before we took the lock.
> 
> It could happen like this:
> 
> * Trigger driver registers trigger - sets *priv.
> * SPI peripheral driver gets reference to trigger.
> * Trigger driver unregisters trigger - removes *priv.
> * SPI peripheral tries to call trigger function.
> 

Ah I see... we're using scoped_guard() in the unregister path.

- Nuno Sá
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver
  2024-10-23 20:59 ` [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver David Lechner
@ 2024-10-25 12:07   ` Nuno Sá
  2024-10-25 16:28     ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 12:07 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

Hi David,

Looks mostly good... Just one minor comments from me.

On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
> Add a new driver for a generic PWM trigger for SPI offloads.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> v4 changes: new patch in v4
> ---
>  drivers/spi/Kconfig                   |  12 +++
>  drivers/spi/Makefile                  |   3 +
>  drivers/spi/spi-offload-trigger-pwm.c | 169 ++++++++++++++++++++++++++++++++++
>  3 files changed, 184 insertions(+)
> 
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index d65074b85f62..50d04fa317b7 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -1286,4 +1286,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 6a470eb475a2..3a76b9c61486 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -161,3 +161,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 000000000000..ffb0bf75cace
> --- /dev/null
> +++ b/drivers/spi/spi-offload-trigger-pwm.c
> @@ -0,0 +1,169 @@
> +// 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/spi-offload.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(void *priv,
> +					  enum spi_offload_trigger_type type,
> +					  u64 *args, u32 nargs)
> +{
> +	if (nargs)
> +		return false;
> +
> +	return type == SPI_OFFLOAD_TRIGGER_PERIODIC;

Hmm will we ever be in a place where a trigger provide might have multiple types? If
so, then I'm mostly fine with this match() callback. But we could still avoid it if
we use a bitmask for trigger types and having any trigger provider to give the
supported types. Then the core could pretty much do the match between the requested
trigger type and what the provider supports.

> +}
> +
> +static int spi_offload_trigger_pwm_validate(void *priv,
> +					    struct spi_offload_trigger_config
> *config)
> +{
> +	struct spi_offload_trigger_pwm_state *st = priv;
> +	struct spi_offload_trigger_periodic *periodic = &config->periodic;
> +	struct pwm_waveform wf = { };
> +	int ret;
> +
> +	if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
> +		return -EINVAL;

Checking the above every time seems redundant to me. We should match it once during
the trigger request and then just use that trigger type. Otherwise I'm not seeing the
point of the match() callback.

> +
> +	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(void *priv,
> +					  struct spi_offload_trigger_config
> *config)
> +{
> +	struct spi_offload_trigger_pwm_state *st = priv;
> +	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(void *priv)
> +{
> +	struct spi_offload_trigger_pwm_state *st = priv;
> +	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 = {
> +		.name = "trigger",

pwm-trigger or trigger-pwm?

> +		.id = 0,

nit: Not really needed

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs
  2024-10-23 20:59 ` [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs David Lechner
@ 2024-10-25 12:24   ` Nuno Sá
  0 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 12:24 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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>
> ---
> 
> 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       | 76 +++++++++++++++++++++++++++++++++++++++++
>  drivers/spi/spi.c               | 10 ++++++
>  include/linux/spi/spi-offload.h | 24 +++++++++++++
>  include/linux/spi/spi.h         |  3 ++
>  4 files changed, 113 insertions(+)
> 
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index 2a1f9587f27a..dd4cb3c2e985 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -8,6 +8,7 @@
>  
>  #include <linux/cleanup.h>
>  #include <linux/device.h>
> +#include <linux/dmaengine.h>
>  #include <linux/export.h>
>  #include <linux/list.h>
>  #include <linux/mutex.h>
> @@ -282,6 +283,81 @@ 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);
> +}
> +
> +/**
> + * spi_offload_tx_stream_request_dma_chan_info - Get the DMA channel info for the
> TX stream
> + * @spi: SPI device
> + * @id: Function ID if SPI device uses more than one offload or NULL.
> + *
> + * This is the DMA channel that will provide data to transfers that use the
> + * %SPI_OFFLOAD_XFER_TX_STREAM offload flag.
> + *
> + * The caller is responsible for calling spi_offload_free_dma_chan_info() on the
> + * returned pointer.

I guess the above does not make sense now. But I would still document (just to really
make it explicit) that the lifetime of the DMA channel is effectively being handed
over to the consumer.



- Nuno Sá

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties
  2024-10-23 20:59 ` [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
@ 2024-10-25 12:26   ` Nuno Sá
  0 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 12:26 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
> The AXI SPI Engine has support for hardware offloading capabilities.
> This includes a connection to a DMA controller for streaming RX data
> and a trigger input for starting execution of the SPI message programmed
> in the offload.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 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           | 22 ++++++++++++++++++++++
>  1 file changed, 22 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
> b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
> index d48faa42d025..5281b4871209 100644
> --- a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
> +++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
> @@ -41,6 +41,24 @@ 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.
> +    $ref: /schemas/types.yaml#/definitions/phandle-array
> +
> +  dmas:
> +    description:
> +      DMA channels connected to the output stream interface of an offload
> instance.
> +    minItems: 1
> +    maxItems: 32
> +
> +  dma-names:
> +    minItems: 1
> +    maxItems: 32
> +    items:
> +      pattern: "^offload(?:[12]?[0-9]|3[01])-rx$"

I think the core is already capable of tx offload? If so, we could already have that
in the bindings.

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
  2024-10-24 13:27   ` Nuno Sá
@ 2024-10-25 12:59   ` Nuno Sá
  2024-10-25 16:39     ` Nuno Sá
  2024-10-26 15:05   ` Jonathan Cameron
  2024-10-30 15:55   ` Mark Brown
  3 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 12:59 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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
> callback 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 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> 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.
> ---
>  drivers/spi/Kconfig             |   3 ++
>  drivers/spi/Makefile            |   1 +
>  drivers/spi/spi-offload.c       | 104 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi-offload.h |  64 +++++++++++++++++++++++++
>  include/linux/spi/spi.h         |  16 +++++++
>  5 files changed, 188 insertions(+)
> 
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 823797217404..d65074b85f62 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 a9b1bc259b68..6a470eb475a2 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 000000000000..c344cbf50bdb
> --- /dev/null
> +++ b/drivers/spi/spi-offload.c
> @@ -0,0 +1,104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#define DEFAULT_SYMBOL_NAMESPACE SPI_OFFLOAD
> +
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/spi/spi-offload.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +/**
> + * devm_spi_offload_alloc() - Allocate offload instances
> + * @dev: Device for devm purposes
> + * @num_offloads: Number of offloads to allocate
> + * @priv_size: Size of private data to allocate for each offload
> + *
> + * Offload providers should use this to allocate offload instances.
> + *
> + * Return: Pointer to array of offloads or error on failure.
> + */
> +struct spi_offload *devm_spi_offload_alloc(struct device *dev,
> +					   size_t num_offloads,
> +					   size_t priv_size)
> +{
> +	struct spi_offload *offloads;
> +	void *privs;
> +	size_t i;
> +
> +	offloads = devm_kcalloc(dev, num_offloads, sizeof(*offloads) + priv_size,
> +				GFP_KERNEL);
> +	if (!offloads)
> +		return ERR_PTR(-ENOMEM);
> +
> +	privs = (void *)(offloads + num_offloads);
> +
> +	for (i = 0; i < num_offloads; i++) {
> +		struct spi_offload *offload = offloads + i;
> +		void *priv = privs + i * priv_size;
> +
> +		offload->provider_dev = dev;
> +		offload->priv = priv;
> +	}

Hmm looking at the spi_engine implementation, got me thinking about this. I think
like this we might mess up with natural alignments which can be pretty nasty. Maybe
do something like devres [1] (I guess we do not need the flex array though)?

Now that I also look at this better, I would not do it like this. I would keep it
simple and just allocate one spi_offload object and be done with it. In the future
and when we actually support more than one instance you could introduce a
devm_spi_offload_alloc_array() variant and I'm still not sure if it's that useful.
Anyways this is just personal preference I guess...

[1]: https://elixir.bootlin.com/linux/v6.12-rc4/source/drivers/base/devres.c#L35

- Nuno Sá


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support
  2024-10-23 20:59 ` [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support David Lechner
@ 2024-10-25 13:09   ` Nuno Sá
  2024-10-25 16:35     ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 13:09 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> 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 | 273 ++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 268 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 50d04fa317b7..af3143ec5245 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -168,6 +168,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 2d24d762b5bd..1710847d81a1 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,8 +17,10 @@
>  #include <linux/module.h>
>  #include <linux/overflow.h>
>  #include <linux/platform_device.h>
> +#include <linux/spi/spi-offload.h>
>  #include <linux/spi/spi.h>
>  

...

> +#define SPI_ENGINE_REG_OFFLOAD_MEM_ADDR_WIDTH	0x10
>  #define SPI_ENGINE_REG_RESET			0x40
>  
>  #define SPI_ENGINE_REG_INT_ENABLE		0x80
> @@ -23,6 +28,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
> @@ -33,10 +39,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)
> @@ -78,6 +98,14 @@
>  #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
> +
> +#define SPI_ENGINE_OFFLOAD_CAPS (SPI_OFFLOAD_CAP_TRIGGER | \
> +				 SPI_OFFLOAD_CAP_TX_STATIC_DATA | \
> +				 SPI_OFFLOAD_CAP_RX_STREAM_DMA)
> +
>  struct spi_engine_program {
>  	unsigned int length;
>  	uint16_t instructions[] __counted_by(length);
> @@ -105,6 +133,16 @@ struct spi_engine_message_state {
>  	uint8_t *rx_buf;
>  };
>  
> +enum {
> +	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;
> @@ -117,6 +155,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 *offloads;
> +	unsigned int num_offloads;
>  };
>  
>  static void spi_engine_program_add_cmd(struct spi_engine_program *p,
> @@ -164,7 +207,7 @@ static void spi_engine_gen_xfer(struct spi_engine_program *p,
> bool dry,
>  
>  		if (xfer->tx_buf)
>  			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,
> @@ -220,16 +263,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,
> @@ -544,11 +595,94 @@ 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) {
> +		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_sdo_mem_size)
> +		return -EINVAL;
> +
> +	if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags))
> +		return -EBUSY;
> +

This is odd. Any special reason for using this with aquire - release semantics? Can
optimize() and unoptimize() run concurrently? Because if they can this does not give
us mutual exclusion and we really need to do what we're doing with kind of stuff :)

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-23 20:59 ` [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() David Lechner
@ 2024-10-25 13:24   ` Nuno Sá
  2024-10-25 16:42     ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 13:24 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

I still need to look better at this but I do have one though already :)

On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
> Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
> ---
> 
> v4 changes:
> * This replaces "iio: buffer-dmaengine: generalize requesting DMA channel"
> ---
>  drivers/iio/buffer/industrialio-buffer-dmaengine.c | 107 +++++++++++++++------
>  include/linux/iio/buffer-dmaengine.h               |   5 +
>  2 files changed, 81 insertions(+), 31 deletions(-)
> 
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> index 054af21dfa65..602cb2e147a6 100644
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -33,6 +33,7 @@ struct dmaengine_buffer {
>  	struct iio_dma_buffer_queue queue;
>  
>  	struct dma_chan *chan;
> +	bool owns_chan;
>  	struct list_head active;
>  
>  	size_t align;
> @@ -216,28 +217,23 @@ static const struct iio_dev_attr
> *iio_dmaengine_buffer_attrs[] = {
>   * 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,
> +						     bool owns_chan)
>  {
>  	struct dmaengine_buffer *dmaengine_buffer;
>  	unsigned int width, src_width, dest_width;
>  	struct dma_slave_caps caps;
> -	struct dma_chan *chan;
>  	int 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;
> +	if (!dmaengine_buffer) {
> +		ret = -ENOMEM;
> +		goto err_release;
>  	}
>  
>  	ret = dma_get_slave_caps(chan, &caps);
>  	if (ret < 0)
> -		goto err_release;
> +		goto err_free;
>  
>  	/* Needs to be aligned to the maximum of the minimums */
>  	if (caps.src_addr_widths)
> @@ -252,6 +248,7 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct
> device *dev,
>  
>  	INIT_LIST_HEAD(&dmaengine_buffer->active);
>  	dmaengine_buffer->chan = chan;
> +	dmaengine_buffer->owns_chan = owns_chan;
>  	dmaengine_buffer->align = width;
>  	dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev);
>  
> @@ -263,10 +260,12 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct
> device *dev,
>  
>  	return &dmaengine_buffer->queue.buffer;
>  
> -err_release:
> -	dma_release_channel(chan);
>  err_free:
>  	kfree(dmaengine_buffer);
> +err_release:
> +	if (owns_chan)
> +		dma_release_channel(chan);
> +
>  	return ERR_PTR(ret);
>  }
>  
> @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
>  		iio_buffer_to_dmaengine_buffer(buffer);
>  
>  	iio_dma_buffer_exit(&dmaengine_buffer->queue);
> -	dma_release_channel(dmaengine_buffer->chan);
> -
>  	iio_buffer_put(buffer);
> +
> +	if (dmaengine_buffer->owns_chan)
> +		dma_release_channel(dmaengine_buffer->chan);

Not sure if I agree much with this owns_chan flag. The way I see it, we should always
handover the lifetime of the DMA channel to the IIO DMA framework. Note that even the
device you pass in for both requesting the channel of the spi_offload  and for
setting up the DMA buffer is the same (and i suspect it will always be) so I would
not go with the trouble. And with this assumption we could simplify a bit more the
spi implementation.

And not even related but I even suspect the current implementation could be
problematic. Basically I'm suspecting that the lifetime of the DMA channel should be
attached to the lifetime of the iio_buffer. IOW, we should only release the channel
in iio_dmaengine_buffer_release() - in which case the current implementation with the
spi_offload would also be buggy.

But bah, the second point is completely theoretical and likely very hard to reproduce
in real life (if reproducible at all - for now it's only something I suspect)

- Nuno Sá 



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver
  2024-10-25 12:07   ` Nuno Sá
@ 2024-10-25 16:28     ` David Lechner
  2024-10-28 13:47       ` Nuno Sá
  0 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-25 16:28 UTC (permalink / raw)
  To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On 10/25/24 7:07 AM, Nuno Sá wrote:
> Hi David,
> 
> Looks mostly good... Just one minor comments from me.
> 
> On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
>> Add a new driver for a generic PWM trigger for SPI offloads.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>

...

>> +static bool spi_offload_trigger_pwm_match(void *priv,
>> +					  enum spi_offload_trigger_type type,
>> +					  u64 *args, u32 nargs)
>> +{
>> +	if (nargs)
>> +		return false;
>> +
>> +	return type == SPI_OFFLOAD_TRIGGER_PERIODIC;
> 
> Hmm will we ever be in a place where a trigger provide might have multiple types? If
> so, then I'm mostly fine with this match() callback. But we could still avoid it if
> we use a bitmask for trigger types and having any trigger provider to give the
> supported types. Then the core could pretty much do the match between the requested
> trigger type and what the provider supports.

We will still need some callback though to handle drivers that use
phandle args.

> 
>> +}
>> +
>> +static int spi_offload_trigger_pwm_validate(void *priv,
>> +					    struct spi_offload_trigger_config
>> *config)
>> +{
>> +	struct spi_offload_trigger_pwm_state *st = priv;
>> +	struct spi_offload_trigger_periodic *periodic = &config->periodic;
>> +	struct pwm_waveform wf = { };
>> +	int ret;
>> +
>> +	if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
>> +		return -EINVAL;
> 
> Checking the above every time seems redundant to me. We should match it once during
> the trigger request and then just use that trigger type. Otherwise I'm not seeing the
> point of the match() callback.
> 

Here it is validating struct spi_offload_trigger_config has the right
type, which is needed before we can safely trust that the correct
union member was used in that struct. So it has a different purpose from
the match check.


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support
  2024-10-25 13:09   ` Nuno Sá
@ 2024-10-25 16:35     ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-25 16:35 UTC (permalink / raw)
  To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On 10/25/24 8:09 AM, Nuno Sá wrote:
> On Wed, 2024-10-23 at 15:59 -0500, 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.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>

...

>> +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) {
>> +		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_sdo_mem_size)
>> +		return -EINVAL;
>> +
>> +	if (test_and_set_bit_lock(SPI_ENGINE_OFFLOAD_FLAG_PREPARED, &priv->flags))
>> +		return -EBUSY;
>> +
> 
> This is odd. Any special reason for using this with aquire - release semantics? Can
> optimize() and unoptimize() run concurrently? Because if they can this does not give
> us mutual exclusion and we really need to do what we're doing with kind of stuff :)
> 
> - Nuno Sá
> 
> 

This looks like another leftover from an in-between revision that
didn't get fully cleaned up. I will reconsider what is need here.

But to answer the question, strictly speaking, there isn't anything
to prevent two concurrent calls spi_optimize_message() with different
messages but the same offload instance.

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-25 12:59   ` Nuno Sá
@ 2024-10-25 16:39     ` Nuno Sá
  0 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 16:39 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

Oct 25, 2024 14:59:20 Nuno Sá <noname.nuno@gmail.com>:

> On Wed, 2024-10-23 at 15:59 -0500, 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
>> callback 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 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.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>
>> 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.
>> ---
>>  drivers/spi/Kconfig             |   3 ++
>>  drivers/spi/Makefile            |   1 +
>>  drivers/spi/spi-offload.c       | 104 ++++++++++++++++++++++++++++++++++++++++
>>  include/linux/spi/spi-offload.h |  64 +++++++++++++++++++++++++
>>  include/linux/spi/spi.h         |  16 +++++++
>>  5 files changed, 188 insertions(+)
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 823797217404..d65074b85f62 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 a9b1bc259b68..6a470eb475a2 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 000000000000..c344cbf50bdb
>> --- /dev/null
>> +++ b/drivers/spi/spi-offload.c
>> @@ -0,0 +1,104 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2024 Analog Devices Inc.
>> + * Copyright (C) 2024 BayLibre, SAS
>> + */
>> +
>> +#define DEFAULT_SYMBOL_NAMESPACE SPI_OFFLOAD
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/device.h>
>> +#include <linux/export.h>
>> +#include <linux/mutex.h>
>> +#include <linux/property.h>
>> +#include <linux/spi/spi-offload.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/types.h>
>> +
>> +/**
>> + * devm_spi_offload_alloc() - Allocate offload instances
>> + * @dev: Device for devm purposes
>> + * @num_offloads: Number of offloads to allocate
>> + * @priv_size: Size of private data to allocate for each offload
>> + *
>> + * Offload providers should use this to allocate offload instances.
>> + *
>> + * Return: Pointer to array of offloads or error on failure.
>> + */
>> +struct spi_offload *devm_spi_offload_alloc(struct device *dev,
>> +                      size_t num_offloads,
>> +                      size_t priv_size)
>> +{
>> +   struct spi_offload *offloads;
>> +   void *privs;
>> +   size_t i;
>> +
>> +   offloads = devm_kcalloc(dev, num_offloads, sizeof(*offloads) + priv_size,
>> +               GFP_KERNEL);
>> +   if (!offloads)
>> +       return ERR_PTR(-ENOMEM);
>> +
>> +   privs = (void *)(offloads + num_offloads);
>> +
>> +   for (i = 0; i < num_offloads; i++) {
>> +       struct spi_offload *offload = offloads + i;
>> +       void *priv = privs + i * priv_size;
>> +
>> +       offload->provider_dev = dev;
>> +       offload->priv = priv;
>> +   }
>
> Hmm looking at the spi_engine implementation, got me thinking about this. I think
> like this we might mess up with natural alignments which can be pretty nasty. Maybe
> do something like devres [1] (I guess we do not need the flex array though)?
>

Actually we should use the flex array or something similar to what IIO does.

PS: just trying some email client from my phone so hopefully this is not too messed up.

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-25 13:24   ` Nuno Sá
@ 2024-10-25 16:42     ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-25 16:42 UTC (permalink / raw)
  To: Nuno Sá, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On 10/25/24 8:24 AM, Nuno Sá wrote:
> I still need to look better at this but I do have one though already :)
> 
> On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
>> Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
>> ---
>>
>> v4 changes:
>> * This replaces "iio: buffer-dmaengine: generalize requesting DMA channel"
>> ---

...

>> @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
>>  		iio_buffer_to_dmaengine_buffer(buffer);
>>  
>>  	iio_dma_buffer_exit(&dmaengine_buffer->queue);
>> -	dma_release_channel(dmaengine_buffer->chan);
>> -
>>  	iio_buffer_put(buffer);
>> +
>> +	if (dmaengine_buffer->owns_chan)
>> +		dma_release_channel(dmaengine_buffer->chan);
> 
> Not sure if I agree much with this owns_chan flag. The way I see it, we should always
> handover the lifetime of the DMA channel to the IIO DMA framework. Note that even the
> device you pass in for both requesting the channel of the spi_offload  and for
> setting up the DMA buffer is the same (and i suspect it will always be) so I would
> not go with the trouble. And with this assumption we could simplify a bit more the
> spi implementation.

I tried something like this in v3 but Jonathan didn't seem to like it.

https://lore.kernel.org/all/20240727144303.4a8604cb@jic23-huawei/

> 
> And not even related but I even suspect the current implementation could be
> problematic. Basically I'm suspecting that the lifetime of the DMA channel should be
> attached to the lifetime of the iio_buffer. IOW, we should only release the channel
> in iio_dmaengine_buffer_release() - in which case the current implementation with the
> spi_offload would also be buggy.

The buffer can outlive the iio device driver that created the buffer?

> 
> But bah, the second point is completely theoretical and likely very hard to reproduce
> in real life (if reproducible at all - for now it's only something I suspect)
> 
> - Nuno Sá 
> 
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
@ 2024-10-25 18:40 Nuno Sá
  2024-10-26 15:48 ` Jonathan Cameron
  0 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-25 18:40 UTC (permalink / raw)
  To: David Lechner
  Cc: Mark Brown, Jonathan Cameron, 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

Oct 25, 2024 18:42:02 David Lechner <dlechner@baylibre.com>:

> On 10/25/24 8:24 AM, Nuno Sá wrote:
>> I still need to look better at this but I do have one though already 
>> :)
>>
>> On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
>>> Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
>>> ---
>>>
>>> v4 changes:
>>> * This replaces "iio: buffer-dmaengine: generalize requesting DMA 
>>> channel"
>>> ---
>
> ...
>
>>> @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct 
>>> iio_buffer *buffer)
>>>         iio_buffer_to_dmaengine_buffer(buffer);
>>>  
>>>     iio_dma_buffer_exit(&dmaengine_buffer->queue);
>>> -   dma_release_channel(dmaengine_buffer->chan);
>>> -
>>>     iio_buffer_put(buffer);
>>> +
>>> +   if (dmaengine_buffer->owns_chan)
>>> +       dma_release_channel(dmaengine_buffer->chan);
>>
>> Not sure if I agree much with this owns_chan flag. The way I see it, 
>> we should always
>> handover the lifetime of the DMA channel to the IIO DMA framework. 
>> Note that even the
>> device you pass in for both requesting the channel of the spi_offload  
>> and for
>> setting up the DMA buffer is the same (and i suspect it will always 
>> be) so I would
>> not go with the trouble. And with this assumption we could simplify a 
>> bit more the
>> spi implementation.
>
> I tried something like this in v3 but Jonathan didn't seem to like it.
>
> https://lore.kernel.org/all/20240727144303.4a8604cb@jic23-huawei/
>
>>
>> And not even related but I even suspect the current implementation 
>> could be
>> problematic. Basically I'm suspecting that the lifetime of the DMA 
>> channel should be
>> attached to the lifetime of the iio_buffer. IOW, we should only 
>> release the channel
>> in iio_dmaengine_buffer_release() - in which case the current 
>> implementation with the
>> spi_offload would also be buggy.
>
> The buffer can outlive the iio device driver that created the buffer?

Yes, it can as the IIO device itself. In case a userspace app has an open 
FD for the buffer chardev, we get a reference that is only released when 
the FD is closed (which can outlive the device behind bound to its 
driver). That is why we nullify indio_dev->info and check for it on the 
read() and write() fops.

FWIW, I raised concerns about this in the past (as we don't have any lock 
in those paths) but Jonathan rightfully wanted to see a real race. And I 
was too lazy to try and reproduce one but I'm still fairly sure we have 
theoretical (at least) races in those paths. And one of them could be (I 
think) concurrently hitting a DMA submit block while the device is being 
unbound. In that case the DMA chan would be already released and we could 
still try to initiate a transfer. I did not check if that would crash or 
something but it should still not happen.

- Nuno Sá

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
  2024-10-24 13:27   ` Nuno Sá
  2024-10-25 12:59   ` Nuno Sá
@ 2024-10-26 15:05   ` Jonathan Cameron
  2024-11-11 17:14     ` David Lechner
  2024-10-30 15:55   ` Mark Brown
  3 siblings, 1 reply; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:05 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, 23 Oct 2024 15:59:09 -0500
David Lechner <dlechner@baylibre.com> 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
> callback 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 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
A few minor additions to what has already been raised.

Jonathan

> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> new file mode 100644
> index 000000000000..c344cbf50bdb
> --- /dev/null
> +++ b/drivers/spi/spi-offload.c
> @@ -0,0 +1,104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 Analog Devices Inc.
> + * Copyright (C) 2024 BayLibre, SAS
> + */
> +
> +#define DEFAULT_SYMBOL_NAMESPACE SPI_OFFLOAD
> +
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
?  If it is needed for later patch bring it in then.


> +#include <linux/spi/spi-offload.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
>
> +
> +/**
> + * 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_offload *offload;
> +	int ret;
> +
> +	if (!spi || !config)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (!spi->controller->get_offload)
> +		return ERR_PTR(-ENODEV);
> +
> +	offload = spi->controller->get_offload(spi, config);

Why let this return an offload that is already in use?
Maybe make that a problem for the spi controller
Seems odd to pass it spi then set it later.

I.e. have this return ERR_PTR(-EBUSY);


> +	if (IS_ERR(offload))
> +		return offload;
> +
> +	if (offload->spi)
> +		return ERR_PTR(-EBUSY);
> +
> +	offload->spi = spi;
> +	get_device(offload->provider_dev);
> +
> +	ret = devm_add_action_or_reset(dev, spi_offload_put, offload);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return offload;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_get);

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
  2024-10-24 14:04   ` Nuno Sá
@ 2024-10-26 15:14   ` Jonathan Cameron
  2024-10-28 13:53   ` Nuno Sá
  2 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:14 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, 23 Oct 2024 15:59:10 -0500
David Lechner <dlechner@baylibre.com> 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
A few generic comments inline.

Jonathan

> ---
> 
> In previous versions, we locked the SPI bus when the hardware trigger
> was enabled, but we found this to be too restrictive. In one use case,
> to avoid a race condition, we need to enable the SPI offload via a
> hardware trigger, then write a SPI message to the peripheral to place
> it into a mode that will generate the trigger. If we did it the other
> way around, we could miss the first trigger.
> 
> Another likely use case will be enabling two offloads/triggers at one
> time on the same device, e.g. a read trigger and a write trigger. So
> the exclusive bus lock for a single trigger would be too restrictive in
> this case too.
> 
> So for now, I'm going with Nuno's suggestion to leave any locking up to
> the individual controller driver. If we do find we need something more
> generic in the future, we could add a new spi_bus_lock_exclusive() API
> that causes spi_bus_lock() to fail instead of waiting and add "locked"
> versions of trigger enable functions. This would allow a peripheral to
> claim exclusive use of the bus indefinitely while still being able to
> do any SPI messaging that it needs.
> 
> 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       | 266 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi-offload.h |  78 ++++++++++++
>  2 files changed, 344 insertions(+)
> 
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index c344cbf50bdb..2a1f9587f27a 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -9,12 +9,26 @@
>  #include <linux/cleanup.h>
>  #include <linux/device.h>
>  #include <linux/export.h>
> +#include <linux/list.h>
>  #include <linux/mutex.h>
> +#include <linux/of.h>
>  #include <linux/property.h>
>  #include <linux/spi/spi-offload.h>
>  #include <linux/spi/spi.h>
>  #include <linux/types.h>
>  
> +struct spi_offload_trigger {
> +	struct list_head list;
> +	struct device dev;
> +	/* synchronizes calling ops and driver registration */
> +	struct mutex lock;
> +	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 instances
>   * @dev: Device for devm purposes
> @@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
>  	return offload;
>  }
>  EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> +
> +static void spi_offload_trigger_release(void *data)
> +{
> +	struct spi_offload_trigger *trigger = data;
> +
> +	guard(mutex)(&trigger->lock);
> +	if (trigger->priv && trigger->ops->release)
> +		trigger->ops->release(trigger->priv);
> +
> +	put_device(&trigger->dev);
> +}
> +
> +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;
> +	bool match = false;
> +	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);
> +

> +	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) = args.fwnode;
I'm not fond of __free usage like this because code could get added above
this line and it wouldn't be obvious that it doesn't release the handle.

Annoying though it is, maybe just do manual release of the fwnode.  Ora
factor out the next chunk to a helper function so you can just put the fwnode after
that is called.


> +
> +	guard(mutex)(&spi_offload_triggers_lock);
> +
> +	list_for_each_entry(trigger, &spi_offload_triggers, list) {
> +		if (trigger->dev.fwnode != args.fwnode)
> +			continue;
> +
> +		match = trigger->ops->match(trigger->priv, type, args.args, args.nargs);
> +		if (match)
> +			break;
> +	}
> +
> +	if (!match)
> +		return ERR_PTR(-EPROBE_DEFER);
> +
> +	guard(mutex)(&trigger->lock);
> +
> +	if (!trigger->priv)
> +		return ERR_PTR(-ENODEV);
> +
> +	if (trigger->ops->request) {
> +		ret = trigger->ops->request(trigger->priv, type, args.args, args.nargs);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	}
> +
> +	get_device(&trigger->dev);
> +
> +	ret = devm_add_action_or_reset(dev, spi_offload_trigger_release, trigger);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return trigger;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_get);

> +int devm_spi_offload_trigger_register(struct device *dev,
> +				      struct spi_offload_trigger *trigger,
> +				      void *priv)
> +{
> +	int ret;
> +
> +	ret = device_add(&trigger->dev);
> +	if (ret)
> +		return ret;
> +
> +	trigger->priv = priv;
> +
> +	guard(mutex)(&spi_offload_triggers_lock);
> +	list_add_tail(&trigger->list, &spi_offload_triggers);
> +
> +	ret = devm_add_action_or_reset(dev, spi_offload_trigger_unregister, trigger);
I guess there may be more here later in the series, but if not.

	return devm_add_...

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(devm_spi_offload_trigger_register);

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-23 20:59 ` [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger David Lechner
@ 2024-10-26 15:18   ` Jonathan Cameron
  2024-10-27  0:20     ` David Lechner
                       ` (2 more replies)
  0 siblings, 3 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:18 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, 23 Oct 2024 15:59:12 -0500
David Lechner <dlechner@baylibre.com> wrote:

> Add a new binding for using a PWM signal as a trigger for SPI offloads.

I don't have a better suggestion for this, but it does smell rather like
other bridge binding (iio-hwmon for example) where we have had push back on
representing something that doesn't really exist but is just a way to
tie two bits of hardware together. Those kind of exist because we snuck
them in a long time back when no one was paying attention.

So this one may need more explanation and justification and I'd definitely
like some DT maintainer review on this at a fairly early stage!
(might have happened in earlier reviews but it has been a while so I've
forgotten if it did)

Jonathan


> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> v4 changes: new patch in v4
> ---
>  .../devicetree/bindings/spi/trigger-pwm.yaml       | 39 ++++++++++++++++++++++
>  1 file changed, 39 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/spi/trigger-pwm.yaml b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
> new file mode 100644
> index 000000000000..987638aa4732
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
> @@ -0,0 +1,39 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/spi/trigger-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Generic SPI offload trigger using PWM
> +
> +description: Remaps a PWM channel as a trigger source.
> +
> +maintainers:
> +  - David Lechner <dlechner@baylibre.com>
> +
> +$ref: /schemas/spi/trigger-source.yaml#
> +
> +properties:
> +  compatible:
> +    const: trigger-pwm
> +
> +  '#trigger-source-cells':
> +    const: 0
> +
> +  pwms:
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +  - '#trigger-source-cells'
> +  - pwms
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    trigger {
> +        compatible = "trigger-pwm";
> +        #trigger-source-cells = <0>;
> +        pwms = <&pwm 0 1000000 0>;
> +    };
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext
  2024-10-23 20:59 ` [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext David Lechner
@ 2024-10-26 15:29   ` Jonathan Cameron
  0 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:29 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, 23 Oct 2024 15:59:17 -0500
David Lechner <dlechner@baylibre.com> wrote:

> The iio_dmaengine_buffer_setup_ext() function is public and should be
> documented. Also, while touching this, fix the description of @dev in
> related functions. @dev does not strictly have to be the parent of the
> IIO device. It is only passed to dma_request_chan() so strictly
> speaking, it can be any device that is a valid DMA channel consumer.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> v4 changes:
> * This patch is new in v4.
> 
> Jonathan, I think this patch stands on its own if you want to take it
> earlier than the rest of this series.
I may do at some point, but want a few more eyes on it first so let's leave
it here for now.

Seems fine to me.


> ---
>  drivers/iio/buffer/industrialio-buffer-dmaengine.c | 19 +++++++++++++++++--
>  1 file changed, 17 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> index 19af1caf14cd..054af21dfa65 100644
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -206,7 +206,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
>  
>  /**
>   * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
> - * @dev: Parent device for the buffer
> + * @dev: DMA channel consumer device
>   * @channel: DMA channel name, typically "rx".
>   *
>   * This allocates a new IIO buffer which internally uses the DMAengine framework
> @@ -288,6 +288,21 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
>  }
>  EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER);
>  
> +/**
> + * iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
> + * @dev: DMA channel consumer device
> + * @indio_dev: IIO device to which to attach this buffer.
> + * @channel: DMA channel name, typically "rx".
> + * @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.
> + *
> + * Once done using the buffer iio_dmaengine_buffer_free() should be used to
> + * release it.
> + */
>  struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
>  						  struct iio_dev *indio_dev,
>  						  const char *channel,
> @@ -321,7 +336,7 @@ static void __devm_iio_dmaengine_buffer_free(void *buffer)
>  
>  /**
>   * devm_iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
> - * @dev: Parent device for the buffer
> + * @dev: Device for devm ownership and DMA channel consumer device
>   * @indio_dev: IIO device to which to attach this buffer.
>   * @channel: DMA channel name, typically "rx".
>   * @dir: Direction of buffer (in or out)
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-25 18:40 [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() Nuno Sá
@ 2024-10-26 15:48 ` Jonathan Cameron
  2024-10-28 11:08   ` Nuno Sá
  0 siblings, 1 reply; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:48 UTC (permalink / raw)
  To: Nuno Sá
  Cc: David Lechner, 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 Fri, 25 Oct 2024 20:40:42 +0200 (GMT+02:00)
Nuno Sá <noname.nuno@gmail.com> wrote:

> Oct 25, 2024 18:42:02 David Lechner <dlechner@baylibre.com>:
> 
> > On 10/25/24 8:24 AM, Nuno Sá wrote:  
> >> I still need to look better at this but I do have one though already 
> >> :)
> >>
> >> On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:  
> >>> Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
> >>> ---
> >>>
> >>> v4 changes:
> >>> * This replaces "iio: buffer-dmaengine: generalize requesting DMA 
> >>> channel"
> >>> ---  
> >
> > ...
> >  
> >>> @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct 
> >>> iio_buffer *buffer)
> >>>         iio_buffer_to_dmaengine_buffer(buffer);
> >>>  
> >>>     iio_dma_buffer_exit(&dmaengine_buffer->queue);
> >>> -   dma_release_channel(dmaengine_buffer->chan);
> >>> -
> >>>     iio_buffer_put(buffer);
> >>> +
> >>> +   if (dmaengine_buffer->owns_chan)
> >>> +       dma_release_channel(dmaengine_buffer->chan);  
> >>
> >> Not sure if I agree much with this owns_chan flag. The way I see it, 
> >> we should always
> >> handover the lifetime of the DMA channel to the IIO DMA framework. 
> >> Note that even the
> >> device you pass in for both requesting the channel of the spi_offload  
> >> and for
> >> setting up the DMA buffer is the same (and i suspect it will always 
> >> be) so I would
> >> not go with the trouble. And with this assumption we could simplify a 
> >> bit more the
> >> spi implementation.  
> >
> > I tried something like this in v3 but Jonathan didn't seem to like it.
> >
> > https://lore.kernel.org/all/20240727144303.4a8604cb@jic23-huawei/
> >  
> >>
> >> And not even related but I even suspect the current implementation 
> >> could be
> >> problematic. Basically I'm suspecting that the lifetime of the DMA 
> >> channel should be
> >> attached to the lifetime of the iio_buffer. IOW, we should only 
> >> release the channel
> >> in iio_dmaengine_buffer_release() - in which case the current 
> >> implementation with the
> >> spi_offload would also be buggy.  
> >
> > The buffer can outlive the iio device driver that created the buffer?  
> 
> Yes, it can as the IIO device itself. In case a userspace app has an open 
> FD for the buffer chardev, we get a reference that is only released when 
> the FD is closed (which can outlive the device behind bound to its 
> driver). That is why we nullify indio_dev->info and check for it on the 
> read() and write() fops.
> 
> FWIW, I raised concerns about this in the past (as we don't have any lock 
> in those paths) but Jonathan rightfully wanted to see a real race. And I 
> was too lazy to try and reproduce one but I'm still fairly sure we have 
> theoretical (at least) races in those paths. And one of them could be (I 
> think) concurrently hitting a DMA submit block while the device is being 
> unbound. In that case the DMA chan would be already released and we could 
> still try to initiate a transfer. I did not check if that would crash or 
> something but it should still not happen.
> 
There are a few places where I've been meaning to have another look
at our protections during unregister. May well be problems hiding here
and in general the thinking on how to do this in the kernel has slowly
been changing so we might be able to clean things up in general.

I 'think' the person who looked at this in most depth was Lars-Peter but
long long ago!

Jonathan


> - Nuno Sá


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload
  2024-10-23 20:59 ` [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload David Lechner
@ 2024-10-26 15:51   ` Jonathan Cameron
  0 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 15:51 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, 23 Oct 2024 15:59:20 -0500
David Lechner <dlechner@baylibre.com> wrote:

> This adds support for SPI offload to the ad7944 driver. This allows
> reading data at the max sample rate of 2.5 MSPS.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
>
>  static int ad7944_probe(struct spi_device *spi)
>  {
>  	const struct ad7944_chip_info *chip_info;
> @@ -590,20 +743,75 @@ 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");
> +
> +	if (ret == -ENODEV) {
> +		dev_info(dev, "SPI offload not available\n");
Too noisy given this applies whenever this device is connected to a normal
SPI bus.  Or an old DT is used.

> +
>
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-23 20:59 ` [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload David Lechner
@ 2024-10-26 16:00   ` Jonathan Cameron
  2024-10-27  0:01     ` David Lechner
  2024-10-27  0:05     ` David Lechner
  0 siblings, 2 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-26 16:00 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, 23 Oct 2024 15:59:22 -0500
David Lechner <dlechner@baylibre.com> 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
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
A few questions inline. In general looks ok, but it's complex code and I'm
too snowed under for a very close look at the whole thing for a least a few weeks.

Jonathan

> ---
>  drivers/iio/adc/Kconfig  |   1 +
>  drivers/iio/adc/ad4695.c | 470 +++++++++++++++++++++++++++++++++++++++++++----
>  2 files changed, 440 insertions(+), 31 deletions(-)
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 92dfb495a8ce..f76a3f62a9ad 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -53,6 +53,7 @@ config AD4695
>  	depends on SPI
>  	select REGMAP_SPI
>  	select IIO_BUFFER
> +	select IIO_BUFFER_DMAENGINE
>  	select IIO_TRIGGERED_BUFFER
>  	help
>  	  Say yes here to build support for Analog Devices AD4695 and similar

> +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;
> +	xfer->bits_per_word = 16;
> +	xfer->len = 2;
> +
> +	spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
> +	st->buf_read_msg.offload = st->offload;
> +
> +	st->spi->max_speed_hz = st->spi_max_speed_hz;
> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
> +	st->spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
> +	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
> +	 * due to issues with ordering with respect to entering/exiting
> +	 * conversion mode.
Give some detail on the operations order.

> +	 */
> +	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:
> +	/* have to unwind in a different order to avoid triggering offload */

Needs more details here.

> +	spi_offload_trigger_disable(st->offload, st->offload_trigger);
> +	ad4695_cnv_manual_trigger(st);
> +	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_write_raw(struct iio_dev *indio_dev,
>  			    struct iio_chan_spec const *chan,
>  			    int val, int val2, long mask)
> @@ -779,6 +992,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)
> +				return -EINVAL;
> +
> +			guard(mutex)(&st->cnv_pwm_lock);
> +			pwm_get_state(st->cnv_pwm, &state);

What limits this to rates the ADC can cope with?

> +			state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val);
> +			return pwm_apply_might_sleep(st->cnv_pwm, &state);
> +		}
>  		default:
>  			return -EINVAL;
>  		}

>  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;
> @@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
>  		return -EINVAL;
>  
>  	/* Registers cannot be read at the max allowable speed */
> +	st->spi_max_speed_hz = spi->max_speed_hz;
>  	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
>  
> +	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);

Why do you need to put it back in devm? What happens after this but without
a driver restart that uses that faster rate?

> +	if (ret)
> +		return ret;
> +
>  	st->regmap = devm_regmap_init_spi(spi, &ad4695_regmap_config);
>  	if (IS_ERR(st->regmap))
>  		return dev_err_probe(dev, PTR_ERR(st->regmap),
> @@ -1014,6 +1391,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);
> @@ -1139,14 +1521,39 @@ static int ad4695_probe(struct spi_device *spi)
>  	indio_dev->info = &ad4695_info;
>  	indio_dev->modes = INDIO_DIRECT_MODE;
>  	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;
> +	static const struct spi_offload_config ad4695_offload_config = {
> +		.capability_flags = SPI_OFFLOAD_CAP_TRIGGER
> +				  | SPI_OFFLOAD_CAP_RX_STREAM_DMA,
> +	};
> +
> +	st->offload = devm_spi_offload_get(dev, spi, &ad4695_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 (ret == -ENODEV) {
> +		/* If no SPI offload, fall back to low speed usage. */
> +		dev_info(dev, "SPI offload not available\n");

As previous. Too noisy for a normal thing that might happen.
Maybe if we can't figure it out from anything userspace an see we could add
a print on the 'offload is enabled' side of things.

> +
> +		/* 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;
> +	}

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-26 16:00   ` Jonathan Cameron
@ 2024-10-27  0:01     ` David Lechner
  2024-10-27  9:12       ` Jonathan Cameron
  2024-10-27  0:05     ` David Lechner
  1 sibling, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-27  0:01 UTC (permalink / raw)
  To: Jonathan Cameron
  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 10/26/24 11:00 AM, Jonathan Cameron wrote:
> On Wed, 23 Oct 2024 15:59:22 -0500
> David Lechner <dlechner@baylibre.com> 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
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
> A few questions inline. In general looks ok, but it's complex code and I'm
> too snowed under for a very close look at the whole thing for a least a few weeks.
> 
> Jonathan
> 
>> ---
>>  drivers/iio/adc/Kconfig  |   1 +
>>  drivers/iio/adc/ad4695.c | 470 +++++++++++++++++++++++++++++++++++++++++++----
>>  2 files changed, 440 insertions(+), 31 deletions(-)
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 92dfb495a8ce..f76a3f62a9ad 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -53,6 +53,7 @@ config AD4695
>>  	depends on SPI
>>  	select REGMAP_SPI
>>  	select IIO_BUFFER
>> +	select IIO_BUFFER_DMAENGINE
>>  	select IIO_TRIGGERED_BUFFER
>>  	help
>>  	  Say yes here to build support for Analog Devices AD4695 and similar
> 
>> +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;
>> +	xfer->bits_per_word = 16;
>> +	xfer->len = 2;
>> +
>> +	spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
>> +	st->buf_read_msg.offload = st->offload;
>> +
>> +	st->spi->max_speed_hz = st->spi_max_speed_hz;
>> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
>> +	st->spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
>> +	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
>> +	 * due to issues with ordering with respect to entering/exiting
>> +	 * conversion mode.
> Give some detail on the operations order.
> 
>> +	 */
>> +	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:
>> +	/* have to unwind in a different order to avoid triggering offload */
> 
> Needs more details here.
> 
>> +	spi_offload_trigger_disable(st->offload, st->offload_trigger);
>> +	ad4695_cnv_manual_trigger(st);
>> +	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_write_raw(struct iio_dev *indio_dev,
>>  			    struct iio_chan_spec const *chan,
>>  			    int val, int val2, long mask)
>> @@ -779,6 +992,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)
>> +				return -EINVAL;
>> +
>> +			guard(mutex)(&st->cnv_pwm_lock);
>> +			pwm_get_state(st->cnv_pwm, &state);
> 
> What limits this to rates the ADC can cope with?
> 
>> +			state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val);
>> +			return pwm_apply_might_sleep(st->cnv_pwm, &state);
>> +		}
>>  		default:
>>  			return -EINVAL;
>>  		}
> 
>>  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;
>> @@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
>>  		return -EINVAL;
>>  
>>  	/* Registers cannot be read at the max allowable speed */
>> +	st->spi_max_speed_hz = spi->max_speed_hz;
>>  	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
>>  
>> +	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);
> 
> Why do you need to put it back in devm? What happens after this but without
> a driver restart that uses that faster rate?
> 
I should have added a comment here as this was a weird bug to trace.

The core SPI framework sets the initial value of spi->max_speed_hz
to the minimum of the controller max rate and the max rate specified
by the devicetree.

The SPI device lives beyond this driver, so if we bind the driver
and set spi->max_speed_hz to something other than what the SPI core
set it, then the next time we bind the driver, we don't get the
the max rate from the SPI core, but rather we changed it to when
the driver unbound.

So on the second bind, the max rate would be the slow register
read rate instead of the actual max allowable rate.

So we need to reset spi->max_speed_hz to what it was originally
on driver unbind so that everything works as expected on the
next bind.

(Or we call this a SPI core bug and fix it there instead).


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-26 16:00   ` Jonathan Cameron
  2024-10-27  0:01     ` David Lechner
@ 2024-10-27  0:05     ` David Lechner
  2024-10-27  9:15       ` Jonathan Cameron
  1 sibling, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-27  0:05 UTC (permalink / raw)
  To: Jonathan Cameron
  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 10/26/24 11:00 AM, Jonathan Cameron wrote:
> On Wed, 23 Oct 2024 15:59:22 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 

...

>>  static int ad4695_write_raw(struct iio_dev *indio_dev,
>>  			    struct iio_chan_spec const *chan,
>>  			    int val, int val2, long mask)
>> @@ -779,6 +992,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)
>> +				return -EINVAL;
>> +
>> +			guard(mutex)(&st->cnv_pwm_lock);
>> +			pwm_get_state(st->cnv_pwm, &state);
> 
> What limits this to rates the ADC can cope with?
> 

Nothing at the moment. The "obvious" thing to do would
be to limit this to the max rate from the datasheet.

But that feels a little too strict to me since maybe the
PWM can't get exactly the max rate, but can get the max
rate + 1% or so. It seems like we should allow that too.
It's not like the ADC is going to not work if we go a
few Hz over the datasheet rating.

Maybe limit it to max + 10% or something like that?

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-26 15:18   ` Jonathan Cameron
@ 2024-10-27  0:20     ` David Lechner
  2024-10-27 20:24     ` Conor Dooley
  2024-10-31 18:16     ` Conor Dooley
  2 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-27  0:20 UTC (permalink / raw)
  To: Jonathan Cameron
  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 10/26/24 10:18 AM, Jonathan Cameron wrote:
> On Wed, 23 Oct 2024 15:59:12 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
>> Add a new binding for using a PWM signal as a trigger for SPI offloads.
> 
> I don't have a better suggestion for this, but it does smell rather like
> other bridge binding (iio-hwmon for example) where we have had push back on
> representing something that doesn't really exist but is just a way to
> tie two bits of hardware together. Those kind of exist because we snuck
> them in a long time back when no one was paying attention.
> 
> So this one may need more explanation and justification and I'd definitely
> like some DT maintainer review on this at a fairly early stage!
> (might have happened in earlier reviews but it has been a while so I've
> forgotten if it did)
> 
> Jonathan
> 
We could probably make it work like the leds version of this
binding where the trigger-sources property can have phandles
to anything, not just a dedicated class of device. It just
gets messy to implement because every subsystem needs to have
core code modified to be able to handle using a device or
one channel/gpio/etc. of a device as a trigger instead of
whatever it normally is.

> 
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>
>> v4 changes: new patch in v4
>> ---
>>  .../devicetree/bindings/spi/trigger-pwm.yaml       | 39 ++++++++++++++++++++++
>>  1 file changed, 39 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/spi/trigger-pwm.yaml b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
>> new file mode 100644
>> index 000000000000..987638aa4732
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/spi/trigger-pwm.yaml
>> @@ -0,0 +1,39 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/spi/trigger-pwm.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Generic SPI offload trigger using PWM
>> +
>> +description: Remaps a PWM channel as a trigger source.
>> +
>> +maintainers:
>> +  - David Lechner <dlechner@baylibre.com>
>> +
>> +$ref: /schemas/spi/trigger-source.yaml#
>> +
>> +properties:
>> +  compatible:
>> +    const: trigger-pwm
>> +
>> +  '#trigger-source-cells':
>> +    const: 0
>> +
>> +  pwms:
>> +    maxItems: 1
>> +
>> +required:
>> +  - compatible
>> +  - '#trigger-source-cells'
>> +  - pwms
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    trigger {
>> +        compatible = "trigger-pwm";
>> +        #trigger-source-cells = <0>;
>> +        pwms = <&pwm 0 1000000 0>;
>> +    };
>>
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-27  0:01     ` David Lechner
@ 2024-10-27  9:12       ` Jonathan Cameron
  2024-10-27 19:52         ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-27  9: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

On Sat, 26 Oct 2024 19:01:53 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 10/26/24 11:00 AM, Jonathan Cameron wrote:
> > On Wed, 23 Oct 2024 15:59:22 -0500
> > David Lechner <dlechner@baylibre.com> 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
> >>
> >> Signed-off-by: David Lechner <dlechner@baylibre.com>  
> > A few questions inline. In general looks ok, but it's complex code and I'm
> > too snowed under for a very close look at the whole thing for a least a few weeks.
> > 
> > Jonathan
> >   
> >> ---
> >>  drivers/iio/adc/Kconfig  |   1 +
> >>  drivers/iio/adc/ad4695.c | 470 +++++++++++++++++++++++++++++++++++++++++++----
> >>  2 files changed, 440 insertions(+), 31 deletions(-)
> >>
> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> >> index 92dfb495a8ce..f76a3f62a9ad 100644
> >> --- a/drivers/iio/adc/Kconfig
> >> +++ b/drivers/iio/adc/Kconfig
> >> @@ -53,6 +53,7 @@ config AD4695
> >>  	depends on SPI
> >>  	select REGMAP_SPI
> >>  	select IIO_BUFFER
> >> +	select IIO_BUFFER_DMAENGINE
> >>  	select IIO_TRIGGERED_BUFFER
> >>  	help
> >>  	  Say yes here to build support for Analog Devices AD4695 and similar  
> >   
> >> +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;
> >> +	xfer->bits_per_word = 16;
> >> +	xfer->len = 2;
> >> +
> >> +	spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1);
> >> +	st->buf_read_msg.offload = st->offload;
> >> +
> >> +	st->spi->max_speed_hz = st->spi_max_speed_hz;
> >> +	ret = spi_optimize_message(st->spi, &st->buf_read_msg);
> >> +	st->spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
> >> +	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
> >> +	 * due to issues with ordering with respect to entering/exiting
> >> +	 * conversion mode.  
> > Give some detail on the operations order.
> >   
> >> +	 */
> >> +	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:
> >> +	/* have to unwind in a different order to avoid triggering offload */  
> > 
> > Needs more details here.
> >   
> >> +	spi_offload_trigger_disable(st->offload, st->offload_trigger);
> >> +	ad4695_cnv_manual_trigger(st);
> >> +	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_write_raw(struct iio_dev *indio_dev,
> >>  			    struct iio_chan_spec const *chan,
> >>  			    int val, int val2, long mask)
> >> @@ -779,6 +992,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)
> >> +				return -EINVAL;
> >> +
> >> +			guard(mutex)(&st->cnv_pwm_lock);
> >> +			pwm_get_state(st->cnv_pwm, &state);  
> > 
> > What limits this to rates the ADC can cope with?
> >   
> >> +			state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val);
> >> +			return pwm_apply_might_sleep(st->cnv_pwm, &state);
> >> +		}
> >>  		default:
> >>  			return -EINVAL;
> >>  		}  
> >   
> >>  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;
> >> @@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
> >>  		return -EINVAL;
> >>  
> >>  	/* Registers cannot be read at the max allowable speed */
> >> +	st->spi_max_speed_hz = spi->max_speed_hz;
> >>  	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
> >>  
> >> +	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);  
> > 
> > Why do you need to put it back in devm? What happens after this but without
> > a driver restart that uses that faster rate?
> >   
> I should have added a comment here as this was a weird bug to trace.
> 
> The core SPI framework sets the initial value of spi->max_speed_hz
> to the minimum of the controller max rate and the max rate specified
> by the devicetree.
> 
> The SPI device lives beyond this driver, so if we bind the driver
> and set spi->max_speed_hz to something other than what the SPI core
> set it, then the next time we bind the driver, we don't get the
> the max rate from the SPI core, but rather we changed it to when
> the driver unbound.
> 
> So on the second bind, the max rate would be the slow register
> read rate instead of the actual max allowable rate.
> 
> So we need to reset spi->max_speed_hz to what it was originally
> on driver unbind so that everything works as expected on the
> next bind.
> 
> (Or we call this a SPI core bug and fix it there instead).
Definitely a question to ask.  Directly accessing spi_max_speed_hz may
be the fundamental issue as I don't think the driver is generally
expected to touch that in a dynamic fashion.  Should we be instead setting it
per transfer for the ones that need it controlled?

Jonathan



> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-27  0:05     ` David Lechner
@ 2024-10-27  9:15       ` Jonathan Cameron
  0 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-27  9:15 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 Sat, 26 Oct 2024 19:05:44 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 10/26/24 11:00 AM, Jonathan Cameron wrote:
> > On Wed, 23 Oct 2024 15:59:22 -0500
> > David Lechner <dlechner@baylibre.com> wrote:
> >   
> 
> ...
> 
> >>  static int ad4695_write_raw(struct iio_dev *indio_dev,
> >>  			    struct iio_chan_spec const *chan,
> >>  			    int val, int val2, long mask)
> >> @@ -779,6 +992,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)
> >> +				return -EINVAL;
> >> +
> >> +			guard(mutex)(&st->cnv_pwm_lock);
> >> +			pwm_get_state(st->cnv_pwm, &state);  
> > 
> > What limits this to rates the ADC can cope with?
> >   
> 
> Nothing at the moment. The "obvious" thing to do would
> be to limit this to the max rate from the datasheet.
> 
> But that feels a little too strict to me since maybe the
> PWM can't get exactly the max rate, but can get the max
> rate + 1% or so. It seems like we should allow that too.
> It's not like the ADC is going to not work if we go a
> few Hz over the datasheet rating.
> 
> Maybe limit it to max + 10% or something like that?

Clamp it at datasheet value.   That's what is presumably verified
not 10% over.  If that needs relaxing in future, the datasheet should
be updated to reflect the higher verified value.

Jonathan


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-27  9:12       ` Jonathan Cameron
@ 2024-10-27 19:52         ` David Lechner
  2024-10-28 16:39           ` Jonathan Cameron
  0 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-10-27 19:52 UTC (permalink / raw)
  To: Jonathan Cameron
  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 10/27/24 4:12 AM, Jonathan Cameron wrote:
> On Sat, 26 Oct 2024 19:01:53 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
>> On 10/26/24 11:00 AM, Jonathan Cameron wrote:
>>> On Wed, 23 Oct 2024 15:59:22 -0500
>>> David Lechner <dlechner@baylibre.com> wrote:
>>>   

...

>>>   
>>>>  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;
>>>> @@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
>>>>  		return -EINVAL;
>>>>  
>>>>  	/* Registers cannot be read at the max allowable speed */
>>>> +	st->spi_max_speed_hz = spi->max_speed_hz;
>>>>  	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
>>>>  
>>>> +	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);  
>>>
>>> Why do you need to put it back in devm? What happens after this but without
>>> a driver restart that uses that faster rate?
>>>   
>> I should have added a comment here as this was a weird bug to trace.
>>
>> The core SPI framework sets the initial value of spi->max_speed_hz
>> to the minimum of the controller max rate and the max rate specified
>> by the devicetree.
>>
>> The SPI device lives beyond this driver, so if we bind the driver
>> and set spi->max_speed_hz to something other than what the SPI core
>> set it, then the next time we bind the driver, we don't get the
>> the max rate from the SPI core, but rather we changed it to when
>> the driver unbound.
>>
>> So on the second bind, the max rate would be the slow register
>> read rate instead of the actual max allowable rate.
>>
>> So we need to reset spi->max_speed_hz to what it was originally
>> on driver unbind so that everything works as expected on the
>> next bind.
>>
>> (Or we call this a SPI core bug and fix it there instead).
> Definitely a question to ask.  Directly accessing spi_max_speed_hz may
> be the fundamental issue as I don't think the driver is generally
> expected to touch that in a dynamic fashion.  Should we be instead setting it
> per transfer for the ones that need it controlled?
> 
> Jonathan
> 

The problem is that we are using regmap and that doesn't have
a way to specify the max frequency for register reads that is
different from other uses of the SPI bus (i.e. reading sample
data). So we could fix it in the generic SPI regmap (not exactly
trivial) or we could write our own regmap read/write callbacks
in this driver that properly sets the per-transfer max speed.


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-26 15:18   ` Jonathan Cameron
  2024-10-27  0:20     ` David Lechner
@ 2024-10-27 20:24     ` Conor Dooley
  2024-10-31 18:16     ` Conor Dooley
  2 siblings, 0 replies; 59+ messages in thread
From: Conor Dooley @ 2024-10-27 20:24 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, 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

[-- Attachment #1: Type: text/plain, Size: 370 bytes --]

On Sat, Oct 26, 2024 at 04:18:37PM +0100, Jonathan Cameron wrote:

> So this one may need more explanation and justification and I'd definitely
> like some DT maintainer review on this at a fairly early stage!
> (might have happened in earlier reviews but it has been a while so I've
> forgotten if it did)

I intend looking at this, but it'll be Wednesday before I do.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-26 15:48 ` Jonathan Cameron
@ 2024-10-28 11:08   ` Nuno Sá
  2024-10-28 16:35     ` Jonathan Cameron
  0 siblings, 1 reply; 59+ messages in thread
From: Nuno Sá @ 2024-10-28 11:08 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, 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 Sat, 2024-10-26 at 16:48 +0100, Jonathan Cameron wrote:
> On Fri, 25 Oct 2024 20:40:42 +0200 (GMT+02:00)
> Nuno Sá <noname.nuno@gmail.com> wrote:
> 
> > Oct 25, 2024 18:42:02 David Lechner <dlechner@baylibre.com>:
> > 
> > > On 10/25/24 8:24 AM, Nuno Sá wrote:  
> > > > I still need to look better at this but I do have one though already 
> > > > :)
> > > > 
> > > > On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:  
> > > > > Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
> > > > > ---
> > > > > 
> > > > > v4 changes:
> > > > > * This replaces "iio: buffer-dmaengine: generalize requesting DMA 
> > > > > channel"
> > > > > ---  
> > > 
> > > ...
> > >  
> > > > > @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct 
> > > > > iio_buffer *buffer)
> > > > >         iio_buffer_to_dmaengine_buffer(buffer);
> > > > >  
> > > > >     iio_dma_buffer_exit(&dmaengine_buffer->queue);
> > > > > -   dma_release_channel(dmaengine_buffer->chan);
> > > > > -
> > > > >     iio_buffer_put(buffer);
> > > > > +
> > > > > +   if (dmaengine_buffer->owns_chan)
> > > > > +       dma_release_channel(dmaengine_buffer->chan);  
> > > > 
> > > > Not sure if I agree much with this owns_chan flag. The way I see it, 
> > > > we should always
> > > > handover the lifetime of the DMA channel to the IIO DMA framework. 
> > > > Note that even the
> > > > device you pass in for both requesting the channel of the spi_offload  
> > > > and for
> > > > setting up the DMA buffer is the same (and i suspect it will always 
> > > > be) so I would
> > > > not go with the trouble. And with this assumption we could simplify a 
> > > > bit more the
> > > > spi implementation.  
> > > 
> > > I tried something like this in v3 but Jonathan didn't seem to like it.
> > > 
> > > https://lore.kernel.org/all/20240727144303.4a8604cb@jic23-huawei/
> > >  
> > > > 
> > > > And not even related but I even suspect the current implementation 
> > > > could be
> > > > problematic. Basically I'm suspecting that the lifetime of the DMA 
> > > > channel should be
> > > > attached to the lifetime of the iio_buffer. IOW, we should only 
> > > > release the channel
> > > > in iio_dmaengine_buffer_release() - in which case the current 
> > > > implementation with the
> > > > spi_offload would also be buggy.  
> > > 
> > > The buffer can outlive the iio device driver that created the buffer?  
> > 
> > Yes, it can as the IIO device itself. In case a userspace app has an open 
> > FD for the buffer chardev, we get a reference that is only released when 
> > the FD is closed (which can outlive the device behind bound to its 
> > driver). That is why we nullify indio_dev->info and check for it on the 
> > read() and write() fops.
> > 
> > FWIW, I raised concerns about this in the past (as we don't have any lock 
> > in those paths) but Jonathan rightfully wanted to see a real race. And I 
> > was too lazy to try and reproduce one but I'm still fairly sure we have 
> > theoretical (at least) races in those paths. And one of them could be (I 
> > think) concurrently hitting a DMA submit block while the device is being 
> > unbound. In that case the DMA chan would be already released and we could 
> > still try to initiate a transfer. I did not check if that would crash or 
> > something but it should still not happen.
> > 
> There are a few places where I've been meaning to have another look
> at our protections during unregister. May well be problems hiding here
> and in general the thinking on how to do this in the kernel has slowly
> been changing so we might be able to clean things up in general.
> 

Yeah, I'm fairly sure things like [1] are not enough in preventing potential nasty
races (though they should be hard to trigger). OTOH, in [2], we do have proper
locking.

Simple solution would be to use the info lock in the buffer read() and write() paths.
I do realize that's a fastpath but I don't think that would be such a contended lock.
But we can surely do better and RCU could be a good candidate for this (we could do
something similar to what gpiolib is doing) and I wouldn't expect it to be that
complicated to implement. Biggest issue by making info a __rcu pointer would be to
change all IIO drivers to set the pointer with rcu_assign_pointer(). Though during
probe there's no potential race so what we have today should be fine (just not sure
if things like sparse would not complain about the "raw" assignment).

[1]: https://elixir.bootlin.com/linux/v6.12-rc4/source/drivers/iio/industrialio-buffer.c#L176
[2]: https://elixir.bootlin.com/linux/v6.12-rc4/source/drivers/iio/industrialio-core.c#L1825


- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver
  2024-10-25 16:28     ` David Lechner
@ 2024-10-28 13:47       ` Nuno Sá
  0 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-28 13:47 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Fri, 2024-10-25 at 11:28 -0500, David Lechner wrote:
> On 10/25/24 7:07 AM, Nuno Sá wrote:
> > Hi David,
> > 
> > Looks mostly good... Just one minor comments from me.
> > 
> > On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:
> > > Add a new driver for a generic PWM trigger for SPI offloads.
> > > 
> > > Signed-off-by: David Lechner <dlechner@baylibre.com>
> > > ---
> > > 
> 
> ...
> 
> > > +static bool spi_offload_trigger_pwm_match(void *priv,
> > > +					  enum spi_offload_trigger_type type,
> > > +					  u64 *args, u32 nargs)
> > > +{
> > > +	if (nargs)
> > > +		return false;
> > > +
> > > +	return type == SPI_OFFLOAD_TRIGGER_PERIODIC;
> > 
> > Hmm will we ever be in a place where a trigger provide might have multiple types?
> > If
> > so, then I'm mostly fine with this match() callback. But we could still avoid it
> > if
> > we use a bitmask for trigger types and having any trigger provider to give the
> > supported types. Then the core could pretty much do the match between the
> > requested
> > trigger type and what the provider supports.
> 
> We will still need some callback though to handle drivers that use
> phandle args.

Hmmm true.
> 
> > 
> > > +}
> > > +
> > > +static int spi_offload_trigger_pwm_validate(void *priv,
> > > +					    struct spi_offload_trigger_config
> > > *config)
> > > +{
> > > +	struct spi_offload_trigger_pwm_state *st = priv;
> > > +	struct spi_offload_trigger_periodic *periodic = &config->periodic;
> > > +	struct pwm_waveform wf = { };
> > > +	int ret;
> > > +
> > > +	if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC)
> > > +		return -EINVAL;
> > 
> > Checking the above every time seems redundant to me. We should match it once
> > during
> > the trigger request and then just use that trigger type. Otherwise I'm not seeing
> > the
> > point of the match() callback.
> > 
> 
> Here it is validating struct spi_offload_trigger_config has the right
> type, which is needed before we can safely trust that the correct
> union member was used in that struct. So it has a different purpose from
> the match check.
> 

I'm still not convinced tbh. We already pass in the type for the match() callback
which is done when we get the trigger. I don't expect (at least at this point) for a
given offload trigger to dynamically change. And if we do allow that, we should still
need a new API to change between triggers (which could then be another validation
point). But key point is that at any given time, only one trigger should be "in use".
For this first simple case It really feels that passing around the trigger type (and
validating on every API) is redundant. We could do it once in the match() callback
and the pwm driver could then assume the periodic trigger is the one being use.

Having said the above, this really does not matter that much so I'm not arguing more.
If you prefer to be extra cautious, fair enough :)

- Nuno Sá


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers
  2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
  2024-10-24 14:04   ` Nuno Sá
  2024-10-26 15:14   ` Jonathan Cameron
@ 2024-10-28 13:53   ` Nuno Sá
  2 siblings, 0 replies; 59+ messages in thread
From: Nuno Sá @ 2024-10-28 13:53 UTC (permalink / raw)
  To: David Lechner, Mark Brown, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Nuno Sá,
	Uwe Kleine-König
  Cc: Michael Hennerich, Lars-Peter Clausen, David Jander, Martin Sperl,
	linux-spi, devicetree, linux-kernel, linux-iio, linux-pwm

On Wed, 2024-10-23 at 15:59 -0500, 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.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> In previous versions, we locked the SPI bus when the hardware trigger
> was enabled, but we found this to be too restrictive. In one use case,
> to avoid a race condition, we need to enable the SPI offload via a
> hardware trigger, then write a SPI message to the peripheral to place
> it into a mode that will generate the trigger. If we did it the other
> way around, we could miss the first trigger.
> 
> Another likely use case will be enabling two offloads/triggers at one
> time on the same device, e.g. a read trigger and a write trigger. So
> the exclusive bus lock for a single trigger would be too restrictive in
> this case too.
> 
> So for now, I'm going with Nuno's suggestion to leave any locking up to
> the individual controller driver. If we do find we need something more
> generic in the future, we could add a new spi_bus_lock_exclusive() API
> that causes spi_bus_lock() to fail instead of waiting and add "locked"
> versions of trigger enable functions. This would allow a peripheral to
> claim exclusive use of the bus indefinitely while still being able to
> do any SPI messaging that it needs.
> 
> 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       | 266 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi-offload.h |  78 ++++++++++++
>  2 files changed, 344 insertions(+)
> 
> diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c
> index c344cbf50bdb..2a1f9587f27a 100644
> --- a/drivers/spi/spi-offload.c
> +++ b/drivers/spi/spi-offload.c
> @@ -9,12 +9,26 @@
>  #include <linux/cleanup.h>
>  #include <linux/device.h>
>  #include <linux/export.h>
> +#include <linux/list.h>
>  #include <linux/mutex.h>
> +#include <linux/of.h>
>  #include <linux/property.h>
>  #include <linux/spi/spi-offload.h>
>  #include <linux/spi/spi.h>
>  #include <linux/types.h>
>  
> +struct spi_offload_trigger {
> +	struct list_head list;
> +	struct device dev;
> +	/* synchronizes calling ops and driver registration */
> +	struct mutex lock;
> +	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 instances
>   * @dev: Device for devm purposes
> @@ -102,3 +116,255 @@ struct spi_offload *devm_spi_offload_get(struct device *dev,
>  	return offload;
>  }
>  EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> +
> +static void spi_offload_trigger_release(void *data)
> +{
> +	struct spi_offload_trigger *trigger = data;
> +
> +	guard(mutex)(&trigger->lock);
> +	if (trigger->priv && trigger->ops->release)
> +		trigger->ops->release(trigger->priv);
> +
> +	put_device(&trigger->dev);
> +}
> +
> +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;
> +	bool match = false;
> +	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);
> +
> +	struct fwnode_handle *trigger_fwnode __free(fwnode_handle) = args.fwnode;
> +
> +	guard(mutex)(&spi_offload_triggers_lock);
> +
> +	list_for_each_entry(trigger, &spi_offload_triggers, list) {
> +		if (trigger->dev.fwnode != args.fwnode)
> +			continue;

device_match_fwnode()

- Nuno Sá



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2()
  2024-10-28 11:08   ` Nuno Sá
@ 2024-10-28 16:35     ` Jonathan Cameron
  0 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-28 16:35 UTC (permalink / raw)
  To: Nuno Sá
  Cc: Jonathan Cameron, David Lechner, 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 Mon, 28 Oct 2024 12:08:49 +0100
Nuno Sá <noname.nuno@gmail.com> wrote:

> On Sat, 2024-10-26 at 16:48 +0100, Jonathan Cameron wrote:
> > On Fri, 25 Oct 2024 20:40:42 +0200 (GMT+02:00)
> > Nuno Sá <noname.nuno@gmail.com> wrote:
> >   
> > > Oct 25, 2024 18:42:02 David Lechner <dlechner@baylibre.com>:
> > >   
> > > > On 10/25/24 8:24 AM, Nuno Sá wrote:    
> > > > > I still need to look better at this but I do have one though already 
> > > > > :)
> > > > > 
> > > > > On Wed, 2024-10-23 at 15:59 -0500, David Lechner wrote:    
> > > > > > Add a new devm_iio_dmaengine_buffer_setup_ext2() 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>
> > > > > > ---
> > > > > > 
> > > > > > v4 changes:
> > > > > > * This replaces "iio: buffer-dmaengine: generalize requesting DMA 
> > > > > > channel"
> > > > > > ---    
> > > > 
> > > > ...
> > > >    
> > > > > > @@ -282,12 +281,38 @@ void iio_dmaengine_buffer_free(struct 
> > > > > > iio_buffer *buffer)
> > > > > >         iio_buffer_to_dmaengine_buffer(buffer);
> > > > > >  
> > > > > >     iio_dma_buffer_exit(&dmaengine_buffer->queue);
> > > > > > -   dma_release_channel(dmaengine_buffer->chan);
> > > > > > -
> > > > > >     iio_buffer_put(buffer);
> > > > > > +
> > > > > > +   if (dmaengine_buffer->owns_chan)
> > > > > > +       dma_release_channel(dmaengine_buffer->chan);    
> > > > > 
> > > > > Not sure if I agree much with this owns_chan flag. The way I see it, 
> > > > > we should always
> > > > > handover the lifetime of the DMA channel to the IIO DMA framework. 
> > > > > Note that even the
> > > > > device you pass in for both requesting the channel of the spi_offload  
> > > > > and for
> > > > > setting up the DMA buffer is the same (and i suspect it will always 
> > > > > be) so I would
> > > > > not go with the trouble. And with this assumption we could simplify a 
> > > > > bit more the
> > > > > spi implementation.    
> > > > 
> > > > I tried something like this in v3 but Jonathan didn't seem to like it.
> > > > 
> > > > https://lore.kernel.org/all/20240727144303.4a8604cb@jic23-huawei/
> > > >    
> > > > > 
> > > > > And not even related but I even suspect the current implementation 
> > > > > could be
> > > > > problematic. Basically I'm suspecting that the lifetime of the DMA 
> > > > > channel should be
> > > > > attached to the lifetime of the iio_buffer. IOW, we should only 
> > > > > release the channel
> > > > > in iio_dmaengine_buffer_release() - in which case the current 
> > > > > implementation with the
> > > > > spi_offload would also be buggy.    
> > > > 
> > > > The buffer can outlive the iio device driver that created the buffer?    
> > > 
> > > Yes, it can as the IIO device itself. In case a userspace app has an open 
> > > FD for the buffer chardev, we get a reference that is only released when 
> > > the FD is closed (which can outlive the device behind bound to its 
> > > driver). That is why we nullify indio_dev->info and check for it on the 
> > > read() and write() fops.
> > > 
> > > FWIW, I raised concerns about this in the past (as we don't have any lock 
> > > in those paths) but Jonathan rightfully wanted to see a real race. And I 
> > > was too lazy to try and reproduce one but I'm still fairly sure we have 
> > > theoretical (at least) races in those paths. And one of them could be (I 
> > > think) concurrently hitting a DMA submit block while the device is being 
> > > unbound. In that case the DMA chan would be already released and we could 
> > > still try to initiate a transfer. I did not check if that would crash or 
> > > something but it should still not happen.
> > >   
> > There are a few places where I've been meaning to have another look
> > at our protections during unregister. May well be problems hiding here
> > and in general the thinking on how to do this in the kernel has slowly
> > been changing so we might be able to clean things up in general.
> >   
> 
> Yeah, I'm fairly sure things like [1] are not enough in preventing potential nasty
> races (though they should be hard to trigger). OTOH, in [2], we do have proper
> locking.
> 
> Simple solution would be to use the info lock in the buffer read() and write() paths.
> I do realize that's a fastpath but I don't think that would be such a contended lock.
> But we can surely do better and RCU could be a good candidate for this (we could do
> something similar to what gpiolib is doing) and I wouldn't expect it to be that
> complicated to implement. Biggest issue by making info a __rcu pointer would be to
> change all IIO drivers to set the pointer with rcu_assign_pointer(). Though during
> probe there's no potential race so what we have today should be fine (just not sure
> if things like sparse would not complain about the "raw" assignment).

Not tried it, but could probably do something cheeky in iio_device_register()
using a second pointer (maybe in a union with the first ;)

So I agree, smells bad but I've not chased far enough to know how real the problems
are.  In many cases the read only accesses are to data, not to hardware so 'might'
be fine. I'm just not sure.  I'll try and get time for a close look but won't
be until towards the end of November.

Jonathan

> 
> [1]: https://elixir.bootlin.com/linux/v6.12-rc4/source/drivers/iio/industrialio-buffer.c#L176
> [2]: https://elixir.bootlin.com/linux/v6.12-rc4/source/drivers/iio/industrialio-core.c#L1825
> 
> 
> - Nuno Sá
> 
> 
> 
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload
  2024-10-27 19:52         ` David Lechner
@ 2024-10-28 16:39           ` Jonathan Cameron
  0 siblings, 0 replies; 59+ messages in thread
From: Jonathan Cameron @ 2024-10-28 16:39 UTC (permalink / raw)
  To: David Lechner
  Cc: Jonathan Cameron, 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 Sun, 27 Oct 2024 14:52:17 -0500
David Lechner <dlechner@baylibre.com> wrote:

> On 10/27/24 4:12 AM, Jonathan Cameron wrote:
> > On Sat, 26 Oct 2024 19:01:53 -0500
> > David Lechner <dlechner@baylibre.com> wrote:
> >   
> >> On 10/26/24 11:00 AM, Jonathan Cameron wrote:  
> >>> On Wed, 23 Oct 2024 15:59:22 -0500
> >>> David Lechner <dlechner@baylibre.com> wrote:
> >>>     
> 
> ...
> 
> >>>     
> >>>>  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;
> >>>> @@ -1002,8 +1374,13 @@ static int ad4695_probe(struct spi_device *spi)
> >>>>  		return -EINVAL;
> >>>>  
> >>>>  	/* Registers cannot be read at the max allowable speed */
> >>>> +	st->spi_max_speed_hz = spi->max_speed_hz;
> >>>>  	spi->max_speed_hz = AD4695_REG_ACCESS_SCLK_HZ;
> >>>>  
> >>>> +	ret = devm_add_action_or_reset(dev, ad4695_restore_spi_max_speed_hz, st);    
> >>>
> >>> Why do you need to put it back in devm? What happens after this but without
> >>> a driver restart that uses that faster rate?
> >>>     
> >> I should have added a comment here as this was a weird bug to trace.
> >>
> >> The core SPI framework sets the initial value of spi->max_speed_hz
> >> to the minimum of the controller max rate and the max rate specified
> >> by the devicetree.
> >>
> >> The SPI device lives beyond this driver, so if we bind the driver
> >> and set spi->max_speed_hz to something other than what the SPI core
> >> set it, then the next time we bind the driver, we don't get the
> >> the max rate from the SPI core, but rather we changed it to when
> >> the driver unbound.
> >>
> >> So on the second bind, the max rate would be the slow register
> >> read rate instead of the actual max allowable rate.
> >>
> >> So we need to reset spi->max_speed_hz to what it was originally
> >> on driver unbind so that everything works as expected on the
> >> next bind.
> >>
> >> (Or we call this a SPI core bug and fix it there instead).  
> > Definitely a question to ask.  Directly accessing spi_max_speed_hz may
> > be the fundamental issue as I don't think the driver is generally
> > expected to touch that in a dynamic fashion.  Should we be instead setting it
> > per transfer for the ones that need it controlled?
> > 
> > Jonathan
> >   
> 
> The problem is that we are using regmap and that doesn't have
> a way to specify the max frequency for register reads that is
> different from other uses of the SPI bus (i.e. reading sample
> data). So we could fix it in the generic SPI regmap (not exactly
> trivial) or we could write our own regmap read/write callbacks
> in this driver that properly sets the per-transfer max speed.

Custom read / write callbacks seems the best approach at first
glance, given this is pretty rare thing to do. 

> 
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw()
  2024-10-23 20:59 ` [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw() David Lechner
@ 2024-10-29  8:05   ` Uwe Kleine-König
  2024-10-29 15:30     ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: Uwe Kleine-König @ 2024-10-29  8:05 UTC (permalink / raw)
  To: David Lechner
  Cc: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Michael Hennerich, Lars-Peter Clausen,
	David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
	linux-iio, linux-pwm

[-- Attachment #1: Type: text/plain, Size: 3180 bytes --]

Hello David,

On Wed, Oct 23, 2024 at 03:59:08PM -0500, David Lechner wrote:
> Export the pwm_get_state_hw() function. This is useful in cases where
> we want to know what the hardware is actually doing, rather than what
> what we requested it should do.
> 
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> 
> v4 changes: new patch in v4
> 
> And FYI for Uwe and Jonathan, there are a couple of other series
> introducing PWM conversion triggers that could make use of this
> so that the sampling_frequency attribute can return the actual rate
> rather than the requested rate.
> 
> Already applied:
> https://lore.kernel.org/linux-iio/20241015-ad7606_add_iio_backend_support-v5-4-654faf1ae08c@baylibre.com/
> 
> Under review:
> https://lore.kernel.org/linux-iio/aea7f92b-3d12-4ced-b1c8-90bcf1d992d3@baylibre.com/T/#m1377d5acd7e996acd1f59038bdd09f0742d3ac35
> ---
>  drivers/pwm/core.c  | 55 +++++++++++++++++++++++++++++++++++++----------------
>  include/linux/pwm.h |  1 +
>  2 files changed, 40 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
> index 634be56e204b..a214d0165d09 100644
> --- a/drivers/pwm/core.c
> +++ b/drivers/pwm/core.c
> @@ -718,7 +718,7 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
>  }
>  EXPORT_SYMBOL_GPL(pwm_apply_atomic);
>  
> -static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
> +static int __pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
>  {
>  	struct pwm_chip *chip = pwm->chip;
>  	const struct pwm_ops *ops = chip->ops;
> @@ -730,29 +730,50 @@ static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
>  
>  		BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
>  
> -		scoped_guard(pwmchip, chip) {
> -
> -			ret = __pwm_read_waveform(chip, pwm, &wfhw);
> -			if (ret)
> -				return ret;
> +		ret = __pwm_read_waveform(chip, pwm, &wfhw);
> +		if (ret)
> +			return ret;
>  
> -			ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
> -			if (ret)
> -				return ret;
> -		}
> +		ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
> +		if (ret)
> +			return ret;
>  
>  		pwm_wf2state(&wf, state);
>  
>  	} else if (ops->get_state) {
> -		scoped_guard(pwmchip, chip)
> -			ret = ops->get_state(chip, pwm, state);
> -
> +		ret = ops->get_state(chip, pwm, state);
>  		trace_pwm_get(pwm, state, ret);
>  	}
>  
>  	return ret;
>  }

I don't understand why you introduce __pwm_get_state_hw() (a variant of
pwm_get_state_hw() that expects the caller to hold the chip lock) when the
single caller (apart from plain pwm_get_state_hw()) could just continue
to use pwm_get_state_hw().

In principle I'm open to such a patch and wonder if there is already a
merge plan for this series. If you send a simpler patch soon with the
same objective, I'll make sure it goes into v6.13-rc1 in the assumption
that it's to late for the whole series to go in then. Or do you still
target 6.13-rc1 for the spi bits? Then it would probably better to let
this patch go in with the rest via the spi tree.

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw()
  2024-10-29  8:05   ` Uwe Kleine-König
@ 2024-10-29 15:30     ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-10-29 15:30 UTC (permalink / raw)
  To: Uwe Kleine-König, Guillaume Stols
  Cc: Mark Brown, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Nuno Sá, Michael Hennerich, Lars-Peter Clausen,
	David Jander, Martin Sperl, linux-spi, devicetree, linux-kernel,
	linux-iio, linux-pwm

On 10/29/24 3:05 AM, Uwe Kleine-König wrote:
> Hello David,
> 
> On Wed, Oct 23, 2024 at 03:59:08PM -0500, David Lechner wrote:
>> Export the pwm_get_state_hw() function. This is useful in cases where
>> we want to know what the hardware is actually doing, rather than what
>> what we requested it should do.
>>
>> Signed-off-by: David Lechner <dlechner@baylibre.com>
>> ---
>>
>> v4 changes: new patch in v4
>>
>> And FYI for Uwe and Jonathan, there are a couple of other series
>> introducing PWM conversion triggers that could make use of this
>> so that the sampling_frequency attribute can return the actual rate
>> rather than the requested rate.
>>
>> Already applied:
>> https://lore.kernel.org/linux-iio/20241015-ad7606_add_iio_backend_support-v5-4-654faf1ae08c@baylibre.com/
>>
>> Under review:
>> https://lore.kernel.org/linux-iio/aea7f92b-3d12-4ced-b1c8-90bcf1d992d3@baylibre.com/T/#m1377d5acd7e996acd1f59038bdd09f0742d3ac35
>> ---
>>  drivers/pwm/core.c  | 55 +++++++++++++++++++++++++++++++++++++----------------
>>  include/linux/pwm.h |  1 +
>>  2 files changed, 40 insertions(+), 16 deletions(-)
>>
>> diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
>> index 634be56e204b..a214d0165d09 100644
>> --- a/drivers/pwm/core.c
>> +++ b/drivers/pwm/core.c
>> @@ -718,7 +718,7 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
>>  }
>>  EXPORT_SYMBOL_GPL(pwm_apply_atomic);
>>  
>> -static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
>> +static int __pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
>>  {
>>  	struct pwm_chip *chip = pwm->chip;
>>  	const struct pwm_ops *ops = chip->ops;
>> @@ -730,29 +730,50 @@ static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
>>  
>>  		BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
>>  
>> -		scoped_guard(pwmchip, chip) {
>> -
>> -			ret = __pwm_read_waveform(chip, pwm, &wfhw);
>> -			if (ret)
>> -				return ret;
>> +		ret = __pwm_read_waveform(chip, pwm, &wfhw);
>> +		if (ret)
>> +			return ret;
>>  
>> -			ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
>> -			if (ret)
>> -				return ret;
>> -		}
>> +		ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
>> +		if (ret)
>> +			return ret;
>>  
>>  		pwm_wf2state(&wf, state);
>>  
>>  	} else if (ops->get_state) {
>> -		scoped_guard(pwmchip, chip)
>> -			ret = ops->get_state(chip, pwm, state);
>> -
>> +		ret = ops->get_state(chip, pwm, state);
>>  		trace_pwm_get(pwm, state, ret);
>>  	}
>>  
>>  	return ret;
>>  }
> 
> I don't understand why you introduce __pwm_get_state_hw() (a variant of
> pwm_get_state_hw() that expects the caller to hold the chip lock) when the
> single caller (apart from plain pwm_get_state_hw()) could just continue
> to use pwm_get_state_hw().

Hmm... it seems like I thought there was a good reason for it at the
time, but looking at it again, I agree with your assessment.

> 
> In principle I'm open to such a patch and wonder if there is already a
> merge plan for this series. If you send a simpler patch soon with the
> same objective, I'll make sure it goes into v6.13-rc1 in the assumption
> that it's to late for the whole series to go in then. Or do you still
> target 6.13-rc1 for the spi bits? Then it would probably better to let
> this patch go in with the rest via the spi tree.

The SPI offload stuff is not likely to be merged soon. But there is
ad7606 + AXI ADC support from Guillaume that was just merged that
could make use of this. So I can send this as a stand-alone patch
so that it can be made available for that too.


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
                     ` (2 preceding siblings ...)
  2024-10-26 15:05   ` Jonathan Cameron
@ 2024-10-30 15:55   ` Mark Brown
  3 siblings, 0 replies; 59+ messages in thread
From: Mark Brown @ 2024-10-30 15:55 UTC (permalink / raw)
  To: David Lechner
  Cc: Jonathan Cameron, 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

[-- Attachment #1: Type: text/plain, Size: 385 bytes --]

On Wed, Oct 23, 2024 at 03:59:09PM -0500, David Lechner wrote:

> +struct spi_offload *devm_spi_offload_alloc(struct device *dev,
> +					   size_t num_offloads,
> +					   size_t priv_size)

> +	privs = (void *)(offloads + num_offloads);

Casting to or from void * is generally suspicious.

> +		void *priv = privs + i * priv_size;

Can we have some brackets here for clarity please?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-26 15:18   ` Jonathan Cameron
  2024-10-27  0:20     ` David Lechner
  2024-10-27 20:24     ` Conor Dooley
@ 2024-10-31 18:16     ` Conor Dooley
  2024-11-11 17:31       ` David Lechner
  2 siblings, 1 reply; 59+ messages in thread
From: Conor Dooley @ 2024-10-31 18:16 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: David Lechner, 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

[-- Attachment #1: Type: text/plain, Size: 1397 bytes --]

On Sat, Oct 26, 2024 at 04:18:37PM +0100, Jonathan Cameron wrote:
> On Wed, 23 Oct 2024 15:59:12 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 
> > Add a new binding for using a PWM signal as a trigger for SPI offloads.
> 
> I don't have a better suggestion for this, but it does smell rather like
> other bridge binding (iio-hwmon for example) where we have had push back on
> representing something that doesn't really exist but is just a way to
> tie two bits of hardware together. Those kind of exist because we snuck
> them in a long time back when no one was paying attention.

I dunno. iio-hwmon to me is a particularly strange one, because it is
the exact same device being used in different subsystems. Like that
voltage monitoring device with 10000 compatibles that I CCed you and
Peter on the other day feels like it should really in your subsytem. A
"hwmon" isn't a class of device at all.

This however, I think is more like pwm-clock (or clk-pwm, they both
exist and are opposites) where the node is used to change the type of
device rather than the subsystem using it.

> So this one may need more explanation and justification and I'd definitely
> like some DT maintainer review on this at a fairly early stage!

Ye, /shrug. Maybe the others have dissenting opinions. I'd like to hear
from them, but I don't personally have a problem with this.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-10-26 15:05   ` Jonathan Cameron
@ 2024-11-11 17:14     ` David Lechner
  2024-11-11 19:02       ` David Lechner
  0 siblings, 1 reply; 59+ messages in thread
From: David Lechner @ 2024-11-11 17:14 UTC (permalink / raw)
  To: Jonathan Cameron
  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 10/26/24 10:05 AM, Jonathan Cameron wrote:
> On Wed, 23 Oct 2024 15:59:09 -0500
> David Lechner <dlechner@baylibre.com> wrote:
> 

...

>> +struct spi_offload *devm_spi_offload_get(struct device *dev,
>> +					 struct spi_device *spi,
>> +					 const struct spi_offload_config *config)
>> +{
>> +	struct spi_offload *offload;
>> +	int ret;
>> +
>> +	if (!spi || !config)
>> +		return ERR_PTR(-EINVAL);
>> +
>> +	if (!spi->controller->get_offload)
>> +		return ERR_PTR(-ENODEV);
>> +
>> +	offload = spi->controller->get_offload(spi, config);
> 
> Why let this return an offload that is already in use?
> Maybe make that a problem for the spi controller
> Seems odd to pass it spi then set it later.
> 
> I.e. have this return ERR_PTR(-EBUSY);

I would expect that to effectively be handled by the
if (IS_ERR(offload)) below. Only the controller can
know which offloads are already in use, so the callback
should return the appropriate -EBUSY in that case.

> 
> 
>> +	if (IS_ERR(offload))
>> +		return offload;
>> +
>> +	if (offload->spi)
>> +		return ERR_PTR(-EBUSY);
>> +
>> +	offload->spi = spi;
>> +	get_device(offload->provider_dev);
>> +
>> +	ret = devm_add_action_or_reset(dev, spi_offload_put, offload);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	return offload;
>> +}
>> +EXPORT_SYMBOL_GPL(devm_spi_offload_get);


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger
  2024-10-31 18:16     ` Conor Dooley
@ 2024-11-11 17:31       ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-11-11 17:31 UTC (permalink / raw)
  To: Conor Dooley, Jonathan Cameron
  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 10/31/24 1:16 PM, Conor Dooley wrote:
> On Sat, Oct 26, 2024 at 04:18:37PM +0100, Jonathan Cameron wrote:
>> On Wed, 23 Oct 2024 15:59:12 -0500
>> David Lechner <dlechner@baylibre.com> wrote:
>>
>>> Add a new binding for using a PWM signal as a trigger for SPI offloads.
>>
>> I don't have a better suggestion for this, but it does smell rather like
>> other bridge binding (iio-hwmon for example) where we have had push back on
>> representing something that doesn't really exist but is just a way to
>> tie two bits of hardware together. Those kind of exist because we snuck
>> them in a long time back when no one was paying attention.
> 
> I dunno. iio-hwmon to me is a particularly strange one, because it is
> the exact same device being used in different subsystems. Like that
> voltage monitoring device with 10000 compatibles that I CCed you and
> Peter on the other day feels like it should really in your subsytem. A
> "hwmon" isn't a class of device at all.
> 
> This however, I think is more like pwm-clock (or clk-pwm, they both
> exist and are opposites) where the node is used to change the type of
> device rather than the subsystem using it.

Yes, this is the key reason for the binding. When I was looking at
the trigger bindings in the leds subsystem, I came to the realization
that we need some way to get the underlying type of the trigger. In the
leds bindings, I don't think this was intentional, but effectively this
is done with the linux,default-trigger property.

So unless there is a reason why copying the clk-pwm/pwm-clock style
bindings is not a good idea, that seems the preferable way to do it
to me and I'll stick with that.

> 
>> So this one may need more explanation and justification and I'd definitely
>> like some DT maintainer review on this at a fairly early stage!
> 
> Ye, /shrug. Maybe the others have dissenting opinions. I'd like to hear
> from them, but I don't personally have a problem with this.



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH RFC v4 02/15] spi: add basic support for SPI offloading
  2024-11-11 17:14     ` David Lechner
@ 2024-11-11 19:02       ` David Lechner
  0 siblings, 0 replies; 59+ messages in thread
From: David Lechner @ 2024-11-11 19:02 UTC (permalink / raw)
  To: Jonathan Cameron
  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 11/11/24 11:14 AM, David Lechner wrote:
> On 10/26/24 10:05 AM, Jonathan Cameron wrote:
>> On Wed, 23 Oct 2024 15:59:09 -0500
>> David Lechner <dlechner@baylibre.com> wrote:
>>
> 
> ...
> 
>>> +struct spi_offload *devm_spi_offload_get(struct device *dev,
>>> +					 struct spi_device *spi,
>>> +					 const struct spi_offload_config *config)
>>> +{
>>> +	struct spi_offload *offload;
>>> +	int ret;
>>> +
>>> +	if (!spi || !config)
>>> +		return ERR_PTR(-EINVAL);
>>> +
>>> +	if (!spi->controller->get_offload)
>>> +		return ERR_PTR(-ENODEV);
>>> +
>>> +	offload = spi->controller->get_offload(spi, config);
>>
>> Why let this return an offload that is already in use?
>> Maybe make that a problem for the spi controller
>> Seems odd to pass it spi then set it later.
>>
>> I.e. have this return ERR_PTR(-EBUSY);
> 
> I would expect that to effectively be handled by the
> if (IS_ERR(offload)) below. Only the controller can
> know which offloads are already in use, so the callback
> should return the appropriate -EBUSY in that case.

Just realized I said exactly what you said! Will fix this.

> 
>>
>>
>>> +	if (IS_ERR(offload))
>>> +		return offload;
>>> +
>>> +	if (offload->spi)
>>> +		return ERR_PTR(-EBUSY);
>>> +
>>> +	offload->spi = spi;
>>> +	get_device(offload->provider_dev);
>>> +
>>> +	ret = devm_add_action_or_reset(dev, spi_offload_put, offload);
>>> +	if (ret)
>>> +		return ERR_PTR(ret);
>>> +
>>> +	return offload;
>>> +}
>>> +EXPORT_SYMBOL_GPL(devm_spi_offload_get);
> 


^ permalink raw reply	[flat|nested] 59+ messages in thread

end of thread, other threads:[~2024-11-11 19:02 UTC | newest]

Thread overview: 59+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-23 20:59 [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 01/15] pwm: core: export pwm_get_state_hw() David Lechner
2024-10-29  8:05   ` Uwe Kleine-König
2024-10-29 15:30     ` David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 02/15] spi: add basic support for SPI offloading David Lechner
2024-10-24 13:27   ` Nuno Sá
2024-10-24 14:49     ` David Lechner
2024-10-25 12:59   ` Nuno Sá
2024-10-25 16:39     ` Nuno Sá
2024-10-26 15:05   ` Jonathan Cameron
2024-11-11 17:14     ` David Lechner
2024-11-11 19:02       ` David Lechner
2024-10-30 15:55   ` Mark Brown
2024-10-23 20:59 ` [PATCH RFC v4 03/15] spi: offload: add support for hardware triggers David Lechner
2024-10-24 14:04   ` Nuno Sá
2024-10-24 15:02     ` David Lechner
2024-10-25  6:29       ` Nuno Sá
2024-10-26 15:14   ` Jonathan Cameron
2024-10-28 13:53   ` Nuno Sá
2024-10-23 20:59 ` [PATCH RFC v4 04/15] spi: dt-bindings: add trigger-source.yaml David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 05/15] spi: dt-bindings: add PWM SPI offload trigger David Lechner
2024-10-26 15:18   ` Jonathan Cameron
2024-10-27  0:20     ` David Lechner
2024-10-27 20:24     ` Conor Dooley
2024-10-31 18:16     ` Conor Dooley
2024-11-11 17:31       ` David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 06/15] spi: offload-trigger: add PWM trigger driver David Lechner
2024-10-25 12:07   ` Nuno Sá
2024-10-25 16:28     ` David Lechner
2024-10-28 13:47       ` Nuno Sá
2024-10-23 20:59 ` [PATCH RFC v4 07/15] spi: add offload TX/RX streaming APIs David Lechner
2024-10-25 12:24   ` Nuno Sá
2024-10-23 20:59 ` [PATCH RFC v4 08/15] spi: dt-bindings: axi-spi-engine: add SPI offload properties David Lechner
2024-10-25 12:26   ` Nuno Sá
2024-10-23 20:59 ` [PATCH RFC v4 09/15] spi: axi-spi-engine: implement offload support David Lechner
2024-10-25 13:09   ` Nuno Sá
2024-10-25 16:35     ` David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 10/15] iio: buffer-dmaengine: document iio_dmaengine_buffer_setup_ext David Lechner
2024-10-26 15:29   ` Jonathan Cameron
2024-10-23 20:59 ` [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() David Lechner
2024-10-25 13:24   ` Nuno Sá
2024-10-25 16:42     ` David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 12/15] iio: adc: ad7944: don't use storagebits for sizing David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 13/15] iio: adc: ad7944: add support for SPI offload David Lechner
2024-10-26 15:51   ` Jonathan Cameron
2024-10-23 20:59 ` [PATCH RFC v4 14/15] dt-bindings: iio: adc: adi,ad4695: add SPI offload properties David Lechner
2024-10-23 20:59 ` [PATCH RFC v4 15/15] iio: adc: ad4695: Add support for SPI offload David Lechner
2024-10-26 16:00   ` Jonathan Cameron
2024-10-27  0:01     ` David Lechner
2024-10-27  9:12       ` Jonathan Cameron
2024-10-27 19:52         ` David Lechner
2024-10-28 16:39           ` Jonathan Cameron
2024-10-27  0:05     ` David Lechner
2024-10-27  9:15       ` Jonathan Cameron
2024-10-24 14:12 ` [PATCH RFC v4 00/15] spi: axi-spi-engine: add offload support Nuno Sá
  -- strict thread matches above, loose matches on Subject: below --
2024-10-25 18:40 [PATCH RFC v4 11/15] iio: buffer-dmaengine: add devm_iio_dmaengine_buffer_setup_ext2() Nuno Sá
2024-10-26 15:48 ` Jonathan Cameron
2024-10-28 11:08   ` Nuno Sá
2024-10-28 16:35     ` Jonathan Cameron

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).