The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support
@ 2026-07-14  5:55 Jonathan Santos
  2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
                   ` (7 more replies)
  0 siblings, 8 replies; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:55 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

This series introduces two SPI subsystem features: per-transfer chipselect
masks and multi-CS device support. Together they address the multi-device
setup described in [1] and the limitation noted in [2], where no SPI
controller completely handles logical chip selects beyond the first one.

The first part of the set addresses multi-CS support. Some SPI controllers
can assert multiple chip selects simultaneously, but the existing code
hardcoded CS index 0 in both spi_set_cs() and of_spi_parse_dt(),
preventing this from working.

The second part addresses dynamic lane selection for STRIPE mode. In
SPI_MULTI_LANE_MODE_STRIPE, all available lanes are currently always
active. Some peripherals need to select a different subset of rx/tx lanes
per transfer. New fields are added to the spi_transfer struct to allow
drivers to specify which lanes to use for each transfer. The documentation
is also updated to describe this new behavior.

[1]: https://lore.kernel.org/linux-iio/af0EGv172ZMl%2F6N5@JSANTO12-L01.ad.analog.com/T/#t
[2]: https://lore.kernel.org/all/20250915183725.219473-1-jonas.gorski@gmail.com/

Jonathan Santos (6):
  spi: support simultaneous assertion of multiple CS
  spi: add per-transfer CS mask
  spi: spi-engine-ex: Add support for multi-CS devices
  spi: Documentation: multiple-data-lanes: describe rx and tx lane mask
  spi: add rx and tx lane mask to spi_transfer struct
  spi: axi-spi-engine: add support for dynamic multi-lane selection

 Documentation/spi/multiple-data-lanes.rst |  30 ++++++
 drivers/spi/spi-axi-spi-engine.c          | 106 +++++++++++++++++-----
 drivers/spi/spi.c                         |  97 +++++++++++++++++---
 include/linux/spi/spi.h                   |  12 +++
 4 files changed, 206 insertions(+), 39 deletions(-)


base-commit: 093239070573637ad2b4cb56abc9c4c7ee109294
-- 
2.34.1


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

* [PATCH 1/6] spi: support simultaneous assertion of multiple CS
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
@ 2026-07-14  5:55 ` Jonathan Santos
  2026-07-14  9:06   ` Andy Shevchenko
  2026-07-14  9:08   ` Nuno Sá
  2026-07-14  5:56 ` [PATCH 2/6] spi: add per-transfer CS mask Jonathan Santos
                   ` (6 subsequent siblings)
  7 siblings, 2 replies; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:55 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

Some SPI controllers allow multiple CS lines to be toggled at the same
time. The existing code always used CS index 0 when tracking the last
active CS in spi_set_cs(), and unconditionally set cs_index_mask to
BIT(0) when parsing DT, both forcing the single CS usage.

Modify spi_set_cs() to iterate last_cs[] using each logical CS index
instead of always reading index 0. Modify of_spi_parse_dt() to build
cs_index_mask from all parsed CS entries rather than hardcoding BIT(0),
so the controller correctly identifies which CS lines belong to a device
when asserting them simultaneously.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 drivers/spi/spi.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index d9e6b4b87c89..55fb96fea243 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1090,7 +1090,7 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
 	spi->controller->last_cs_index_mask = spi->cs_index_mask;
 	for (idx = 0; idx < SPI_DEVICE_CS_CNT_MAX; idx++) {
 		if (enable && idx < spi->num_chipselect)
-			spi->controller->last_cs[idx] = spi_get_chipselect(spi, 0);
+			spi->controller->last_cs[idx] = spi_get_chipselect(spi, idx);
 		else
 			spi->controller->last_cs[idx] = SPI_INVALID_CS;
 	}
@@ -2594,10 +2594,11 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
 		spi_set_chipselect(spi, idx, cs[idx]);
 
 	/*
-	 * By default spi->chip_select[0] will hold the physical CS number,
-	 * so set bit 0 in spi->cs_index_mask.
+	 * Set cs_index_mask to indicate which logical CS indices are active.
+	 * Each bit corresponds to a logical CS index in the spi->chip_select array.
 	 */
-	spi->cs_index_mask = BIT(0);
+	for (idx = 0; idx < rc; idx++)
+		spi->cs_index_mask |= BIT(idx);
 
 	/* Device speed */
 	if (!of_property_read_u32(nc, "spi-max-frequency", &value))
-- 
2.34.1


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

* [PATCH 2/6] spi: add per-transfer CS mask
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
  2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
