* [PATCH 0/3] spi: cadence-xspi: add ACMD mode support
@ 2026-10-29 16:03 Nuno Sá
2026-10-29 16:03 ` [PATCH 1/3] spi: cadence-xspi: rename sdma_error to xfer_error Nuno Sá
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Nuno Sá @ 2026-10-29 16:03 UTC (permalink / raw)
To: linux-spi, linux-kernel, Parshuram Thombare, devicetree
Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jisheng Zhang, Fei Xie
The Cadence xSPI driver currently drives every operation through STIG
mode, where the CPU builds each command and drains the data. The
controller also has an auto command (ACMD) mode where it runs whole
sequences on its own and can move data with its internal DMA, which
pays off for bulk reads.
Whether ACMD and the internal DMA can be used depends on the SoC
integration (external SRAM, the DMA bus requester interface being
wired), which the controller registers do not expose. Hence two new
devicetree properties: cdns,has-acmd and cdns,has-internal-dma.
For now, ACMD is only used for reads of at least 2048 bytes with an
address phase, using the internal DMA, and only when both properties
are set. Smaller reads and all other operations stay on STIG.
NOR and NAND are handled the same way: NAND operations are still issued
one by one, not combined into a single ACMD sequence, so no NAND
geometry is needed. The real gain is on large reads which, for NAND,
means chips supporting buffer/sequential read mode.
Another attempt for ACMD was sent in [1] but that relies on knowing the
NAND geometry which brings much more complexity given that it needs a
proper interface implemented in the spi-nand core. So we agreed on
starting with this simpler approach and then build on top of it if we
agree it pays off (mostly on cpu load).
Some stats:
ACMD
-----
SPI-NOR - is25wx01g (only reads > 2k are done in ACMD )
-------
flash_speed -c 5 -d /dev/mtd4
not NAND flash, assume page size is 512 bytes.
scanning for bad eraseblocks
scanned 5 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 1054 KiB/s
testing eraseblock read speed
eraseblock read speed is 106666 KiB/s
testing page write speed
page write speed is 1035 KiB/s
testing page read speed
page read speed is 15238 KiB/s
SPI-NAND - w25n08lw (all reads are ACMD given that page size is 4k)
--------
flash_speed -c 5 -d /dev/mtd5
scanning for bad eraseblocks
scanned 5 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 6497 KiB/s
testing eraseblock read speed
eraseblock read speed is 44137 KiB/s
testing page write speed
page write speed is 6336 KiB/s
testing page read speed
page read speed is 16842 KiB/s
STIG
----
SPI-NOR
-------
flash_speed -c 5 -d /dev/mtd4
not NAND flash, assume page size is 512 bytes.
scanning for bad eraseblocks
scanned 5 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 1054 KiB/s
testing eraseblock read speed
eraseblock read speed is 64000 KiB/s
testing page write speed
page write speed is 1042 KiB/s
testing page read speed
page read speed is 14883 KiB/s
SPI-NAND (Cont mode)
--------
flash_speed -c 5 -d /dev/mtd5
scanning for bad eraseblocks
scanned 5 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 6564 KiB/s
testing eraseblock read speed
eraseblock read speed is 18823 KiB/s
testing page write speed
page write speed is 6400 KiB/s
testing page read speed
page read speed is 18028 KiB/s
For now, we have a static threshold to decide to use ACMD or not. I do
understand that's not ideal. It might depend on sclk and number of lanes
so not sure if that value will be accurate for everybody. So, open to
suggestions if someone has something in mind regarding this.
[1]: https://lore.kernel.org/linux-spi/20260921093701.1341766-1-fei.xie@horizon.auto/
---
Nuno Sá (3):
spi: cadence-xspi: rename sdma_error to xfer_error
spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties
spi: cadence-xspi: add ACMD mode support
.../devicetree/bindings/spi/cdns,xspi.yaml | 18 ++
drivers/spi/spi-cadence-xspi.c | 269 +++++++++++++++++++--
2 files changed, 273 insertions(+), 14 deletions(-)
---
base-commit: 7da2aca123754c0e06092bbe179d58078b58d8d4
change-id: 20261027-xspi-cadence-add-acmd-3c0e342cb4f4
--
Thanks!
- Nuno Sá
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 1/3] spi: cadence-xspi: rename sdma_error to xfer_error 2026-10-29 16:03 [PATCH 0/3] spi: cadence-xspi: add ACMD mode support Nuno Sá @ 2026-10-29 16:03 ` Nuno Sá 2026-10-29 16:03 ` [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties Nuno Sá 2026-10-29 16:03 ` [PATCH 3/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2 siblings, 0 replies; 10+ messages in thread From: Nuno Sá @ 2026-10-29 16:03 UTC (permalink / raw) To: linux-spi, linux-kernel, Parshuram Thombare, devicetree Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie Rename the sdma_error flag to xfer_error given that a following patch will support ACMD mode. And so, let's keep the name more generic. No functional change intended. Signed-off-by: Nuno Sá <nuno.sa@analog.com> --- drivers/spi/spi-cadence-xspi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 3687853e4ce4..905292fa50a2 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -338,7 +338,7 @@ struct cdns_xspi_dev { struct completion cmd_complete; struct completion auto_cmd_complete; struct completion sdma_complete; - bool sdma_error; + bool xfer_error; void *in_buffer; const void *out_buffer; @@ -544,7 +544,7 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG); cdns_xspi->set_interrupts_handler(cdns_xspi, true); - cdns_xspi->sdma_error = false; + cdns_xspi->xfer_error = false; memset(cmd_regs, 0, sizeof(cmd_regs)); cmd_regs[1] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_1(op, data_phase); @@ -575,7 +575,7 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, cdns_xspi_trigger_command(cdns_xspi, cmd_regs); wait_for_completion(&cdns_xspi->sdma_complete); - if (cdns_xspi->sdma_error) { + if (cdns_xspi->xfer_error) { cdns_xspi->set_interrupts_handler(cdns_xspi, false); return -EIO; } @@ -703,7 +703,7 @@ static irqreturn_t cdns_xspi_irq_handler(int this_irq, void *dev) if (irq_status & CDNS_XSPI_SDMA_ERROR) { dev_err(cdns_xspi->dev, "Slave DMA transaction error\n"); - cdns_xspi->sdma_error = true; + cdns_xspi->xfer_error = true; complete(&cdns_xspi->sdma_complete); } -- 2.56.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties 2026-10-29 16:03 [PATCH 0/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2026-10-29 16:03 ` [PATCH 1/3] spi: cadence-xspi: rename sdma_error to xfer_error Nuno Sá @ 2026-10-29 16:03 ` Nuno Sá 2026-10-29 18:31 ` Conor Dooley 2026-10-29 16:03 ` [PATCH 3/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2 siblings, 1 reply; 10+ messages in thread From: Nuno Sá @ 2026-10-29 16:03 UTC (permalink / raw) To: linux-spi, linux-kernel, Parshuram Thombare, devicetree Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie The Cadence xSPI controller supports an auto command (ACMD) mode where high level sequences (read, program, erase) are handled by the controller and data can be moved by its internal DMA. Whether ACMD can be used, and whether the internal DMA bus requester interface is wired, depends on how the IP is integrated (e.g. external SRAM, interconnect wiring) and cannot be discovered from the controller. Add the cdns,has-acmd and cdns,has-internal-dma boolean properties to describe that. The internal DMA is only used by ACMD, so properly reflect that. Signed-off-by: Nuno Sá <nuno.sa@analog.com> --- Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml index 38a5795589de..9708ede79730 100644 --- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml +++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml @@ -40,11 +40,29 @@ properties: interrupts: maxItems: 1 + cdns,has-acmd: + type: boolean + description: + The controller supports the auto command (ACMD) mode. The IP may need + external hardware (like SRAM) at SoC integration to fully leverage + ACMD. + + cdns,has-internal-dma: + type: boolean + description: + The controller internal DMA bus requester interface is wired at SoC + integration, so the controller can move ACMD data to and from memory + on its own. + required: - compatible - reg - interrupts +dependencies: + cdns,has-internal-dma: + - cdns,has-acmd + allOf: - $ref: spi-controller.yaml# - if: -- 2.56.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties 2026-10-29 16:03 ` [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties Nuno Sá @ 2026-10-29 18:31 ` Conor Dooley 2026-10-30 9:57 ` Nuno Sá 0 siblings, 1 reply; 10+ messages in thread From: Conor Dooley @ 2026-10-29 18:31 UTC (permalink / raw) To: Nuno Sá Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie [-- Attachment #1: Type: text/plain, Size: 2066 bytes --] On Thu, Oct 29, 2026 at 04:03:17PM +0000, Nuno Sá wrote: > The Cadence xSPI controller supports an auto command (ACMD) mode where > high level sequences (read, program, erase) are handled by the > controller and data can be moved by its internal DMA. Whether ACMD can > be used, and whether the internal DMA bus requester interface is wired, > depends on how the IP is integrated (e.g. external SRAM, interconnect > wiring) and cannot be discovered from the controller. > > Add the cdns,has-acmd and cdns,has-internal-dma boolean properties to > describe that. The internal DMA is only used by ACMD, so properly > reflect that. Why would these properties be needed? What the controller support surely can be determined from the specific compatible for the SoC integration? > > Signed-off-by: Nuno Sá <nuno.sa@analog.com> > --- > Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 18 ++++++++++++++++++ > 1 file changed, 18 insertions(+) > > diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > index 38a5795589de..9708ede79730 100644 > --- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > +++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > @@ -40,11 +40,29 @@ properties: > interrupts: > maxItems: 1 > > + cdns,has-acmd: > + type: boolean > + description: > + The controller supports the auto command (ACMD) mode. The IP may need > + external hardware (like SRAM) at SoC integration to fully leverage > + ACMD. > + > + cdns,has-internal-dma: > + type: boolean > + description: > + The controller internal DMA bus requester interface is wired at SoC > + integration, so the controller can move ACMD data to and from memory > + on its own. > + > required: > - compatible > - reg > - interrupts > > +dependencies: > + cdns,has-internal-dma: > + - cdns,has-acmd > + > allOf: > - $ref: spi-controller.yaml# > - if: > > -- > 2.56.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties 2026-10-29 18:31 ` Conor Dooley @ 2026-10-30 9:57 ` Nuno Sá 0 siblings, 0 replies; 10+ messages in thread From: Nuno Sá @ 2026-10-30 9:57 UTC (permalink / raw) To: Conor Dooley Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie On Thu, Oct 29, 2026 at 06:31:00PM +0000, Conor Dooley wrote: > On Thu, Oct 29, 2026 at 04:03:17PM +0000, Nuno Sá wrote: > > The Cadence xSPI controller supports an auto command (ACMD) mode where > > high level sequences (read, program, erase) are handled by the > > controller and data can be moved by its internal DMA. Whether ACMD can > > be used, and whether the internal DMA bus requester interface is wired, > > depends on how the IP is integrated (e.g. external SRAM, interconnect > > wiring) and cannot be discovered from the controller. > > > > Add the cdns,has-acmd and cdns,has-internal-dma boolean properties to > > describe that. The internal DMA is only used by ACMD, so properly > > reflect that. > > Why would these properties be needed? What the controller support surely > can be determined from the specific compatible for the SoC integration? Alright, I'm more used to describe peripherals than this kind of IPs but I imagined that these being properties of the IP that would make sense to describe them in binding. Ok, but I sees this leaves the door open to just add properties in integrations that would not make sense and if we're going to have any compatible conditional kind of handling, I guess it makes them pointless. - Nuno Sá > > > > > Signed-off-by: Nuno Sá <nuno.sa@analog.com> > > --- > > Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 18 ++++++++++++++++++ > > 1 file changed, 18 insertions(+) > > > > diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > > index 38a5795589de..9708ede79730 100644 > > --- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > > +++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml > > @@ -40,11 +40,29 @@ properties: > > interrupts: > > maxItems: 1 > > > > + cdns,has-acmd: > > + type: boolean > > + description: > > + The controller supports the auto command (ACMD) mode. The IP may need > > + external hardware (like SRAM) at SoC integration to fully leverage > > + ACMD. > > + > > + cdns,has-internal-dma: > > + type: boolean > > + description: > > + The controller internal DMA bus requester interface is wired at SoC > > + integration, so the controller can move ACMD data to and from memory > > + on its own. > > + > > required: > > - compatible > > - reg > > - interrupts > > > > +dependencies: > > + cdns,has-internal-dma: > > + - cdns,has-acmd > > + > > allOf: > > - $ref: spi-controller.yaml# > > - if: > > > > -- > > 2.56.0 > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 3/3] spi: cadence-xspi: add ACMD mode support 2026-10-29 16:03 [PATCH 0/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2026-10-29 16:03 ` [PATCH 1/3] spi: cadence-xspi: rename sdma_error to xfer_error Nuno Sá 2026-10-29 16:03 ` [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties Nuno Sá @ 2026-10-29 16:03 ` Nuno Sá 2026-10-29 16:51 ` Mark Brown 2 siblings, 1 reply; 10+ messages in thread From: Nuno Sá @ 2026-10-29 16:03 UTC (permalink / raw) To: linux-spi, linux-kernel, Parshuram Thombare, devicetree Cc: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie So far every operation goes through STIG mode, where the CPU builds each command and drains the data. The controller also has an auto command (ACMD) mode where it runs a whole read sequence on its own and, with the internal DMA, writes the data straight to memory. Use ACMD with the internal DMA for reads with an address phase of at least CDNS_XSPI_ACMD_MIN_BYTES (2048). Below that, the per-command setup costs more than the saved PIO drain. Smaller reads and all other operations stay on STIG. Such reads are not clamped to the SDMA size anymore, since ACMD does not go through the SDMA port. Signed-off-by: Nuno Sá <nuno.sa@analog.com> --- drivers/spi/spi-cadence-xspi.c | 261 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 251 insertions(+), 10 deletions(-) diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c index 905292fa50a2..b2c1ba86fa14 100644 --- a/drivers/spi/spi-cadence-xspi.c +++ b/drivers/spi/spi-cadence-xspi.c @@ -4,6 +4,7 @@ #include <linux/completion.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/interrupt.h> @@ -63,6 +64,7 @@ #define CDNS_XSPI_INIT_LEGACY BIT(9) #define CDNS_XSPI_INIT_FAIL BIT(8) #define CDNS_XSPI_CTRL_BUSY BIT(7) +#define CDNS_XSPI_ACMD_ENGINE_BUSY BIT(2) /* Controller interrupt status register */ #define CDNS_XSPI_INTR_STATUS_REG 0x0110 @@ -103,6 +105,44 @@ #define CDNS_XSPI_SDMA_TRD_INFO_REG 0x0244 #define CDNS_XSPI_SDMA_DIR BIT(8) +#define CDNS_XSPI_ACMD_THRD_0 0 +#define CDNS_XSPI_ACMD_THRD_0_MASK BIT(CDNS_XSPI_ACMD_THRD_0) + +/* Command status pointer register, selects the thread CMD_STAT refers to */ +#define CDNS_XSPI_CMD_STATUS_PTR_REG 0x0040 +#define CDNS_XSPI_CMD_STATUS_PTR_SEL GENMASK(2, 0) + +/* CMD0 layout for ACMD PIO sequences */ +#define CDNS_XSPI_ACMD_MODE GENMASK(31, 30) +#define CDNS_XSPI_ACMD_MODE_PIO 1 +#define CDNS_XSPI_ACMD_TRD_NUM GENMASK(26, 24) +#define CDNS_XSPI_ACMD_BANK GENMASK(22, 20) +#define CDNS_XSPI_ACMD_DMA_SEL BIT(19) +#define CDNS_XSPI_ACMD_INT BIT(18) +#define CDNS_XSPI_ACMD_CMD_TYPE GENMASK(15, 0) + +/* ACMD sequence types. Any other value is reserved. */ +#define CDNS_XSPI_ACMD_TYPE_READ 0x2200 + +/* Common sequence configuration */ +#define CDNS_XSPI_SEQ_GCTL0_REG 0x0390 +#define CDNS_XSPI_SEQ_GCTL0_TYP GENMASK(24, 23) +#define CDNS_XSPI_SEQ_GCTL0_TYP_PROFILE_1 0 +#define CDNS_XSPI_SEQ_GCTL0_PSIZ_RD GENMASK(3, 0) +#define CDNS_XSPI_SEQ_GCTL0_PSIZ_RD_UNLIMITED 0xf + +/* Read sequence configuration */ +#define CDNS_XSPI_READ_SEQ_CFG0_REG 0x0430 +#define CDNS_XSPI_READ_SEQ_CFG0_CMD_VALUE GENMASK(7, 0) +#define CDNS_XSPI_READ_SEQ_CFG0_CMD_IOS GENMASK(9, 8) +#define CDNS_XSPI_READ_SEQ_CFG0_ADDR_CNT GENMASK(14, 12) +#define CDNS_XSPI_READ_SEQ_CFG0_ADDR_IOS GENMASK(17, 16) +#define CDNS_XSPI_READ_SEQ_CFG0_DAT_IOS GENMASK(21, 20) +#define CDNS_XSPI_READ_SEQ_CFG0_DMY_CNT GENMASK(29, 24) +#define CDNS_XSPI_READ_SEQ_CFG1_REG 0x0434 + +#define CDNS_XSPI_ACMD_MIN_BYTES 2048 + /* Controller features register */ #define CDNS_XSPI_CTRL_FEATURES_REG 0x0F04 #define CDNS_XSPI_NUM_BANKS GENMASK(25, 24) @@ -139,6 +179,8 @@ /* STIG command status fields */ #define CDNS_XSPI_CMD_STATUS_COMPLETED BIT(15) #define CDNS_XSPI_CMD_STATUS_FAILED BIT(14) +/* Bit 4 is defined in ACMD mode only */ +#define CDNS_XSPI_CMD_STATUS_DEVICE_ERROR BIT(4) #define CDNS_XSPI_CMD_STATUS_DQS_ERROR BIT(3) #define CDNS_XSPI_CMD_STATUS_CRC_ERROR BIT(2) #define CDNS_XSPI_CMD_STATUS_BUS_ERROR BIT(1) @@ -335,6 +377,9 @@ struct cdns_xspi_dev { unsigned int sdmasize; unsigned int sdma_xfer_len; + int cur_work_mode; + bool has_acmd; + struct completion cmd_complete; struct completion auto_cmd_complete; struct completion sdma_complete; @@ -367,6 +412,18 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi) 100, 1000); } +static int cdns_xspi_wait_for_acmd_idle(struct cdns_xspi_dev *cdns_xspi) +{ + u32 ctrl_status; + + return readl_relaxed_poll_timeout(cdns_xspi->iobase + + CDNS_XSPI_CTRL_STATUS_REG, + ctrl_status, + ((ctrl_status & + CDNS_XSPI_ACMD_ENGINE_BUSY) == 0), + 100, 1000); +} + static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi, u32 cmd_regs[6]) { @@ -378,13 +435,19 @@ static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi, writel(cmd_regs[0], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_0); } -static int cdns_xspi_check_command_status(struct cdns_xspi_dev *cdns_xspi) +static int cdns_xspi_check_command_status(struct cdns_xspi_dev *cdns_xspi, + bool acmd) { int ret = 0; u32 cmd_status = readl(cdns_xspi->iobase + CDNS_XSPI_CMD_STATUS_REG); if (cmd_status & CDNS_XSPI_CMD_STATUS_COMPLETED) { if ((cmd_status & CDNS_XSPI_CMD_STATUS_FAILED) != 0) { + if (acmd && cmd_status & CDNS_XSPI_CMD_STATUS_DEVICE_ERROR) { + dev_err(cdns_xspi->dev, + "Device reported an error\n"); + ret = -EPROTO; + } if (cmd_status & CDNS_XSPI_CMD_STATUS_DQS_ERROR) { dev_err(cdns_xspi->dev, "Incorrect DQS pulses detected\n"); @@ -427,6 +490,39 @@ static void cdns_xspi_set_interrupts(struct cdns_xspi_dev *cdns_xspi, writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG); } +static void cdns_xspi_set_thread_error_interrupt(struct cdns_xspi_dev *cdns_xspi, + bool enabled) +{ + u32 intr_enable; + + intr_enable = readl(cdns_xspi->iobase + CDNS_XSPI_TRD_ERR_INTR_EN); + if (enabled) + intr_enable |= CDNS_XSPI_ACMD_THRD_0_MASK; + else + intr_enable &= ~CDNS_XSPI_ACMD_THRD_0_MASK; + writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_TRD_ERR_INTR_EN); +} + +static void cdns_xspi_acmd_init(struct cdns_xspi_dev *cdns_xspi) +{ + u32 seq_cfg; + + /* + * For now, ACMD relies on the profile 1 sequence with no read page + * limit (so SPI NAND continuous reads work). The read is fully + * described by READ_SEQ_CFG0: no mode bits (they replace its dummy + * count) nor opcode extension. + */ + seq_cfg = readl(cdns_xspi->iobase + CDNS_XSPI_SEQ_GCTL0_REG); + FIELD_MODIFY(CDNS_XSPI_SEQ_GCTL0_TYP, &seq_cfg, + CDNS_XSPI_SEQ_GCTL0_TYP_PROFILE_1); + FIELD_MODIFY(CDNS_XSPI_SEQ_GCTL0_PSIZ_RD, &seq_cfg, + CDNS_XSPI_SEQ_GCTL0_PSIZ_RD_UNLIMITED); + writel(seq_cfg, cdns_xspi->iobase + CDNS_XSPI_SEQ_GCTL0_REG); + + writel(0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG1_REG); +} + static int cdns_xspi_controller_init(struct cdns_xspi_dev *cdns_xspi) { u32 ctrl_ver; @@ -447,6 +543,9 @@ static int cdns_xspi_controller_init(struct cdns_xspi_dev *cdns_xspi) cdns_xspi->dma_data_width = (ctrl_features & CDNS_XSPI_DMA_DATA_WIDTH) ? 8 : 4; cdns_xspi->set_interrupts_handler(cdns_xspi, false); + if (cdns_xspi->has_acmd) + cdns_xspi_acmd_init(cdns_xspi); + return 0; } @@ -527,6 +626,24 @@ static int cdns_xspi_sdma_handle(struct cdns_xspi_dev *cdns_xspi) return 0; } +static int cdns_xspi_set_work_mode(struct cdns_xspi_dev *cdns_xspi, int mode) +{ + int ret; + + if (cdns_xspi->cur_work_mode == mode) + return 0; + + ret = cdns_xspi_wait_for_controller_idle(cdns_xspi); + if (ret < 0) + return ret; + + writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, mode), + cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG); + cdns_xspi->cur_work_mode = mode; + + return 0; +} + static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, const struct spi_mem_op *op, bool data_phase) @@ -536,12 +653,9 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, int ret; int dummybytes = op->dummy.nbytes; - ret = cdns_xspi_wait_for_controller_idle(cdns_xspi); - if (ret < 0) - return -EIO; - - writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG), - cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG); + ret = cdns_xspi_set_work_mode(cdns_xspi, CDNS_XSPI_WORK_MODE_STIG); + if (ret) + return ret; cdns_xspi->set_interrupts_handler(cdns_xspi, true); cdns_xspi->xfer_error = false; @@ -589,13 +703,112 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi, wait_for_completion(&cdns_xspi->cmd_complete); cdns_xspi->set_interrupts_handler(cdns_xspi, false); - cmd_status = cdns_xspi_check_command_status(cdns_xspi); + cmd_status = cdns_xspi_check_command_status(cdns_xspi, false); if (cmd_status) return -EPROTO; return 0; } +static void cdns_xspi_acmd_setup_read_seq(struct cdns_xspi_dev *cdns_xspi, + const struct spi_mem_op *op) +{ + u32 cfg0; + + cfg0 = FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_CMD_VALUE, op->cmd.opcode) | + FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_CMD_IOS, + ilog2(op->cmd.buswidth)) | + FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_ADDR_CNT, op->addr.nbytes) | + FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_ADDR_IOS, + ilog2(op->addr.buswidth)) | + FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_DAT_IOS, + ilog2(op->data.buswidth)); + + if (op->dummy.nbytes && op->dummy.buswidth) + cfg0 |= FIELD_PREP(CDNS_XSPI_READ_SEQ_CFG0_DMY_CNT, + (op->dummy.nbytes * 8) / op->dummy.buswidth); + + writel(cfg0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG0_REG); +} + +static int cdns_xspi_acmd_check_status(struct cdns_xspi_dev *cdns_xspi) +{ + writel(FIELD_PREP(CDNS_XSPI_CMD_STATUS_PTR_SEL, CDNS_XSPI_ACMD_THRD_0), + cdns_xspi->iobase + CDNS_XSPI_CMD_STATUS_PTR_REG); + + return cdns_xspi_check_command_status(cdns_xspi, true); +} + +static int cdns_xspi_acmd_read(struct cdns_xspi_dev *cdns_xspi, + const struct spi_mem_op *op) +{ + dma_addr_t dma_addr; + u32 cmd_regs[6]; + int ret; + + ret = cdns_xspi_set_work_mode(cdns_xspi, CDNS_XSPI_WORK_MODE_ACMD); + if (ret) + return ret; + + ret = cdns_xspi_wait_for_acmd_idle(cdns_xspi); + if (ret) + return ret; + + cdns_xspi_acmd_setup_read_seq(cdns_xspi, op); + + dma_addr = dma_map_single(cdns_xspi->dev, op->data.buf.in, + op->data.nbytes, DMA_FROM_DEVICE); + if (dma_mapping_error(cdns_xspi->dev, dma_addr)) + return -ENOMEM; + + cdns_xspi->xfer_error = false; + writel(CDNS_XSPI_ACMD_THRD_0_MASK, + cdns_xspi->iobase + CDNS_XSPI_TRD_COMP_INTR_STATUS); + writel(CDNS_XSPI_ACMD_THRD_0_MASK, + cdns_xspi->iobase + CDNS_XSPI_TRD_ERR_INTR_STATUS); + cdns_xspi_set_thread_error_interrupt(cdns_xspi, true); + cdns_xspi->set_interrupts_handler(cdns_xspi, true); + + cmd_regs[1] = lower_32_bits(op->addr.val); + cmd_regs[5] = upper_32_bits(op->addr.val); + cmd_regs[2] = lower_32_bits(dma_addr); + cmd_regs[3] = upper_32_bits(dma_addr); + cmd_regs[4] = op->data.nbytes - 1; + cmd_regs[0] = FIELD_PREP(CDNS_XSPI_ACMD_MODE, + CDNS_XSPI_ACMD_MODE_PIO) | + FIELD_PREP(CDNS_XSPI_ACMD_TRD_NUM, CDNS_XSPI_ACMD_THRD_0) | + FIELD_PREP(CDNS_XSPI_ACMD_BANK, cdns_xspi->cur_cs) | + CDNS_XSPI_ACMD_DMA_SEL | CDNS_XSPI_ACMD_INT | + FIELD_PREP(CDNS_XSPI_ACMD_CMD_TYPE, + CDNS_XSPI_ACMD_TYPE_READ); + + reinit_completion(&cdns_xspi->auto_cmd_complete); + cdns_xspi_trigger_command(cdns_xspi, cmd_regs); + wait_for_completion(&cdns_xspi->auto_cmd_complete); + + cdns_xspi->set_interrupts_handler(cdns_xspi, false); + cdns_xspi_set_thread_error_interrupt(cdns_xspi, false); + dma_unmap_single(cdns_xspi->dev, dma_addr, op->data.nbytes, + DMA_FROM_DEVICE); + + if (cdns_xspi->xfer_error) + return -EIO; + + return cdns_xspi_acmd_check_status(cdns_xspi); +} + +static bool cdns_xspi_acmd_can_read(const struct cdns_xspi_dev *cdns_xspi, + const struct spi_mem_op *op) +{ + if (!cdns_xspi->has_acmd) + return false; + + if (op->data.dir != SPI_MEM_DATA_IN || !op->addr.nbytes) + return false; + + return op->data.nbytes >= CDNS_XSPI_ACMD_MIN_BYTES; +} + static int cdns_xspi_mem_op(struct cdns_xspi_dev *cdns_xspi, struct spi_mem *mem, const struct spi_mem_op *op) @@ -605,6 +818,9 @@ static int cdns_xspi_mem_op(struct cdns_xspi_dev *cdns_xspi, if (cdns_xspi->cur_cs != spi_get_chipselect(mem->spi, 0)) cdns_xspi->cur_cs = spi_get_chipselect(mem->spi, 0); + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) + return cdns_xspi_acmd_read(cdns_xspi, op); + return cdns_xspi_send_stig_command(cdns_xspi, op, (dir != SPI_MEM_NO_DATA)); } @@ -677,6 +893,9 @@ static int cdns_xspi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op * struct cdns_xspi_dev *cdns_xspi = spi_controller_get_devdata(mem->spi->controller); + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) + return 0; + op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize); return 0; @@ -691,8 +910,9 @@ static const struct spi_controller_mem_ops cadence_xspi_mem_ops = { static irqreturn_t cdns_xspi_irq_handler(int this_irq, void *dev) { struct cdns_xspi_dev *cdns_xspi = dev; - u32 irq_status; + bool acmd_done = false; irqreturn_t result = IRQ_NONE; + u32 irq_status; irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG); @@ -721,11 +941,28 @@ static irqreturn_t cdns_xspi_irq_handler(int this_irq, void *dev) writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_TRD_COMP_INTR_STATUS); - complete(&cdns_xspi->auto_cmd_complete); + if (irq_status & CDNS_XSPI_ACMD_THRD_0_MASK) + acmd_done = true; result = IRQ_HANDLED; } + irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_TRD_ERR_INTR_STATUS); + if (irq_status) { + writel(irq_status, + cdns_xspi->iobase + CDNS_XSPI_TRD_ERR_INTR_STATUS); + + if (irq_status & CDNS_XSPI_ACMD_THRD_0_MASK) { + cdns_xspi->xfer_error = true; + acmd_done = true; + } + + result = IRQ_HANDLED; + } + + if (acmd_done) + complete(&cdns_xspi->auto_cmd_complete); + return result; } @@ -1258,6 +1495,10 @@ static int cdns_xspi_probe(struct platform_device *pdev) if (ret) return -ENODEV; + /* For now, ACMD is only supported together with the internal DMA */ + cdns_xspi->has_acmd = device_property_read_bool(dev, "cdns,has-acmd") && + device_property_read_bool(dev, "cdns,has-internal-dma"); + cdns_xspi->iobase = devm_platform_ioremap_resource_byname(pdev, "io"); if (IS_ERR(cdns_xspi->iobase)) { cdns_xspi->iobase = devm_platform_ioremap_resource(pdev, 0); -- 2.56.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 3/3] spi: cadence-xspi: add ACMD mode support 2026-10-29 16:03 ` [PATCH 3/3] spi: cadence-xspi: add ACMD mode support Nuno Sá @ 2026-10-29 16:51 ` Mark Brown 2026-10-30 10:16 ` Nuno Sá 0 siblings, 1 reply; 10+ messages in thread From: Mark Brown @ 2026-10-29 16:51 UTC (permalink / raw) To: Nuno Sá Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie > So far every operation goes through STIG mode, where the CPU builds > each command and drains the data. The controller also has an auto > command (ACMD) mode where it runs a whole read sequence on its own and, > with the internal DMA, writes the data straight to memory. > +static int cdns_xspi_set_work_mode(struct cdns_xspi_dev *cdns_xspi, int mode) > +{ > + int ret; > + > + if (cdns_xspi->cur_work_mode == mode) > + return 0; We need some suspend/resume handling to invalidate the configuration when the IP power goes so we set everything up again. I'm not seeing anything here. > @@ -677,6 +893,9 @@ static int cdns_xspi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op * > struct cdns_xspi_dev *cdns_xspi = > spi_controller_get_devdata(mem->spi->controller); > > + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) > + return 0; That function has a minimum size but not a maximum size... > + > op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize); ...which we have here? -- Mark Brown <broonie@kernel.org> ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 3/3] spi: cadence-xspi: add ACMD mode support 2026-10-29 16:51 ` Mark Brown @ 2026-10-30 10:16 ` Nuno Sá 2026-10-30 11:03 ` Mark Brown 0 siblings, 1 reply; 10+ messages in thread From: Nuno Sá @ 2026-10-30 10:16 UTC (permalink / raw) To: Mark Brown Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie On Thu, Oct 29, 2026 at 04:51:32PM +0000, Mark Brown wrote: > > So far every operation goes through STIG mode, where the CPU builds > > each command and drains the data. The controller also has an auto > > command (ACMD) mode where it runs a whole read sequence on its own and, > > with the internal DMA, writes the data straight to memory. > > > +static int cdns_xspi_set_work_mode(struct cdns_xspi_dev *cdns_xspi, int mode) > > +{ > > + int ret; > > + > > + if (cdns_xspi->cur_work_mode == mode) > > + return 0; > > We need some suspend/resume handling to invalidate the configuration > when the IP power goes so we set everything up again. I'm not seeing > anything here. Yeah, I did saw sashiko complain (and my local LLM also raised it actually) but I do wonder how can that happen? So we need to assume that in suspend we might lose power (maybe because it's regulator powers down) and need to do redo all the important initialization bits? > > > @@ -677,6 +893,9 @@ static int cdns_xspi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op * > > struct cdns_xspi_dev *cdns_xspi = > > spi_controller_get_devdata(mem->spi->controller); > > > > + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) > > + return 0; > > That function has a minimum size but not a maximum size... > > > + > > op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize); > > ...which we have here? For ACMD, in theory we can just configure UINT32_MAX on the register so not sure if it makes sense to clamp to that? Unrelated question, Conor is pushing against the new bindings and that it should be inferred from the compatible. I tested this on [1] which is not an upstream platform so I wonder if it's ok to add support for the complete integration even if the SoC is not upstream? Long term plan is definitely to upstream it but we do have a lot of debt that needs clean up. [1]: https://www.analog.com/en/products/adsp-sc846.html - Nuno Sá > > -- > Mark Brown <broonie@kernel.org> ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 3/3] spi: cadence-xspi: add ACMD mode support 2026-10-30 10:16 ` Nuno Sá @ 2026-10-30 11:03 ` Mark Brown 2026-10-30 16:00 ` Nuno Sá 0 siblings, 1 reply; 10+ messages in thread From: Mark Brown @ 2026-10-30 11:03 UTC (permalink / raw) To: Nuno Sá Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie [-- Attachment #1: Type: text/plain, Size: 1634 bytes --] On Fri, Oct 30, 2026 at 10:16:00AM +0000, Nuno Sá wrote: > On Thu, Oct 29, 2026 at 04:51:32PM +0000, Mark Brown wrote: > > We need some suspend/resume handling to invalidate the configuration > > when the IP power goes so we set everything up again. I'm not seeing > > anything here. > Yeah, I did saw sashiko complain (and my local LLM also raised it > actually) but I do wonder how can that happen? So we need to assume that > in suspend we might lose power (maybe because it's regulator powers down) > and need to do redo all the important initialization bits? Yes, might not even be regulators - some SoCs have good internal power domains and can gate parts of themselves very effectively. > > > + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) > > > + return 0; > > That function has a minimum size but not a maximum size... > > > + > > > op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize); > > ...which we have here? > For ACMD, in theory we can just configure UINT32_MAX on the register so > not sure if it makes sense to clamp to that? No, if that really is the limit it's fine. > Conor is pushing against the new bindings and that it should be inferred > from the compatible. I tested this on [1] which is not an upstream > platform so I wonder if it's ok to add support for the complete > integration even if the SoC is not upstream? So long as it's an actual SoC that exists rather than some random FPGA configuration or whatever upstreaming just the SPI driver is totally fine, that's one less piece that needs doing whenever the rest of it is handled. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 3/3] spi: cadence-xspi: add ACMD mode support 2026-10-30 11:03 ` Mark Brown @ 2026-10-30 16:00 ` Nuno Sá 0 siblings, 0 replies; 10+ messages in thread From: Nuno Sá @ 2026-10-30 16:00 UTC (permalink / raw) To: Mark Brown Cc: linux-spi, linux-kernel, Parshuram Thombare, devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jisheng Zhang, Fei Xie On Fri, Oct 30, 2026 at 11:03:07AM +0000, Mark Brown wrote: > On Fri, Oct 30, 2026 at 10:16:00AM +0000, Nuno Sá wrote: > > On Thu, Oct 29, 2026 at 04:51:32PM +0000, Mark Brown wrote: > > > > We need some suspend/resume handling to invalidate the configuration > > > when the IP power goes so we set everything up again. I'm not seeing > > > anything here. > > > Yeah, I did saw sashiko complain (and my local LLM also raised it > > actually) but I do wonder how can that happen? So we need to assume that > > in suspend we might lose power (maybe because it's regulator powers down) > > and need to do redo all the important initialization bits? > > Yes, might not even be regulators - some SoCs have good internal power > domains and can gate parts of themselves very effectively. > > > > > + if (cdns_xspi_acmd_can_read(cdns_xspi, op)) > > > > + return 0; > > > > That function has a minimum size but not a maximum size... > > > > > + > > > > op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize); > > > > ...which we have here? > > > For ACMD, in theory we can just configure UINT32_MAX on the register so > > not sure if it makes sense to clamp to that? > > No, if that really is the limit it's fine. > > > Conor is pushing against the new bindings and that it should be inferred > > from the compatible. I tested this on [1] which is not an upstream > > platform so I wonder if it's ok to add support for the complete > > integration even if the SoC is not upstream? > > So long as it's an actual SoC that exists rather than some random FPGA > configuration or whatever upstreaming just the SPI driver is totally > fine, that's one less piece that needs doing whenever the rest of it is > handled. Nice! Then v2 will bring the work for the SoC integration! Yes, real SoC with two A55 cores and a shark DSP. - Nuno Sá ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-30 15:59 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-29 16:03 [PATCH 0/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2026-10-29 16:03 ` [PATCH 1/3] spi: cadence-xspi: rename sdma_error to xfer_error Nuno Sá 2026-10-29 16:03 ` [PATCH 2/3] spi: dt-bindings: cdns,xspi: add ACMD and internal DMA properties Nuno Sá 2026-10-29 18:31 ` Conor Dooley 2026-10-30 9:57 ` Nuno Sá 2026-10-29 16:03 ` [PATCH 3/3] spi: cadence-xspi: add ACMD mode support Nuno Sá 2026-10-29 16:51 ` Mark Brown 2026-10-30 10:16 ` Nuno Sá 2026-10-30 11:03 ` Mark Brown 2026-10-30 16:00 ` Nuno Sá
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox