summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2025-12-01 10:37:45 -0600
committerTom Rini <[email protected]>2025-12-01 10:37:45 -0600
commitc6ee44b5573a90b0a3a36cf858954e04439ae632 (patch)
treed43af0def2d0caf0196a680be7ba9b59add17219 /drivers
parent532582d217f3a00bac68003574ec158314fc2823 (diff)
parent87cb318344250ac7d598284c0a1928df6bcd1f4e (diff)
Merge tag 'u-boot-socfpga-next-20251201' of https://source.denx.de/u-boot/custodians/u-boot-socfpga
This pull request delivers a broad set of improvements across the SoCFPGA family, including Agilex5, Cyclone V, SoC64, and common code. Key updates include refined boot flows, new driver enablement, handoff tooling enhancements, and several stability fixes. Highlights: * Agilex5: - Enable FAT-based environment storage - MMC driver restores legacy clkmgr-based clock lookup - Cleanup of MMC raw mode enablement logic * Cyclone V: - SPL FAT boot support and updated bootcmd sequence - Disable SPL SPI to prevent contention with FAT-based boot - New board handoff script and BSP generator tooling - Optimized Makefile support for SoCFPGA handoff workflows* New drivers: - Cadence xSPI driver with full protocol and command support - SPL enablement for DW APB GPIO controller * Networking: - xgmac MDIO now supports Clause 45 read/write operations * NAND / SoC64: - Enable ONFI detection in Denali NAND controller for SoC64 devices * DTS and board updates: - Sync common SoCFPGA U-Boot DTS with kernel sources - Fixes for FPGA2SDRAM configuration and SoCFPGA boot stall behavior - Vining_FPGA migrated to the modern LED framework - Device tree relocation no longer forced off for Vining FPGA * Tooling: - Introduces a new Python-based Cyclone V BSP generator covering EMIF, IOCSR, HPS, XML parsing, rendering, and documentation to simplify board enablement and handoff regeneration workflows Overall, this series improves boot robustness, enhances xSPI and MDIO capabilities, modernizes board support, and introduces new tooling to streamline SoCFPGA handoff generation. Pipelines test passing https://source.denx.de/u-boot/custodians/u-boot-socfpga/-/pipelines/28569
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gpio/Kconfig8
-rw-r--r--drivers/gpio/Makefile2
-rw-r--r--drivers/mmc/socfpga_dw_mmc.c44
-rw-r--r--drivers/mtd/nand/raw/Kconfig1
-rw-r--r--drivers/net/dwc_eth_xgmac.c60
-rw-r--r--drivers/net/dwc_eth_xgmac.h1
-rw-r--r--drivers/spi/Kconfig15
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/cadence_xspi.c449
-rw-r--r--drivers/spi/cadence_xspi.h226
10 files changed, 771 insertions, 36 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index b5729a39774..2ed2bc82946 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -138,6 +138,14 @@ config DWAPB_GPIO
help
Support for the Designware APB GPIO driver.
+config SPL_DWAPB_GPIO
+ bool "DWAPB GPIO driver in SPL"
+ depends on SPL_DM_GPIO
+ help
+ Support for the Designware APB GPIO driver in SPL.
+
+ If unsure, say N.
+
config AT91_GPIO
bool "AT91 PIO GPIO driver"
depends on ARCH_AT91
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 73c94329e36..910478c0c7a 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -4,12 +4,12 @@
# Wolfgang Denk, DENX Software Engineering, [email protected].
ifndef CONFIG_XPL_BUILD
-obj-$(CONFIG_DWAPB_GPIO) += dwapb_gpio.o
obj-$(CONFIG_AXP_GPIO) += axp_gpio.o
obj-$(CONFIG_DM_74X164) += 74x164_gpio.o
endif
obj-$(CONFIG_$(PHASE_)DM_GPIO) += gpio-uclass.o
+obj-$(CONFIG_$(PHASE_)DWAPB_GPIO) += dwapb_gpio.o
obj-$(CONFIG_$(PHASE_)DM_PCA953X) += pca953x_gpio.o
obj-$(CONFIG_ADI_GPIO) += gpio-adi-adsp.o
diff --git a/drivers/mmc/socfpga_dw_mmc.c b/drivers/mmc/socfpga_dw_mmc.c
index db4e0129c2e..6219284df3e 100644
--- a/drivers/mmc/socfpga_dw_mmc.c
+++ b/drivers/mmc/socfpga_dw_mmc.c
@@ -58,17 +58,24 @@ static int socfpga_dwmci_clksel(struct dwmci_host *host)
u32 sdmmc_mask = ((priv->smplsel & 0x7) << SYSMGR_SDMMC_SMPLSEL_SHIFT) |
((priv->drvsel & 0x7) << SYSMGR_SDMMC_DRVSEL_SHIFT);
- ret = clk_get_by_name(priv->dev, "ciu", &priv->mmc_clk_ciu);
- if (ret) {
- debug("%s: Failed to get SDMMC clock from dts\n", __func__);
- return ret;
- }
+ if (!IS_ENABLED(CONFIG_TARGET_SOCFPGA_AGILEX) &&
+ !IS_ENABLED(CONFIG_TARGET_SOCFPGA_AGILEX7M)) {
+ /* Disable SDMMC clock. */
+ clrbits_le32(socfpga_get_clkmgr_addr() + CLKMGR_PERPLL_EN,
+ CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);
+ } else {
+ ret = clk_get_by_name(priv->dev, "ciu", &priv->mmc_clk_ciu);
+ if (ret) {
+ debug("%s: Failed to get SDMMC clock from dts\n", __func__);
+ return ret;
+ }
- /* Disable SDMMC clock. */
- ret = clk_disable(&priv->mmc_clk_ciu);
- if (ret) {
- printf("%s: Failed to disable SDMMC clock\n", __func__);
- return ret;
+ /* Disable SDMMC clock. */
+ ret = clk_disable(&priv->mmc_clk_ciu);
+ if (ret) {
+ printf("%s: Failed to disable SDMMC clock\n", __func__);
+ return ret;
+ }
}
debug("%s: drvsel %d smplsel %d\n", __func__,
@@ -88,11 +95,18 @@ static int socfpga_dwmci_clksel(struct dwmci_host *host)
readl(socfpga_get_sysmgr_addr() + SYSMGR_SDMMC));
#endif
- /* Enable SDMMC clock */
- ret = clk_enable(&priv->mmc_clk_ciu);
- if (ret) {
- printf("%s: Failed to enable SDMMC clock\n", __func__);
- return ret;
+ if (!IS_ENABLED(CONFIG_TARGET_SOCFPGA_AGILEX) &&
+ !IS_ENABLED(CONFIG_TARGET_SOCFPGA_AGILEX7M)) {
+ /* Enable SDMMC clock */
+ setbits_le32(socfpga_get_clkmgr_addr() + CLKMGR_PERPLL_EN,
+ CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);
+ } else {
+ /* Enable SDMMC clock */
+ ret = clk_enable(&priv->mmc_clk_ciu);
+ if (ret) {
+ printf("%s: Failed to enable SDMMC clock\n", __func__);
+ return ret;
+ }
}
return 0;
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index c76c10e1ef9..a93b00cdb7e 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -212,6 +212,7 @@ config NAND_CADENCE
config NAND_DENALI
bool
select SYS_NAND_SELF_INIT
+ select SYS_NAND_ONFI_DETECTION if TARGET_SOCFPGA_SOC64
imply CMD_NAND
config NAND_DENALI_DT
diff --git a/drivers/net/dwc_eth_xgmac.c b/drivers/net/dwc_eth_xgmac.c
index d29d871ea8c..458b87af7a2 100644
--- a/drivers/net/dwc_eth_xgmac.c
+++ b/drivers/net/dwc_eth_xgmac.c
@@ -140,9 +140,34 @@ static int xgmac_mdio_wait_idle(struct xgmac_priv *xgmac)
XGMAC_TIMEOUT_100MS, true);
}
+static u32 xgmac_set_clause(struct xgmac_priv *xgmac, int mdio_addr, int mdio_devad,
+ int mdio_reg, bool is_c45)
+{
+ u32 hw_addr;
+ u32 val;
+
+ if (is_c45) {
+ val = readl(&xgmac->mac_regs->mdio_clause_22_port);
+ val &= ~BIT(mdio_addr);
+ writel(val, &xgmac->mac_regs->mdio_clause_22_port);
+ hw_addr = (mdio_addr << XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) |
+ (mdio_reg & XGMAC_MAC_MDIO_REG_ADDR_C45P_MASK);
+ hw_addr |= mdio_devad << XGMAC_MAC_MDIO_ADDRESS_DA_SHIFT;
+ } else {
+ /* Set clause 22 format */
+ val = BIT(mdio_addr);
+ writel(val, &xgmac->mac_regs->mdio_clause_22_port);
+ hw_addr = (mdio_addr << XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) |
+ (mdio_reg & XGMAC_MAC_MDIO_REG_ADDR_C22P_MASK);
+ }
+
+ return hw_addr;
+}
+
static int xgmac_mdio_read(struct mii_dev *bus, int mdio_addr, int mdio_devad,
int mdio_reg)
{
+ bool is_c45 = (mdio_devad != MDIO_DEVAD_NONE);
struct xgmac_priv *xgmac = bus->priv;
u32 val;
u32 hw_addr;
@@ -159,19 +184,16 @@ static int xgmac_mdio_read(struct mii_dev *bus, int mdio_addr, int mdio_devad,
return ret;
}
- /* Set clause 22 format */
- val = BIT(mdio_addr);
- writel(val, &xgmac->mac_regs->mdio_clause_22_port);
-
- hw_addr = (mdio_addr << XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) |
- (mdio_reg & XGMAC_MAC_MDIO_REG_ADDR_C22P_MASK);
+ hw_addr = xgmac_set_clause(xgmac, mdio_addr, mdio_devad, mdio_reg, is_c45);
val = xgmac->config->config_mac_mdio <<
XGMAC_MAC_MDIO_ADDRESS_CR_SHIFT;
- val |= XGMAC_MAC_MDIO_ADDRESS_SADDR |
- XGMAC_MDIO_SINGLE_CMD_ADDR_CMD_READ |
- XGMAC_MAC_MDIO_ADDRESS_SBUSY;
+ if (!is_c45)
+ val |= XGMAC_MAC_MDIO_ADDRESS_SADDR;
+
+ val |= XGMAC_MDIO_SINGLE_CMD_ADDR_CMD_READ |
+ XGMAC_MAC_MDIO_ADDRESS_SBUSY;
ret = xgmac_mdio_wait_idle(xgmac);
if (ret) {
@@ -203,6 +225,7 @@ static int xgmac_mdio_read(struct mii_dev *bus, int mdio_addr, int mdio_devad,
static int xgmac_mdio_write(struct mii_dev *bus, int mdio_addr, int mdio_devad,
int mdio_reg, u16 mdio_val)
{
+ bool is_c45 = (mdio_devad != MDIO_DEVAD_NONE);
struct xgmac_priv *xgmac = bus->priv;
u32 val;
u32 hw_addr;
@@ -219,21 +242,18 @@ static int xgmac_mdio_write(struct mii_dev *bus, int mdio_addr, int mdio_devad,
return ret;
}
- /* Set clause 22 format */
- val = BIT(mdio_addr);
- writel(val, &xgmac->mac_regs->mdio_clause_22_port);
-
- hw_addr = (mdio_addr << XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) |
- (mdio_reg & XGMAC_MAC_MDIO_REG_ADDR_C22P_MASK);
-
- hw_addr |= (mdio_reg >> XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) <<
- XGMAC_MAC_MDIO_ADDRESS_DA_SHIFT;
+ hw_addr = xgmac_set_clause(xgmac, mdio_addr, mdio_devad, mdio_reg, is_c45);
val = (xgmac->config->config_mac_mdio <<
XGMAC_MAC_MDIO_ADDRESS_CR_SHIFT);
- val |= XGMAC_MAC_MDIO_ADDRESS_SADDR |
- mdio_val | XGMAC_MDIO_SINGLE_CMD_ADDR_CMD_WRITE |
+ if (!is_c45) {
+ hw_addr |= (mdio_reg >> XGMAC_MAC_MDIO_ADDRESS_PA_SHIFT) <<
+ XGMAC_MAC_MDIO_ADDRESS_DA_SHIFT;
+ val |= XGMAC_MAC_MDIO_ADDRESS_SADDR;
+ }
+
+ val |= mdio_val | XGMAC_MDIO_SINGLE_CMD_ADDR_CMD_WRITE |
XGMAC_MAC_MDIO_ADDRESS_SBUSY;
ret = xgmac_mdio_wait_idle(xgmac);
diff --git a/drivers/net/dwc_eth_xgmac.h b/drivers/net/dwc_eth_xgmac.h
index 259f815f3f2..90c2e22997c 100644
--- a/drivers/net/dwc_eth_xgmac.h
+++ b/drivers/net/dwc_eth_xgmac.h
@@ -109,6 +109,7 @@ struct xgmac_mac_regs {
#define XGMAC_MAC_MDIO_ADDRESS_SBUSY BIT(22)
#define XGMAC_MAC_MDIO_REG_ADDR_C22P_MASK GENMASK(4, 0)
#define XGMAC_MAC_MDIO_DATA_GD_MASK GENMASK(15, 0)
+#define XGMAC_MAC_MDIO_REG_ADDR_C45P_MASK GENMASK(15, 0)
/* MTL Registers */
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index c88918606d2..2afb15fff1e 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -180,6 +180,21 @@ config CADENCE_OSPI_VERSAL
This option is used to enable Versal OSPI DMA operations which
are used for ospi flash read using cadence qspi controller.
+config CADENCE_XSPI
+ bool "Cadence XSPI driver (Experimental feature)"
+ help
+ Important: this feature is experimental so far and tested only
+ on simulated environment.
+
+ Enable the Cadence eXpanded Serial Periheral Interface (xSPI) flash
+ driver. This driver can be used to access the SPI NOR flash on
+ platforms embedding this Cadence IP core up to 8 bit wide bus.
+ xSPI flash controller IP offers three work mode, Auto Command (ACMD)
+ work mode, Software Triggered Instruction Generator (STIG) work mode
+ and Direct work mode. This flash controller able to coomunicate
+ with Flash Memory Devices supporting JESD216 and JESD251 stadards
+ by using the Auto Command work mode.
+
config CF_SPI
bool "ColdFire SPI driver"
depends on M68K
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5129d649f84..d5d1e543588 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -8,6 +8,7 @@ ifdef CONFIG_$(PHASE_)DM_SPI
obj-y += spi-uclass.o
obj-$(CONFIG_CADENCE_QSPI) += cadence_qspi.o cadence_qspi_apb.o
obj-$(CONFIG_CADENCE_OSPI_VERSAL) += cadence_ospi_versal.o
+obj-$(CONFIG_CADENCE_XSPI) += cadence_xspi.o
obj-$(CONFIG_SANDBOX) += spi-emul-uclass.o
obj-$(CONFIG_SOFT_SPI) += soft_spi.o
obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o
diff --git a/drivers/spi/cadence_xspi.c b/drivers/spi/cadence_xspi.c
new file mode 100644
index 00000000000..717f226b709
--- /dev/null
+++ b/drivers/spi/cadence_xspi.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025
+ * Altera Corporation <www.altera.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <malloc.h>
+#include <reset.h>
+#include <spi.h>
+#include <spi-mem.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/sizes.h>
+#include <linux/time.h>
+#include "cadence_xspi.h"
+
+static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_plat *cdns_xspi)
+{
+ u32 ctrl_stat;
+
+ return readl_relaxed_poll_timeout(cdns_xspi->iobase +
+ CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat,
+ !(ctrl_stat &
+ CDNS_XSPI_CTRL_BUSY),
+ 1000);
+}
+
+static int cdns_xspi_wait_for_sdma_complete(struct cdns_xspi_plat *cdns_xspi)
+{
+ u32 irq_status;
+ int ret = 0;
+
+ ret = readl_relaxed_poll_timeout(cdns_xspi->iobase +
+ CDNS_XSPI_INTR_STATUS_REG,
+ irq_status,
+ (irq_status &
+ CDNS_XSPI_SDMA_TRIGGER),
+ 1000);
+
+ if (!ret) {
+ /*
+ * SDMA return an interrupt, need to clear
+ * the interrupt after read, wrtting 1 to clear the bit.
+ */
+ setbits_le32(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ CDNS_XSPI_SDMA_TRIGGER);
+ }
+
+ /* Check if SDMA ERROR happened */
+ if (irq_status & CDNS_XSPI_SDMA_ERROR) {
+ /*
+ * Need to clear the SDMA_ERROR interrupt
+ * after read, wrtting 1 to clear the bit.
+ */
+ dev_err(cdns_xspi->dev,
+ "Slave DMA transaction error\n");
+
+ cdns_xspi->sdma_error = true;
+ setbits_le32(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ CDNS_XSPI_SDMA_ERROR);
+
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int cdns_xspi_wait_for_cmd_complete(struct cdns_xspi_plat *cdns_xspi)
+{
+ u32 irq_status;
+ int ret = 0;
+
+ ret = readl_relaxed_poll_timeout(cdns_xspi->iobase +
+ CDNS_XSPI_INTR_STATUS_REG,
+ irq_status,
+ (irq_status &
+ CDNS_XSPI_STIG_DONE),
+ 100000);
+
+ irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ if (!ret) {
+ /*
+ * Need to clear the interrupt after read,
+ * wrtting 1 to the clear the bit.
+ */
+ writel(irq_status & CDNS_XSPI_STIG_DONE,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+ }
+
+ return ret;
+}
+
+static void cdns_xspi_trigger_command(struct cdns_xspi_plat *cdns_xspi,
+ u32 cmd_regs[6])
+{
+ writel(cmd_regs[5], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_5);
+ writel(cmd_regs[4], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_4);
+ writel(cmd_regs[3], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_3);
+ writel(cmd_regs[2], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_2);
+ writel(cmd_regs[1], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_1);
+ writel(cmd_regs[0], cdns_xspi->iobase + CDNS_XSPI_CMD_REG_0);
+}
+
+static int cdns_xspi_check_command_status(struct cdns_xspi_plat *cdns_xspi)
+{
+ int ret = 0;
+ u32 cmd_status = readl(cdns_xspi->iobase + CDNS_XSPI_CMD_STATUS_REG);
+
+ /* Check if the command has completed */
+ if (cmd_status & CDNS_XSPI_CMD_STATUS_COMPLETED) {
+ /*Check for failure status and report each type of error */
+ if ((cmd_status & CDNS_XSPI_CMD_STATUS_FAILED) != 0) {
+ if (cmd_status & CDNS_XSPI_CMD_STATUS_DQS_ERROR)
+ dev_err(cdns_xspi->dev,
+ "Incorrect DQS pulses detected\n");
+
+ if (cmd_status & CDNS_XSPI_CMD_STATUS_CRC_ERROR)
+ dev_err(cdns_xspi->dev,
+ "CRC error received\n");
+
+ if (cmd_status & CDNS_XSPI_CMD_STATUS_BUS_ERROR)
+ dev_err(cdns_xspi->dev,
+ "Error resp on system DMA interface\n");
+
+ if (cmd_status & CDNS_XSPI_CMD_STATUS_INV_SEQ_ERROR)
+ dev_err(cdns_xspi->dev,
+ "Invalid command sequence detected\n");
+
+ ret = -EPROTO;
+ }
+ } else {
+ /* Command did not complete at all -- fatal error */
+ dev_err(cdns_xspi->dev, "Fatal err - command not completed\n");
+ ret = -EPROTO;
+ }
+
+ return ret;
+}
+
+static void cdns_xspi_set_interrupts(struct cdns_xspi_plat *cdns_xspi,
+ bool enabled)
+{
+ u32 intr_enable;
+
+ intr_enable = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG);
+ if (enabled)
+ intr_enable |= CDNS_XSPI_INTR_MASK;
+ else
+ intr_enable &= ~CDNS_XSPI_INTR_MASK;
+ writel(intr_enable, cdns_xspi->iobase + CDNS_XSPI_INTR_ENABLE_REG);
+}
+
+static int cdns_xspi_controller_init(struct cdns_xspi_plat *cdns_xspi)
+{
+ u32 ctrl_ver;
+ u32 ctrl_features;
+ u16 hw_magic_num;
+
+ ctrl_ver = readl(cdns_xspi->iobase + CDNS_XSPI_CTRL_VERSION_REG);
+ hw_magic_num = FIELD_GET(CDNS_XSPI_MAGIC_NUM, ctrl_ver);
+ if (hw_magic_num != CDNS_XSPI_MAGIC_NUM_VALUE) {
+ dev_err(cdns_xspi->dev,
+ "Incorrect XSPI magic number: %x, expected: %x\n",
+ hw_magic_num, CDNS_XSPI_MAGIC_NUM_VALUE);
+ return -ENXIO;
+ }
+
+ ctrl_features = readl(cdns_xspi->iobase + CDNS_XSPI_CTRL_FEATURES_REG);
+ cdns_xspi->hw_num_banks = FIELD_GET(CDNS_XSPI_NUM_BANKS, ctrl_features);
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+
+ return 0;
+}
+
+static void cdns_xspi_sdma_handle(struct cdns_xspi_plat *cdns_xspi)
+{
+ u32 sdma_size, sdma_trd_info;
+ u8 sdma_dir;
+ u8 *in_buf;
+ u8 *out_buf;
+
+ sdma_size = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_SIZE_REG);
+ sdma_trd_info = readl(cdns_xspi->iobase + CDNS_XSPI_SDMA_TRD_INFO_REG);
+ sdma_dir = FIELD_GET(CDNS_XSPI_SDMA_DIR, sdma_trd_info);
+
+ in_buf = (u8 *)cdns_xspi->in_buffer;
+ out_buf = (u8 *)cdns_xspi->out_buffer;
+
+ switch (sdma_dir) {
+ case CDNS_XSPI_SDMA_DIR_READ:
+ if (in_buf)
+ memcpy_fromio(in_buf, cdns_xspi->sdmabase, sdma_size);
+ break;
+
+ case CDNS_XSPI_SDMA_DIR_WRITE:
+ if (in_buf)
+ memcpy_toio(cdns_xspi->sdmabase, out_buf, sdma_size);
+ break;
+
+ default:
+ /* Handle unexpected direction */
+ dev_warn(cdns_xspi->dev,
+ "Unknown SDMA direction: %u\n", sdma_dir);
+ break;
+ }
+}
+
+static int cdns_xspi_send_stig_command(struct cdns_xspi_plat *cdns_xspi,
+ const struct spi_mem_op *op,
+ bool data_phase)
+{
+ u32 cmd_regs[6] = {0};
+ int ret = 0;
+ int dummybytes = op->dummy.nbytes;
+
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ if (ret < 0)
+ return ret;
+
+ writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
+ cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
+
+ cdns_xspi->set_interrupts_handler(cdns_xspi, true);
+ cdns_xspi->sdma_error = false;
+
+ cmd_regs[1] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_1(op, data_phase);
+ cmd_regs[2] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_2(op);
+ if (dummybytes != 0) {
+ cmd_regs[3] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_3(op, 1);
+ dummybytes--;
+ } else {
+ cmd_regs[3] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_3(op, 0);
+ }
+ cmd_regs[4] = CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_4(op,
+ cdns_xspi->cur_cs);
+
+ cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
+
+ if (data_phase) {
+ cmd_regs[0] = CDNS_XSPI_STIG_DONE_FLAG;
+ cmd_regs[1] = CDNS_XSPI_CMD_FLD_DSEQ_CMD_1;
+ cmd_regs[2] = CDNS_XSPI_CMD_FLD_DSEQ_CMD_2(op);
+ cmd_regs[3] = CDNS_XSPI_CMD_FLD_DSEQ_CMD_3(op, dummybytes);
+ cmd_regs[4] = CDNS_XSPI_CMD_FLD_DSEQ_CMD_4(op,
+ cdns_xspi->cur_cs);
+
+ cdns_xspi->in_buffer = op->data.buf.in;
+ cdns_xspi->out_buffer = op->data.buf.out;
+
+ cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
+
+ cdns_xspi_wait_for_sdma_complete(cdns_xspi);
+
+ if (cdns_xspi->sdma_error) {
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ return -EIO;
+ }
+ cdns_xspi_sdma_handle(cdns_xspi);
+ }
+
+ cdns_xspi_wait_for_cmd_complete(cdns_xspi);
+ ret = cdns_xspi_check_command_status(cdns_xspi);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int cdns_xspi_mem_op(struct udevice *bus,
+ const struct spi_mem_op *op,
+ unsigned int cs)
+{
+ struct cdns_xspi_plat *plat = dev_get_plat(bus);
+ enum spi_mem_data_dir dir = op->data.dir;
+
+ if (plat->cur_cs != cs)
+ plat->cur_cs = cs;
+
+ return cdns_xspi_send_stig_command(plat, op,
+ (dir != SPI_MEM_NO_DATA));
+}
+
+static int cdns_xspi_mem_op_execute(struct spi_slave *spi,
+ const struct spi_mem_op *op)
+{
+ struct udevice *bus = spi->dev->parent;
+ unsigned int cs = 0;
+ int ret = 0;
+
+ cs = spi_chip_select(spi->dev);
+
+ if (cs < 0) {
+ /*
+ * spi_chip_select will return error number when not
+ * able to get chip select.
+ */
+ pr_err("%s: Unable to get chip select, ret=%d",
+ spi->dev->name, cs);
+ return cs;
+ }
+
+ ret = cdns_xspi_mem_op(bus, op, cs);
+
+ return ret;
+}
+
+static int cdns_xspi_adjust_mem_op_size(struct spi_slave *spi,
+ struct spi_mem_op *op)
+{
+ struct udevice *bus = spi->dev->parent;
+ struct cdns_xspi_plat *plat = dev_get_plat(bus);
+
+ op->data.nbytes = clamp_val(op->data.nbytes, 0, plat->sdmasize);
+
+ return 0;
+}
+
+static const struct spi_controller_mem_ops cadence_xspi_mem_ops = {
+ .exec_op = cdns_xspi_mem_op_execute,
+ .adjust_op_size = cdns_xspi_adjust_mem_op_size,
+};
+
+static void cdns_xspi_print_phy_config(struct cdns_xspi_plat *cdns_xspi)
+{
+ struct device *dev = cdns_xspi->dev;
+
+ dev_info(dev, "PHY configuration\n");
+ dev_info(dev, " * xspi_dll_phy_ctrl: %08x\n",
+ readl(cdns_xspi->iobase + CDNS_XSPI_DLL_PHY_CTRL));
+ dev_info(dev, " * phy_dq_timing: %08x\n",
+ readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DQ_TIMING));
+ dev_info(dev, " * phy_dqs_timing: %08x\n",
+ readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DQS_TIMING));
+ dev_info(dev, " * phy_gate_loopback_ctrl: %08x\n",
+ readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_GATE_LPBCK_CTRL));
+ dev_info(dev, " * phy_dll_slave_ctrl: %08x\n",
+ readl(cdns_xspi->auxbase + CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL));
+}
+
+static int cdns_xspi_probe(struct udevice *bus)
+{
+ struct cdns_xspi_plat *cdns_xspi = dev_get_plat(bus);
+ struct resource res;
+ int ret = 0;
+
+ cdns_xspi->sdma_handler = &cdns_xspi_sdma_handle;
+ cdns_xspi->set_interrupts_handler = &cdns_xspi_set_interrupts;
+ cdns_xspi->cur_cs = 0;
+
+ ret = dev_read_resource_byname(bus, "io", &res);
+ if (ret)
+ return ret;
+
+ cdns_xspi->iobase = devm_ioremap(bus, res.start, resource_size(&res));
+
+ if (IS_ERR(cdns_xspi->iobase)) {
+ dev_err(bus, "Failed to remap controller base address\n");
+ return PTR_ERR(cdns_xspi->iobase);
+ }
+
+ ret = dev_read_resource_byname(bus, "sdma", &res);
+ if (ret)
+ return ret;
+
+ cdns_xspi->sdmabase = devm_ioremap(bus, res.start, resource_size(&res));
+
+ if (IS_ERR(cdns_xspi->sdmabase)) {
+ dev_err(bus, "Failed to remap SDMA address\n");
+ return PTR_ERR(cdns_xspi->sdmabase);
+ }
+ cdns_xspi->sdmasize = resource_size(&res);
+
+ ret = dev_read_resource_byname(bus, "aux", &res);
+ if (ret)
+ return ret;
+
+ cdns_xspi->auxbase = devm_ioremap(bus, res.start, resource_size(&res));
+
+ if (IS_ERR(cdns_xspi->auxbase)) {
+ dev_err(bus, "Failed to remap AUX address\n");
+ return PTR_ERR(cdns_xspi->auxbase);
+ }
+
+ cdns_xspi_print_phy_config(cdns_xspi);
+
+ ret = cdns_xspi_controller_init(cdns_xspi);
+
+ if (ret) {
+ dev_err(bus, "Failed to initialize controller\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cdns_xspi_remove(struct udevice *dev)
+{
+ struct cdns_xspi_plat *plat = dev_get_plat(dev);
+ int ret = 0;
+
+ if (plat->resets)
+ ret = reset_release_bulk(plat->resets);
+
+ return ret;
+}
+
+static int cadence_spi_set_speed(struct udevice *bus, uint hz)
+{
+ return 0;
+}
+
+static int cadence_spi_set_mode(struct udevice *bus, uint mode)
+{
+ return 0;
+}
+
+static const struct dm_spi_ops cdns_xspi_ops = {
+ .set_speed = cadence_spi_set_speed,
+ .set_mode = cadence_spi_set_mode,
+ .mem_ops = &cadence_xspi_mem_ops,
+};
+
+static const struct udevice_id cdns_xspi_of_match[] = {
+ {
+ .compatible = "cdns,xspi-nor",
+ },
+ {/* end of table */}
+};
+
+U_BOOT_DRIVER(cadence_xspi) = {
+ .name = CDNS_XSPI_NAME,
+ .id = UCLASS_SPI,
+ .of_match = cdns_xspi_of_match,
+ .ops = &cdns_xspi_ops,
+ .probe = cdns_xspi_probe,
+ .remove = cdns_xspi_remove,
+ .flags = DM_FLAG_OS_PREPARE,
+};
diff --git a/drivers/spi/cadence_xspi.h b/drivers/spi/cadence_xspi.h
new file mode 100644
index 00000000000..e408d27303f
--- /dev/null
+++ b/drivers/spi/cadence_xspi.h
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2025
+ * Altera Corporation <www.altera.com>
+ */
+
+#ifndef __CADENCE_XSPI_H__
+#define __CADENCE_XSPI_H__
+
+#include <spi-mem.h>
+#include <reset.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/bitfield.h>
+#include <linux/log2.h>
+
+#define CDNS_XSPI_MAGIC_NUM_VALUE 0x6522
+#define CDNS_XSPI_MAX_BANKS 8
+#define CDNS_XSPI_NAME "cadence_xspi"
+
+/*
+ * Note: below are additional auxiliary registers to
+ * configure XSPI controller pin-strap settings
+ */
+
+/* PHY DQ timing register */
+#define CDNS_XSPI_CCP_PHY_DQ_TIMING 0x0000
+
+/* PHY DQS timing register */
+#define CDNS_XSPI_CCP_PHY_DQS_TIMING 0x0004
+
+/* PHY gate loopback control register */
+#define CDNS_XSPI_CCP_PHY_GATE_LPBCK_CTRL 0x0008
+
+/* PHY DLL slave control register */
+#define CDNS_XSPI_CCP_PHY_DLL_SLAVE_CTRL 0x0010
+
+/* DLL PHY control register */
+#define CDNS_XSPI_DLL_PHY_CTRL 0x1034
+
+/* Command registers */
+#define CDNS_XSPI_CMD_REG_0 0x0000
+#define CDNS_XSPI_CMD_REG_1 0x0004
+#define CDNS_XSPI_CMD_REG_2 0x0008
+#define CDNS_XSPI_CMD_REG_3 0x000C
+#define CDNS_XSPI_CMD_REG_4 0x0010
+#define CDNS_XSPI_CMD_REG_5 0x0014
+
+/* Command status registers */
+#define CDNS_XSPI_CMD_STATUS_REG 0x0044
+
+/* Controller status register */
+#define CDNS_XSPI_CTRL_STATUS_REG 0x0100
+#define CDNS_XSPI_INIT_COMPLETED BIT(16)
+#define CDNS_XSPI_INIT_LEGACY BIT(9)
+#define CDNS_XSPI_INIT_FAIL BIT(8)
+#define CDNS_XSPI_CTRL_BUSY BIT(7)
+
+/* Controller interrupt status register */
+#define CDNS_XSPI_INTR_STATUS_REG 0x0110
+#define CDNS_XSPI_STIG_DONE BIT(23)
+#define CDNS_XSPI_SDMA_ERROR BIT(22)
+#define CDNS_XSPI_SDMA_TRIGGER BIT(21)
+#define CDNS_XSPI_CMD_IGNRD_EN BIT(20)
+#define CDNS_XSPI_DDMA_TERR_EN BIT(18)
+#define CDNS_XSPI_CDMA_TREE_EN BIT(17)
+#define CDNS_XSPI_CTRL_IDLE_EN BIT(16)
+
+#define CDNS_XSPI_TRD_COMP_INTR_STATUS 0x0120
+#define CDNS_XSPI_TRD_ERR_INTR_STATUS 0x0130
+#define CDNS_XSPI_TRD_ERR_INTR_EN 0x0134
+
+/* Controller interrupt enable register */
+#define CDNS_XSPI_INTR_ENABLE_REG 0x0114
+#define CDNS_XSPI_INTR_EN BIT(31)
+#define CDNS_XSPI_STIG_DONE_EN BIT(23)
+#define CDNS_XSPI_SDMA_ERROR_EN BIT(22)
+#define CDNS_XSPI_SDMA_TRIGGER_EN BIT(21)
+
+#define CDNS_XSPI_INTR_MASK (CDNS_XSPI_INTR_EN | \
+ CDNS_XSPI_STIG_DONE_EN | \
+ CDNS_XSPI_SDMA_ERROR_EN | \
+ CDNS_XSPI_SDMA_TRIGGER_EN)
+
+/* Controller config register */
+#define CDNS_XSPI_CTRL_CONFIG_REG 0x0230
+#define CDNS_XSPI_CTRL_WORK_MODE GENMASK(6, 5)
+
+#define CDNS_XSPI_WORK_MODE_DIRECT 0
+#define CDNS_XSPI_WORK_MODE_STIG 1
+#define CDNS_XSPI_WORK_MODE_ACMD 3
+
+/* SDMA trigger transaction registers */
+#define CDNS_XSPI_SDMA_SIZE_REG 0x0240
+#define CDNS_XSPI_SDMA_TRD_INFO_REG 0x0244
+#define CDNS_XSPI_SDMA_DIR BIT(8)
+
+/* Controller features register */
+#define CDNS_XSPI_CTRL_FEATURES_REG 0x0F04
+#define CDNS_XSPI_NUM_BANKS GENMASK(25, 24)
+#define CDNS_XSPI_DMA_DATA_WIDTH BIT(21)
+#define CDNS_XSPI_NUM_THREADS GENMASK(3, 0)
+
+/* Controller version register */
+#define CDNS_XSPI_CTRL_VERSION_REG 0x0F00
+#define CDNS_XSPI_MAGIC_NUM GENMASK(31, 16)
+#define CDNS_XSPI_CTRL_REV GENMASK(7, 0)
+
+/* STIG Profile 1.0 instruction fields (split into registers) */
+#define CDNS_XSPI_CMD_INSTR_TYPE GENMASK(6, 0)
+#define CDNS_XSPI_CMD_P1_R1_ADDR0 GENMASK(31, 24)
+#define CDNS_XSPI_CMD_P1_R2_ADDR1 GENMASK(7, 0)
+#define CDNS_XSPI_CMD_P1_R2_ADDR2 GENMASK(15, 8)
+#define CDNS_XSPI_CMD_P1_R2_ADDR3 GENMASK(23, 16)
+#define CDNS_XSPI_CMD_P1_R2_ADDR4 GENMASK(31, 24)
+#define CDNS_XSPI_CMD_P1_R3_ADDR5 GENMASK(7, 0)
+#define CDNS_XSPI_CMD_P1_R3_CMD GENMASK(23, 16)
+#define CDNS_XSPI_CMD_P1_R3_NUM_ADDR_BYTES GENMASK(30, 28)
+#define CDNS_XSPI_CMD_P1_R4_ADDR_IOS GENMASK(1, 0)
+#define CDNS_XSPI_CMD_P1_R4_CMD_IOS GENMASK(9, 8)
+#define CDNS_XSPI_CMD_P1_R4_BANK GENMASK(14, 12)
+
+/* STIG data sequence instruction fields (split into registers) */
+#define CDNS_XSPI_CMD_DSEQ_R2_DCNT_L GENMASK(31, 16)
+#define CDNS_XSPI_CMD_DSEQ_R3_DCNT_H GENMASK(15, 0)
+#define CDNS_XSPI_CMD_DSEQ_R3_NUM_OF_DUMMY GENMASK(25, 20)
+#define CDNS_XSPI_CMD_DSEQ_R4_BANK GENMASK(14, 12)
+#define CDNS_XSPI_CMD_DSEQ_R4_DATA_IOS GENMASK(9, 8)
+#define CDNS_XSPI_CMD_DSEQ_R4_DIR BIT(4)
+
+/* STIG command status fields */
+#define CDNS_XSPI_CMD_STATUS_COMPLETED BIT(15)
+#define CDNS_XSPI_CMD_STATUS_FAILED BIT(14)
+#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)
+#define CDNS_XSPI_CMD_STATUS_INV_SEQ_ERROR BIT(0)
+
+#define CDNS_XSPI_STIG_DONE_FLAG BIT(0)
+#define CDNS_XSPI_TRD_STATUS 0x0104
+
+#define MODE_NO_OF_BYTES GENMASK(25, 24)
+#define MODEBYTES_COUNT 1
+
+/* Helper macros for filling command registers */
+#define CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_1(op, data_phase) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_INSTR_TYPE, (data_phase) ? \
+ CDNS_XSPI_STIG_INSTR_TYPE_1 : CDNS_XSPI_STIG_INSTR_TYPE_0) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R1_ADDR0, (op)->addr.val & 0xff))
+
+#define CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_2(op) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R2_ADDR1, ((op)->addr.val >> 8) & 0xFF) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R2_ADDR2, ((op)->addr.val >> 16) & 0xFF) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R2_ADDR3, ((op)->addr.val >> 24) & 0xFF) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R2_ADDR4, ((op)->addr.val >> 32) & 0xFF))
+
+#define CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_3(op, modebytes) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R3_ADDR5, ((op)->addr.val >> 40) & 0xFF) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R3_CMD, (op)->cmd.opcode) | \
+ FIELD_PREP(MODE_NO_OF_BYTES, modebytes) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R3_NUM_ADDR_BYTES, (op)->addr.nbytes))
+
+#define CDNS_XSPI_CMD_FLD_P1_INSTR_CMD_4(op, chipsel) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R4_ADDR_IOS, ilog2((op)->addr.buswidth)) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R4_CMD_IOS, ilog2((op)->cmd.buswidth)) | \
+ FIELD_PREP(CDNS_XSPI_CMD_P1_R4_BANK, chipsel))
+
+#define CDNS_XSPI_CMD_FLD_DSEQ_CMD_1 \
+ FIELD_PREP(CDNS_XSPI_CMD_INSTR_TYPE, CDNS_XSPI_STIG_INSTR_TYPE_DATA_SEQ)
+
+#define CDNS_XSPI_CMD_FLD_DSEQ_CMD_2(op) \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R2_DCNT_L, (op)->data.nbytes & 0xFFFF)
+
+#define CDNS_XSPI_CMD_FLD_DSEQ_CMD_3(op, dummybytes) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R3_DCNT_H, \
+ ((op)->data.nbytes >> 16) & 0xffff) | \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R3_NUM_OF_DUMMY, \
+ (op)->dummy.buswidth != 0 ? \
+ (((dummybytes) * 8) / (op)->dummy.buswidth) : \
+ 0))
+
+#define CDNS_XSPI_CMD_FLD_DSEQ_CMD_4(op, chipsel) ( \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R4_BANK, chipsel) | \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R4_DATA_IOS, \
+ ilog2((op)->data.buswidth)) | \
+ FIELD_PREP(CDNS_XSPI_CMD_DSEQ_R4_DIR, \
+ ((op)->data.dir == SPI_MEM_DATA_IN) ? \
+ CDNS_XSPI_STIG_CMD_DIR_READ : CDNS_XSPI_STIG_CMD_DIR_WRITE))
+
+enum cdns_xspi_stig_instr_type {
+ CDNS_XSPI_STIG_INSTR_TYPE_0,
+ CDNS_XSPI_STIG_INSTR_TYPE_1,
+ CDNS_XSPI_STIG_INSTR_TYPE_DATA_SEQ = 127,
+};
+
+enum cdns_xspi_sdma_dir {
+ CDNS_XSPI_SDMA_DIR_READ,
+ CDNS_XSPI_SDMA_DIR_WRITE,
+};
+
+enum cdns_xspi_stig_cmd_dir {
+ CDNS_XSPI_STIG_CMD_DIR_READ,
+ CDNS_XSPI_STIG_CMD_DIR_WRITE,
+};
+
+struct cdns_xspi_plat {
+ struct device *dev;
+ struct reset_ctl_bulk *resets;
+
+ void __iomem *iobase;
+ void __iomem *auxbase;
+ void __iomem *sdmabase;
+
+ int cur_cs;
+ unsigned int sdmasize;
+ bool sdma_error;
+
+ void *in_buffer;
+ const void *out_buffer;
+
+ u8 hw_num_banks;
+
+ void (*sdma_handler)(struct cdns_xspi_plat *cdns_xspi);
+ void (*set_interrupts_handler)(struct cdns_xspi_plat *cdns_xspi, bool enabled);
+};
+
+#endif /* __CADENCE_XSPI_H__ */