@ 2026-07-14  5:56 ` Jonathan Santos
  2026-07-14  9:12   ` Nuno Sá
  2026-07-14  9:23   ` Andy Shevchenko
  2026-07-14  5:56 ` [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices Jonathan Santos
                   ` (5 subsequent siblings)
  7 siblings, 2 replies; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:56 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

Multi-CS devices assert all their chip selects simultaneously by default
via cs_index_mask. Some use cases require selecting a subset of those CS
lines per transfer (e.g. routing data to one of several subdevices sharing
the bus).

Add a cs_select_mask field to spi_transfer. When the mask is set and the
controller has SPI_CONTROLLER_MULTI_CS, spi_set_cs() builds a temporary
spi_device whose chip_select[] and cs_gpiod[] arrays contain only the lines
indicated by the mask, then delegates to the renamed _spi_set_cs().
When cs_select_mask is 0 or the controller does not support multi-CS,
the device default (cs_index_mask) is used unchanged.

Update spi_transfer_one_message() to pass the relevant spi_transfer to
every spi_set_cs() call so CS transitions between transfers with
different masks are handled correctly.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 drivers/spi/spi.c       | 88 +++++++++++++++++++++++++++++++++++------
 include/linux/spi/spi.h |  4 ++
 2 files changed, 81 insertions(+), 11 deletions(-)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 55fb96fea243..6d644adf8445 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1071,7 +1071,7 @@ static void spi_toggle_csgpiod(struct spi_device *spi, u8 idx, bool enable, bool
 		spi_delay_exec(&spi->cs_inactive, NULL);
 }
 
-static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
+static void _spi_set_cs(struct spi_device *spi, bool enable, bool force)
 {
 	bool activate = enable;
 	u8 idx;
@@ -1129,6 +1129,61 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
 	}
 }
 
+static void spi_set_cs(struct spi_device *spi,
+		       struct spi_transfer *xfer,
+		       bool enable, bool force)
+{
+	struct spi_controller *ctlr = spi->controller;
+	struct spi_device temp_spi;
+	u8 cs_mask, cs_count, idx;
+
+	/*
+	 * If no transfer-specific CS mask, or device does not support multi-CS,
+	 * fall back to device default
+	 */
+	if (!xfer || !(ctlr->flags & SPI_CONTROLLER_MULTI_CS))
+		goto out_default_cs;
+
+	if (!xfer->cs_select_mask)
+		goto out_default_cs;
+
+	/*
+	 * Handle multi-CS controller path.
+	 * For multi-CS controllers, we need to create a temporary spi_device
+	 * structure that reflects the transfer's CS selection
+	 */
+	cs_mask = xfer->cs_select_mask & spi->cs_index_mask;
+	temp_spi = *spi;
+	cs_count = 0;
+
+	temp_spi.cs_index_mask = 0;
+	for (idx = 0; idx < spi->num_chipselect; idx++) {
+		if (cs_mask & BIT(idx)) {
+			temp_spi.chip_select[cs_count] = spi->chip_select[idx];
+			if (spi->cs_gpiod[idx])
+				temp_spi.cs_gpiod[cs_count] = spi->cs_gpiod[idx];
+			temp_spi.cs_index_mask |= BIT(cs_count);
+			cs_count++;
+		}
+	}
+	temp_spi.num_chipselect = cs_count;
+
+	_spi_set_cs(&temp_spi, enable, force);
+
+	/*
+	 * Copy the last CS state from temp device back to original controller
+	 * to keep the tracking.
+	 */
+	ctlr->last_cs_index_mask = temp_spi.controller->last_cs_index_mask;
+	ctlr->last_cs_mode_high = temp_spi.controller->last_cs_mode_high;
+	memcpy(ctlr->last_cs, temp_spi.controller->last_cs, sizeof(ctlr->last_cs));
+
+	return;
+
+out_default_cs:
+	_spi_set_cs(spi, enable, force);
+}
+
 #ifdef CONFIG_HAS_DMA
 static int spi_map_buf_attrs(struct spi_controller *ctlr, struct device *dev,
 			     struct sg_table *sgt, void *buf, size_t len,
@@ -1612,7 +1667,7 @@ static int spi_transfer_one_message(struct spi_controller *ctlr,
 	struct spi_statistics __percpu *stats = msg->spi->pcpu_statistics;
 
 	xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list);
-	spi_set_cs(msg->spi, !xfer->cs_off, false);
+	spi_set_cs(msg->spi, xfer, !xfer->cs_off, false);
 
 	SPI_STATISTICS_INCREMENT_FIELD(statm, messages);
 	SPI_STATISTICS_INCREMENT_FIELD(stats, messages);
@@ -1680,28 +1735,39 @@ static int spi_transfer_one_message(struct spi_controller *ctlr,
 
 		spi_transfer_delay_exec(xfer);
 
+		struct spi_transfer *next_xfer = list_next_entry(xfer, transfer_list);
 		if (xfer->cs_change) {
 			if (list_is_last(&xfer->transfer_list,
 					 &msg->transfers)) {
 				keep_cs = true;
 			} else {
 				if (!xfer->cs_off)
-					spi_set_cs(msg->spi, false, false);
+					spi_set_cs(msg->spi, xfer, false, false);
 				_spi_transfer_cs_change_delay(msg, xfer);
-				if (!list_next_entry(xfer, transfer_list)->cs_off)
-					spi_set_cs(msg->spi, true, false);
+				if (!next_xfer->cs_off)
+					spi_set_cs(msg->spi, next_xfer, true, false);
 			}
 		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
-			   xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) {
-			spi_set_cs(msg->spi, xfer->cs_off, false);
+			   xfer->cs_off != next_xfer->cs_off) {
+			spi_set_cs(msg->spi, next_xfer, xfer->cs_off, false);
+		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
+			   xfer->cs_select_mask != next_xfer->cs_select_mask) {
+			/* disable the current xfer CS before enabling the next xfer CS */
+			spi_set_cs(msg->spi, xfer, false, false);
+			spi_set_cs(msg->spi, next_xfer, true, false);
 		}
 
 		msg->actual_length += xfer->len;
 	}
 
 out:
-	if (ret != 0 || !keep_cs)
-		spi_set_cs(msg->spi, false, false);
+	if (ret != 0 || !keep_cs) {
+		/* Use the last transfer's CS configuration for proper cleanup */
+		struct spi_transfer *last_xfer = list_last_entry(&msg->transfers,
+								 struct spi_transfer,
+								 transfer_list);
+		spi_set_cs(msg->spi, last_xfer, false, false);
+	}
 
 	if (msg->status == -EINPROGRESS)
 		msg->status = ret;
@@ -4138,10 +4204,10 @@ static int __spi_setup(struct spi_device *spi, bool initial_setup)
 		 */
 		status = 0;
 
-		spi_set_cs(spi, false, true);
+		spi_set_cs(spi, NULL, false, true);
 		pm_runtime_put_autosuspend(spi->controller->dev.parent);
 	} else {
-		spi_set_cs(spi, false, true);
+		spi_set_cs(spi, NULL, false, true);
 	}
 
 	mutex_unlock(&spi->controller->io_mutex);
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 4c285d3ede1d..a9988db3fe2f 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -993,6 +993,9 @@ struct spi_res {
  *      for this transfer. If 0 the default (from @spi_device) is used.
  * @dummy_data: indicates transfer is dummy bytes transfer.
  * @cs_off: performs the transfer with chipselect off.
+ * @cs_select_mask: bitmask of chipselects from the @spi_device chipselect
+ *      array to assert for this transfer, overriding @spi_device.cs_index_mask.
+ *      If 0 the device default (@spi_device.cs_index_mask) is used.
  * @cs_change: affects chipselect after this transfer completes
  * @cs_change_delay: delay between cs deassert and assert when
  *      @cs_change is set and @spi_transfer is not the last in @spi_message
@@ -1120,6 +1123,7 @@ struct spi_transfer {
 	unsigned	dummy_data:1;
 	unsigned	cs_off:1;
 	unsigned	cs_change:1;
+	unsigned	cs_select_mask:8;
 	unsigned	tx_nbits:4;
 	unsigned	rx_nbits:4;
 
-- 
2.34.1


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

* [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
  2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
  2026-07-14  5:56 ` [PATCH 2/6] spi: add per-transfer CS mask Jonathan Santos
@ 2026-07-14  5:56 ` Jonathan Santos
  2026-07-14  9:17   ` Andy Shevchenko
  2026-07-14  5:56 ` [PATCH 4/6] spi: Documentation: multiple-data-lanes: describe rx and tx lane mask Jonathan Santos
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:56 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

The AXI SPI Engine controller hardcoded CS index 0 when generating
assert commands, so only the first chip select was being toggled even
when a device declared multiple CS lines in the device tree.

Modify spi_engine_gen_cs() to accept a cs_select_mask argument and
iterate all bits set in the effective mask (the intersection of
xfer->cs_select_mask with spi->cs_index_mask, falling back to
spi->cs_index_mask when the transfer mask is 0). Update
spi_engine_compile_message() to pass each transfer's cs_select_mask
at every CS transition, including cs_change and cs_select_mask
change boundaries between consecutive transfers.

Likewise, modify spi_engine_setup() replacing the single-index CS
assert with a for_each_set_bit() loop over cs_index_mask.

Set SPI_CONTROLLER_MULTI_CS flag in the probe path so the core
multi-CS path in spi_set_cs() is activated for this controller.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 drivers/spi/spi-axi-spi-engine.c | 44 ++++++++++++++++++++++----------
 1 file changed, 31 insertions(+), 13 deletions(-)

diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
index 02bbc5d0cfc5..f2d0a6dd593b 100644
--- a/drivers/spi/spi-axi-spi-engine.c
+++ b/drivers/spi/spi-axi-spi-engine.c
@@ -46,6 +46,7 @@
 #define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK	0xec
 
 #define SPI_ENGINE_MAX_NUM_OFFLOADS		32
+#define SPI_ENGINE_MAX_CS			8
 
 #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))
@@ -279,12 +280,17 @@ static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry,
 }
 
 static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
-		struct spi_device *spi, bool assert)
+		struct spi_device *spi, bool assert, unsigned long xfer_cs_mask)
 {
+	unsigned long cs_index_mask = !xfer_cs_mask
+		? spi->cs_index_mask
+		: spi->cs_index_mask & xfer_cs_mask;
 	unsigned int mask = 0xff;
+	unsigned int cs_bit;
 
 	if (assert)
-		mask ^= BIT(spi_get_chipselect(spi, 0));
+		for_each_set_bit(cs_bit, &cs_index_mask, SPI_ENGINE_MAX_CS)
+			mask ^= BIT(spi_get_chipselect(spi, cs_bit));
 
 	spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(0, mask));
 }
@@ -406,7 +412,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 	}
 
 	xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list);
-	spi_engine_gen_cs(p, dry, spi, !xfer->cs_off);
+	spi_engine_gen_cs(p, dry, spi, !xfer->cs_off, xfer->cs_select_mask);
 
 	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
 		if (xfer->rx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM ||
@@ -451,28 +457,33 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 		spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
 				     inst_ns, xfer->effective_speed_hz);
 
+		struct spi_transfer *next_xfer = list_next_entry(xfer, transfer_list);
 		if (xfer->cs_change) {
 			if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
 				keep_cs = true;
 			} else {
 				if (!xfer->cs_off)
-					spi_engine_gen_cs(p, dry, spi, false);
+					spi_engine_gen_cs(p, dry, spi, false, xfer->cs_select_mask);
 
 				spi_engine_gen_sleep(p, dry, spi_delay_to_ns(
 					&xfer->cs_change_delay, xfer), inst_ns,
 					xfer->effective_speed_hz);
 
-				if (!list_next_entry(xfer, transfer_list)->cs_off)
-					spi_engine_gen_cs(p, dry, spi, true);
+				if (!next_xfer->cs_off)
+					spi_engine_gen_cs(p, dry, spi, true,
+							  next_xfer->cs_select_mask);
 			}
 		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
-			   xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) {
-			spi_engine_gen_cs(p, dry, spi, xfer->cs_off);
+			   xfer->cs_off != next_xfer->cs_off) {
+			spi_engine_gen_cs(p, dry, spi, xfer->cs_off, xfer->cs_select_mask);
+		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
+			   xfer->cs_select_mask != next_xfer->cs_select_mask) {
+			spi_engine_gen_cs(p, dry, spi, true, next_xfer->cs_select_mask);
 		}
 	}
 
 	if (!keep_cs)
-		spi_engine_gen_cs(p, dry, spi, false);
+		spi_engine_gen_cs(p, dry, spi, false, 0);
 
 	/*
 	 * Restore clockdiv to default so that future gen_sleep commands don't
@@ -886,12 +897,18 @@ static int spi_engine_setup(struct spi_device *device)
 {
 	struct spi_controller *host = device->controller;
 	struct spi_engine *spi_engine = spi_controller_get_devdata(host);
+	unsigned long cs_index_mask = device->cs_index_mask;
 	unsigned int reg;
+	u32 cs_bit;
 
-	if (device->mode & SPI_CS_HIGH)
-		spi_engine->cs_inv |= BIT(spi_get_chipselect(device, 0));
-	else
-		spi_engine->cs_inv &= ~BIT(spi_get_chipselect(device, 0));
+	for_each_set_bit(cs_bit, &cs_index_mask, SPI_ENGINE_MAX_CS) {
+		if (device->mode & SPI_CS_HIGH)
+			spi_engine->cs_inv |= BIT(spi_get_chipselect(device,
+								     cs_bit));
+		else
+			spi_engine->cs_inv &= ~BIT(spi_get_chipselect(device,
+								      cs_bit));
+	}
 
 	writel_relaxed(SPI_ENGINE_CMD_SYNC(0),
 		       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
@@ -1222,6 +1239,7 @@ static int spi_engine_probe(struct platform_device *pdev)
 	host->unoptimize_message = spi_engine_unoptimize_message;
 	host->get_offload = spi_engine_get_offload;
 	host->put_offload = spi_engine_put_offload;
+	host->flags |= SPI_CONTROLLER_MULTI_CS;
 	host->num_chipselect = 8;
 
 	if (adi_axi_pcore_ver_gteq(version, 1, 2)) {
-- 
2.34.1


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

* [PATCH 4/6] spi: Documentation: multiple-data-lanes: describe rx and tx lane mask
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
                   ` (2 preceding siblings ...)
  2026-07-14  5:56 ` [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices Jonathan Santos
@ 2026-07-14  5:56 ` Jonathan Santos
  2026-07-14  5:56 ` [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct Jonathan Santos
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:56 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

Document that when either rx_lane_mask or tx_lane_mask is non-zero, STRIPE
operates only over the active lanes indicated by the mask. The transfer
buffer must be sized as a multiple of the active lane count rather than
the total lane count.

To maintain backwards compatibility, when both masks are zero, the default
behavior is to enable all lanes.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 Documentation/spi/multiple-data-lanes.rst | 30 +++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/Documentation/spi/multiple-data-lanes.rst b/Documentation/spi/multiple-data-lanes.rst
index 69cb532d052f..578a6a8e4380 100644
--- a/Documentation/spi/multiple-data-lanes.rst
+++ b/Documentation/spi/multiple-data-lanes.rst
@@ -200,6 +200,36 @@ The possible values for this field have the following semantics:
     After the transfer, ``rx_buf[0] == 0x11`` (word from SDO 0) and
     ``rx_buf[1] == 0x88`` (word from SDO 1).
 
+    When ``rx_lane_mask`` or ``tx_lane_mask`` is set, STRIPE operates over
+    active lanes only. Buffer size must be sized as a multiple of the number
+    of active lanes, not the total number of lanes. The mask must be a subset
+    of the total lanes, bits that correspond to lanes not defined for the device
+    are ignored. When ``rx_lane_mask`` or ``tx_lane_mask`` is set to zero, all
+    lanes are considered active.
+
+    Example::
+
+        struct spi_transfer xfer = {
+            .rx_buf = rx_buf,
+            .len = 2,
+            .multi_lane_mode = SPI_MULTI_BUS_MODE_STRIPE,
+            .rx_lane_mask = 0x0A, /* only lanes 1 and 3 are active */
+        };
+
+        spi_sync_transfer(spi, &xfer, 1);
+
+    Each rx wire has a different data word sent simultaneously::
+
+        controller    < data bits <     peripheral
+        ----------   ----------------   ----------
+            SDI 0    1-1-0-1-0-1-1-0    SDO 0
+            SDI 1    0-1-1-0-0-1-0-1    SDO 1
+            SDI 2    1-0-1-1-0-0-1-1    SDO 2
+            SDI 3    0-0-1-0-1-1-0-1    SDO 3
+
+    After the transfer, ``rx_buf[0] == 0x65`` (word from SDO 1) and
+    ``rx_buf[1] == 0x2D`` (word from SDO 3).
+
 
 -----------------------------
 SPI controller driver support
-- 
2.34.1


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

* [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
                   ` (3 preceding siblings ...)
  2026-07-14  5:56 ` [PATCH 4/6] spi: Documentation: multiple-data-lanes: describe rx and tx lane mask Jonathan Santos
@ 2026-07-14  5:56 ` Jonathan Santos
  2026-07-14  9:22   ` Nuno Sá
  2026-07-14  9:26   ` Andy Shevchenko
  2026-07-14  5:57 ` [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection Jonathan Santos
                   ` (2 subsequent siblings)
  7 siblings, 2 replies; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:56 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

In SPI_MULTI_LANE_MODE_STRIPE mode, a transfer currently activates all
available lanes, but some use cases require operating over a subset of
lanes per transfer.

Enable dynamic lane selection for tx and rx by adding new fields to the
spi_transfer struct. Controllers that support multi-lane selection must
parse the transfer struct to update the rx and tx mask lane-wise.

Peripherals need to set values to the new fields only if multi_lane_mode
is set to SPI_MULTI_LANE_MODE_STRIPE and they need to enable at least one
lane, not necessary for enabling all lanes.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 include/linux/spi/spi.h | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index a9988db3fe2f..f4473d53b6b5 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -986,6 +986,12 @@ struct spi_res {
  *      (SPI_NBITS_SINGLE) is used.
  * @multi_lane_mode: How to serialize data on multiple lanes. One of the
  *      SPI_MULTI_LANE_MODE_* values.
+ * @rx_lane_mask: Bitmask of lanes to use for receive in
+ *      SPI_MULTI_LANE_MODE_STRIPE mode. Each set bit enables the corresponding
+ *      lane. If 0 all available lanes are used.
+ * @tx_lane_mask: Bitmask of lanes to use for transmit in
+ *      SPI_MULTI_LANE_MODE_STRIPE mode. Each set bit enables the corresponding
+ *      lane. If 0 all available lanes are used.
  * @len: size of rx and tx buffers (in bytes)
  * @speed_hz: Select a speed other than the device default for this
  *      transfer. If 0 the default (from @spi_device) is used.
@@ -1131,6 +1137,8 @@ struct spi_transfer {
 #define SPI_MULTI_LANE_MODE_STRIPE	1 /* one data word per lane */
 #define SPI_MULTI_LANE_MODE_MIRROR	2 /* same word sent on all lanes */
 	unsigned	multi_lane_mode: 2;
+	unsigned	rx_lane_mask: 8;
+	unsigned	tx_lane_mask: 8;
 
 	unsigned	timestamped:1;
 	bool		dtr_mode;
-- 
2.34.1


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

* [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
                   ` (4 preceding siblings ...)
  2026-07-14  5:56 ` [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct Jonathan Santos
@ 2026-07-14  5:57 ` Jonathan Santos
  2026-07-14 10:29   ` Nuno Sá
  2026-07-14  8:57 ` [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Andy Shevchenko
  2026-07-14 10:29 ` Nuno Sá
  7 siblings, 1 reply; 17+ messages in thread
From: Jonathan Santos @ 2026-07-14  5:57 UTC (permalink / raw)
  To: linux-spi, linux-kernel
  Cc: Jonathan Santos, dlechner, nuno.sa, michael.hennerich, broonie,
	jonath4nns, marcelo.schmitt1, andy

In SPI_MULTI_LANE_MODE_STRIPE mode, a transfer currently activates all
available lanes. Some use cases require operating over a subset of lanes
per transfer.

Make use of the rx_lane_mask and tx_lane_mask to allow setting the
multi-lane configuration transfer-wise. Rename rx_all_lanes_mask and
tx_all_lanes_mask in spi_engine_offload to rx_lanes_mask and
tx_lanes_mask, as these fields now hold the active lane mask for the
message rather than all available lanes.

Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
---
 drivers/spi/spi-axi-spi-engine.c | 62 ++++++++++++++++++++++++++------
 1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
index f2d0a6dd593b..d6c5264fee9a 100644
--- a/drivers/spi/spi-axi-spi-engine.c
+++ b/drivers/spi/spi-axi-spi-engine.c
@@ -115,6 +115,10 @@
 #define SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN	-1
 #define SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING	-2
 
+/* Extending lane_mask values for optimizing messages. */
+#define SPI_ENGINE_LANE_MASK_UNKNOWN		-1
+#define SPI_ENGINE_LANE_MASK_CONFLICTING	-2
+
 struct spi_engine_program {
 	unsigned int length;
 	uint16_t instructions[] __counted_by(length);
@@ -155,8 +159,8 @@ struct spi_engine_offload {
 	unsigned int multi_lane_mode;
 	u8 rx_primary_lane_mask;
 	u8 tx_primary_lane_mask;
-	u8 rx_all_lanes_mask;
-	u8 tx_all_lanes_mask;
+	u8 rx_lanes_mask;
+	u8 tx_lanes_mask;
 	u8 bits_per_word;
 };
 
@@ -312,6 +316,8 @@ 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;
 	int multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN;
+	int rx_lane_mask = SPI_ENGINE_LANE_MASK_UNKNOWN;
+	int tx_lane_mask = SPI_ENGINE_LANE_MASK_UNKNOWN;
 	u8 min_bits_per_word = U8_MAX;
 	u8 max_bits_per_word = 0;
 
@@ -339,11 +345,21 @@ static int spi_engine_precompile_message(struct spi_message *msg)
 				return -EINVAL;
 			}
 
-			/* If all xfers have the same multi-lane mode, we can optimize. */
+			/* If all xfers have the same multi-lane mode and mask, we can optimize. */
 			if (multi_lane_mode == SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN)
 				multi_lane_mode = xfer->multi_lane_mode;
 			else if (multi_lane_mode != xfer->multi_lane_mode)
 				multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING;
+
+			if (rx_lane_mask == SPI_ENGINE_LANE_MASK_UNKNOWN)
+				rx_lane_mask = xfer->rx_lane_mask;
+			else if (xfer->rx_lane_mask != rx_lane_mask)
+				rx_lane_mask = SPI_ENGINE_LANE_MASK_CONFLICTING;
+
+			if (tx_lane_mask == SPI_ENGINE_LANE_MASK_UNKNOWN)
+				tx_lane_mask = xfer->tx_lane_mask;
+			else if (xfer->tx_lane_mask != tx_lane_mask)
+				tx_lane_mask = SPI_ENGINE_LANE_MASK_CONFLICTING;
 		}
 	}
 
@@ -364,8 +380,13 @@ static int spi_engine_precompile_message(struct spi_message *msg)
 					     &priv->rx_primary_lane_mask,
 					     &priv->tx_primary_lane_mask);
 		spi_engine_all_lanes_flags(msg->spi,
-					   &priv->rx_all_lanes_mask,
-					   &priv->tx_all_lanes_mask);
+					   &priv->rx_lanes_mask,
+					   &priv->tx_lanes_mask);
+		if (rx_lane_mask > 0)
+			priv->rx_lanes_mask &= rx_lane_mask;
+
+		if (tx_lane_mask > 0)
+			priv->tx_lanes_mask &= tx_lane_mask;
 	}
 
 	return 0;
@@ -380,6 +401,9 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 	struct spi_transfer *xfer;
 	int clk_div, new_clk_div, inst_ns;
 	int prev_multi_lane_mode = SPI_MULTI_LANE_MODE_SINGLE;
+	int prev_rx_lane_mask = 0;
+	int prev_tx_lane_mask = 0;
+	u8 num_rx_active_lanes;
 	bool keep_cs = false;
 	u8 bits_per_word = 0;
 
@@ -405,6 +429,8 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 		 */
 		bits_per_word = priv->bits_per_word;
 		prev_multi_lane_mode = priv->multi_lane_mode;
+		prev_rx_lane_mask = priv->rx_lanes_mask;
+		prev_tx_lane_mask = priv->tx_lanes_mask;
 	} else {
 		spi_engine_program_add_cmd(p, dry,
 			SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG,
@@ -417,15 +443,27 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
 		if (xfer->rx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM ||
 		    xfer->tx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM) {
-			if (xfer->multi_lane_mode != prev_multi_lane_mode) {
+			num_rx_active_lanes = spi->num_rx_lanes;
+
+			if (xfer->multi_lane_mode != prev_multi_lane_mode ||
+			    xfer->rx_lane_mask != prev_rx_lane_mask ||
+			    xfer->tx_lane_mask != prev_tx_lane_mask) {
 				u8 tx_lane_flags, rx_lane_flags;
 
-				if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE)
+				if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
 					spi_engine_all_lanes_flags(spi, &rx_lane_flags,
 								   &tx_lane_flags);
-				else
+					if (xfer->rx_lane_mask) {
+						rx_lane_flags &= xfer->rx_lane_mask;
+						num_rx_active_lanes = hweight8(rx_lane_flags);
+					}
+
+					if (xfer->tx_lane_mask)
+						tx_lane_flags &= xfer->tx_lane_mask;
+				} else {
 					spi_engine_primary_lane_flag(spi, &rx_lane_flags,
 								     &tx_lane_flags);
+				}
 
 				spi_engine_program_add_cmd(p, dry,
 					SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
@@ -435,6 +473,8 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 							     tx_lane_flags));
 			}
 			prev_multi_lane_mode = xfer->multi_lane_mode;
+			prev_rx_lane_mask = xfer->rx_lane_mask;
+			prev_tx_lane_mask = xfer->tx_lane_mask;
 		}
 
 		new_clk_div = host->max_speed_hz / xfer->effective_speed_hz;
