* [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR
@ 2026-09-21 9:36 Fei Xie
2026-09-21 9:36 ` [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible Fei Xie
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Fei Xie @ 2026-09-21 9:36 UTC (permalink / raw)
To: Mark Brown
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel, fei.xie
From: "fei.xie" <fei.xie@horizon.auto>
The Cadence XSPI controller provides an automatic command (ACMD) engine.
It can execute complete flash transactions, including command sequences,
device-ready polling and master-DMA data transfers. Software submits a PIO
command containing the flash address, DMA address and transfer length.
For SPI NAND, for example, a read transaction combines PAGE READ, status
polling and READ CACHE in hardware. Program and erase transactions also
combine write enable, data or erase commands, and status polling. This
avoids submitting each command separately through STIG mode.
This RFC contains a working controller-local implementation for both SPI
NAND and SPI NOR. Unsupported operations continue to use STIG mode.
There is an important layering issue in this implementation. In order to
program the hardware sequences, the controller driver obtains upper-layer
driver data and directly uses struct spinand_device or struct spi_nor. It
also keeps state across individual spi_mem_exec_op() calls in order to
recognize a complete NAND transaction. We do not consider either property
desirable for a final implementation.
The hardware nevertheless needs information which is not available in one
spi_mem_op. A NAND read is represented by separate PAGE READ, status-poll
and READ CACHE operations, while the ACMD engine needs all three before the
transaction is started. NAND geometry is also needed to translate row and
column addresses for the ACMD address space.
We would appreciate guidance on the preferred interface boundary:
1. Is a controller-local implementation acceptable because these command
sequences are specific to the Cadence ACMD engine?
2. Should SPI mem instead provide an interface for submitting an ordered
group of operations, including status-poll semantics, with SPI NAND
and SPI NOR constructing the group and the controller translating it
into hardware sequences?
3. If the latter is preferred, should NAND geometry be supplied through
the sequence interface, or should it be exposed to controllers
separately?
We have prototyped the second approach locally by adding sequence callbacks
to drivers/spi/spi-mem.c and constructing the NAND transactions in
drivers/mtd/nand/spi/core.c. It removes the controller dependency on MTD
private structures, but the initial version duplicated page/OOB preparation
and made the generic sequence interface carry NAND-specific geometry. That
prototype is therefore not included here; we would first like agreement on
the interface.
Performance
===========
The SPI NAND path was tested on a Horizon Robotics J6B platform with a
GigaDevice GD5F4GM8RE device. A vendor Linux 6.1 backport of the same ACMD
implementation was used for the hardware tests.
Under the same 80 MHz transfer configuration, the sequential-read results
are:
STIG using readq: 8.24 MB/s (7.86 MiB/s)
ACMD PIO + MDMA: 10.08 MB/s (9.61 MiB/s), about 22% higher
STIG read CPU: 59.74% / 40.87%
ACMD read CPU: approximately 16%
Using the lower STIG CPU figure for a conservative comparison, ACMD reduced
read CPU usage by 24.87 percentage points, or approximately 61% relative.
The CPU reduction comes from moving device-ready polling and command
sequencing into the controller. Software no longer needs to submit and
wait for each STIG command while a page is being transferred.
The ACMD program path averaged 2.94 MiB/s over ten 128 KiB writes,
excluding erase time. A 128 KiB random-data erase/program/read test
produced identical SHA-256 hashes and byte-for-byte comparison. Read,
program and erase were also traced to confirm that the ACMD path was used.
The mainline series has been compile-tested for arm64. The SPI NAND
hardware tests were performed with the Linux 6.1 backport because the test
platform currently runs the vendor 6.1 kernel. SPI NOR has been
compile-tested but has not yet been tested on hardware.
This series is intended to start the API and layering discussion rather
than to propose the current upper-layer coupling as the final design.
fei.xie (4):
dt-bindings: spi: cdns,xspi: add SPI NAND compatible
spi: cadence-xspi: add ACMD support for SPI NAND
spi: cadence-xspi: factor out reusable ACMD helpers
spi: cadence-xspi: add ACMD support for SPI NOR
.../devicetree/bindings/spi/cdns,xspi.yaml | 3 +-
drivers/spi/spi-cadence-xspi.c | 1259 ++++++++++++++++-
2 files changed, 1260 insertions(+), 2 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible
2026-09-21 9:36 [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR Fei Xie
@ 2026-09-21 9:36 ` Fei Xie
2026-09-21 14:01 ` Mark Brown
2026-09-21 9:36 ` [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND Fei Xie
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Fei Xie @ 2026-09-21 9:36 UTC (permalink / raw)
To: Mark Brown
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel, fei.xie
From: "fei.xie" <fei.xie@horizon.auto>
Document the Cadence XSPI controller compatible used with SPI NAND.
Signed-off-by: fei.xie <fei.xie@horizon.auto>
---
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 38a5795589de2..3decf45d1b484 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -13,11 +13,12 @@ maintainers:
description: |
The XSPI controller allows SPI protocol communication in
single, dual, quad or octal wire transmission modes for
- read/write access to slaves such as SPI-NOR flash.
+ read/write access to slaves such as SPI-NOR and SPI-NAND flash.
properties:
compatible:
enum:
+ - cdns,xspi-nand
- cdns,xspi-nor
- marvell,cn10-xspi-nor
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND
2026-09-21 9:36 [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR Fei Xie
2026-09-21 9:36 ` [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible Fei Xie
@ 2026-09-21 9:36 ` Fei Xie
2026-09-21 14:51 ` Mark Brown
2026-09-21 9:37 ` [RFC PATCH 3/4] spi: cadence-xspi: factor out reusable ACMD helpers Fei Xie
2026-09-21 9:37 ` [RFC PATCH 4/4] spi: cadence-xspi: add ACMD support for SPI NOR Fei Xie
3 siblings, 1 reply; 7+ messages in thread
From: Fei Xie @ 2026-09-21 9:36 UTC (permalink / raw)
To: Mark Brown
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel, fei.xie
From: "fei.xie" <fei.xie@horizon.auto>
Add PIO master-DMA support for the controller auto-command mode.
Configure the SPI NAND read, program, erase, status and reset
sequences, retaining STIG for operations not consumed by ACMD.
Signed-off-by: fei.xie <fei.xie@horizon.auto>
---
drivers/spi/spi-cadence-xspi.c | 782 +++++++++++++++++++++++++++++++++
1 file changed, 782 insertions(+)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 39c868a5b171c..0fedf6cd6a590 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>
@@ -11,6 +12,8 @@
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mtd/spinand.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -54,7 +57,33 @@
#define CDNS_XSPI_CMD_REG_4 0x0010
#define CDNS_XSPI_CMD_REG_5 0x0014
+/* Auto command fields in command register 0 */
+#define CDNS_XSPI_ACMD_MODE GENMASK(31, 30)
+#define CDNS_XSPI_ACMD_TRD_NUM GENMASK(26, 24)
+#define CDNS_XSPI_ACMD_BANK_NUM GENMASK(22, 20)
+#define CDNS_XSPI_ACMD_DMA_SEL BIT(19)
+#define CDNS_XSPI_ACMD_INT_EN BIT(18)
+#define CDNS_XSPI_ACMD_CMD_TYPE GENMASK(15, 0)
+
+#define CDNS_XSPI_ACMD_READ_OP 0x2200
+#define CDNS_XSPI_ACMD_PROG_OP 0x2100
+#define CDNS_XSPI_ACMD_ERASE_OP 0x1000
+#define CDNS_XSPI_ACMD_RESET_OP 0x1100
+#define CDNS_XSPI_ACMD_DATA_THREAD 7
+#define CDNS_XSPI_ACMD_ERASE_THREAD 5
+#define CDNS_XSPI_ACMD_TIMEOUT_MS 1000
+#define CDNS_XSPI_ACMD_MODE_PIO 1
+
+#define CDNS_XSPI_NAND_OP_GET_FEATURE 0x0f
+#define CDNS_XSPI_NAND_OP_WRITE_ENABLE 0x06
+#define CDNS_XSPI_NAND_OP_PROGRAM_EXECUTE 0x10
+#define CDNS_XSPI_NAND_OP_PAGE_READ 0x13
+#define CDNS_XSPI_NAND_OP_BLOCK_ERASE 0xd8
+#define CDNS_XSPI_NAND_OP_RESET 0xff
+#define CDNS_XSPI_NAND_STATUS_REG 0xc0
+
/* Command status registers */
+#define CDNS_XSPI_CMD_STATUS_PTR_REG 0x0040
#define CDNS_XSPI_CMD_STATUS_REG 0x0044
/* Controller status register */
@@ -143,10 +172,126 @@
#define CDNS_XSPI_CMD_STATUS_CRC_ERROR BIT(2)
#define CDNS_XSPI_CMD_STATUS_BUS_ERROR BIT(1)
#define CDNS_XSPI_CMD_STATUS_INV_SEQ_ERROR BIT(0)
+/* Reset sequence config register */
+#define CDNS_XSPI_RST_SEQ_CFG_0 0x0400
+#define CDNS_XSPI_RST_SEQ_P1_CMD1_VAL GENMASK(15, 8)
+
+/* Erase sequence config registers */
+#define CDNS_XSPI_ERSS_SEQ_CFG_0 0x0410
+#define CDNS_XSPI_ERSS_SEQ_P1_CMD_VAL GENMASK(7, 0)
+#define CDNS_XSPI_ERSS_SEQ_P1_CMD_IOS GENMASK(9, 8)
+#define CDNS_XSPI_ERSS_SEQ_P1_CMD_EDGE BIT(11)
+#define CDNS_XSPI_ERSS_SEQ_P1_ADDR_CNT GENMASK(14, 12)
+#define CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_EN BIT(15)
+#define CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_VAL GENMASK(23, 16)
+#define CDNS_XSPI_ERSS_SEQ_P1_ADDR_IOS GENMASK(25, 24)
+#define CDNS_XSPI_ERSS_SEQ_P1_ADDR_EDGE BIT(28)
+
+#define CDNS_XSPI_ERSS_SEQ_CFG_1 0x0414
+#define CDNS_XSPI_ERSS_SEQ_P1_SECT_SIZE GENMASK(4, 0)
+
+#define CDNS_XSPI_ERSS_SEQ_CFG_2 0x0418
+
+/* Program sequence config registers */
+#define CDNS_XSPI_PROG_SEQ_CFG_0 0x0420
+#define CDNS_XSPI_PROG_SEQ_P1_CMD_VAL GENMASK(7, 0)
+#define CDNS_XSPI_PROG_SEQ_P1_CMD_IOS GENMASK(9, 8)
+#define CDNS_XSPI_PROG_SEQ_P1_CMD_EDGE BIT(11)
+#define CDNS_XSPI_PROG_SEQ_P1_ADDR_CNT GENMASK(14, 12)
+#define CDNS_XSPI_PROG_SEQ_P1_ADDR_IOS GENMASK(17, 16)
+#define CDNS_XSPI_PROG_SEQ_P1_ADDR_EDGE BIT(19)
+#define CDNS_XSPI_PROG_SEQ_P1_DATA_IOS GENMASK(21, 20)
+#define CDNS_XSPI_PROG_SEQ_P1_DATA_EDGE BIT(23)
+#define CDNS_XSPI_PROG_SEQ_P1_DUMMY_CNT GENMASK(29, 24)
+
+#define CDNS_XSPI_PROG_SEQ_CFG_1 0x0424
+#define CDNS_XSPI_PROG_SEQ_P1_CMD_EXT_EN BIT(0)
+#define CDNS_XSPI_PROG_SEQ_P1_CMD_EXT_VAL GENMASK(15, 8)
+
+#define CDNS_XSPI_PROG_SEQ_CFG_2 0x0428
+
+/* Read sequence config registers */
+#define CDNS_XSPI_READ_SEQ_CFG_0 0x0430
+#define CDNS_XSPI_READ_SEQ_P1_CMD_VAL GENMASK(7, 0)
+#define CDNS_XSPI_READ_SEQ_P1_CMD_IOS GENMASK(9, 8)
+#define CDNS_XSPI_READ_SEQ_P1_CMD_EDGE BIT(11)
+#define CDNS_XSPI_READ_SEQ_P1_ADDR_CNT GENMASK(14, 12)
+#define CDNS_XSPI_READ_SEQ_P1_ADDR_IOS GENMASK(17, 16)
+#define CDNS_XSPI_READ_SEQ_P1_ADDR_EDGE BIT(19)
+#define CDNS_XSPI_READ_SEQ_P1_DATA_IOS GENMASK(21, 20)
+#define CDNS_XSPI_READ_SEQ_P1_DATA_EDGE BIT(23)
+#define CDNS_XSPI_READ_SEQ_P1_DUMMY_CNT GENMASK(29, 24)
+
+#define CDNS_XSPI_READ_SEQ_CFG_1 0x0434
+#define CDNS_XSPI_READ_SEQ_P1_CMD_EXT_EN BIT(0)
+#define CDNS_XSPI_READ_SEQ_P1_CACHE_RANDOM_READ_EN BIT(4)
+#define CDNS_XSPI_READ_SEQ_CFG_2 0x0438
+
+/* Write enable sequence config register */
+#define CDNS_XSPI_WE_SEQ_CFG_0 0x0440
+#define CDNS_XSPI_WE_SEQ_P1_CMD_VAL GENMASK(7, 0)
+#define CDNS_XSPI_WE_SEQ_P1_CMD_IOS GENMASK(9, 8)
+#define CDNS_XSPI_WE_SEQ_P1_CMD_EDGE BIT(11)
+#define CDNS_XSPI_WE_SEQ_P1_EN BIT(24)
+
+/* Status sequence config registers */
+#define CDNS_XSPI_STAT_SEQ_CFG_0 0x0450
+#define CDNS_XSPI_STAT_SEQ_P1_ADDR_CNT GENMASK(9, 8)
+#define CDNS_XSPI_STAT_SEQ_CFG_1 0x0454
+#define CDNS_XSPI_P1_DEV_RDY_ADDR_EN BIT(6)
+#define CDNS_XSPI_P1_PROG_FAIL_ADDR_EN BIT(22)
+#define CDNS_XSPI_P1_ERS_FAIL_ADDR_EN BIT(30)
+
+#define CDNS_XSPI_STAT_SEQ_CFG_2 0x0458
+#define CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_CMD_VAL GENMASK(7, 0)
+#define CDNS_XSPI_STAT_SEQ_P1_ERS_FAIL_CMD_VAL GENMASK(15, 8)
+#define CDNS_XSPI_STAT_SEQ_P1_PROG_FAIL_CMD_VAL GENMASK(31, 24)
+
+#define CDNS_XSPI_STAT_SEQ_CFG_3 0x045c
+#define CDNS_XSPI_STAT_SEQ_CFG_4 0x0460
+#define CDNS_XSPI_STAT_SEQ_CFG_5 0x0464
+#define CDNS_XSPI_STAT_SEQ_DEV_RDY_IDX GENMASK(3, 0)
+#define CDNS_XSPI_STAT_SEQ_DEV_RDY_EN BIT(6)
+#define CDNS_XSPI_STAT_SEQ_ERS_FAIL_IDX GENMASK(11, 8)
+#define CDNS_XSPI_STAT_SEQ_ERS_FAIL_VAL BIT(12)
+#define CDNS_XSPI_STAT_SEQ_ERS_FAIL_EN BIT(14)
+#define CDNS_XSPI_STAT_SEQ_PROG_FAIL_IDX GENMASK(27, 24)
+#define CDNS_XSPI_STAT_SEQ_PROG_FAIL_VAL BIT(28)
+#define CDNS_XSPI_STAT_SEQ_PROG_FAIL_EN BIT(30)
+
+#define CDNS_XSPI_STAT_SEQ_CFG_7 0x046c
+#define CDNS_XSPI_STAT_SEQ_CFG_8 0x0470
+#define CDNS_XSPI_STAT_SEQ_CFG_9 0x0474
+
+#define CDNS_XSPI_STAT_SEQ_CFG_10 0x0478
+#define CDNS_XSPI_STAT_SEQ_ECC_FAIL_EN BIT(31)
+#define CDNS_XSPI_STAT_SEQ_CRDY_VAL BIT(27)
+#define CDNS_XSPI_STAT_SEQ_CRDY_IDX GENMASK(26, 24)
+#define CDNS_XSPI_STAT_SEQ_ECC_CORR_VAL GENMASK(23, 16)
+#define CDNS_XSPI_STAT_SEQ_ECC_FAIL_VAL GENMASK(15, 8)
+#define CDNS_XSPI_STAT_SEQ_ECC_FAIL_MASK GENMASK(7, 0)
#define CDNS_XSPI_STIG_DONE_FLAG BIT(0)
#define CDNS_XSPI_TRD_STATUS 0x0104
+#define CDNS_XSPI_FLASH_TYPE_NOR 0
+#define CDNS_XSPI_FLASH_TYPE_NAND 1
+
+#define CDNS_XSPI_GLOBAL_SEQ_CFG 0x0390
+#define CDNS_XSPI_SEQ_TYPE GENMASK(24, 23)
+#define CDNS_XSPI_SEQ_PAGE_SIZE_PGM GENMASK(7, 4)
+#define CDNS_XSPI_SEQ_PAGE_SIZE_RD GENMASK(3, 0)
+#define CDNS_XSPI_SEQ_SPI_NAND 3
+
+#define CDNS_XSPI_GLOBAL_SEQ_CFG_1 0x0394
+#define CDNS_XSPI_SEQ_PLANE_CNT GENMASK(29, 28)
+#define CDNS_XSPI_SEQ_PAGE_PER_BLOCK GENMASK(26, 24)
+#define CDNS_XSPI_SEQ_PAGE_CA_SIZE BIT(16)
+#define CDNS_XSPI_SEQ_PAGE_SIZE_EXT GENMASK(8, 0)
+
+#define CDNS_XSPI_XIP_MODE_CFG 0x0388
+#define CDNS_XSPI_XIP_EN BIT(0)
+
#define MODE_NO_OF_BYTES GENMASK(25, 24)
#define MODEBYTES_COUNT 1
@@ -305,6 +450,7 @@ enum cdns_xspi_stig_cmd_dir {
struct cdns_xspi_driver_data {
bool mrvl_hw_overlay;
+ bool use_acmd;
u32 dll_phy_ctrl;
u32 ctb_rfile_phy_ctrl;
u32 rfile_phy_tsel;
@@ -313,12 +459,26 @@ struct cdns_xspi_driver_data {
u32 rfile_phy_gate_lpbk_ctrl;
u32 rfile_phy_dll_master_ctrl;
u32 rfile_phy_dll_slave_ctrl;
+ u8 flash_type;
};
static struct cdns_xspi_driver_data cdns_driver_data = {
.mrvl_hw_overlay = false,
};
+static struct cdns_xspi_driver_data cdns_nand_driver_data = {
+ .mrvl_hw_overlay = false,
+ .use_acmd = true,
+ .flash_type = CDNS_XSPI_FLASH_TYPE_NAND,
+};
+
+struct cdns_xspi_acmd_info {
+ u64 row_addr;
+ u64 column_addr;
+ size_t data_nbytes;
+ bool row_addr_valid;
+ bool initialized;
+};
struct cdns_xspi_dev {
struct platform_device *pdev;
struct spi_controller *host;
@@ -351,6 +511,13 @@ struct cdns_xspi_dev {
bool xfer_in_progress;
int current_xfer_qword;
+ u32 work_mode;
+ u8 flash_type;
+
+ struct cdns_xspi_acmd_info acmd_info;
+ void *dma_buf;
+ dma_addr_t dma_addr;
+ u32 dma_buf_len;
};
static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
@@ -425,6 +592,268 @@ 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_nand_cfg_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand)
+{
+ u32 seq_cfg, seq_cfg1;
+
+ seq_cfg = readl(cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG);
+ seq_cfg1 = readl(cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG_1);
+ seq_cfg = u32_replace_bits(seq_cfg, CDNS_XSPI_SEQ_SPI_NAND,
+ CDNS_XSPI_SEQ_TYPE);
+ seq_cfg = u32_replace_bits(seq_cfg,
+ ilog2(spinand->base.memorg.pagesize),
+ CDNS_XSPI_SEQ_PAGE_SIZE_PGM);
+ seq_cfg = u32_replace_bits(seq_cfg,
+ ilog2(spinand->base.memorg.pagesize),
+ CDNS_XSPI_SEQ_PAGE_SIZE_RD);
+ seq_cfg1 = u32_replace_bits(seq_cfg1,
+ ilog2(spinand->base.memorg.planes_per_lun),
+ CDNS_XSPI_SEQ_PLANE_CNT);
+ seq_cfg1 = u32_replace_bits(seq_cfg1,
+ ilog2(spinand->base.memorg.pages_per_eraseblock),
+ CDNS_XSPI_SEQ_PAGE_PER_BLOCK);
+ seq_cfg1 = u32_replace_bits(seq_cfg1,
+ !!(spinand->base.memorg.pagesize & BIT(12)),
+ CDNS_XSPI_SEQ_PAGE_CA_SIZE);
+ seq_cfg1 = u32_replace_bits(seq_cfg1, spinand->base.memorg.oobsize,
+ CDNS_XSPI_SEQ_PAGE_SIZE_EXT);
+
+ writel(seq_cfg, cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG);
+ writel(seq_cfg1, cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG_1);
+}
+
+static void cdns_xspi_nand_read_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand)
+{
+ const struct spi_mem_op *op = spinand->op_templates->read_cache;
+ u32 dummy_cycles = 0;
+ u32 read_seq_cfg0;
+ u32 read_seq_cfg1;
+
+ if (op->dummy.nbytes && op->dummy.buswidth)
+ dummy_cycles = op->dummy.nbytes * BITS_PER_BYTE /
+ (op->dummy.buswidth * (op->dummy.dtr + 1));
+
+ read_seq_cfg0 =
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_VAL, op->cmd.opcode) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_CNT,
+ op->addr.nbytes) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_IOS,
+ ilog2(op->addr.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_EDGE, op->addr.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DATA_IOS,
+ ilog2(op->data.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DATA_EDGE, op->data.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DUMMY_CNT, dummy_cycles);
+ read_seq_cfg1 = FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes > 1) |
+ CDNS_XSPI_READ_SEQ_P1_CACHE_RANDOM_READ_EN;
+
+ writel(read_seq_cfg0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_0);
+ writel(read_seq_cfg1, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_2);
+
+ dev_dbg(cdns_xspi->dev,
+ "ACMD read: op=%02x %u-%u-%u%s addr=%u dummy=%u cfg=%08x/%08x\n",
+ op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
+ op->data.buswidth, op->data.dtr ? " DTR" : " SDR",
+ op->addr.nbytes, dummy_cycles, read_seq_cfg0, read_seq_cfg1);
+}
+
+static void cdns_xspi_nand_write_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand)
+{
+ const struct spi_mem_op *op = spinand->op_templates->write_cache;
+ u32 dummy_cycles = 0;
+ u32 write_seq_cfg0;
+ u32 write_seq_cfg1;
+
+ if (op->dummy.nbytes && op->dummy.buswidth)
+ dummy_cycles = op->dummy.nbytes * BITS_PER_BYTE /
+ (op->dummy.buswidth * (op->dummy.dtr + 1));
+
+ write_seq_cfg0 =
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_VAL, op->cmd.opcode) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_CNT,
+ op->addr.nbytes) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_IOS,
+ ilog2(op->addr.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_EDGE, op->addr.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DATA_IOS,
+ ilog2(op->data.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DATA_EDGE, op->data.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DUMMY_CNT, dummy_cycles);
+ write_seq_cfg1 = FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes > 1);
+
+ writel(write_seq_cfg0, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_0);
+ writel(write_seq_cfg1, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_2);
+
+ dev_dbg(cdns_xspi->dev,
+ "ACMD program: op=%02x %u-%u-%u%s addr=%u dummy=%u cfg=%08x/%08x\n",
+ op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
+ op->data.buswidth, op->data.dtr ? " DTR" : " SDR",
+ op->addr.nbytes, dummy_cycles, write_seq_cfg0, write_seq_cfg1);
+}
+
+static void cdns_xspi_nand_erase_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand)
+{
+ u32 erase_seq_cfg0;
+ u32 erase_seq_cfg1;
+
+ /* SPI-NAND block erase is always D8h with a 3-byte row address. */
+ erase_seq_cfg0 =
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_VAL, 0xd8) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_IOS, 0) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EDGE, 0) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_CNT, 3) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_EN, 0) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_VAL, 0) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_IOS, 0) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_EDGE, 0);
+ erase_seq_cfg1 = FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_SECT_SIZE,
+ ilog2(nanddev_eraseblock_size(&spinand->base)));
+
+ writel(erase_seq_cfg0,
+ cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_0);
+ writel(erase_seq_cfg1,
+ cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_2);
+
+ dev_dbg(cdns_xspi->dev,
+ "ACMD erase sequence: cfg0=%08x cfg1=%08x\n",
+ erase_seq_cfg0, erase_seq_cfg1);
+}
+
+static void cdns_xspi_nand_status_seq_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 stat_seq_cfg1;
+ u32 stat_seq_cfg2;
+ u32 stat_seq_cfg5;
+
+ writel(FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_ADDR_CNT, 0),
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_0);
+
+ stat_seq_cfg1 = CDNS_XSPI_P1_DEV_RDY_ADDR_EN |
+ CDNS_XSPI_P1_PROG_FAIL_ADDR_EN |
+ CDNS_XSPI_P1_ERS_FAIL_ADDR_EN;
+ writel(stat_seq_cfg1, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_1);
+
+ stat_seq_cfg2 = FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_CMD_VAL,
+ CDNS_XSPI_NAND_OP_GET_FEATURE) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_PROG_FAIL_CMD_VAL,
+ CDNS_XSPI_NAND_OP_GET_FEATURE) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_ERS_FAIL_CMD_VAL,
+ CDNS_XSPI_NAND_OP_GET_FEATURE);
+ writel(stat_seq_cfg2, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_2);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_3);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_4);
+
+ stat_seq_cfg5 =
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_DEV_RDY_IDX,
+ __ffs(STATUS_BUSY)) |
+ CDNS_XSPI_STAT_SEQ_DEV_RDY_EN |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_ERS_FAIL_IDX,
+ __ffs(STATUS_ERASE_FAILED)) |
+ CDNS_XSPI_STAT_SEQ_ERS_FAIL_VAL |
+ CDNS_XSPI_STAT_SEQ_ERS_FAIL_EN |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_PROG_FAIL_IDX,
+ __ffs(STATUS_PROG_FAILED)) |
+ CDNS_XSPI_STAT_SEQ_PROG_FAIL_VAL |
+ CDNS_XSPI_STAT_SEQ_PROG_FAIL_EN;
+ writel(stat_seq_cfg5, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_5);
+ writel(CDNS_XSPI_NAND_STATUS_REG,
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_7);
+ writel(CDNS_XSPI_NAND_STATUS_REG,
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_8);
+ writel(CDNS_XSPI_NAND_STATUS_REG,
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_9);
+
+ writel(CDNS_XSPI_STAT_SEQ_ECC_FAIL_EN |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_CRDY_IDX, __ffs(STATUS_BUSY)) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_CRDY_VAL, 0) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_ECC_CORR_VAL,
+ STATUS_ECC_HAS_BITFLIPS) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_ECC_FAIL_VAL,
+ STATUS_ECC_UNCOR_ERROR) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_ECC_FAIL_MASK,
+ STATUS_ECC_MASK),
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_10);
+}
+
+static void cdns_xspi_nand_write_enable_seq_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 cfg;
+
+ cfg = readl(cdns_xspi->iobase + CDNS_XSPI_WE_SEQ_CFG_0);
+ cfg = u32_replace_bits(cfg, 1, CDNS_XSPI_WE_SEQ_P1_EN);
+ cfg = u32_replace_bits(cfg, CDNS_XSPI_NAND_OP_WRITE_ENABLE,
+ CDNS_XSPI_WE_SEQ_P1_CMD_VAL);
+ cfg = u32_replace_bits(cfg, 0, CDNS_XSPI_WE_SEQ_P1_CMD_IOS);
+ cfg = u32_replace_bits(cfg, 0, CDNS_XSPI_WE_SEQ_P1_CMD_EDGE);
+ writel(cfg, cdns_xspi->iobase + CDNS_XSPI_WE_SEQ_CFG_0);
+}
+
+static void cdns_xspi_set_mode_acmd(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 reg_val;
+
+ reg_val = readl(cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
+ reg_val = u32_replace_bits(reg_val, CDNS_XSPI_WORK_MODE_ACMD,
+ CDNS_XSPI_CTRL_WORK_MODE);
+ writel(reg_val, cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
+}
+
+static void cdns_xspi_nand_reset_seq_init(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 cfg;
+
+ cfg = FIELD_PREP(CDNS_XSPI_RST_SEQ_P1_CMD1_VAL,
+ CDNS_XSPI_NAND_OP_RESET);
+ writel(cfg, cdns_xspi->iobase + CDNS_XSPI_RST_SEQ_CFG_0);
+}
+
+static int cdns_xspi_nand_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand)
+{
+ u32 reg_val;
+
+ cdns_xspi_nand_cfg_seq_init(cdns_xspi, spinand);
+ cdns_xspi_nand_read_seq_init(cdns_xspi, spinand);
+ cdns_xspi_nand_reset_seq_init(cdns_xspi);
+ cdns_xspi_nand_write_seq_init(cdns_xspi, spinand);
+ cdns_xspi_nand_write_enable_seq_init(cdns_xspi);
+ cdns_xspi_nand_status_seq_init(cdns_xspi);
+ cdns_xspi_nand_erase_seq_init(cdns_xspi, spinand);
+
+ reg_val = readl(cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
+ if (reg_val & CDNS_XSPI_XIP_EN) {
+ reg_val &= ~CDNS_XSPI_XIP_EN;
+ writel(reg_val, cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
+ }
+ cdns_xspi->dma_buf_len = spinand->base.memorg.pagesize +
+ spinand->base.memorg.oobsize;
+ cdns_xspi->dma_buf = dmam_alloc_coherent(cdns_xspi->dev,
+ cdns_xspi->dma_buf_len,
+ &cdns_xspi->dma_addr,
+ GFP_KERNEL);
+ if (!cdns_xspi->dma_buf)
+ return -ENOMEM;
+
+ cdns_xspi->acmd_info.initialized = true;
+
+ return 0;
+}
+
static int cdns_xspi_controller_init(struct cdns_xspi_dev *cdns_xspi)
{
u32 ctrl_ver;
@@ -580,6 +1009,349 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
return 0;
}
+static int cdns_xspi_acmd_get_thread_status(struct cdns_xspi_dev *cdns_xspi,
+ u32 thread)
+{
+ writel(thread, cdns_xspi->iobase + CDNS_XSPI_CMD_STATUS_PTR_REG);
+
+ return cdns_xspi_check_command_status(cdns_xspi);
+}
+
+static int cdns_xspi_acmd_run(struct cdns_xspi_dev *cdns_xspi, u32 cmd_regs[6],
+ u32 thread)
+{
+ unsigned long timeout;
+ int ret;
+
+ cdns_xspi_set_mode_acmd(cdns_xspi);
+ reinit_completion(&cdns_xspi->auto_cmd_complete);
+ cdns_xspi_set_interrupts(cdns_xspi, true);
+ cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
+
+ timeout = msecs_to_jiffies(CDNS_XSPI_ACMD_TIMEOUT_MS);
+ if (!wait_for_completion_timeout(&cdns_xspi->auto_cmd_complete,
+ timeout)) {
+ dev_err(cdns_xspi->dev, "ACMD command timed out\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = cdns_xspi_acmd_get_thread_status(cdns_xspi, thread);
+ }
+
+ cdns_xspi_set_interrupts(cdns_xspi, false);
+
+ return ret;
+}
+
+static int cdns_xspi_nand_addr(struct spinand_device *spinand, u64 row,
+ u64 column, u64 *xspi_addr)
+{
+ const struct nand_memory_organization *memorg = &spinand->base.memorg;
+ u64 column_limit, page_stride;
+
+ page_stride = roundup_pow_of_two(memorg->pagesize + memorg->oobsize);
+ if (check_mul_overflow(page_stride, memorg->planes_per_lun,
+ &column_limit) ||
+ column >= column_limit ||
+ check_mul_overflow(row, page_stride, xspi_addr) ||
+ check_add_overflow(*xspi_addr, column, xspi_addr))
+ return -ERANGE;
+
+ return 0;
+}
+
+static u32 cdns_xspi_acmd_cmd(struct cdns_xspi_dev *cdns_xspi, u32 type,
+ u32 thread, bool use_dma)
+{
+ return FIELD_PREP(CDNS_XSPI_ACMD_MODE, CDNS_XSPI_ACMD_MODE_PIO) |
+ FIELD_PREP(CDNS_XSPI_ACMD_TRD_NUM, thread) |
+ FIELD_PREP(CDNS_XSPI_ACMD_BANK_NUM, cdns_xspi->cur_cs) |
+ (use_dma ? CDNS_XSPI_ACMD_DMA_SEL : 0) |
+ CDNS_XSPI_ACMD_INT_EN |
+ FIELD_PREP(CDNS_XSPI_ACMD_CMD_TYPE, type);
+}
+
+static int cdns_xspi_pio_mdma_erase(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand,
+ const struct spi_mem_op *op)
+{
+ u32 cmd_regs[6] = { 0 };
+ u64 xspi_addr;
+ int ret;
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "controller did not become idle before erase\n");
+ return ret;
+ }
+
+ ret = cdns_xspi_nand_addr(spinand, op->addr.val, 0, &xspi_addr);
+ if (ret)
+ return ret;
+
+ if (upper_32_bits(xspi_addr)) {
+ dev_err(cdns_xspi->dev,
+ "erase address 0x%llx exceeds the ACMD PIO range\n",
+ xspi_addr);
+ return -ERANGE;
+ }
+
+ cmd_regs[1] = lower_32_bits(xspi_addr);
+ cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
+ CDNS_XSPI_ACMD_ERASE_OP,
+ CDNS_XSPI_ACMD_ERASE_THREAD, false);
+
+ ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
+ CDNS_XSPI_ACMD_ERASE_THREAD);
+ if (ret)
+ dev_err(cdns_xspi->dev, "ACMD erase failed: %d\n", ret);
+
+ return ret;
+}
+
+static int cdns_xspi_pio_reset(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 cmd_regs[6] = { 0 };
+ int ret;
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "controller did not become idle before reset\n");
+ return ret;
+ }
+ cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
+ CDNS_XSPI_ACMD_RESET_OP,
+ CDNS_XSPI_ACMD_DATA_THREAD, false);
+ ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
+ CDNS_XSPI_ACMD_DATA_THREAD);
+ if (ret)
+ return ret;
+
+ cdns_xspi->acmd_info.row_addr_valid = false;
+ cdns_xspi->acmd_info.column_addr = 0;
+ cdns_xspi->acmd_info.data_nbytes = 0;
+ cdns_xspi->out_buffer = NULL;
+
+ return 0;
+}
+
+static int cdns_xspi_pio_mdma_program(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand,
+ const struct spi_mem_op *op)
+{
+ u32 cmd_regs[6] = { 0 };
+ u64 xspi_addr;
+ int ret;
+
+ if (!cdns_xspi->out_buffer || !cdns_xspi->acmd_info.data_nbytes) {
+ dev_err(cdns_xspi->dev, "missing program-load data\n");
+ ret = -EINVAL;
+ goto out_clear_program_state;
+ }
+
+ if (cdns_xspi->acmd_info.data_nbytes > cdns_xspi->dma_buf_len) {
+ ret = -EMSGSIZE;
+ goto out_clear_program_state;
+ }
+
+ memcpy(cdns_xspi->dma_buf, cdns_xspi->out_buffer,
+ cdns_xspi->acmd_info.data_nbytes);
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "controller did not become idle before program\n");
+ goto out_clear_program_state;
+ }
+
+ ret = cdns_xspi_nand_addr(spinand, op->addr.val,
+ cdns_xspi->acmd_info.column_addr,
+ &xspi_addr);
+ if (ret)
+ goto out_clear_program_state;
+
+ if (upper_32_bits(xspi_addr)) {
+ dev_err(cdns_xspi->dev,
+ "program address 0x%llx exceeds the ACMD PIO range\n",
+ xspi_addr);
+ ret = -ERANGE;
+ goto out_clear_program_state;
+ }
+
+ cmd_regs[1] = lower_32_bits(xspi_addr);
+ cmd_regs[2] = lower_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[3] = upper_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[4] = cdns_xspi->acmd_info.data_nbytes - 1;
+ cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
+ CDNS_XSPI_ACMD_PROG_OP,
+ CDNS_XSPI_ACMD_DATA_THREAD, true);
+
+ ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
+ CDNS_XSPI_ACMD_DATA_THREAD);
+
+out_clear_program_state:
+ cdns_xspi->acmd_info.column_addr = 0;
+ cdns_xspi->acmd_info.data_nbytes = 0;
+ cdns_xspi->out_buffer = NULL;
+ return ret;
+}
+
+static int cdns_xspi_pio_mdma_read(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand,
+ const struct spi_mem_op *op)
+{
+ u32 cmd_regs[6] = { 0 };
+ u64 xspi_addr;
+ int ret;
+
+ if (!cdns_xspi->acmd_info.row_addr_valid) {
+ dev_err(cdns_xspi->dev,
+ "read cache command without a pending page read\n");
+ return -EINVAL;
+ }
+
+ if (op->data.dir != SPI_MEM_DATA_IN || !op->data.nbytes ||
+ !op->data.buf.in) {
+ ret = -EINVAL;
+ goto out_clear_read_state;
+ }
+
+ if (op->data.nbytes > cdns_xspi->dma_buf_len) {
+ ret = -EMSGSIZE;
+ goto out_clear_read_state;
+ }
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "controller did not become idle before read\n");
+ goto out_clear_read_state;
+ }
+
+ ret = cdns_xspi_nand_addr(spinand,
+ cdns_xspi->acmd_info.row_addr,
+ op->addr.val, &xspi_addr);
+ if (ret)
+ goto out_clear_read_state;
+
+ if (upper_32_bits(xspi_addr)) {
+ dev_err(cdns_xspi->dev,
+ "read address 0x%llx exceeds the ACMD PIO range\n",
+ xspi_addr);
+ ret = -ERANGE;
+ goto out_clear_read_state;
+ }
+
+ cmd_regs[1] = lower_32_bits(xspi_addr);
+ cmd_regs[2] = lower_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[3] = upper_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[4] = op->data.nbytes - 1;
+ cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
+ CDNS_XSPI_ACMD_READ_OP,
+ CDNS_XSPI_ACMD_DATA_THREAD, true);
+
+ ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
+ CDNS_XSPI_ACMD_DATA_THREAD);
+ if (ret) {
+ dev_err(cdns_xspi->dev, "ACMD read failed: %d\n", ret);
+ goto out_clear_read_state;
+ }
+
+ memcpy(op->data.buf.in, cdns_xspi->dma_buf, op->data.nbytes);
+
+out_clear_read_state:
+ cdns_xspi->acmd_info.row_addr_valid = false;
+ cdns_xspi->acmd_info.row_addr = 0;
+ return ret;
+}
+
+static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ struct spinand_device *spinand;
+ const struct spi_mem_op *read_cache;
+ const struct spi_mem_op *write_cache;
+ const struct spi_mem_op *update_cache;
+ int ret;
+
+ if (cdns_xspi->flash_type != CDNS_XSPI_FLASH_TYPE_NAND)
+ goto use_stig;
+
+ spinand = spi_mem_get_drvdata(mem);
+ if (!spinand || !spinand->op_templates ||
+ !spinand->op_templates->read_cache ||
+ !spinand->op_templates->write_cache ||
+ !spinand->op_templates->update_cache)
+ goto use_stig;
+
+ if (!cdns_xspi->acmd_info.initialized) {
+ ret = cdns_xspi_nand_init(cdns_xspi, spinand);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "failed to initialize ACMD: %d\n", ret);
+ return ret;
+ }
+ }
+
+ read_cache = spinand->op_templates->read_cache;
+ write_cache = spinand->op_templates->write_cache;
+ update_cache = spinand->op_templates->update_cache;
+
+ if ((write_cache && op->cmd.opcode == write_cache->cmd.opcode) ||
+ (update_cache && op->cmd.opcode == update_cache->cmd.opcode)) {
+ if (op->data.dir != SPI_MEM_DATA_OUT || !op->data.nbytes ||
+ !op->data.buf.out)
+ return -EINVAL;
+
+ cdns_xspi_nand_write_enable_seq_init(cdns_xspi);
+ cdns_xspi->out_buffer = op->data.buf.out;
+ cdns_xspi->acmd_info.column_addr = op->addr.val;
+ cdns_xspi->acmd_info.data_nbytes = op->data.nbytes;
+ return 0;
+ }
+
+ switch (op->cmd.opcode) {
+ case CDNS_XSPI_NAND_OP_PAGE_READ:
+ cdns_xspi->acmd_info.row_addr = op->addr.val;
+ cdns_xspi->acmd_info.row_addr_valid = true;
+ return 0;
+
+ case CDNS_XSPI_NAND_OP_GET_FEATURE:
+ if (op->addr.val != CDNS_XSPI_NAND_STATUS_REG ||
+ !cdns_xspi->acmd_info.row_addr_valid)
+ break;
+
+ if (op->data.dir != SPI_MEM_DATA_IN || !op->data.nbytes ||
+ !op->data.buf.in)
+ return -EINVAL;
+
+ memset(op->data.buf.in, 0, op->data.nbytes);
+ return 0;
+
+ case CDNS_XSPI_NAND_OP_RESET:
+ return cdns_xspi_pio_reset(cdns_xspi);
+
+ case CDNS_XSPI_NAND_OP_BLOCK_ERASE:
+ return cdns_xspi_pio_mdma_erase(cdns_xspi, spinand, op);
+
+ case CDNS_XSPI_NAND_OP_PROGRAM_EXECUTE:
+ return cdns_xspi_pio_mdma_program(cdns_xspi, spinand, op);
+
+ default:
+ break;
+ }
+
+ if (read_cache && op->cmd.opcode == read_cache->cmd.opcode)
+ return cdns_xspi_pio_mdma_read(cdns_xspi, spinand, op);
+
+use_stig:
+ /* ACMD does not consume this operation; execute it through STIG. */
+ return cdns_xspi_send_stig_command(cdns_xspi, op,
+ op->data.dir != SPI_MEM_NO_DATA);
+}
+
static int cdns_xspi_mem_op(struct cdns_xspi_dev *cdns_xspi,
struct spi_mem *mem,
const struct spi_mem_op *op)
@@ -589,6 +1361,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->work_mode == CDNS_XSPI_WORK_MODE_ACMD)
+ return cdns_xspi_send_pio_command(cdns_xspi, mem, op);
+
return cdns_xspi_send_stig_command(cdns_xspi, op,
(dir != SPI_MEM_NO_DATA));
}
@@ -1217,6 +1992,9 @@ static int cdns_xspi_probe(struct platform_device *pdev)
cdns_xspi->host = host;
cdns_xspi->dev = &pdev->dev;
cdns_xspi->cur_cs = 0;
+ cdns_xspi->flash_type = cdns_xspi->driver_data->flash_type;
+ cdns_xspi->work_mode = cdns_xspi->driver_data->use_acmd ?
+ CDNS_XSPI_WORK_MODE_ACMD : CDNS_XSPI_WORK_MODE_STIG;
init_completion(&cdns_xspi->cmd_complete);
init_completion(&cdns_xspi->auto_cmd_complete);
@@ -1344,6 +2122,10 @@ static const struct of_device_id cdns_xspi_of_match[] = {
.data = &marvell_driver_data,
},
#endif
+ {
+ .compatible = "cdns,xspi-nand",
+ .data = &cdns_nand_driver_data,
+ },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, cdns_xspi_of_match);
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 3/4] spi: cadence-xspi: factor out reusable ACMD helpers
2026-09-21 9:36 [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR Fei Xie
2026-09-21 9:36 ` [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible Fei Xie
2026-09-21 9:36 ` [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND Fei Xie
@ 2026-09-21 9:37 ` Fei Xie
2026-09-21 9:37 ` [RFC PATCH 4/4] spi: cadence-xspi: add ACMD support for SPI NOR Fei Xie
3 siblings, 0 replies; 7+ messages in thread
From: Fei Xie @ 2026-09-21 9:37 UTC (permalink / raw)
To: Mark Brown
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel, fei.xie
From: "fei.xie" <fei.xie@horizon.auto>
Move DMA buffer allocation, XIP setup and PIO command execution into
common helpers.
Keep SPI NAND address conversion and operation state handling in
NAND-specific wrappers. This is a behavior-neutral preparation for
sharing the ACMD PIO and MDMA path with other flash types.
Signed-off-by: fei.xie <fei.xie@horizon.auto>
---
drivers/spi/spi-cadence-xspi.c | 223 ++++++++++++++++++++-------------
1 file changed, 136 insertions(+), 87 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 0fedf6cd6a590..114c1e0fd79fb 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -822,10 +822,35 @@ static void cdns_xspi_nand_reset_seq_init(struct cdns_xspi_dev *cdns_xspi)
writel(cfg, cdns_xspi->iobase + CDNS_XSPI_RST_SEQ_CFG_0);
}
+static int cdns_xspi_acmd_alloc_dma_buf(struct cdns_xspi_dev *cdns_xspi,
+ size_t len)
+{
+ cdns_xspi->dma_buf = dmam_alloc_coherent(cdns_xspi->dev, len,
+ &cdns_xspi->dma_addr,
+ GFP_KERNEL);
+ if (!cdns_xspi->dma_buf)
+ return -ENOMEM;
+
+ cdns_xspi->dma_buf_len = len;
+
+ return 0;
+}
+
+static void cdns_xspi_acmd_disable_xip(struct cdns_xspi_dev *cdns_xspi)
+{
+ u32 reg_val;
+
+ reg_val = readl(cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
+ if (reg_val & CDNS_XSPI_XIP_EN) {
+ reg_val &= ~CDNS_XSPI_XIP_EN;
+ writel(reg_val, cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
+ }
+}
+
static int cdns_xspi_nand_init(struct cdns_xspi_dev *cdns_xspi,
struct spinand_device *spinand)
{
- u32 reg_val;
+ int ret;
cdns_xspi_nand_cfg_seq_init(cdns_xspi, spinand);
cdns_xspi_nand_read_seq_init(cdns_xspi, spinand);
@@ -835,19 +860,12 @@ static int cdns_xspi_nand_init(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_nand_status_seq_init(cdns_xspi);
cdns_xspi_nand_erase_seq_init(cdns_xspi, spinand);
- reg_val = readl(cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
- if (reg_val & CDNS_XSPI_XIP_EN) {
- reg_val &= ~CDNS_XSPI_XIP_EN;
- writel(reg_val, cdns_xspi->iobase + CDNS_XSPI_XIP_MODE_CFG);
- }
- cdns_xspi->dma_buf_len = spinand->base.memorg.pagesize +
- spinand->base.memorg.oobsize;
- cdns_xspi->dma_buf = dmam_alloc_coherent(cdns_xspi->dev,
- cdns_xspi->dma_buf_len,
- &cdns_xspi->dma_addr,
- GFP_KERNEL);
- if (!cdns_xspi->dma_buf)
- return -ENOMEM;
+ cdns_xspi_acmd_disable_xip(cdns_xspi);
+ ret = cdns_xspi_acmd_alloc_dma_buf(cdns_xspi,
+ spinand->base.memorg.pagesize +
+ spinand->base.memorg.oobsize);
+ if (ret)
+ return ret;
cdns_xspi->acmd_info.initialized = true;
@@ -1070,12 +1088,10 @@ static u32 cdns_xspi_acmd_cmd(struct cdns_xspi_dev *cdns_xspi, u32 type,
FIELD_PREP(CDNS_XSPI_ACMD_CMD_TYPE, type);
}
-static int cdns_xspi_pio_mdma_erase(struct cdns_xspi_dev *cdns_xspi,
- struct spinand_device *spinand,
- const struct spi_mem_op *op)
+static int cdns_xspi_pio_erase(struct cdns_xspi_dev *cdns_xspi,
+ u64 xspi_addr)
{
u32 cmd_regs[6] = { 0 };
- u64 xspi_addr;
int ret;
ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
@@ -1085,10 +1101,6 @@ static int cdns_xspi_pio_mdma_erase(struct cdns_xspi_dev *cdns_xspi,
return ret;
}
- ret = cdns_xspi_nand_addr(spinand, op->addr.val, 0, &xspi_addr);
- if (ret)
- return ret;
-
if (upper_32_bits(xspi_addr)) {
dev_err(cdns_xspi->dev,
"erase address 0x%llx exceeds the ACMD PIO range\n",
@@ -1137,71 +1149,141 @@ static int cdns_xspi_pio_reset(struct cdns_xspi_dev *cdns_xspi)
}
static int cdns_xspi_pio_mdma_program(struct cdns_xspi_dev *cdns_xspi,
- struct spinand_device *spinand,
- const struct spi_mem_op *op)
+ u64 xspi_addr, const void *buf,
+ size_t nbytes)
{
u32 cmd_regs[6] = { 0 };
- u64 xspi_addr;
int ret;
- if (!cdns_xspi->out_buffer || !cdns_xspi->acmd_info.data_nbytes) {
- dev_err(cdns_xspi->dev, "missing program-load data\n");
- ret = -EINVAL;
- goto out_clear_program_state;
- }
+ if (!buf || !nbytes)
+ return -EINVAL;
- if (cdns_xspi->acmd_info.data_nbytes > cdns_xspi->dma_buf_len) {
- ret = -EMSGSIZE;
- goto out_clear_program_state;
+ if (nbytes > cdns_xspi->dma_buf_len)
+ return -EMSGSIZE;
+
+ if (upper_32_bits(xspi_addr)) {
+ dev_err(cdns_xspi->dev,
+ "program address 0x%llx exceeds the ACMD PIO range\n",
+ xspi_addr);
+ return -ERANGE;
}
- memcpy(cdns_xspi->dma_buf, cdns_xspi->out_buffer,
- cdns_xspi->acmd_info.data_nbytes);
+ memcpy(cdns_xspi->dma_buf, buf, nbytes);
ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
if (ret) {
dev_err(cdns_xspi->dev,
"controller did not become idle before program\n");
- goto out_clear_program_state;
+ return ret;
}
- ret = cdns_xspi_nand_addr(spinand, op->addr.val,
- cdns_xspi->acmd_info.column_addr,
- &xspi_addr);
- if (ret)
- goto out_clear_program_state;
+ cmd_regs[1] = lower_32_bits(xspi_addr);
+ cmd_regs[2] = lower_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[3] = upper_32_bits(cdns_xspi->dma_addr);
+ cmd_regs[4] = nbytes - 1;
+ cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
+ CDNS_XSPI_ACMD_PROG_OP,
+ CDNS_XSPI_ACMD_DATA_THREAD, true);
+
+ return cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
+ CDNS_XSPI_ACMD_DATA_THREAD);
+}
+
+static int cdns_xspi_pio_mdma_read(struct cdns_xspi_dev *cdns_xspi,
+ u64 xspi_addr, void *buf, size_t nbytes)
+{
+ u32 cmd_regs[6] = { 0 };
+ int ret;
+
+ if (!buf || !nbytes)
+ return -EINVAL;
+
+ if (nbytes > cdns_xspi->dma_buf_len)
+ return -EMSGSIZE;
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "controller did not become idle before read\n");
+ return ret;
+ }
if (upper_32_bits(xspi_addr)) {
dev_err(cdns_xspi->dev,
- "program address 0x%llx exceeds the ACMD PIO range\n",
+ "read address 0x%llx exceeds the ACMD PIO range\n",
xspi_addr);
- ret = -ERANGE;
- goto out_clear_program_state;
+ return -ERANGE;
}
cmd_regs[1] = lower_32_bits(xspi_addr);
cmd_regs[2] = lower_32_bits(cdns_xspi->dma_addr);
cmd_regs[3] = upper_32_bits(cdns_xspi->dma_addr);
- cmd_regs[4] = cdns_xspi->acmd_info.data_nbytes - 1;
+ cmd_regs[4] = nbytes - 1;
cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
- CDNS_XSPI_ACMD_PROG_OP,
+ CDNS_XSPI_ACMD_READ_OP,
CDNS_XSPI_ACMD_DATA_THREAD, true);
ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
CDNS_XSPI_ACMD_DATA_THREAD);
+ if (ret) {
+ dev_err(cdns_xspi->dev, "ACMD read failed: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(buf, cdns_xspi->dma_buf, nbytes);
+
+ return 0;
+}
+
+static int cdns_xspi_nand_pio_erase(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand,
+ const struct spi_mem_op *op)
+{
+ u64 xspi_addr;
+ int ret;
+
+ ret = cdns_xspi_nand_addr(spinand, op->addr.val, 0, &xspi_addr);
+ if (ret)
+ return ret;
+
+ return cdns_xspi_pio_erase(cdns_xspi, xspi_addr);
+}
+
+static int cdns_xspi_nand_pio_program(struct cdns_xspi_dev *cdns_xspi,
+ struct spinand_device *spinand,
+ const struct spi_mem_op *op)
+{
+ u64 xspi_addr;
+ int ret;
+
+ if (!cdns_xspi->out_buffer || !cdns_xspi->acmd_info.data_nbytes) {
+ dev_err(cdns_xspi->dev, "missing program-load data\n");
+ ret = -EINVAL;
+ goto out_clear_program_state;
+ }
+
+ ret = cdns_xspi_nand_addr(spinand, op->addr.val,
+ cdns_xspi->acmd_info.column_addr,
+ &xspi_addr);
+ if (ret)
+ goto out_clear_program_state;
+
+ ret = cdns_xspi_pio_mdma_program(cdns_xspi, xspi_addr,
+ cdns_xspi->out_buffer,
+ cdns_xspi->acmd_info.data_nbytes);
out_clear_program_state:
cdns_xspi->acmd_info.column_addr = 0;
cdns_xspi->acmd_info.data_nbytes = 0;
cdns_xspi->out_buffer = NULL;
+
return ret;
}
-static int cdns_xspi_pio_mdma_read(struct cdns_xspi_dev *cdns_xspi,
+static int cdns_xspi_nand_pio_read(struct cdns_xspi_dev *cdns_xspi,
struct spinand_device *spinand,
const struct spi_mem_op *op)
{
- u32 cmd_regs[6] = { 0 };
u64 xspi_addr;
int ret;
@@ -1217,52 +1299,19 @@ static int cdns_xspi_pio_mdma_read(struct cdns_xspi_dev *cdns_xspi,
goto out_clear_read_state;
}
- if (op->data.nbytes > cdns_xspi->dma_buf_len) {
- ret = -EMSGSIZE;
- goto out_clear_read_state;
- }
-
- ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
- if (ret) {
- dev_err(cdns_xspi->dev,
- "controller did not become idle before read\n");
- goto out_clear_read_state;
- }
-
ret = cdns_xspi_nand_addr(spinand,
cdns_xspi->acmd_info.row_addr,
op->addr.val, &xspi_addr);
if (ret)
goto out_clear_read_state;
- if (upper_32_bits(xspi_addr)) {
- dev_err(cdns_xspi->dev,
- "read address 0x%llx exceeds the ACMD PIO range\n",
- xspi_addr);
- ret = -ERANGE;
- goto out_clear_read_state;
- }
-
- cmd_regs[1] = lower_32_bits(xspi_addr);
- cmd_regs[2] = lower_32_bits(cdns_xspi->dma_addr);
- cmd_regs[3] = upper_32_bits(cdns_xspi->dma_addr);
- cmd_regs[4] = op->data.nbytes - 1;
- cmd_regs[0] = cdns_xspi_acmd_cmd(cdns_xspi,
- CDNS_XSPI_ACMD_READ_OP,
- CDNS_XSPI_ACMD_DATA_THREAD, true);
-
- ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
- CDNS_XSPI_ACMD_DATA_THREAD);
- if (ret) {
- dev_err(cdns_xspi->dev, "ACMD read failed: %d\n", ret);
- goto out_clear_read_state;
- }
-
- memcpy(op->data.buf.in, cdns_xspi->dma_buf, op->data.nbytes);
+ ret = cdns_xspi_pio_mdma_read(cdns_xspi, xspi_addr,
+ op->data.buf.in, op->data.nbytes);
out_clear_read_state:
cdns_xspi->acmd_info.row_addr_valid = false;
cdns_xspi->acmd_info.row_addr = 0;
+
return ret;
}
@@ -1334,17 +1383,17 @@ static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
return cdns_xspi_pio_reset(cdns_xspi);
case CDNS_XSPI_NAND_OP_BLOCK_ERASE:
- return cdns_xspi_pio_mdma_erase(cdns_xspi, spinand, op);
+ return cdns_xspi_nand_pio_erase(cdns_xspi, spinand, op);
case CDNS_XSPI_NAND_OP_PROGRAM_EXECUTE:
- return cdns_xspi_pio_mdma_program(cdns_xspi, spinand, op);
+ return cdns_xspi_nand_pio_program(cdns_xspi, spinand, op);
default:
break;
}
if (read_cache && op->cmd.opcode == read_cache->cmd.opcode)
- return cdns_xspi_pio_mdma_read(cdns_xspi, spinand, op);
+ return cdns_xspi_nand_pio_read(cdns_xspi, spinand, op);
use_stig:
/* ACMD does not consume this operation; execute it through STIG. */
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 4/4] spi: cadence-xspi: add ACMD support for SPI NOR
2026-09-21 9:36 [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR Fei Xie
` (2 preceding siblings ...)
2026-09-21 9:37 ` [RFC PATCH 3/4] spi: cadence-xspi: factor out reusable ACMD helpers Fei Xie
@ 2026-09-21 9:37 ` Fei Xie
3 siblings, 0 replies; 7+ messages in thread
From: Fei Xie @ 2026-09-21 9:37 UTC (permalink / raw)
To: Mark Brown
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel, fei.xie
From: "fei.xie" <fei.xie@horizon.auto>
Program the automatic command sequences from the SPI NOR operation
templates.
Use the PIO command path with MDMA data transfers for reads, page
programs and erases. Unsupported command layouts continue to use STIG
mode.
Signed-off-by: fei.xie <fei.xie@horizon.auto>
---
drivers/spi/spi-cadence-xspi.c | 440 ++++++++++++++++++++++++++++++++-
1 file changed, 433 insertions(+), 7 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 114c1e0fd79fb..5900d4cbffb5c 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -12,11 +12,13 @@
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mtd/spi-nor.h>
#include <linux/mtd/spinand.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
+#include <linux/sizes.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/bitfield.h>
@@ -73,6 +75,7 @@
#define CDNS_XSPI_ACMD_ERASE_THREAD 5
#define CDNS_XSPI_ACMD_TIMEOUT_MS 1000
#define CDNS_XSPI_ACMD_MODE_PIO 1
+#define CDNS_XSPI_NOR_DMA_BUF_SIZE SZ_32K
#define CDNS_XSPI_NAND_OP_GET_FEATURE 0x0f
#define CDNS_XSPI_NAND_OP_WRITE_ENABLE 0x06
@@ -225,6 +228,7 @@
#define CDNS_XSPI_READ_SEQ_CFG_1 0x0434
#define CDNS_XSPI_READ_SEQ_P1_CMD_EXT_EN BIT(0)
#define CDNS_XSPI_READ_SEQ_P1_CACHE_RANDOM_READ_EN BIT(4)
+#define CDNS_XSPI_READ_SEQ_P1_CMD_EXT_VAL GENMASK(15, 8)
#define CDNS_XSPI_READ_SEQ_CFG_2 0x0438
/* Write enable sequence config register */
@@ -232,12 +236,22 @@
#define CDNS_XSPI_WE_SEQ_P1_CMD_VAL GENMASK(7, 0)
#define CDNS_XSPI_WE_SEQ_P1_CMD_IOS GENMASK(9, 8)
#define CDNS_XSPI_WE_SEQ_P1_CMD_EDGE BIT(11)
+#define CDNS_XSPI_WE_SEQ_P1_CMD_EXT_EN BIT(15)
+#define CDNS_XSPI_WE_SEQ_P1_CMD_EXT_VAL GENMASK(23, 16)
#define CDNS_XSPI_WE_SEQ_P1_EN BIT(24)
/* Status sequence config registers */
#define CDNS_XSPI_STAT_SEQ_CFG_0 0x0450
+#define CDNS_XSPI_STAT_SEQ_P1_CMD_IOS GENMASK(1, 0)
+#define CDNS_XSPI_STAT_SEQ_P1_CMD_EDGE BIT(4)
+#define CDNS_XSPI_STAT_SEQ_P1_CMD_EXT_EN BIT(5)
#define CDNS_XSPI_STAT_SEQ_P1_ADDR_CNT GENMASK(9, 8)
+#define CDNS_XSPI_STAT_SEQ_P1_ADDR_IOS GENMASK(11, 10)
+#define CDNS_XSPI_STAT_SEQ_P1_ADDR_EDGE BIT(12)
+#define CDNS_XSPI_STAT_SEQ_P1_DATA_IOS GENMASK(21, 20)
+#define CDNS_XSPI_STAT_SEQ_P1_DATA_EDGE BIT(22)
#define CDNS_XSPI_STAT_SEQ_CFG_1 0x0454
+#define CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_DUMMY_CNT GENMASK(5, 0)
#define CDNS_XSPI_P1_DEV_RDY_ADDR_EN BIT(6)
#define CDNS_XSPI_P1_PROG_FAIL_ADDR_EN BIT(22)
#define CDNS_XSPI_P1_ERS_FAIL_ADDR_EN BIT(30)
@@ -248,9 +262,11 @@
#define CDNS_XSPI_STAT_SEQ_P1_PROG_FAIL_CMD_VAL GENMASK(31, 24)
#define CDNS_XSPI_STAT_SEQ_CFG_3 0x045c
+#define CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_CMD_EXT_VAL GENMASK(7, 0)
#define CDNS_XSPI_STAT_SEQ_CFG_4 0x0460
#define CDNS_XSPI_STAT_SEQ_CFG_5 0x0464
#define CDNS_XSPI_STAT_SEQ_DEV_RDY_IDX GENMASK(3, 0)
+#define CDNS_XSPI_STAT_SEQ_DEV_RDY_SIZE BIT(5)
#define CDNS_XSPI_STAT_SEQ_DEV_RDY_EN BIT(6)
#define CDNS_XSPI_STAT_SEQ_ERS_FAIL_IDX GENMASK(11, 8)
#define CDNS_XSPI_STAT_SEQ_ERS_FAIL_VAL BIT(12)
@@ -288,6 +304,7 @@
#define CDNS_XSPI_SEQ_PAGE_PER_BLOCK GENMASK(26, 24)
#define CDNS_XSPI_SEQ_PAGE_CA_SIZE BIT(16)
#define CDNS_XSPI_SEQ_PAGE_SIZE_EXT GENMASK(8, 0)
+#define CDNS_XSPI_SEQ_SPI_NOR 0
#define CDNS_XSPI_XIP_MODE_CFG 0x0388
#define CDNS_XSPI_XIP_EN BIT(0)
@@ -476,7 +493,13 @@ struct cdns_xspi_acmd_info {
u64 row_addr;
u64 column_addr;
size_t data_nbytes;
+ u8 nor_status_buswidth;
+ u8 nor_write_enable_buswidth;
bool row_addr_valid;
+ bool nor_status_dtr;
+ bool nor_write_enable_dtr;
+ bool nor_status_initialized;
+ bool nor_write_enable_initialized;
bool initialized;
};
struct cdns_xspi_dev {
@@ -592,6 +615,31 @@ static void cdns_xspi_set_interrupts(struct cdns_xspi_dev *cdns_xspi,
writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG);
}
+static u8 cdns_xspi_get_opcode(const struct spi_mem_op *op)
+{
+ if (op->cmd.nbytes == 2)
+ return op->cmd.opcode >> 8;
+
+ return op->cmd.opcode;
+}
+
+static u8 cdns_xspi_get_opcode_extension(const struct spi_mem_op *op)
+{
+ if (op->cmd.nbytes == 2)
+ return op->cmd.opcode;
+
+ return 0;
+}
+
+static u32 cdns_xspi_get_dummy_cycles(const struct spi_mem_op *op)
+{
+ if (!op->dummy.nbytes || !op->dummy.buswidth)
+ return 0;
+
+ return op->dummy.nbytes * BITS_PER_BYTE /
+ (op->dummy.buswidth * (op->dummy.dtr + 1));
+}
+
static void cdns_xspi_nand_cfg_seq_init(struct cdns_xspi_dev *cdns_xspi,
struct spinand_device *spinand)
{
@@ -872,6 +920,268 @@ static int cdns_xspi_nand_init(struct cdns_xspi_dev *cdns_xspi,
return 0;
}
+static int cdns_xspi_nor_cfg_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_nor *nor)
+{
+ unsigned int page_size;
+ u32 seq_cfg;
+
+ if (!is_power_of_2(nor->mtd.writebufsize))
+ return -EOPNOTSUPP;
+
+ page_size = ilog2(nor->mtd.writebufsize);
+ if (!FIELD_FIT(CDNS_XSPI_SEQ_PAGE_SIZE_PGM, page_size))
+ return -EOPNOTSUPP;
+
+ seq_cfg = readl(cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG);
+ seq_cfg = u32_replace_bits(seq_cfg, CDNS_XSPI_SEQ_SPI_NOR,
+ CDNS_XSPI_SEQ_TYPE);
+ seq_cfg = u32_replace_bits(seq_cfg, page_size,
+ CDNS_XSPI_SEQ_PAGE_SIZE_PGM);
+ seq_cfg = u32_replace_bits(seq_cfg,
+ ilog2(CDNS_XSPI_NOR_DMA_BUF_SIZE),
+ CDNS_XSPI_SEQ_PAGE_SIZE_RD);
+ writel(seq_cfg, cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_GLOBAL_SEQ_CFG_1);
+
+ return 0;
+}
+
+static int cdns_xspi_nor_read_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ const struct spi_mem_op *op)
+{
+ u32 dummy_cycles = cdns_xspi_get_dummy_cycles(op);
+ u32 cfg0;
+ u32 cfg1;
+
+ if (!FIELD_FIT(CDNS_XSPI_READ_SEQ_P1_ADDR_CNT, op->addr.nbytes) ||
+ !FIELD_FIT(CDNS_XSPI_READ_SEQ_P1_DUMMY_CNT, dummy_cycles))
+ return -EOPNOTSUPP;
+
+ cfg0 = FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_VAL,
+ cdns_xspi_get_opcode(op)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_CNT, op->addr.nbytes) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_IOS,
+ ilog2(op->addr.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_ADDR_EDGE, op->addr.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DATA_IOS,
+ ilog2(op->data.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DATA_EDGE, op->data.dtr) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_DUMMY_CNT, dummy_cycles);
+ cfg1 = FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes == 2) |
+ FIELD_PREP(CDNS_XSPI_READ_SEQ_P1_CMD_EXT_VAL,
+ cdns_xspi_get_opcode_extension(op));
+
+ writel(cfg0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_0);
+ writel(cfg1, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_READ_SEQ_CFG_2);
+
+ return 0;
+}
+
+static int cdns_xspi_nor_program_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ const struct spi_mem_op *op)
+{
+ u32 dummy_cycles = cdns_xspi_get_dummy_cycles(op);
+ u32 cfg0;
+ u32 cfg1;
+
+ if (!FIELD_FIT(CDNS_XSPI_PROG_SEQ_P1_ADDR_CNT, op->addr.nbytes) ||
+ !FIELD_FIT(CDNS_XSPI_PROG_SEQ_P1_DUMMY_CNT, dummy_cycles))
+ return -EOPNOTSUPP;
+
+ cfg0 = FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_VAL,
+ cdns_xspi_get_opcode(op)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_CNT, op->addr.nbytes) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_IOS,
+ ilog2(op->addr.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_ADDR_EDGE, op->addr.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DATA_IOS,
+ ilog2(op->data.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DATA_EDGE, op->data.dtr) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_DUMMY_CNT, dummy_cycles);
+ cfg1 = FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes == 2) |
+ FIELD_PREP(CDNS_XSPI_PROG_SEQ_P1_CMD_EXT_VAL,
+ cdns_xspi_get_opcode_extension(op));
+
+ writel(cfg0, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_0);
+ writel(cfg1, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_PROG_SEQ_CFG_2);
+
+ return 0;
+}
+
+static int cdns_xspi_nor_erase_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_nor *nor,
+ const struct spi_mem_op *op)
+{
+ unsigned int erase_size;
+ u32 cfg0;
+
+ if (!is_power_of_2(nor->mtd.erasesize))
+ return -EOPNOTSUPP;
+
+ erase_size = ilog2(nor->mtd.erasesize);
+ if (!FIELD_FIT(CDNS_XSPI_ERSS_SEQ_P1_ADDR_CNT, op->addr.nbytes) ||
+ !FIELD_FIT(CDNS_XSPI_ERSS_SEQ_P1_SECT_SIZE, erase_size))
+ return -EOPNOTSUPP;
+
+ cfg0 = FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_VAL,
+ cdns_xspi_get_opcode(op)) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_CNT, op->addr.nbytes) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes == 2) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_EXT_VAL,
+ cdns_xspi_get_opcode_extension(op)) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_IOS,
+ ilog2(op->addr.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_ADDR_EDGE, op->addr.dtr);
+
+ writel(cfg0, cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_0);
+ writel(FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_SECT_SIZE, erase_size),
+ cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_1);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_ERSS_SEQ_CFG_2);
+
+ return 0;
+}
+
+static int cdns_xspi_nor_status_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ const struct spi_mem_op *op)
+{
+ u32 dummy_cycles = cdns_xspi_get_dummy_cycles(op);
+ u32 addr_count = op->addr.nbytes ? op->addr.nbytes - 1 : 0;
+ u32 cfg0;
+ u32 cfg1;
+ u32 cfg5;
+
+ if (op->data.dir != SPI_MEM_DATA_IN || !op->data.nbytes ||
+ !FIELD_FIT(CDNS_XSPI_STAT_SEQ_P1_ADDR_CNT, addr_count) ||
+ !FIELD_FIT(CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_DUMMY_CNT,
+ dummy_cycles) || upper_32_bits(op->addr.val))
+ return -EOPNOTSUPP;
+
+ cfg0 = FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes == 2) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_ADDR_CNT, addr_count) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_ADDR_IOS,
+ op->addr.buswidth ? ilog2(op->addr.buswidth) : 0) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_ADDR_EDGE, op->addr.dtr) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DATA_IOS,
+ ilog2(op->data.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DATA_EDGE, op->data.dtr);
+ cfg1 = FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_DUMMY_CNT,
+ dummy_cycles) |
+ (op->addr.nbytes ? CDNS_XSPI_P1_DEV_RDY_ADDR_EN : 0);
+ cfg5 = FIELD_PREP(CDNS_XSPI_STAT_SEQ_DEV_RDY_IDX, __ffs(SR_WIP)) |
+ (op->data.dtr ? CDNS_XSPI_STAT_SEQ_DEV_RDY_SIZE : 0) |
+ CDNS_XSPI_STAT_SEQ_DEV_RDY_EN;
+
+ writel(cfg0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_0);
+ writel(cfg1, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_1);
+ writel(FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_CMD_VAL,
+ cdns_xspi_get_opcode(op)),
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_2);
+ writel(FIELD_PREP(CDNS_XSPI_STAT_SEQ_P1_DEV_RDY_CMD_EXT_VAL,
+ cdns_xspi_get_opcode_extension(op)),
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_3);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_4);
+ writel(cfg5, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_5);
+ writel(lower_32_bits(op->addr.val),
+ cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_7);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_8);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_9);
+ writel(0, cdns_xspi->iobase + CDNS_XSPI_STAT_SEQ_CFG_10);
+
+ cdns_xspi->acmd_info.nor_status_initialized = true;
+ cdns_xspi->acmd_info.nor_status_buswidth = op->cmd.buswidth;
+ cdns_xspi->acmd_info.nor_status_dtr = op->cmd.dtr;
+
+ return 0;
+}
+
+static void
+cdns_xspi_nor_write_enable_seq_init(struct cdns_xspi_dev *cdns_xspi,
+ const struct spi_mem_op *op)
+{
+ u32 cfg;
+
+ cfg = FIELD_PREP(CDNS_XSPI_WE_SEQ_P1_CMD_VAL,
+ cdns_xspi_get_opcode(op)) |
+ FIELD_PREP(CDNS_XSPI_WE_SEQ_P1_CMD_IOS,
+ ilog2(op->cmd.buswidth)) |
+ FIELD_PREP(CDNS_XSPI_WE_SEQ_P1_CMD_EDGE, op->cmd.dtr) |
+ FIELD_PREP(CDNS_XSPI_WE_SEQ_P1_CMD_EXT_EN,
+ op->cmd.nbytes == 2) |
+ FIELD_PREP(CDNS_XSPI_WE_SEQ_P1_CMD_EXT_VAL,
+ cdns_xspi_get_opcode_extension(op)) |
+ CDNS_XSPI_WE_SEQ_P1_EN;
+ writel(cfg, cdns_xspi->iobase + CDNS_XSPI_WE_SEQ_CFG_0);
+ cdns_xspi->acmd_info.nor_write_enable_initialized = true;
+ cdns_xspi->acmd_info.nor_write_enable_buswidth = op->cmd.buswidth;
+ cdns_xspi->acmd_info.nor_write_enable_dtr = op->cmd.dtr;
+}
+
+static int cdns_xspi_nor_init(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_nor *nor)
+{
+ struct spi_mem_op status_op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, NULL, 1));
+ int ret;
+
+ ret = cdns_xspi_nor_cfg_seq_init(cdns_xspi, nor);
+ if (ret)
+ return ret;
+
+ cdns_xspi_acmd_disable_xip(cdns_xspi);
+ ret = cdns_xspi_acmd_alloc_dma_buf(cdns_xspi,
+ CDNS_XSPI_NOR_DMA_BUF_SIZE);
+ if (ret)
+ return ret;
+
+ if (!spi_nor_protocol_is_dtr(nor->reg_proto) &&
+ !cdns_xspi->acmd_info.nor_status_initialized) {
+ ret = cdns_xspi_nor_status_seq_init(cdns_xspi, &status_op);
+ if (ret)
+ return ret;
+ }
+
+ cdns_xspi->acmd_info.initialized = true;
+
+ return 0;
+}
+
+static bool
+cdns_xspi_nor_helper_sequences_ready(const struct cdns_xspi_dev *cdns_xspi,
+ const struct spi_nor *nor)
+{
+ u8 buswidth = spi_nor_get_protocol_inst_nbits(nor->reg_proto);
+ bool dtr = spi_nor_protocol_is_dtr(nor->reg_proto);
+
+ return cdns_xspi->acmd_info.nor_status_initialized &&
+ cdns_xspi->acmd_info.nor_status_buswidth == buswidth &&
+ cdns_xspi->acmd_info.nor_status_dtr == dtr &&
+ cdns_xspi->acmd_info.nor_write_enable_initialized &&
+ cdns_xspi->acmd_info.nor_write_enable_buswidth == buswidth &&
+ cdns_xspi->acmd_info.nor_write_enable_dtr == dtr;
+}
+
static int cdns_xspi_controller_init(struct cdns_xspi_dev *cdns_xspi)
{
u32 ctrl_ver;
@@ -1315,9 +1625,9 @@ static int cdns_xspi_nand_pio_read(struct cdns_xspi_dev *cdns_xspi,
return ret;
}
-static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
- struct spi_mem *mem,
- const struct spi_mem_op *op)
+static int cdns_xspi_send_nand_pio_command(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_mem *mem,
+ const struct spi_mem_op *op)
{
struct spinand_device *spinand;
const struct spi_mem_op *read_cache;
@@ -1325,9 +1635,6 @@ static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
const struct spi_mem_op *update_cache;
int ret;
- if (cdns_xspi->flash_type != CDNS_XSPI_FLASH_TYPE_NAND)
- goto use_stig;
-
spinand = spi_mem_get_drvdata(mem);
if (!spinand || !spinand->op_templates ||
!spinand->op_templates->read_cache ||
@@ -1401,6 +1708,115 @@ static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
op->data.dir != SPI_MEM_NO_DATA);
}
+static int cdns_xspi_send_nor_pio_command(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ struct spi_nor *nor = spi_mem_get_drvdata(mem);
+ u8 opcode = cdns_xspi_get_opcode(op);
+ int ret;
+
+ if (!nor)
+ goto use_stig;
+
+ if (opcode == SPINOR_OP_WREN) {
+ cdns_xspi_nor_write_enable_seq_init(cdns_xspi, op);
+ goto use_stig;
+ }
+
+ if (opcode == SPINOR_OP_RDSR && op->data.dir == SPI_MEM_DATA_IN) {
+ cdns_xspi->acmd_info.nor_status_initialized = false;
+ ret = cdns_xspi_nor_status_seq_init(cdns_xspi, op);
+ if (ret)
+ dev_dbg(cdns_xspi->dev,
+ "cannot use RDSR operation for ACMD status: %d\n",
+ ret);
+ goto use_stig;
+ }
+
+ if (!nor->mtd.size || !nor->mtd.writebufsize)
+ goto use_stig;
+
+ if (opcode != nor->read_opcode && opcode != nor->program_opcode &&
+ opcode != nor->erase_opcode)
+ goto use_stig;
+ if (upper_32_bits(op->addr.val))
+ goto use_stig;
+
+ if (!cdns_xspi->acmd_info.initialized) {
+ ret = cdns_xspi_nor_init(cdns_xspi, nor);
+ if (ret == -EOPNOTSUPP)
+ goto use_stig;
+ if (ret) {
+ dev_err(cdns_xspi->dev,
+ "failed to initialize NOR ACMD: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (opcode == nor->read_opcode && op->data.dir == SPI_MEM_DATA_IN) {
+ ret = cdns_xspi_nor_read_seq_init(cdns_xspi, op);
+ if (ret == -EOPNOTSUPP)
+ goto use_stig;
+ if (ret)
+ return ret;
+
+ return cdns_xspi_pio_mdma_read(cdns_xspi, op->addr.val,
+ op->data.buf.in,
+ op->data.nbytes);
+ }
+
+ if (opcode == nor->program_opcode &&
+ op->data.dir == SPI_MEM_DATA_OUT) {
+ if (!cdns_xspi_nor_helper_sequences_ready(cdns_xspi, nor))
+ goto use_stig;
+
+ ret = cdns_xspi_nor_program_seq_init(cdns_xspi, op);
+ if (ret == -EOPNOTSUPP)
+ goto use_stig;
+ if (ret)
+ return ret;
+
+ return cdns_xspi_pio_mdma_program(cdns_xspi, op->addr.val,
+ op->data.buf.out,
+ op->data.nbytes);
+ }
+
+ if (opcode == nor->erase_opcode && op->data.dir == SPI_MEM_NO_DATA) {
+ if (nor->mtd.numeraseregions ||
+ !cdns_xspi_nor_helper_sequences_ready(cdns_xspi, nor))
+ goto use_stig;
+
+ ret = cdns_xspi_nor_erase_seq_init(cdns_xspi, nor, op);
+ if (ret == -EOPNOTSUPP)
+ goto use_stig;
+ if (ret)
+ return ret;
+
+ return cdns_xspi_pio_erase(cdns_xspi, op->addr.val);
+ }
+
+use_stig:
+ return cdns_xspi_send_stig_command(cdns_xspi, op,
+ op->data.dir != SPI_MEM_NO_DATA);
+}
+
+static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
+ struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ switch (cdns_xspi->flash_type) {
+ case CDNS_XSPI_FLASH_TYPE_NAND:
+ return cdns_xspi_send_nand_pio_command(cdns_xspi, mem, op);
+
+ case CDNS_XSPI_FLASH_TYPE_NOR:
+ return cdns_xspi_send_nor_pio_command(cdns_xspi, mem, op);
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int cdns_xspi_mem_op(struct cdns_xspi_dev *cdns_xspi,
struct spi_mem *mem,
const struct spi_mem_op *op)
@@ -1484,8 +1900,18 @@ 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);
+ size_t max_len = cdns_xspi->sdmasize;
+
+ if (cdns_xspi->work_mode == CDNS_XSPI_WORK_MODE_ACMD) {
+ if (cdns_xspi->acmd_info.initialized)
+ max_len = min_t(size_t, max_len,
+ cdns_xspi->dma_buf_len);
+ else if (cdns_xspi->flash_type == CDNS_XSPI_FLASH_TYPE_NOR)
+ max_len = min_t(size_t, max_len,
+ CDNS_XSPI_NOR_DMA_BUF_SIZE);
+ }
- op->data.nbytes = clamp_val(op->data.nbytes, 0, cdns_xspi->sdmasize);
+ op->data.nbytes = min_t(size_t, op->data.nbytes, max_len);
return 0;
}
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible
2026-09-21 9:36 ` [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible Fei Xie
@ 2026-09-21 14:01 ` Mark Brown
0 siblings, 0 replies; 7+ messages in thread
From: Mark Brown @ 2026-09-21 14:01 UTC (permalink / raw)
To: Fei Xie
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 512 bytes --]
On Mon, Sep 21, 2026 at 05:36:58PM +0800, Fei Xie wrote:
> From: "fei.xie" <fei.xie@horizon.auto>
>
> Document the Cadence XSPI controller compatible used with SPI NAND.
Please submit patches using subject lines reflecting the style for the
subsystem, this makes it easier for people to identify relevant patches.
Look at what existing commits in the area you're changing are doing and
make sure your subject lines visually resemble what they're doing.
There's no need to resubmit to fix this alone.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND
2026-09-21 9:36 ` [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND Fei Xie
@ 2026-09-21 14:51 ` Mark Brown
0 siblings, 0 replies; 7+ messages in thread
From: Mark Brown @ 2026-09-21 14:51 UTC (permalink / raw)
To: Fei Xie
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parshuram Thombare, linux-spi, linux-mtd, devicetree,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 3943 bytes --]
On Mon, Sep 21, 2026 at 05:36:59PM +0800, Fei Xie wrote:
> Add PIO master-DMA support for the controller auto-command mode.
> Configure the SPI NAND read, program, erase, status and reset
> sequences, retaining STIG for operations not consumed by ACMD.
> +struct cdns_xspi_acmd_info {
> + u64 row_addr;
> + u64 column_addr;
> + size_t data_nbytes;
> + bool row_addr_valid;
> + bool initialized;
> +};
> struct cdns_xspi_dev {
Missing blank line.
> +static void cdns_xspi_nand_erase_seq_init(struct cdns_xspi_dev *cdns_xspi,
> + struct spinand_device *spinand)
> +{
> + u32 erase_seq_cfg0;
> + u32 erase_seq_cfg1;
> +
> + /* SPI-NAND block erase is always D8h with a 3-byte row address. */
> + erase_seq_cfg0 =
> + FIELD_PREP(CDNS_XSPI_ERSS_SEQ_P1_CMD_VAL, 0xd8) |
CDNS_XSPI_NAND_OP_BLOCK_ERASE.
> +static int cdns_xspi_nand_init(struct cdns_xspi_dev *cdns_xspi,
> + struct spinand_device *spinand)
> +{
How much of this initialisation is parameters based on the specific
flash passed in - what if there are two different flashes attached to
the same controller for some reason?
> + cdns_xspi->dma_buf_len = spinand->base.memorg.pagesize +
> + spinand->base.memorg.oobsize;
> + cdns_xspi->dma_buf = dmam_alloc_coherent(cdns_xspi->dev,
> + cdns_xspi->dma_buf_len,
> + &cdns_xspi->dma_addr,
> + GFP_KERNEL);
> + if (!cdns_xspi->dma_buf)
> + return -ENOMEM;
Why do we need the DMA buffer, I'd expect whatever reaches the driver to
already be DMA safe?
> +static int cdns_xspi_acmd_run(struct cdns_xspi_dev *cdns_xspi, u32 cmd_regs[6],
> + u32 thread)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + cdns_xspi_set_mode_acmd(cdns_xspi);
> + reinit_completion(&cdns_xspi->auto_cmd_complete);
> + cdns_xspi_set_interrupts(cdns_xspi, true);
We have the set_interrupts() operation.
> + cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
> +
> + timeout = msecs_to_jiffies(CDNS_XSPI_ACMD_TIMEOUT_MS);
> + if (!wait_for_completion_timeout(&cdns_xspi->auto_cmd_complete,
> + timeout)) {
> + dev_err(cdns_xspi->dev, "ACMD command timed out\n");
> + ret = -ETIMEDOUT;
Don't we need to clean up the hardware if this times out?
> + } else {
> + ret = cdns_xspi_acmd_get_thread_status(cdns_xspi, thread);
> + }
Does this need any updates to cover more error types?
> +static int cdns_xspi_pio_mdma_read(struct cdns_xspi_dev *cdns_xspi,
> + struct spinand_device *spinand,
> + const struct spi_mem_op *op)
> +{
> + ret = cdns_xspi_acmd_run(cdns_xspi, cmd_regs,
> + CDNS_XSPI_ACMD_DATA_THREAD);
> + if (ret) {
> + dev_err(cdns_xspi->dev, "ACMD read failed: %d\n", ret);
> + goto out_clear_read_state;
> + }
> +
> + memcpy(op->data.buf.in, cdns_xspi->dma_buf, op->data.nbytes);
> +
> +out_clear_read_state:
> + cdns_xspi->acmd_info.row_addr_valid = false;
> + cdns_xspi->acmd_info.row_addr = 0;
> + return ret;
> +}
Does this do the right thing for short reads?
> +static int cdns_xspi_send_pio_command(struct cdns_xspi_dev *cdns_xspi,
> + struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + struct spinand_device *spinand;
> + const struct spi_mem_op *read_cache;
> + const struct spi_mem_op *write_cache;
> + const struct spi_mem_op *update_cache;
> + int ret;
> +
> + if (cdns_xspi->flash_type != CDNS_XSPI_FLASH_TYPE_NAND)
> + goto use_stig;
> +
> + spinand = spi_mem_get_drvdata(mem);
This appears to be peering into the child's driver data without any
checking that the child is what we expect, this could go horribly wrong.
> + case CDNS_XSPI_NAND_OP_GET_FEATURE:
> + if (op->addr.val != CDNS_XSPI_NAND_STATUS_REG ||
> + !cdns_xspi->acmd_info.row_addr_valid)
> + break;
> +
> + if (op->data.dir != SPI_MEM_DATA_IN || !op->data.nbytes ||
> + !op->data.buf.in)
> + return -EINVAL;
> +
> + memset(op->data.buf.in, 0, op->data.nbytes);
> + return 0;
Are you sure the zeroing makes sense here, for example with ECC?
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-21 14:52 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 9:36 [RFC PATCH 0/4] spi: cadence-xspi: add ACMD PIO support for NAND and NOR Fei Xie
2026-09-21 9:36 ` [RFC PATCH 1/4] dt-bindings: spi: cdns,xspi: add SPI NAND compatible Fei Xie
2026-09-21 14:01 ` Mark Brown
2026-09-21 9:36 ` [RFC PATCH 2/4] spi: cadence-xspi: add ACMD support for SPI NAND Fei Xie
2026-09-21 14:51 ` Mark Brown
2026-09-21 9:37 ` [RFC PATCH 3/4] spi: cadence-xspi: factor out reusable ACMD helpers Fei Xie
2026-09-21 9:37 ` [RFC PATCH 4/4] spi: cadence-xspi: add ACMD support for SPI NOR Fei Xie
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®