* [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration
@ 2026-11-11 13:23 Nuno Sá
2026-11-11 13:23 ` [PATCH v2 1/8] spi: cadence-xspi: fail on command errors without a known cause Nuno Sá
` (7 more replies)
0 siblings, 8 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
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, the series
also adds support for the ADI ADSP-SC846 integration, which enables
ACMD through its driver data. On the way, the platform specifics are
moved into the driver data and optional clocks and resets support is
added for the IP.
For now, ACMD is only used for reads of at least 2048 bytes with an
address phase, using the internal DMA, and only on integrations
supporting it. 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.
Changes in v2:
- Patch 1 (new):
- Fail on command errors reported without a known cause.
- Patch 2 (new):
- Wait for the controller initialization on resume, as the IP might
lose power across suspend (Mark).
- Patch 3 (new):
- dt-bindings: add optional clocks and resets.
- Patch 4 (new):
- Get/enable the optional clock and reset the controller at probe,
waiting for its initialization after the reset.
- Patch 5 (new):
- Move the platform specifics into per compatible driver data.
- Patch 6 (new):
- dt-bindings: add the adi,adsp-sc846-xspi compatible, clocks and resets
required for it.
- Patch 7 (new):
- Add the ADI SC846 integration, PHY setup at probe and resume.
- Patch 8:
- Infer ACMD support from the compatible (driver data) instead of DT
properties (Conor).
- Invalidate the cached work mode and redo the ACMD setup on resume
(Mark).
- Always report the ACMD error from the command status, the thread
error interrupt only wakes up the waiter (sashiko).
- Comment why ACMD reads are not size limited (Mark).
- Dropped the sdma_error to xfer_error rename patch, not needed anymore.
- Dropped the cdns,has-acmd and cdns,has-internal-dma properties patch
(Conor).
- Link to v1: https://patch.msgid.link/20261029-xspi-cadence-add-acmd-v1-0-53cd44690e04@analog.com
[1]: https://lore.kernel.org/linux-spi/20260921093701.1341766-1-fei.xie@horizon.auto/
---
Nuno Sá (8):
spi: cadence-xspi: fail on command errors without a known cause
spi: cadence-xspi: wait for the controller initialization on resume
spi: dt-bindings: cdns,xspi: add clocks and resets
spi: cadence-xspi: support clocks and resets
spi: cadence-xspi: move platform specifics into driver data
spi: dt-bindings: cdns,xspi: add ADI SC846 compatible
spi: cadence-xspi: add ADI SC846 platform support
spi: cadence-xspi: add ACMD mode support
.../devicetree/bindings/spi/cdns,xspi.yaml | 16 +
drivers/spi/spi-cadence-xspi.c | 530 +++++++++++++++++----
2 files changed, 457 insertions(+), 89 deletions(-)
---
base-commit: 7da2aca123754c0e06092bbe179d58078b58d8d4
change-id: 20261027-xspi-cadence-add-acmd-3c0e342cb4f4
--
Thanks!
- Nuno Sá
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/8] spi: cadence-xspi: fail on command errors without a known cause
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 13:23 ` [PATCH v2 2/8] spi: cadence-xspi: wait for the controller initialization on resume Nuno Sá
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
cdns_xspi_check_command_status() only returns an error if one of the
known error bits is set along with the FAIL bit. If the controller
reports a failed command without any of those, the failure is silently
ignored and the operation is reported as successful.
Treat the FAIL bit on its own as an error, and log the whole command
status so the unknown cause can still be looked at.
Fixes: a16cc8077627 ("spi: cadence: add support for Cadence XSPI controller")
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 3687853e4ce4..85cf5cf81c9e 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -405,6 +405,12 @@ static int cdns_xspi_check_command_status(struct cdns_xspi_dev *cdns_xspi)
"Invalid command sequence detected\n");
ret = -EPROTO;
}
+ if (!ret) {
+ dev_err(cdns_xspi->dev,
+ "Unknown reported error (0x%08x)\n",
+ cmd_status);
+ ret = -EPROTO;
+ }
}
} else {
dev_err(cdns_xspi->dev, "Fatal err - command not completed\n");
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 2/8] spi: cadence-xspi: wait for the controller initialization on resume
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
2026-11-11 13:23 ` [PATCH v2 1/8] spi: cadence-xspi: fail on command errors without a known cause Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 13:23 ` [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets Nuno Sá
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
The controller IP might lose power across a system suspend (e.g. its
power domain being gated). In that case, it goes through its reset and
initialization process again on resume, which is only done when the
INIT_COMPLETED bit is set in the controller status register. However,
the resume path reconfigures the controller right away.
Add a helper to wait for the controller to complete its initialization
and use it on resume before configuring anything. If the controller did
not lose power, the bit is already set and there is nothing to wait for.
The helper will also be used after the optional hardware reset, added
in a following patch.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 85cf5cf81c9e..c02069715153 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -367,6 +367,22 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
100, 1000);
}
+static int cdns_xspi_wait_for_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 ctrl_stat;
+ int ret;
+
+ ret = readl_relaxed_poll_timeout(cdns_xspi->iobase +
+ CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat,
+ ctrl_stat & CDNS_XSPI_INIT_COMPLETED,
+ 100, 100 * USEC_PER_MSEC);
+ if (ret)
+ dev_err(cdns_xspi->dev, "Controller initialization timeout\n");
+
+ return ret;
+}
+
static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi,
u32 cmd_regs[6])
{
@@ -1355,6 +1371,15 @@ static int cdns_xspi_suspend(struct device *dev)
static int cdns_xspi_resume(struct device *dev)
{
struct cdns_xspi_dev *cdns_xspi = dev_get_drvdata(dev);
+ int ret;
+
+ /*
+ * The controller might have lost power and so, be going through its
+ * reset and initialization process again.
+ */
+ ret = cdns_xspi_wait_for_init(cdns_xspi);
+ if (ret)
+ return ret;
#ifdef CONFIG_64BIT
if (cdns_xspi->driver_data->mrvl_hw_overlay) {
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
2026-11-11 13:23 ` [PATCH v2 1/8] spi: cadence-xspi: fail on command errors without a known cause Nuno Sá
2026-11-11 13:23 ` [PATCH v2 2/8] spi: cadence-xspi: wait for the controller initialization on resume Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 17:57 ` Conor Dooley
2026-11-11 13:23 ` [PATCH v2 4/8] spi: cadence-xspi: support " Nuno Sá
` (4 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
The Cadence xSPI controller has a reference clock feeding it and, like
most IPs, a reset line that the SoC integration may expose. So far none
of them is described, which only works when the firmware leaves the
clock running and the controller out of reset.
Add the optional clocks and resets properties. They are optional to
keep existing devicetrees valid.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 38a5795589de..3cd7321319bb 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -40,6 +40,12 @@ properties:
interrupts:
maxItems: 1
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
required:
- compatible
- reg
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 4/8] spi: cadence-xspi: support clocks and resets
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
` (2 preceding siblings ...)
2026-11-11 13:23 ` [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 13:23 ` [PATCH v2 5/8] spi: cadence-xspi: move platform specifics into driver data Nuno Sá
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
The driver assumes the controller clock is already running and that the
controller is out of reset, which only holds if the firmware left it
that way.
Get and enable the optional controller clock and, if a reset line is
described, reset the controller during probe so it starts from a known
state. After the reset, wait for the controller to complete its reset
and initialization process before touching it. Both are optional to keep
compatibility with existing devicetrees.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index c02069715153..5d8d102ef397 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -2,6 +2,7 @@
// Cadence XSPI flash controller driver
// Copyright (C) 2020-21 Cadence
+#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -14,6 +15,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
+#include <linux/reset.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/bitfield.h>
@@ -1236,7 +1238,9 @@ static int cdns_xspi_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct spi_controller *host = NULL;
struct cdns_xspi_dev *cdns_xspi = NULL;
+ struct reset_control *rstc;
struct resource *res;
+ struct clk *clk;
int ret;
host = devm_spi_alloc_host(dev, sizeof(*cdns_xspi));
@@ -1322,6 +1326,25 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
#endif
+ /* Optional to keep compatibility with existing devicetrees */
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get clock\n");
+
+ rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(dev, PTR_ERR(rstc), "Failed to get reset\n");
+
+ if (rstc) {
+ ret = reset_control_reset(rstc);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to reset controller\n");
+
+ ret = cdns_xspi_wait_for_init(cdns_xspi);
+ if (ret)
+ return ret;
+ }
+
cdns_xspi->irq = platform_get_irq(pdev, 0);
if (cdns_xspi->irq < 0)
return -ENXIO;
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 5/8] spi: cadence-xspi: move platform specifics into driver data
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
` (3 preceding siblings ...)
2026-11-11 13:23 ` [PATCH v2 4/8] spi: cadence-xspi: support " Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 13:23 ` [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible Nuno Sá
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
The driver uses a mrvl_hw_overlay boolean in the driver data to decide,
at several points in the common probe and resume paths, whether the
Marvell integration specifics have to be applied. Adding another
integration would mean adding more of these conditionals.
Turn the driver data into a set of per-compatible operations instead,
including two optional callbacks: init(), for one time vendor specific
initialization at probe, and hw_init(), for the vendor specific hardware
setup done both at probe and resume. The probe and resume paths now just
use these.
The Marvell XFER register mapping moves into init() while the clock
setup and PHY configuration move into hw_init(). Since the PHY timing
values were only ever the Marvell ones, drop them from the driver data
and write the constants directly, and rename the helper to
cdns_mrvl_xspi_configure_phy() to make that explicit.
No functional change intended.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 148 ++++++++++++++++++++++-------------------
1 file changed, 78 insertions(+), 70 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 5d8d102ef397..d592512dda63 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -306,22 +306,6 @@ enum cdns_xspi_stig_cmd_dir {
CDNS_XSPI_STIG_CMD_DIR_WRITE,
};
-struct cdns_xspi_driver_data {
- bool mrvl_hw_overlay;
- u32 dll_phy_ctrl;
- u32 ctb_rfile_phy_ctrl;
- u32 rfile_phy_tsel;
- u32 rfile_phy_dq_timing;
- u32 rfile_phy_dqs_timing;
- u32 rfile_phy_gate_lpbk_ctrl;
- u32 rfile_phy_dll_master_ctrl;
- u32 rfile_phy_dll_slave_ctrl;
-};
-
-static struct cdns_xspi_driver_data cdns_driver_data = {
- .mrvl_hw_overlay = false,
-};
-
struct cdns_xspi_dev {
struct platform_device *pdev;
struct spi_controller *host;
@@ -357,6 +341,17 @@ struct cdns_xspi_dev {
int current_xfer_qword;
};
+struct cdns_xspi_driver_data {
+ const struct spi_controller_mem_ops *mem_ops;
+ int (*transfer_one_message)(struct spi_controller *ctlr,
+ struct spi_message *msg);
+ int (*sdma_handler)(struct cdns_xspi_dev *cdns_xspi);
+ void (*set_interrupts_handler)(struct cdns_xspi_dev *cdns_xspi,
+ bool enabled);
+ int (*init)(struct cdns_xspi_dev *cdns_xspi);
+ int (*hw_init)(struct cdns_xspi_dev *cdns_xspi);
+};
+
static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
{
u32 ctrl_stat;
@@ -794,18 +789,6 @@ static void cdns_xspi_print_phy_config(struct cdns_xspi_dev *cdns_xspi)
}
#ifdef CONFIG_64BIT
-static struct cdns_xspi_driver_data marvell_driver_data = {
- .mrvl_hw_overlay = true,
- .dll_phy_ctrl = MARVELL_REGS_DLL_PHY_CTRL,
- .ctb_rfile_phy_ctrl = MARVELL_CTB_RFILE_PHY_CTRL,
- .rfile_phy_tsel = MARVELL_RFILE_PHY_TSEL,
- .rfile_phy_dq_timing = MARVELL_RFILE_PHY_DQ_TIMING,
- .rfile_phy_dqs_timing = MARVELL_RFILE_PHY_DQS_TIMING,
- .rfile_phy_gate_lpbk_ctrl = MARVELL_RFILE_PHY_GATE_LPBK_CTRL,
- .rfile_phy_dll_master_ctrl = MARVELL_RFILE_PHY_DLL_MASTER_CTRL,
- .rfile_phy_dll_slave_ctrl = MARVELL_RFILE_PHY_DLL_SLAVE_CTRL,
-};
-
static const int cdns_mrvl_xspi_clk_div_list[] = {
4, //0x0 = Divide by 4. SPI clock is 200 MHz.
6, //0x1 = Divide by 6. SPI clock is 133.33 MHz.
@@ -844,23 +827,23 @@ static bool cdns_xspi_is_dll_locked(struct cdns_xspi_dev *cdns_xspi)
}
/* Static configuration of PHY */
-static bool cdns_xspi_configure_phy(struct cdns_xspi_dev *cdns_xspi)
+static bool cdns_mrvl_xspi_configure_phy(struct cdns_xspi_dev *cdns_xspi)
{
- writel(cdns_xspi->driver_data->dll_phy_ctrl,
+ writel(MARVELL_REGS_DLL_PHY_CTRL,
cdns_xspi->iobase + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
- writel(cdns_xspi->driver_data->ctb_rfile_phy_ctrl,
+ writel(MARVELL_CTB_RFILE_PHY_CTRL,
cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_CTRL);
- writel(cdns_xspi->driver_data->rfile_phy_tsel,
+ writel(MARVELL_RFILE_PHY_TSEL,
cdns_xspi->auxbase + CDNS_XSPI_PHY_CTB_RFILE_PHY_TSEL);
- writel(cdns_xspi->driver_data->rfile_phy_dq_timing,
+ writel(MARVELL_RFILE_PHY_DQ_TIMING,
cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQ_TIMING);
- writel(cdns_xspi->driver_data->rfile_phy_dqs_timing,
+ writel(MARVELL_RFILE_PHY_DQS_TIMING,
cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DQS_TIMING);
- writel(cdns_xspi->driver_data->rfile_phy_gate_lpbk_ctrl,
+ writel(MARVELL_RFILE_PHY_GATE_LPBK_CTRL,
cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_GATE_LPBK_CTRL);
- writel(cdns_xspi->driver_data->rfile_phy_dll_master_ctrl,
+ writel(MARVELL_RFILE_PHY_DLL_MASTER_CTRL,
cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_MASTER_CTRL);
- writel(cdns_xspi->driver_data->rfile_phy_dll_slave_ctrl,
+ writel(MARVELL_RFILE_PHY_DLL_SLAVE_CTRL,
cdns_xspi->auxbase + CDNS_XSPI_PHY_DATASLICE_RFILE_PHY_DLL_SLAVE_CTRL);
cdns_xspi_reset_dll(cdns_xspi);
@@ -1231,6 +1214,31 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
return 0;
}
+
+static int cdns_mrvl_xspi_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ struct platform_device *pdev = cdns_xspi->pdev;
+
+ cdns_xspi->xferbase = devm_platform_ioremap_resource_byname(pdev, "xfer");
+ if (IS_ERR(cdns_xspi->xferbase)) {
+ cdns_xspi->xferbase = devm_platform_ioremap_resource(pdev, 3);
+ if (IS_ERR(cdns_xspi->xferbase)) {
+ dev_info(cdns_xspi->dev, "XFER register base not found, set it\n");
+ // For compatibility with older firmware
+ cdns_xspi->xferbase = cdns_xspi->iobase + 0x8000;
+ }
+ }
+
+ return 0;
+}
+
+static int cdns_mrvl_xspi_hw_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ cdns_mrvl_xspi_setup_clock(cdns_xspi, MRVL_DEFAULT_CLK);
+ cdns_mrvl_xspi_configure_phy(cdns_xspi);
+
+ return 0;
+}
#endif
static int cdns_xspi_probe(struct platform_device *pdev)
@@ -1256,17 +1264,10 @@ static int cdns_xspi_probe(struct platform_device *pdev)
if (!cdns_xspi->driver_data)
return -ENODEV;
- host->mem_ops = &cadence_xspi_mem_ops;
- cdns_xspi->sdma_handler = &cdns_xspi_sdma_handle;
- cdns_xspi->set_interrupts_handler = &cdns_xspi_set_interrupts;
-#ifdef CONFIG_64BIT
- if (cdns_xspi->driver_data->mrvl_hw_overlay) {
- host->mem_ops = &marvell_xspi_mem_ops;
- host->transfer_one_message = cdns_xspi_transfer_one_message_b0;
- cdns_xspi->sdma_handler = &marvell_xspi_sdma_handle;
- cdns_xspi->set_interrupts_handler = &marvell_xspi_set_interrupts;
- }
-#endif
+ host->mem_ops = cdns_xspi->driver_data->mem_ops;
+ host->transfer_one_message = cdns_xspi->driver_data->transfer_one_message;
+ cdns_xspi->sdma_handler = cdns_xspi->driver_data->sdma_handler;
+ cdns_xspi->set_interrupts_handler = cdns_xspi->driver_data->set_interrupts_handler;
host->bus_num = -1;
platform_set_drvdata(pdev, cdns_xspi);
@@ -1312,19 +1313,11 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
}
-#ifdef CONFIG_64BIT
- if (cdns_xspi->driver_data->mrvl_hw_overlay) {
- cdns_xspi->xferbase = devm_platform_ioremap_resource_byname(pdev, "xfer");
- if (IS_ERR(cdns_xspi->xferbase)) {
- cdns_xspi->xferbase = devm_platform_ioremap_resource(pdev, 3);
- if (IS_ERR(cdns_xspi->xferbase)) {
- dev_info(dev, "XFER register base not found, set it\n");
- // For compatibility with older firmware
- cdns_xspi->xferbase = cdns_xspi->iobase + 0x8000;
- }
- }
+ if (cdns_xspi->driver_data->init) {
+ ret = cdns_xspi->driver_data->init(cdns_xspi);
+ if (ret)
+ return ret;
}
-#endif
/* Optional to keep compatibility with existing devicetrees */
clk = devm_clk_get_optional_enabled(dev, NULL);
@@ -1356,12 +1349,11 @@ static int cdns_xspi_probe(struct platform_device *pdev)
return ret;
}
-#ifdef CONFIG_64BIT
- if (cdns_xspi->driver_data->mrvl_hw_overlay) {
- cdns_mrvl_xspi_setup_clock(cdns_xspi, MRVL_DEFAULT_CLK);
- cdns_xspi_configure_phy(cdns_xspi);
+ if (cdns_xspi->driver_data->hw_init) {
+ ret = cdns_xspi->driver_data->hw_init(cdns_xspi);
+ if (ret)
+ return ret;
}
-#endif
cdns_xspi_print_phy_config(cdns_xspi);
@@ -1404,12 +1396,11 @@ static int cdns_xspi_resume(struct device *dev)
if (ret)
return ret;
-#ifdef CONFIG_64BIT
- if (cdns_xspi->driver_data->mrvl_hw_overlay) {
- cdns_mrvl_xspi_setup_clock(cdns_xspi, MRVL_DEFAULT_CLK);
- cdns_xspi_configure_phy(cdns_xspi);
+ if (cdns_xspi->driver_data->hw_init) {
+ ret = cdns_xspi->driver_data->hw_init(cdns_xspi);
+ if (ret)
+ return ret;
}
-#endif
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
@@ -1419,6 +1410,23 @@ static int cdns_xspi_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(cdns_xspi_pm_ops,
cdns_xspi_suspend, cdns_xspi_resume);
+static const struct cdns_xspi_driver_data cdns_driver_data = {
+ .mem_ops = &cadence_xspi_mem_ops,
+ .sdma_handler = cdns_xspi_sdma_handle,
+ .set_interrupts_handler = cdns_xspi_set_interrupts,
+};
+
+#ifdef CONFIG_64BIT
+static const struct cdns_xspi_driver_data marvell_driver_data = {
+ .mem_ops = &marvell_xspi_mem_ops,
+ .transfer_one_message = cdns_xspi_transfer_one_message_b0,
+ .sdma_handler = marvell_xspi_sdma_handle,
+ .set_interrupts_handler = marvell_xspi_set_interrupts,
+ .init = cdns_mrvl_xspi_init,
+ .hw_init = cdns_mrvl_xspi_hw_init,
+};
+#endif
+
static const struct of_device_id cdns_xspi_of_match[] = {
{
.compatible = "cdns,xspi-nor",
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
` (4 preceding siblings ...)
2026-11-11 13:23 ` [PATCH v2 5/8] spi: cadence-xspi: move platform specifics into driver data Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 17:58 ` Conor Dooley
2026-11-11 13:23 ` [PATCH v2 7/8] spi: cadence-xspi: add ADI SC846 platform support Nuno Sá
2026-11-11 13:23 ` [PATCH v2 8/8] spi: cadence-xspi: add ACMD mode support Nuno Sá
7 siblings, 1 reply; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
The Analog Devices ADSP-SC846 SoC integrates the Cadence xSPI controller
with its own PHY timing configuration and with the support needed for
the auto command (ACMD) mode with the internal DMA, so it cannot be
described by the generic "cdns,xspi-nor" compatible. Add a SoC specific
compatible for it.
On this SoC, the controller clock and reset are always wired, so make
them required.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 3cd7321319bb..d6ac05b2efb4 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -18,6 +18,7 @@ description: |
properties:
compatible:
enum:
+ - adi,adsp-sc846-xspi
- cdns,xspi-nor
- marvell,cn10-xspi-nor
@@ -53,6 +54,15 @@ required:
allOf:
- $ref: spi-controller.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: adi,adsp-sc846-xspi
+ then:
+ required:
+ - clocks
+ - resets
- if:
properties:
compatible:
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 7/8] spi: cadence-xspi: add ADI SC846 platform support
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
` (5 preceding siblings ...)
2026-11-11 13:23 ` [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
2026-11-11 13:23 ` [PATCH v2 8/8] spi: cadence-xspi: add ACMD mode support Nuno Sá
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
Add driver data for the Analog Devices ADSP-SC846 integration of the
Cadence xSPI controller. It uses the generic Cadence memory operations
and handlers, and only needs a hw_init() callback programming the SC846
specific PHY settings before resetting the DLL. Being a hw_init()
callback, the PHY is also set up again on resume.
Waiting for the DLL to lock is done on the PHY DLL observable register
rather than on the register used by the Marvell path, as that is what is
documented for this platform.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 75 +++++++++++++++++++++++++++++++++++-------
1 file changed, 64 insertions(+), 11 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index d592512dda63..55b37175f08f 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -42,9 +42,15 @@
/* PHY gate loopback control register */
#define CDNS_XSPI_CCP_PHY_GATE_LPBCK_CTRL 0x0008
+/* PHY DLL master control register */
+#define CDNS_XSPI_CCP_PHY_DLL_MASTER_CTRL 0x000C
+
/* PHY DLL slave control register */
#define CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL 0x0010
+/* PHY DQS input enable timing register */
+#define CDNS_XSPI_CCP_PHY_IE_TIMER 0x0014
+
/* DLL PHY control register */
#define CDNS_XSPI_DLL_PHY_CTRL 0x1034
@@ -235,6 +241,14 @@
#define MARVELL_RFILE_PHY_DLL_MASTER_CTRL 0x00800000
#define MARVELL_RFILE_PHY_DLL_SLAVE_CTRL 0x0000ff01
+/* ADI SC846 PHY default values */
+#define ADI_XSPI_PHY_DQ_TR 0x00000101
+#define ADI_XSPI_PHY_DQS_TR 0x00300400
+#define ADI_XSPI_PHY_GATE_LPBK_CTL 0x00180030
+#define ADI_XSPI_PHY_DLL_MSTR_CTL 0x00140084
+#define ADI_XSPI_PHY_DLL_SLAVE_CTL 0x00003fc4
+#define ADI_XSPI_PHY_IE_TR 0x00100000
+
/* PHY config registers */
#define CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL 0x1034
#define CDNS_XSPI_PHY_CTB_RFILE_PHY_CTRL 0x0080
@@ -788,6 +802,45 @@ static void cdns_xspi_print_phy_config(struct cdns_xspi_dev *cdns_xspi)
readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL));
}
+static void cdns_xspi_reset_dll(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 dll_cntrl = readl(cdns_xspi->iobase +
+ CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
+
+ /* Reset DLL */
+ dll_cntrl |= CDNS_XSPI_DLL_RST_N;
+ writel(dll_cntrl, cdns_xspi->iobase +
+ CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
+}
+
+static int cdns_sc846_xspi_hw_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 dll_obs;
+
+ writel(ADI_XSPI_PHY_DQ_TR,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DQ_TIMING);
+ writel(ADI_XSPI_PHY_DQS_TR,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DQS_TIMING);
+ writel(ADI_XSPI_PHY_GATE_LPBK_CTL,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_GATE_LPBCK_CTRL);
+ writel(ADI_XSPI_PHY_DLL_MSTR_CTL,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_MASTER_CTRL);
+ writel(ADI_XSPI_PHY_DLL_SLAVE_CTL,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL);
+ writel(ADI_XSPI_PHY_IE_TR,
+ cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_IE_TIMER);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
+
+ cdns_xspi_reset_dll(cdns_xspi);
+
+ /*
+ * Unlike the Marvell integration, the DLL lock is documented on the
+ * PHY DLL observable register.
+ */
+ return readl_poll_timeout(cdns_xspi->auxbase + CDNS_XSPI_DATASLICE_RFILE_PHY_DLL_OBS_REG_0,
+ dll_obs, dll_obs & CDNS_XSPI_DLL_LOCK, 10, 10000);
+}
+
#ifdef CONFIG_64BIT
static const int cdns_mrvl_xspi_clk_div_list[] = {
4, //0x0 = Divide by 4. SPI clock is 200 MHz.
@@ -806,17 +859,6 @@ static const int cdns_mrvl_xspi_clk_div_list[] = {
128 //0xD = Divide by 128. SPI clock is 6.25 MHz.
};
-static void cdns_xspi_reset_dll(struct cdns_xspi_dev *cdns_xspi)
-{
- u32 dll_cntrl = readl(cdns_xspi->iobase +
- CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
-
- /* Reset DLL */
- dll_cntrl |= CDNS_XSPI_DLL_RST_N;
- writel(dll_cntrl, cdns_xspi->iobase +
- CDNS_XSPI_RF_MINICTRL_REGS_DLL_PHY_CTRL);
-}
-
static bool cdns_xspi_is_dll_locked(struct cdns_xspi_dev *cdns_xspi)
{
u32 dll_lock;
@@ -1416,6 +1458,13 @@ static const struct cdns_xspi_driver_data cdns_driver_data = {
.set_interrupts_handler = cdns_xspi_set_interrupts,
};
+static const struct cdns_xspi_driver_data sc846_driver_data = {
+ .mem_ops = &cadence_xspi_mem_ops,
+ .sdma_handler = cdns_xspi_sdma_handle,
+ .set_interrupts_handler = cdns_xspi_set_interrupts,
+ .hw_init = cdns_sc846_xspi_hw_init,
+};
+
#ifdef CONFIG_64BIT
static const struct cdns_xspi_driver_data marvell_driver_data = {
.mem_ops = &marvell_xspi_mem_ops,
@@ -1428,6 +1477,10 @@ static const struct cdns_xspi_driver_data marvell_driver_data = {
#endif
static const struct of_device_id cdns_xspi_of_match[] = {
+ {
+ .compatible = "adi,adsp-sc846-xspi",
+ .data = &sc846_driver_data,
+ },
{
.compatible = "cdns,xspi-nor",
.data = &cdns_driver_data,
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 8/8] spi: cadence-xspi: add ACMD mode support
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
` (6 preceding siblings ...)
2026-11-11 13:23 ` [PATCH v2 7/8] spi: cadence-xspi: add ADI SC846 platform support Nuno Sá
@ 2026-11-11 13:23 ` Nuno Sá
7 siblings, 0 replies; 11+ messages in thread
From: Nuno Sá @ 2026-11-11 13:23 UTC (permalink / raw)
To: linux-spi, linux-kernel, devicetree
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
Jisheng Zhang, Fei Xie, Pratyush Yadav, Parshuram Thombare,
Konrad Kociolek, Mark Brown, Jayshri Pawar
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.
Whether ACMD and the internal DMA can be used depends on the SoC
integration, so it is enabled through the driver data. For now, only
the ADI SC846 integration enables it.
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 and its transfer
length is a full 32-bit field.
The thread error interrupt is only used to wake up the waiter. The
cause of the error is always taken from the command status of the
thread.
Since the controller might lose its configuration across suspend, the
cached work mode is invalidated and the ACMD setup is redone on resume.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/spi/spi-cadence-xspi.c | 253 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 245 insertions(+), 8 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 55b37175f08f..061c245130ef 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -5,6 +5,7 @@
#include <linux/clk.h>
#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>
@@ -71,6 +72,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
@@ -111,6 +113,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)
@@ -147,6 +187,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,8 @@ struct cdns_xspi_dev {
unsigned int sdmasize;
unsigned int sdma_xfer_len;
+ int cur_work_mode;
+
struct completion cmd_complete;
struct completion auto_cmd_complete;
struct completion sdma_complete;
@@ -364,6 +408,7 @@ struct cdns_xspi_driver_data {
bool enabled);
int (*init)(struct cdns_xspi_dev *cdns_xspi);
int (*hw_init)(struct cdns_xspi_dev *cdns_xspi);
+ bool has_acmd;
};
static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
@@ -394,6 +439,18 @@ static int cdns_xspi_wait_for_init(struct cdns_xspi_dev *cdns_xspi)
return ret;
}
+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])
{
@@ -412,6 +469,11 @@ static int cdns_xspi_check_command_status(struct cdns_xspi_dev *cdns_xspi)
if (cmd_status & CDNS_XSPI_CMD_STATUS_COMPLETED) {
if ((cmd_status & CDNS_XSPI_CMD_STATUS_FAILED) != 0) {
+ if (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");
@@ -460,6 +522,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;
@@ -480,6 +575,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->driver_data->has_acmd)
+ cdns_xspi_acmd_init(cdns_xspi);
+
return 0;
}
@@ -560,6 +658,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)
@@ -569,12 +685,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->sdma_error = false;
@@ -629,6 +742,101 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
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);
+}
+
+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;
+
+ 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);
+
+ 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->driver_data->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)
@@ -638,6 +846,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));
}
@@ -710,6 +921,10 @@ 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);
+ /* The ACMD transfer length is a full 32-bit field, so no limit */
+ 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;
@@ -724,8 +939,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);
@@ -754,11 +970,27 @@ 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);
+
+ /* For ACMD, the cause of the error is in the command status */
+ if (irq_status & CDNS_XSPI_ACMD_THRD_0_MASK)
+ acmd_done = true;
+
+ result = IRQ_HANDLED;
+ }
+
+ if (acmd_done)
+ complete(&cdns_xspi->auto_cmd_complete);
+
return result;
}
@@ -1444,6 +1676,10 @@ static int cdns_xspi_resume(struct device *dev)
return ret;
}
+ cdns_xspi->cur_work_mode = CDNS_XSPI_WORK_MODE_DIRECT;
+ if (cdns_xspi->driver_data->has_acmd)
+ cdns_xspi_acmd_init(cdns_xspi);
+
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
return spi_controller_resume(cdns_xspi->host);
@@ -1463,6 +1699,7 @@ static const struct cdns_xspi_driver_data sc846_driver_data = {
.sdma_handler = cdns_xspi_sdma_handle,
.set_interrupts_handler = cdns_xspi_set_interrupts,
.hw_init = cdns_sc846_xspi_hw_init,
+ .has_acmd = true,
};
#ifdef CONFIG_64BIT
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets
2026-11-11 13:23 ` [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets Nuno Sá
@ 2026-11-11 17:57 ` Conor Dooley
0 siblings, 0 replies; 11+ messages in thread
From: Conor Dooley @ 2026-11-11 17:57 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-spi, linux-kernel, devicetree, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jisheng Zhang,
Fei Xie, Pratyush Yadav, Parshuram Thombare, Konrad Kociolek,
Mark Brown, Jayshri Pawar
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible
2026-11-11 13:23 ` [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible Nuno Sá
@ 2026-11-11 17:58 ` Conor Dooley
0 siblings, 0 replies; 11+ messages in thread
From: Conor Dooley @ 2026-11-11 17:58 UTC (permalink / raw)
To: Nuno Sá
Cc: linux-spi, linux-kernel, devicetree, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jisheng Zhang,
Fei Xie, Pratyush Yadav, Parshuram Thombare, Konrad Kociolek,
Mark Brown, Jayshri Pawar
[-- Attachment #1: Type: text/plain, Size: 1586 bytes --]
On Wed, Nov 11, 2026 at 01:23:07PM +0000, Nuno Sá wrote:
> The Analog Devices ADSP-SC846 SoC integrates the Cadence xSPI controller
> with its own PHY timing configuration and with the support needed for
> the auto command (ACMD) mode with the internal DMA, so it cannot be
> described by the generic "cdns,xspi-nor" compatible. Add a SoC specific
> compatible for it.
I assume it's the phy timing that means the cdns,xspi-nor compatible
cannot be used as a fallback?
>
> On this SoC, the controller clock and reset are always wired, so make
> them required.
>
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>
> ---
> Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> index 3cd7321319bb..d6ac05b2efb4 100644
> --- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> +++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> @@ -18,6 +18,7 @@ description: |
> properties:
> compatible:
> enum:
> + - adi,adsp-sc846-xspi
> - cdns,xspi-nor
> - marvell,cn10-xspi-nor
>
> @@ -53,6 +54,15 @@ required:
>
> allOf:
> - $ref: spi-controller.yaml#
> + - if:
> + properties:
> + compatible:
> + contains:
> + const: adi,adsp-sc846-xspi
> + then:
> + required:
> + - clocks
> + - resets
> - if:
> properties:
> compatible:
>
> --
> 2.56.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-11 17:58 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 13:23 [PATCH v2 0/8] spi: cadence-xspi: add ACMD mode and ADSP SC846 integration Nuno Sá
2026-11-11 13:23 ` [PATCH v2 1/8] spi: cadence-xspi: fail on command errors without a known cause Nuno Sá
2026-11-11 13:23 ` [PATCH v2 2/8] spi: cadence-xspi: wait for the controller initialization on resume Nuno Sá
2026-11-11 13:23 ` [PATCH v2 3/8] spi: dt-bindings: cdns,xspi: add clocks and resets Nuno Sá
2026-11-11 17:57 ` Conor Dooley
2026-11-11 13:23 ` [PATCH v2 4/8] spi: cadence-xspi: support " Nuno Sá
2026-11-11 13:23 ` [PATCH v2 5/8] spi: cadence-xspi: move platform specifics into driver data Nuno Sá
2026-11-11 13:23 ` [PATCH v2 6/8] spi: dt-bindings: cdns,xspi: add ADI SC846 compatible Nuno Sá
2026-11-11 17:58 ` Conor Dooley
2026-11-11 13:23 ` [PATCH v2 7/8] spi: cadence-xspi: add ADI SC846 platform support Nuno Sá
2026-11-11 13:23 ` [PATCH v2 8/8] spi: cadence-xspi: add ACMD mode support Nuno Sá
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®