@@ -453,7 +493,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
 					bits_per_word));
 		}
 
-		spi_engine_gen_xfer(p, dry, xfer, spi->num_rx_lanes);
+		spi_engine_gen_xfer(p, dry, xfer, num_rx_active_lanes);
 		spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
 				     inst_ns, xfer->effective_speed_hz);
 
@@ -1034,10 +1074,10 @@ static int spi_engine_trigger_enable(struct spi_offload *offload)
 
 	if (priv->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
 		writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
-						    priv->rx_all_lanes_mask),
+						    priv->rx_lanes_mask),
 			       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
 		writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
-						    priv->tx_all_lanes_mask),
+						    priv->tx_lanes_mask),
 			       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
 	}
 
-- 
2.34.1


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

* Re: [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
                   ` (5 preceding siblings ...)
  2026-07-14  5:57 ` [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection Jonathan Santos
@ 2026-07-14  8:57 ` Andy Shevchenko
  2026-07-14 10:29 ` Nuno Sá
  7 siblings, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-14  8:57 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:55:31AM -0300, Jonathan Santos wrote:
> This series introduces two SPI subsystem features: per-transfer chipselect
> masks and multi-CS device support. Together they address the multi-device
> setup described in [1] and the limitation noted in [2], where no SPI
> controller completely handles logical chip selects beyond the first one.
> 
> The first part of the set addresses multi-CS support. Some SPI controllers
> can assert multiple chip selects simultaneously, but the existing code
> hardcoded CS index 0 in both spi_set_cs() and of_spi_parse_dt(),
> preventing this from working.
> 
> The second part addresses dynamic lane selection for STRIPE mode. In
> SPI_MULTI_LANE_MODE_STRIPE, all available lanes are currently always
> active. Some peripherals need to select a different subset of rx/tx lanes
> per transfer. New fields are added to the spi_transfer struct to allow
> drivers to specify which lanes to use for each transfer. The documentation
> is also updated to describe this new behavior.

> [1]: https://lore.kernel.org/linux-iio/af0EGv172ZMl%2F6N5@JSANTO12-L01.ad.analog.com/T/#t
> [2]: https://lore.kernel.org/all/20250915183725.219473-1-jonas.gorski@gmail.com/

Mark usually "merges" a series like this and cover letter goes as merge commit
message. With that in mind, can we use Link tags for the above? Like

Link: ... [1]
Link: ... [2]

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 1/6] spi: support simultaneous assertion of multiple CS
  2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
@ 2026-07-14  9:06   ` Andy Shevchenko
  2026-07-14  9:08   ` Nuno Sá
  1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-14  9:06 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:55:48AM -0300, Jonathan Santos wrote:
> Some SPI controllers allow multiple CS lines to be toggled at the same
> time. The existing code always used CS index 0 when tracking the last
> active CS in spi_set_cs(),

> and unconditionally set cs_index_mask to BIT(0) when parsing DT,

What about other cases (board info, ACPI and ancillary)?

So, the board info in case we need it, should be modified to take an array of
CSs. The ancillary seems by design a single-CS stuff, not sure if we ever would
need multi-CS. And for ACPI it is a gray area since the CS is 64-bit integer
there and theoretically we may interpret some higher bytes as amount of CS or
so, but this has to be specified.

TL;DR: Please, add a summary like the above to the commit message to explain
why those were not taken into account.

> both forcing the single CS usage.

> Modify spi_set_cs() to iterate last_cs[] using each logical CS index
> instead of always reading index 0. Modify of_spi_parse_dt() to build
> cs_index_mask from all parsed CS entries rather than hardcoding BIT(0),
> so the controller correctly identifies which CS lines belong to a device
> when asserting them simultaneously.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 1/6] spi: support simultaneous assertion of multiple CS
  2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
  2026-07-14  9:06   ` Andy Shevchenko
@ 2026-07-14  9:08   ` Nuno Sá
  1 sibling, 0 replies; 17+ messages in thread
From: Nuno Sá @ 2026-07-14  9:08 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:55:48AM -0300, Jonathan Santos wrote:
> Some SPI controllers allow multiple CS lines to be toggled at the same
> time. The existing code always used CS index 0 when tracking the last
> active CS in spi_set_cs(), and unconditionally set cs_index_mask to
> BIT(0) when parsing DT, both forcing the single CS usage.
> 
> Modify spi_set_cs() to iterate last_cs[] using each logical CS index
> instead of always reading index 0. Modify of_spi_parse_dt() to build
> cs_index_mask from all parsed CS entries rather than hardcoding BIT(0),
> so the controller correctly identifies which CS lines belong to a device
> when asserting them simultaneously.
> 
> Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
> ---

LGTM,

Acked-by: Nuno Sá <nuno.sa@analog.com>

>  drivers/spi/spi.c | 9 +++++----
>  1 file changed, 5 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index d9e6b4b87c89..55fb96fea243 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -1090,7 +1090,7 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
>  	spi->controller->last_cs_index_mask = spi->cs_index_mask;
>  	for (idx = 0; idx < SPI_DEVICE_CS_CNT_MAX; idx++) {
>  		if (enable && idx < spi->num_chipselect)
> -			spi->controller->last_cs[idx] = spi_get_chipselect(spi, 0);
> +			spi->controller->last_cs[idx] = spi_get_chipselect(spi, idx);
>  		else
>  			spi->controller->last_cs[idx] = SPI_INVALID_CS;
>  	}
> @@ -2594,10 +2594,11 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
>  		spi_set_chipselect(spi, idx, cs[idx]);
>  
>  	/*
> -	 * By default spi->chip_select[0] will hold the physical CS number,
> -	 * so set bit 0 in spi->cs_index_mask.
> +	 * Set cs_index_mask to indicate which logical CS indices are active.
> +	 * Each bit corresponds to a logical CS index in the spi->chip_select array.
>  	 */
> -	spi->cs_index_mask = BIT(0);
> +	for (idx = 0; idx < rc; idx++)
> +		spi->cs_index_mask |= BIT(idx);
>  
>  	/* Device speed */
>  	if (!of_property_read_u32(nc, "spi-max-frequency", &value))
> -- 
> 2.34.1
> 

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

* Re: [PATCH 2/6] spi: add per-transfer CS mask
  2026-07-14  5:56 ` [PATCH 2/6] spi: add per-transfer CS mask Jonathan Santos
@ 2026-07-14  9:12   ` Nuno Sá
  2026-07-14  9:23   ` Andy Shevchenko
  1 sibling, 0 replies; 17+ messages in thread
From: Nuno Sá @ 2026-07-14  9:12 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:56:02AM -0300, Jonathan Santos wrote:
> Multi-CS devices assert all their chip selects simultaneously by default
> via cs_index_mask. Some use cases require selecting a subset of those CS
> lines per transfer (e.g. routing data to one of several subdevices sharing
> the bus).
> 
> Add a cs_select_mask field to spi_transfer. When the mask is set and the
> controller has SPI_CONTROLLER_MULTI_CS, spi_set_cs() builds a temporary
> spi_device whose chip_select[] and cs_gpiod[] arrays contain only the lines
> indicated by the mask, then delegates to the renamed _spi_set_cs().
> When cs_select_mask is 0 or the controller does not support multi-CS,
> the device default (cs_index_mask) is used unchanged.
> 
> Update spi_transfer_one_message() to pass the relevant spi_transfer to
> every spi_set_cs() call so CS transitions between transfers with
> different masks are handled correctly.
> 
> Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
> ---
>  drivers/spi/spi.c       | 88 +++++++++++++++++++++++++++++++++++------
>  include/linux/spi/spi.h |  4 ++
>  2 files changed, 81 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index 55fb96fea243..6d644adf8445 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -1071,7 +1071,7 @@ static void spi_toggle_csgpiod(struct spi_device *spi, u8 idx, bool enable, bool
>  		spi_delay_exec(&spi->cs_inactive, NULL);
>  }
>  
> -static void spi_set_cs(struct spi_device *spi, bool enable, bool force)

Might be missing something but spi_set_cs() in the current code does:

spi_get_chipselect(spi, 0);

And I'm not really seeing nothing in the patch handling it. Is that not
an issue?

> +static void _spi_set_cs(struct spi_device *spi, bool enable, bool force)
>  {
>  	bool activate = enable;
>  	u8 idx;
> @@ -1129,6 +1129,61 @@ static void spi_set_cs(struct spi_device *spi, bool enable, bool force)
>  	}
>  }
>  
> +static void spi_set_cs(struct spi_device *spi,
> +		       struct spi_transfer *xfer,
> +		       bool enable, bool force)
> +{
> +	struct spi_controller *ctlr = spi->controller;
> +	struct spi_device temp_spi;

Not sure what to feel about the above temp_spi dance. Can't we just pass a cs_mask
into _spi_set_cs() and change the code accordingly? And for the default
case we would just pass spi->cs_index_mask.

- Nuno Sá


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

* Re: [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices
  2026-07-14  5:56 ` [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices Jonathan Santos
@ 2026-07-14  9:17   ` Andy Shevchenko
  0 siblings, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-14  9:17 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:56:16AM -0300, Jonathan Santos wrote:
> The AXI SPI Engine controller hardcoded CS index 0 when generating
> assert commands, so only the first chip select was being toggled even
> when a device declared multiple CS lines in the device tree.
> 
> Modify spi_engine_gen_cs() to accept a cs_select_mask argument and
> iterate all bits set in the effective mask (the intersection of
> xfer->cs_select_mask with spi->cs_index_mask, falling back to
> spi->cs_index_mask when the transfer mask is 0). Update
> spi_engine_compile_message() to pass each transfer's cs_select_mask
> at every CS transition, including cs_change and cs_select_mask
> change boundaries between consecutive transfers.
> 
> Likewise, modify spi_engine_setup() replacing the single-index CS
> assert with a for_each_set_bit() loop over cs_index_mask.
> 
> Set SPI_CONTROLLER_MULTI_CS flag in the probe path so the core
> multi-CS path in spi_set_cs() is activated for this controller.

...

>  static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
> -		struct spi_device *spi, bool assert)
> +		struct spi_device *spi, bool assert, unsigned long xfer_cs_mask)
>  {
> +	unsigned long cs_index_mask = !xfer_cs_mask
> +		? spi->cs_index_mask
> +		: spi->cs_index_mask & xfer_cs_mask;

This is an interesting style. Also, why negative conditional? It's harder
to parse. First, split the assignment and the definition...

	unsigned long cs_index_mask;

>  	unsigned int mask = 0xff;
> +	unsigned int cs_bit;

...and then, for example,

	cs_index_mask = xfer_cs_mask ? spi->cs_index_mask & xfer_cs_mask :
				       spi->cs_index_mask;

>  	if (assert)
> -		mask ^= BIT(spi_get_chipselect(spi, 0));
> +		for_each_set_bit(cs_bit, &cs_index_mask, SPI_ENGINE_MAX_CS)
> +			mask ^= BIT(spi_get_chipselect(spi, cs_bit));

This requires {} now.

>  
>  	spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(0, mask));
>  }

...

> static void spi_engine_compile_message(struct spi_message *msg, bool dry,

>  		spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
>  				     inst_ns, xfer->effective_speed_hz);
>  
> +		struct spi_transfer *next_xfer = list_next_entry(xfer, transfer_list);

Define the variable at the top of the scope.

>  		if (xfer->cs_change) {
>  			if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
>  				keep_cs = true;
>  			} else {
>  				if (!xfer->cs_off)
> -					spi_engine_gen_cs(p, dry, spi, false);
> +					spi_engine_gen_cs(p, dry, spi, false, xfer->cs_select_mask);
>  
>  				spi_engine_gen_sleep(p, dry, spi_delay_to_ns(
>  					&xfer->cs_change_delay, xfer), inst_ns,
>  					xfer->effective_speed_hz);
>  
> -				if (!list_next_entry(xfer, transfer_list)->cs_off)
> -					spi_engine_gen_cs(p, dry, spi, true);
> +				if (!next_xfer->cs_off)
> +					spi_engine_gen_cs(p, dry, spi, true,
> +							  next_xfer->cs_select_mask);
>  			}
>  		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
> -			   xfer->cs_off != list_next_entry(xfer, transfer_list)->cs_off) {
> -			spi_engine_gen_cs(p, dry, spi, xfer->cs_off);
> +			   xfer->cs_off != next_xfer->cs_off) {
> +			spi_engine_gen_cs(p, dry, spi, xfer->cs_off, xfer->cs_select_mask);
> +		} else if (!list_is_last(&xfer->transfer_list, &msg->transfers) &&
> +			   xfer->cs_select_mask != next_xfer->cs_select_mask) {
> +			spi_engine_gen_cs(p, dry, spi, true, next_xfer->cs_select_mask);
>  		}
>  	}

...

> static int spi_engine_setup(struct spi_device *device)
>  {
>  	struct spi_controller *host = device->controller;
>  	struct spi_engine *spi_engine = spi_controller_get_devdata(host);
> +	unsigned long cs_index_mask = device->cs_index_mask;
>  	unsigned int reg;
> +	u32 cs_bit;
>  
> -	if (device->mode & SPI_CS_HIGH)
> -		spi_engine->cs_inv |= BIT(spi_get_chipselect(device, 0));
> -	else
> -		spi_engine->cs_inv &= ~BIT(spi_get_chipselect(device, 0));
> +	for_each_set_bit(cs_bit, &cs_index_mask, SPI_ENGINE_MAX_CS) {
> +		if (device->mode & SPI_CS_HIGH)
> +			spi_engine->cs_inv |= BIT(spi_get_chipselect(device,
> +								     cs_bit));

It will be easier to follow when a single line.

> +		else
> +			spi_engine->cs_inv &= ~BIT(spi_get_chipselect(device,
> +								      cs_bit));

Ditto.

> +	}

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct
  2026-07-14  5:56 ` [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct Jonathan Santos
@ 2026-07-14  9:22   ` Nuno Sá
  2026-07-14  9:26   ` Andy Shevchenko
  1 sibling, 0 replies; 17+ messages in thread
From: Nuno Sá @ 2026-07-14  9:22 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:56:46AM -0300, Jonathan Santos wrote:
> In SPI_MULTI_LANE_MODE_STRIPE mode, a transfer currently activates all
> available lanes, but some use cases require operating over a subset of
> lanes per transfer.
> 
> Enable dynamic lane selection for tx and rx by adding new fields to the
> spi_transfer struct. Controllers that support multi-lane selection must
> parse the transfer struct to update the rx and tx mask lane-wise.
> 
> Peripherals need to set values to the new fields only if multi_lane_mode
> is set to SPI_MULTI_LANE_MODE_STRIPE and they need to enable at least one
> lane, not necessary for enabling all lanes.
> 
> Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
> ---

Acked-by: Nuno Sá <nuno.sa@analog.com>

>  include/linux/spi/spi.h | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
> index a9988db3fe2f..f4473d53b6b5 100644
> --- a/include/linux/spi/spi.h
> +++ b/include/linux/spi/spi.h
> @@ -986,6 +986,12 @@ struct spi_res {
>   *      (SPI_NBITS_SINGLE) is used.
>   * @multi_lane_mode: How to serialize data on multiple lanes. One of the
>   *      SPI_MULTI_LANE_MODE_* values.
> + * @rx_lane_mask: Bitmask of lanes to use for receive in
> + *      SPI_MULTI_LANE_MODE_STRIPE mode. Each set bit enables the corresponding
> + *      lane. If 0 all available lanes are used.
> + * @tx_lane_mask: Bitmask of lanes to use for transmit in
> + *      SPI_MULTI_LANE_MODE_STRIPE mode. Each set bit enables the corresponding
> + *      lane. If 0 all available lanes are used.
>   * @len: size of rx and tx buffers (in bytes)
>   * @speed_hz: Select a speed other than the device default for this
>   *      transfer. If 0 the default (from @spi_device) is used.
> @@ -1131,6 +1137,8 @@ struct spi_transfer {
>  #define SPI_MULTI_LANE_MODE_STRIPE	1 /* one data word per lane */
>  #define SPI_MULTI_LANE_MODE_MIRROR	2 /* same word sent on all lanes */
>  	unsigned	multi_lane_mode: 2;
> +	unsigned	rx_lane_mask: 8;
> +	unsigned	tx_lane_mask: 8;
>  
>  	unsigned	timestamped:1;
>  	bool		dtr_mode;
> -- 
> 2.34.1
> 

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

* Re: [PATCH 2/6] spi: add per-transfer CS mask
  2026-07-14  5:56 ` [PATCH 2/6] spi: add per-transfer CS mask Jonathan Santos
  2026-07-14  9:12   ` Nuno Sá
@ 2026-07-14  9:23   ` Andy Shevchenko
  1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-14  9:23 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:56:02AM -0300, Jonathan Santos wrote:
> Multi-CS devices assert all their chip selects simultaneously by default
> via cs_index_mask. Some use cases require selecting a subset of those CS
> lines per transfer (e.g. routing data to one of several subdevices sharing
> the bus).
> 
> Add a cs_select_mask field to spi_transfer. When the mask is set and the
> controller has SPI_CONTROLLER_MULTI_CS, spi_set_cs() builds a temporary
> spi_device whose chip_select[] and cs_gpiod[] arrays contain only the lines
> indicated by the mask, then delegates to the renamed _spi_set_cs().
> When cs_select_mask is 0 or the controller does not support multi-CS,
> the device default (cs_index_mask) is used unchanged.
> 
> Update spi_transfer_one_message() to pass the relevant spi_transfer to
> every spi_set_cs() call so CS transitions between transfers with
> different masks are handled correctly.

...

> +	/*
> +	 * Handle multi-CS controller path.
> +	 * For multi-CS controllers, we need to create a temporary spi_device
> +	 * structure that reflects the transfer's CS selection

Missing period at the end. Always follow English grammar and punctuation in
multi-line comments.

> +	 */

...

> +		struct spi_transfer *next_xfer = list_next_entry(xfer, transfer_list);

Define it in the top of the respective scope.

...

> +	if (ret != 0 || !keep_cs) {
> +		/* Use the last transfer's CS configuration for proper cleanup */
> +		struct spi_transfer *last_xfer = list_last_entry(&msg->transfers,
> +								 struct spi_transfer,
> +								 transfer_list);

This style is hard to follow, just split assignment and definition.

		struct spi_transfer *last_xfer;

		last_xfer = list_last_entry(&msg->transfers, struct spi_transfer, transfer_list);

> +		spi_set_cs(msg->spi, last_xfer, false, false);
> +	}

...

>  	unsigned	dummy_data:1;
>  	unsigned	cs_off:1;
>  	unsigned	cs_change:1;
> +	unsigned	cs_select_mask:8;
>  	unsigned	tx_nbits:4;
>  	unsigned	rx_nbits:4;

Be careful with these. Have you run `pahole` to check if there is no boundaries
break that makes some unneeded gaps?

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct
  2026-07-14  5:56 ` [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct Jonathan Santos
  2026-07-14  9:22   ` Nuno Sá
@ 2026-07-14  9:26   ` Andy Shevchenko
  1 sibling, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-07-14  9:26 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:56:46AM -0300, Jonathan Santos wrote:
> In SPI_MULTI_LANE_MODE_STRIPE mode, a transfer currently activates all
> available lanes, but some use cases require operating over a subset of
> lanes per transfer.
> 
> Enable dynamic lane selection for tx and rx by adding new fields to the
> spi_transfer struct. Controllers that support multi-lane selection must
> parse the transfer struct to update the rx and tx mask lane-wise.
> 
> Peripherals need to set values to the new fields only if multi_lane_mode
> is set to SPI_MULTI_LANE_MODE_STRIPE and they need to enable at least one
> lane, not necessary for enabling all lanes.

Same comment as per previous patch, check very carefully the layout.
Run `pahole`, count the bits, think about BE architectures. This might
lead to unnecessary gaps or even more units to place all bits.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection
  2026-07-14  5:57 ` [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection Jonathan Santos
@ 2026-07-14 10:29   ` Nuno Sá
  0 siblings, 0 replies; 17+ messages in thread
From: Nuno Sá @ 2026-07-14 10:29 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:57:00AM -0300, Jonathan Santos wrote:
> In SPI_MULTI_LANE_MODE_STRIPE mode, a transfer currently activates all
> available lanes. Some use cases require operating over a subset of lanes
> per transfer.
> 
> Make use of the rx_lane_mask and tx_lane_mask to allow setting the
> multi-lane configuration transfer-wise. Rename rx_all_lanes_mask and
> tx_all_lanes_mask in spi_engine_offload to rx_lanes_mask and
> tx_lanes_mask, as these fields now hold the active lane mask for the
> message rather than all available lanes.
> 
> Signed-off-by: Jonathan Santos <Jonathan.Santos@analog.com>
> ---

LGTM,

Acked-by: Nuno Sá <nuno.sa@analog.com>

>  drivers/spi/spi-axi-spi-engine.c | 62 ++++++++++++++++++++++++++------
>  1 file changed, 51 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c
> index f2d0a6dd593b..d6c5264fee9a 100644
> --- a/drivers/spi/spi-axi-spi-engine.c
> +++ b/drivers/spi/spi-axi-spi-engine.c
> @@ -115,6 +115,10 @@
>  #define SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN	-1
>  #define SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING	-2
>  
> +/* Extending lane_mask values for optimizing messages. */
> +#define SPI_ENGINE_LANE_MASK_UNKNOWN		-1
> +#define SPI_ENGINE_LANE_MASK_CONFLICTING	-2
> +
>  struct spi_engine_program {
>  	unsigned int length;
>  	uint16_t instructions[] __counted_by(length);
> @@ -155,8 +159,8 @@ struct spi_engine_offload {
>  	unsigned int multi_lane_mode;
>  	u8 rx_primary_lane_mask;
>  	u8 tx_primary_lane_mask;
> -	u8 rx_all_lanes_mask;
> -	u8 tx_all_lanes_mask;
> +	u8 rx_lanes_mask;
> +	u8 tx_lanes_mask;
>  	u8 bits_per_word;
>  };
>  
> @@ -312,6 +316,8 @@ 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;
>  	int multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN;
> +	int rx_lane_mask = SPI_ENGINE_LANE_MASK_UNKNOWN;
> +	int tx_lane_mask = SPI_ENGINE_LANE_MASK_UNKNOWN;
>  	u8 min_bits_per_word = U8_MAX;
>  	u8 max_bits_per_word = 0;
>  
> @@ -339,11 +345,21 @@ static int spi_engine_precompile_message(struct spi_message *msg)
>  				return -EINVAL;
>  			}
>  
> -			/* If all xfers have the same multi-lane mode, we can optimize. */
> +			/* If all xfers have the same multi-lane mode and mask, we can optimize. */
>  			if (multi_lane_mode == SPI_ENGINE_MULTI_BUS_MODE_UNKNOWN)
>  				multi_lane_mode = xfer->multi_lane_mode;
>  			else if (multi_lane_mode != xfer->multi_lane_mode)
>  				multi_lane_mode = SPI_ENGINE_MULTI_BUS_MODE_CONFLICTING;
> +
> +			if (rx_lane_mask == SPI_ENGINE_LANE_MASK_UNKNOWN)
> +				rx_lane_mask = xfer->rx_lane_mask;
> +			else if (xfer->rx_lane_mask != rx_lane_mask)
> +				rx_lane_mask = SPI_ENGINE_LANE_MASK_CONFLICTING;
> +
> +			if (tx_lane_mask == SPI_ENGINE_LANE_MASK_UNKNOWN)
> +				tx_lane_mask = xfer->tx_lane_mask;
> +			else if (xfer->tx_lane_mask != tx_lane_mask)
> +				tx_lane_mask = SPI_ENGINE_LANE_MASK_CONFLICTING;
>  		}
>  	}
>  
> @@ -364,8 +380,13 @@ static int spi_engine_precompile_message(struct spi_message *msg)
>  					     &priv->rx_primary_lane_mask,
>  					     &priv->tx_primary_lane_mask);
>  		spi_engine_all_lanes_flags(msg->spi,
> -					   &priv->rx_all_lanes_mask,
> -					   &priv->tx_all_lanes_mask);
> +					   &priv->rx_lanes_mask,
> +					   &priv->tx_lanes_mask);
> +		if (rx_lane_mask > 0)
> +			priv->rx_lanes_mask &= rx_lane_mask;
> +
> +		if (tx_lane_mask > 0)
> +			priv->tx_lanes_mask &= tx_lane_mask;
>  	}
>  
>  	return 0;
> @@ -380,6 +401,9 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
>  	struct spi_transfer *xfer;
>  	int clk_div, new_clk_div, inst_ns;
>  	int prev_multi_lane_mode = SPI_MULTI_LANE_MODE_SINGLE;
> +	int prev_rx_lane_mask = 0;
> +	int prev_tx_lane_mask = 0;
> +	u8 num_rx_active_lanes;
>  	bool keep_cs = false;
>  	u8 bits_per_word = 0;
>  
> @@ -405,6 +429,8 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
>  		 */
>  		bits_per_word = priv->bits_per_word;
>  		prev_multi_lane_mode = priv->multi_lane_mode;
> +		prev_rx_lane_mask = priv->rx_lanes_mask;
> +		prev_tx_lane_mask = priv->tx_lanes_mask;
>  	} else {
>  		spi_engine_program_add_cmd(p, dry,
>  			SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG,
> @@ -417,15 +443,27 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
>  	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
>  		if (xfer->rx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_RX_STREAM ||
>  		    xfer->tx_buf || xfer->offload_flags & SPI_OFFLOAD_XFER_TX_STREAM) {
> -			if (xfer->multi_lane_mode != prev_multi_lane_mode) {
> +			num_rx_active_lanes = spi->num_rx_lanes;
> +
> +			if (xfer->multi_lane_mode != prev_multi_lane_mode ||
> +			    xfer->rx_lane_mask != prev_rx_lane_mask ||
> +			    xfer->tx_lane_mask != prev_tx_lane_mask) {
>  				u8 tx_lane_flags, rx_lane_flags;
>  
> -				if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE)
> +				if (xfer->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
>  					spi_engine_all_lanes_flags(spi, &rx_lane_flags,
>  								   &tx_lane_flags);
> -				else
> +					if (xfer->rx_lane_mask) {
> +						rx_lane_flags &= xfer->rx_lane_mask;
> +						num_rx_active_lanes = hweight8(rx_lane_flags);
> +					}
> +
> +					if (xfer->tx_lane_mask)
> +						tx_lane_flags &= xfer->tx_lane_mask;
> +				} else {
>  					spi_engine_primary_lane_flag(spi, &rx_lane_flags,
>  								     &tx_lane_flags);
> +				}
>  
>  				spi_engine_program_add_cmd(p, dry,
>  					SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
> @@ -435,6 +473,8 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
>  							     tx_lane_flags));
>  			}
>  			prev_multi_lane_mode = xfer->multi_lane_mode;
> +			prev_rx_lane_mask = xfer->rx_lane_mask;
> +			prev_tx_lane_mask = xfer->tx_lane_mask;
>  		}
>  
>  		new_clk_div = host->max_speed_hz / xfer->effective_speed_hz;
> @@ -453,7 +493,7 @@ static void spi_engine_compile_message(struct spi_message *msg, bool dry,
>  					bits_per_word));
>  		}
>  
> -		spi_engine_gen_xfer(p, dry, xfer, spi->num_rx_lanes);
> +		spi_engine_gen_xfer(p, dry, xfer, num_rx_active_lanes);
>  		spi_engine_gen_sleep(p, dry, spi_delay_to_ns(&xfer->delay, xfer),
>  				     inst_ns, xfer->effective_speed_hz);
>  
> @@ -1034,10 +1074,10 @@ static int spi_engine_trigger_enable(struct spi_offload *offload)
>  
>  	if (priv->multi_lane_mode == SPI_MULTI_LANE_MODE_STRIPE) {
>  		writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDI_MASK,
> -						    priv->rx_all_lanes_mask),
> +						    priv->rx_lanes_mask),
>  			       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
>  		writel_relaxed(SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_SDO_MASK,
> -						    priv->tx_all_lanes_mask),
> +						    priv->tx_lanes_mask),
>  			       spi_engine->base + SPI_ENGINE_REG_CMD_FIFO);
>  	}
>  
> -- 
> 2.34.1
> 

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

* Re: [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support
  2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
                   ` (6 preceding siblings ...)
  2026-07-14  8:57 ` [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Andy Shevchenko
@ 2026-07-14 10:29 ` Nuno Sá
  7 siblings, 0 replies; 17+ messages in thread
From: Nuno Sá @ 2026-07-14 10:29 UTC (permalink / raw)
  To: Jonathan Santos
  Cc: linux-spi, linux-kernel, dlechner, nuno.sa, michael.hennerich,
	broonie, jonath4nns, marcelo.schmitt1, andy

On Tue, Jul 14, 2026 at 02:55:31AM -0300, Jonathan Santos wrote:
> This series introduces two SPI subsystem features: per-transfer chipselect
> masks and multi-CS device support. Together they address the multi-device
> setup described in [1] and the limitation noted in [2], where no SPI
> controller completely handles logical chip selects beyond the first one.
> 
> The first part of the set addresses multi-CS support. Some SPI controllers
> can assert multiple chip selects simultaneously, but the existing code
> hardcoded CS index 0 in both spi_set_cs() and of_spi_parse_dt(),
> preventing this from working.
> 
> The second part addresses dynamic lane selection for STRIPE mode. In
> SPI_MULTI_LANE_MODE_STRIPE, all available lanes are currently always
> active. Some peripherals need to select a different subset of rx/tx lanes
> per transfer. New fields are added to the spi_transfer struct to allow
> drivers to specify which lanes to use for each transfer. The documentation
> is also updated to describe this new behavior.
> 
> [1]: https://lore.kernel.org/linux-iio/af0EGv172ZMl%2F6N5@JSANTO12-L01.ad.analog.com/T/#t
> [2]: https://lore.kernel.org/all/20250915183725.219473-1-jonas.gorski@gmail.com/
> 
> Jonathan Santos (6):
>   spi: support simultaneous assertion of multiple CS
>   spi: add per-transfer CS mask
>   spi: spi-engine-ex: Add support for multi-CS devices
>   spi: Documentation: multiple-data-lanes: describe rx and tx lane mask
>   spi: add rx and tx lane mask to spi_transfer struct
>   spi: axi-spi-engine: add support for dynamic multi-lane selection

It would be nice to include an actual user for this in the series.

- Nuno Sá

> 
>  Documentation/spi/multiple-data-lanes.rst |  30 ++++++
>  drivers/spi/spi-axi-spi-engine.c          | 106 +++++++++++++++++-----
>  drivers/spi/spi.c                         |  97 +++++++++++++++++---
>  include/linux/spi/spi.h                   |  12 +++
>  4 files changed, 206 insertions(+), 39 deletions(-)
> 
> 
> base-commit: 093239070573637ad2b4cb56abc9c4c7ee109294
> -- 
> 2.34.1
> 

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

end of thread, other threads:[~2026-07-14 10:30 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14  5:55 [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Jonathan Santos
2026-07-14  5:55 ` [PATCH 1/6] spi: support simultaneous assertion of multiple CS Jonathan Santos
2026-07-14  9:06   ` Andy Shevchenko
2026-07-14  9:08   ` Nuno Sá
2026-07-14  5:56 ` [PATCH 2/6] spi: add per-transfer CS mask Jonathan Santos
2026-07-14  9:12   ` Nuno Sá
2026-07-14  9:23   ` Andy Shevchenko
2026-07-14  5:56 ` [PATCH 3/6] spi: spi-engine-ex: Add support for multi-CS devices Jonathan Santos
2026-07-14  9:17   ` Andy Shevchenko
2026-07-14  5:56 ` [PATCH 4/6] spi: Documentation: multiple-data-lanes: describe rx and tx lane mask Jonathan Santos
2026-07-14  5:56 ` [PATCH 5/6] spi: add rx and tx lane mask to spi_transfer struct Jonathan Santos
2026-07-14  9:22   ` Nuno Sá
2026-07-14  9:26   ` Andy Shevchenko
2026-07-14  5:57 ` [PATCH 6/6] spi: axi-spi-engine: add support for dynamic multi-lane selection Jonathan Santos
2026-07-14 10:29   ` Nuno Sá
2026-07-14  8:57 ` [PATCH 0/6] spi: add multi-CS and per-transfer lane mask support Andy Shevchenko
2026-07-14 10:29 ` Nuno Sá

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox