From c413b6006b5b0f6d86f820794ed7375936f55051 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Mon, 31 Mar 2025 23:37:25 +0000 Subject: [PATCH 1/3] drivers: spi: Added ZynqMP Generic Quad SPI driver Add a driver for the Generic Quad SPI hardware that is part of the Xilinx MPSoC. This is mostly commonly used with QSPI flash devices which are also used for initial image loading, but can also be used with other SPI devices. Signed-off-by: Robert Hancock --- drivers/spi/CMakeLists.txt | 1 + drivers/spi/Kconfig | 1 + drivers/spi/Kconfig.xlnx_zynqmp_gqspi | 14 + drivers/spi/spi_xlnx_zynqmp_gqspi.c | 663 +++++++++++++++++++++ dts/arm/xilinx/zynqmp.dtsi | 11 +- dts/bindings/spi/xlnx,zynqmp-qspi-1.0.yaml | 26 + 6 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 drivers/spi/Kconfig.xlnx_zynqmp_gqspi create mode 100644 drivers/spi/spi_xlnx_zynqmp_gqspi.c create mode 100644 dts/bindings/spi/xlnx,zynqmp-qspi-1.0.yaml diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index cac795b2900b9..e6cfc5c2141ee 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -69,6 +69,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_WCH spi_wch.c) zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI_LDMA spi_xec_qmspi_ldma.c) zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_AXI_QUADSPI spi_xlnx_axi_quadspi.c) +zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_ZYNQMP_GQSPI spi_xlnx_zynqmp_gqspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_XMC4XXX spi_xmc4xxx.c) # zephyr-keep-sorted-stop diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 285b20b11f6cd..11c86c211dfd2 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -146,6 +146,7 @@ source "drivers/spi/Kconfig.test" source "drivers/spi/Kconfig.wch" source "drivers/spi/Kconfig.xec_qmspi" source "drivers/spi/Kconfig.xlnx" +source "drivers/spi/Kconfig.xlnx_zynqmp_gqspi" source "drivers/spi/Kconfig.xmc4xxx" source "drivers/spi/spi_nxp_lpspi/Kconfig" # zephyr-keep-sorted-stop diff --git a/drivers/spi/Kconfig.xlnx_zynqmp_gqspi b/drivers/spi/Kconfig.xlnx_zynqmp_gqspi new file mode 100644 index 0000000000000..4562d58aac468 --- /dev/null +++ b/drivers/spi/Kconfig.xlnx_zynqmp_gqspi @@ -0,0 +1,14 @@ +# Xilinx SPI + +# Copyright (c) 2020 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +config SPI_XLNX_ZYNQMP_GQSPI + bool "Xilinx ZynqMP GQSPI driver" + default y + depends on DT_HAS_XLNX_ZYNQMP_QSPI_1_0_ENABLED + select EVENTS + help + Enable Xilinx ZynqMP Generic Quad SPI driver. + This is normally used with QSPI flash devices but can also be used + with other SPI devices. diff --git a/drivers/spi/spi_xlnx_zynqmp_gqspi.c b/drivers/spi/spi_xlnx_zynqmp_gqspi.c new file mode 100644 index 0000000000000..a270fada617a8 --- /dev/null +++ b/drivers/spi/spi_xlnx_zynqmp_gqspi.c @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2025 Calian Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT xlnx_zynqmp_qspi_1_0 + +#include +#include +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(xlnx_zynqmp_gqspi, CONFIG_SPI_LOG_LEVEL); + +#include "spi_context.h" + +/** + * TODO: DMA mode not yet implemented + */ + +enum ZynqMPGQSPIRegisters { + GQSPI_CFG = 0x100, + GQSPI_ISR = 0x104, + GQSPI_IER = 0x108, + GQSPI_IDR = 0x10C, + GQSPI_IMASK = 0x110, + GQSPI_EN = 0x114, + GQSPI_TXD = 0x11C, + GQSPI_RXD = 0x120, + GQSPI_TX_THRESH = 0x128, + GQSPI_RX_THRESH = 0x12C, + GQSPI_GPIO = 0x130, + GQSPI_LPBK_DLY_ADJ = 0x138, + GQSPI_GEN_FIFO = 0x140, + GQSPI_SEL = 0x144, + GQSPI_FIFO_CTRL = 0x14C, + GQSPI_GF_THRESH = 0x150, + GQSPI_POLL_CFG = 0x154, + GQSPI_POLL_TIMEOUT = 0x158, + GQSPI_DATA_DLY_ADJ = 0x1F8, + GQSPI_MOD_ID = 0x1FC, + GQSPIDMA_DST_ADDR = 0x800, + GQSPIDMA_DST_SIZE = 0x804, + GQSPIDMA_DST_STS = 0x808, + GQSPIDMA_DST_CTRL = 0x80C, + GQSPIDMA_DST_I_STS = 0x814, + GQSPIDMA_DST_I_EN = 0x818, + GQSPIDMA_DST_I_DIS = 0x81C, + GQSPIDMA_DST_I_MASK = 0x820, + GQSPIDMA_DST_CTRL2 = 0x824, + GQSPIDMA_DST_ADDR_MSB = 0x828, +}; + +#define GQSPI_CFG_MODE_EN_MASK BIT_MASK(2) +#define GQSPI_CFG_MODE_EN_SHIFT 30 +#define GQSPI_CFG_MODE_IO 0 +#define GQSPI_CFG_MODE_DMA 2 +#define GQSPI_CFG_GEN_FIFO_START_MANUAL_MASK BIT(29) +#define GQSPI_CFG_START_GEN_FIFO_MASK BIT(28) +#define GQSPI_CFG_ENDIAN_BE_MASK BIT(26) +#define GQSPI_CFG_EN_POLL_TIMEOUT_MASK BIT(20) +#define GQSPI_CFG_WP_HOLD_MASK BIT(19) +#define GQSPI_CFG_BAUD_RATE_DIV_MASK BIT_MASK(3) +#define GQSPI_CFG_BAUD_RATE_DIV_SHIFT 3 +#define GQSPI_CFG_CLK_PH_MASK BIT(2) +#define GQSPI_CFG_CLK_POL_MASK BIT(1) + +#define GQSPI_EN_ENABLE_MASK BIT(0) + +#define GQSPI_SEL_GQSPI_MASK BIT(0) + +#define GQSPI_GEN_FIFO_POLL_MASK BIT(19) +#define GQSPI_GEN_FIFO_STRIPE_MASK BIT(18) +#define GQSPI_GEN_FIFO_RX_EN_MASK BIT(17) +#define GQSPI_GEN_FIFO_TX_EN_MASK BIT(16) +#define GQSPI_GEN_FIFO_BUS_UPPER_MASK BIT(15) +#define GQSPI_GEN_FIFO_BUS_LOWER_MASK BIT(14) +#define GQSPI_GEN_FIFO_CS_UPPER_MASK BIT(13) +#define GQSPI_GEN_FIFO_CS_LOWER_MASK BIT(12) +#define GQSPI_GEN_FIFO_SPI_MODE_MASK BIT_MASK(2) +#define GQSPI_GEN_FIFO_SPI_MODE_SHIFT 10 +#define GQSPI_GEN_FIFO_SPI_MODE_SINGLE 1 +#define GQSPI_GEN_FIFO_SPI_MODE_DUAL 2 +#define GQSPI_GEN_FIFO_SPI_MODE_QUAD 3 +#define GQSPI_GEN_FIFO_EXPONENT_MASK BIT(9) +#define GQSPI_GEN_FIFO_DATA_XFER_MASK BIT(8) +#define GQSPI_GEN_FIFO_IMMED_DATA_MASK BIT_MASK(8) + +#define GQSPI_CS_SETUP_CYCLES 10 +#define GQSPI_CS_HOLD_CYCLES 10 +#define GQSPI_MAX_EXPONENT 28 + +#define GQSPI_GEN_FIFO_DEPTH 32 + +#define GQSPI_FIFO_CTRL_RST_RX_FIFO_MASK BIT(2) +#define GQSPI_FIFO_CTRL_RST_TX_FIFO_MASK BIT(1) +#define GQSPI_FIFO_CTRL_RST_GEN_FIFO_MASK BIT(0) + +/* Bits used in interrupt-related registers */ +#define GQSPI_INT_RX_FIFO_EMPTY BIT(11) +#define GQSPI_INT_GEN_FIFO_FULL BIT(10) +#define GQSPI_INT_GEN_FIFO_NOT_FULL BIT(9) +#define GQSPI_INT_TX_FIFO_EMPTY BIT(8) +#define GQSPI_INT_GEN_FIFO_EMPTY BIT(7) +#define GQSPI_INT_RX_FIFO_FULL BIT(5) +#define GQSPI_INT_RX_FIFO_NOT_EMPTY BIT(4) +#define GQSPI_INT_TX_FIFO_FULL BIT(3) +#define GQSPI_INT_TX_FIFO_NOT_FULL BIT(2) +#define GQSPI_INT_POLL_TIME_EXPIRE BIT(1) +#define GQSPI_INT_ALL_MASK \ + (GQSPI_INT_RX_FIFO_EMPTY | GQSPI_INT_GEN_FIFO_FULL | GQSPI_INT_GEN_FIFO_NOT_FULL | \ + GQSPI_INT_TX_FIFO_EMPTY | GQSPI_INT_GEN_FIFO_EMPTY | GQSPI_INT_RX_FIFO_FULL | \ + GQSPI_INT_RX_FIFO_NOT_EMPTY | GQSPI_INT_TX_FIFO_FULL | GQSPI_INT_TX_FIFO_NOT_FULL | \ + GQSPI_INT_POLL_TIME_EXPIRE) + +#define GQSPIDMA_INT_FIFO_OVERFLOW BIT(7) +#define GQSPIDMA_INT_INVALID_APB BIT(6) +#define GQSPIDMA_INT_THRESH_HIT BIT(5) +#define GQSPIDMA_INT_TIMEOUT_MEM BIT(4) +#define GQSPIDMA_INT_TIMEOUT_STRM BIT(3) +#define GQSPIDMA_INT_AXI_BRESP_ERR BIT(2) +#define GQSPIDMA_INT_DONE BIT(1) +#define GQSPIDMA_INT_ALL_MASK \ + (GQSPIDMA_INT_FIFO_OVERFLOW | GQSPIDMA_INT_INVALID_APB | GQSPIDMA_INT_THRESH_HIT | \ + GQSPIDMA_INT_TIMEOUT_MEM | GQSPIDMA_INT_TIMEOUT_STRM | GQSPIDMA_INT_AXI_BRESP_ERR | \ + GQSPIDMA_INT_DONE) + +/* TODO: Tap delay non-bypass mode not yet supported */ +#define GQSPI_MAX_FREQ_LOOPBACK_DISABLE 40000000 +#define GQSPI_LPBK_DLY_ADJ_LOOPBACK_DISABLE 0x0 +#define GQSPI_DATA_DLY_ADJ_LOOPBACK_DISABLE 0x0 +#define GQSPI_MAX_FREQ_LOOPBACK_ENABLE 100000000 +#define GQSPI_LPBK_DLY_ADJ_LOOPBACK_ENABLE 0x20 +#define GQSPI_DATA_DLY_ADJ_LOOPBACK_ENABLE 0xA0000000 + +struct xlnx_zynqmp_gqspi_config { + mm_reg_t base; + void (*irq_config_func)(const struct device *dev); + uint32_t ref_clock_freq; + bool shared_data_bus; +}; + +struct xlnx_zynqmp_gqspi_data { + struct spi_context ctx; + uint32_t spi_cfg; + struct k_event event; +}; + +static inline uint32_t xlnx_zynqmp_gqspi_read32(const struct device *dev, + enum ZynqMPGQSPIRegisters reg) +{ + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + + return sys_read32(config->base + (mm_reg_t)reg); +} + +static inline void xlnx_zynqmp_gqspi_write32(const struct device *dev, + enum ZynqMPGQSPIRegisters reg, uint32_t value) +{ + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + + sys_write32(value, config->base + (mm_reg_t)reg); +} + +static void xlnx_zynqmp_gqspi_cs_control(const struct device *dev, bool on) +{ + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + struct xlnx_zynqmp_gqspi_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + uint32_t genfifo_entry = GQSPI_GEN_FIFO_SPI_MODE_SINGLE << GQSPI_GEN_FIFO_SPI_MODE_SHIFT; + + if (ctx->config->slave == 1 && !config->shared_data_bus) { + genfifo_entry |= GQSPI_GEN_FIFO_BUS_UPPER_MASK; + } else { + genfifo_entry |= GQSPI_GEN_FIFO_BUS_LOWER_MASK; + } + if (on) { + if (ctx->config->slave == 1) { + genfifo_entry |= GQSPI_GEN_FIFO_CS_UPPER_MASK; + } else { + genfifo_entry |= GQSPI_GEN_FIFO_CS_LOWER_MASK; + } + genfifo_entry |= GQSPI_CS_SETUP_CYCLES; + } else { + if (ctx->config->operation & SPI_HOLD_ON_CS) { + /* Skip slave select de-assert */ + return; + } + genfifo_entry |= GQSPI_CS_HOLD_CYCLES; + } + + LOG_DBG("CS %s, genfifo_entry: 0x%08x", on ? "assert" : "deassert", genfifo_entry); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_GEN_FIFO, genfifo_entry); + + spi_context_cs_control(ctx, on); +} + +static int xlnx_zynqmp_gqspi_configure(const struct device *dev, const struct spi_config *spi_cfg) +{ + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + struct xlnx_zynqmp_gqspi_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + const uint32_t word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); + uint32_t max_frequency = spi_cfg->frequency; + uint32_t baud_div = 0; + uint32_t actual_frequency = config->ref_clock_freq / (2 << baud_div); + + if (spi_context_configured(ctx, spi_cfg)) { + /* Configuration already active */ + return 0; + } + + if (spi_cfg->operation & (SPI_FRAME_FORMAT_TI | SPI_HALF_DUPLEX | SPI_OP_MODE_SLAVE | + SPI_MODE_LOOP | SPI_TRANSFER_LSB | SPI_CS_ACTIVE_HIGH)) { + LOG_ERR("Unsupported SPI operation mode 0x%x", spi_cfg->operation); + return -ENOTSUP; + } + + if ((spi_cfg->operation & SPI_LINES_MASK) == SPI_LINES_OCTAL) { + LOG_ERR("Octal SPI not supported"); + return -ENOTSUP; + } + + if (spi_cfg->slave >= 2) { + LOG_ERR("unsupported slave %d", spi_cfg->slave); + return -ENOTSUP; + } + + if (word_size != 8) { + LOG_ERR("unsupported word size %d bits", word_size); + return -ENOTSUP; + } + + if (max_frequency > GQSPI_MAX_FREQ_LOOPBACK_ENABLE) { + max_frequency = GQSPI_MAX_FREQ_LOOPBACK_ENABLE; + } + + while (actual_frequency > max_frequency && baud_div < GQSPI_CFG_BAUD_RATE_DIV_MASK) { + baud_div++; + actual_frequency = config->ref_clock_freq / (2 << baud_div); + } + if (actual_frequency > max_frequency) { + LOG_ERR("unsupported frequency %d", spi_cfg->frequency); + return -ENOTSUP; + } + data->spi_cfg &= ~GQSPI_CFG_BAUD_RATE_DIV_MASK; + data->spi_cfg |= (baud_div << GQSPI_CFG_BAUD_RATE_DIV_SHIFT); + + if (spi_cfg->operation & SPI_MODE_CPHA) { + data->spi_cfg |= GQSPI_CFG_CLK_PH_MASK; + } else { + data->spi_cfg &= ~GQSPI_CFG_CLK_PH_MASK; + } + + if (spi_cfg->operation & SPI_MODE_CPOL) { + data->spi_cfg |= GQSPI_CFG_CLK_POL_MASK; + } else { + data->spi_cfg &= ~GQSPI_CFG_CLK_POL_MASK; + } + + /* Disable controller */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_EN, 0); + LOG_DBG("GQSPI_CFG: 0x%08x", data->spi_cfg); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_CFG, data->spi_cfg); + + if (actual_frequency > GQSPI_MAX_FREQ_LOOPBACK_DISABLE) { + /* Enable loopback */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_LPBK_DLY_ADJ, + GQSPI_LPBK_DLY_ADJ_LOOPBACK_ENABLE); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_DATA_DLY_ADJ, + GQSPI_DATA_DLY_ADJ_LOOPBACK_ENABLE); + } else { + /* Disable loopback */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_LPBK_DLY_ADJ, + GQSPI_LPBK_DLY_ADJ_LOOPBACK_DISABLE); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_DATA_DLY_ADJ, + GQSPI_DATA_DLY_ADJ_LOOPBACK_DISABLE); + } + + xlnx_zynqmp_gqspi_write32(dev, GQSPI_EN, GQSPI_EN_ENABLE_MASK); + ctx->config = spi_cfg; + + return 0; +} + +static bool xlnx_zynqmp_gqspi_service_fifos(const struct device *dev) +{ + struct xlnx_zynqmp_gqspi_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + uint32_t isr = xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR); + + LOG_DBG("Service FIFOs, ISR: 0x%08x", isr); + /* Note: Each buffer is sent as a separate SPI command, + * so do not mix buffers within the same FIFO word. + */ + while (spi_context_tx_on(ctx)) { + uint32_t tx_bytes; + uint32_t fifo_data = 0; + + if (!spi_context_tx_buf_on(ctx)) { + /* consume dummy TX buffer */ + spi_context_update_tx(ctx, 1, ctx->tx_len); + continue; + } + if (isr & GQSPI_INT_TX_FIFO_FULL) { + break; + } + + tx_bytes = Z_MIN(4, ctx->tx_len); + for (size_t i = 0; i < tx_bytes; i++) { + fifo_data |= ctx->tx_buf[i] << (i * 8); + } + xlnx_zynqmp_gqspi_write32(dev, GQSPI_TXD, fifo_data); + LOG_DBG("TX FIFO data: 0x%08x", fifo_data); + isr = xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR); + spi_context_update_tx(ctx, 1, tx_bytes); + } + + while (spi_context_rx_on(ctx)) { + uint32_t rx_bytes; + uint32_t fifo_data; + + if (!spi_context_rx_buf_on(ctx)) { + /* consume dummy RX buffer */ + spi_context_update_rx(ctx, 1, ctx->rx_len); + continue; + } + if (isr & GQSPI_INT_RX_FIFO_EMPTY) { + break; + } + rx_bytes = Z_MIN(4, ctx->rx_len); + fifo_data = xlnx_zynqmp_gqspi_read32(dev, GQSPI_RXD); + + LOG_DBG("RX FIFO data: 0x%08x", fifo_data); + for (size_t i = 0; i < rx_bytes; i++) { + ctx->rx_buf[i] = (fifo_data >> (i * 8)) & 0xFF; + } + isr = xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR); + spi_context_update_rx(ctx, 1, rx_bytes); + } + LOG_DBG("Service FIFOs done, ISR: 0x%08x", isr); + + if (!spi_context_tx_buf_on(ctx) && !spi_context_rx_buf_on(ctx) && + (isr & GQSPI_INT_GEN_FIFO_EMPTY)) { + LOG_DBG("Transfer complete"); + spi_context_complete(ctx, dev, 0); + return true; + } + xlnx_zynqmp_gqspi_write32(dev, GQSPI_CFG, data->spi_cfg | GQSPI_CFG_START_GEN_FIFO_MASK); + + return false; +} + +static int xlnx_zynqmp_gqspi_transceive(const struct device *dev, const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, bool async, + spi_callback_t cb, void *userdata) +{ + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + struct xlnx_zynqmp_gqspi_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + int ret; + const size_t num_bufs = Z_MAX(tx_bufs ? tx_bufs->count : 0, rx_bufs ? rx_bufs->count : 0); + + /* Maximum 30 buffers to avoid filling generic (command) FIFO. */ + if (num_bufs > GQSPI_GEN_FIFO_DEPTH - 2) { + LOG_ERR("Too many buffers: %zu", num_bufs); + return -ENOTSUP; + } + + spi_context_lock(ctx, async, cb, userdata, spi_cfg); + + /* Clear FIFOs */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_FIFO_CTRL, + GQSPI_FIFO_CTRL_RST_RX_FIFO_MASK | + GQSPI_FIFO_CTRL_RST_TX_FIFO_MASK | + GQSPI_FIFO_CTRL_RST_GEN_FIFO_MASK); + + ret = xlnx_zynqmp_gqspi_configure(dev, spi_cfg); + if (ret) { + goto out; + } + + spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1); + + xlnx_zynqmp_gqspi_cs_control(dev, true); + + for (size_t buf = 0; buf < num_bufs; buf++) { + uint32_t genfifo_entry = GQSPI_GEN_FIFO_DATA_XFER_MASK; + size_t tx_bytes = 0; + size_t rx_bytes = 0; + size_t transfer_bytes; + + if (tx_bufs && buf < tx_bufs->count) { + tx_bytes = tx_bufs->buffers[buf].len; + if (tx_bytes && tx_bufs->buffers[buf].buf) { + genfifo_entry |= GQSPI_GEN_FIFO_TX_EN_MASK; + } + } + if (rx_bufs && buf < rx_bufs->count) { + rx_bytes = rx_bufs->buffers[buf].len; + if (rx_bytes && rx_bufs->buffers[buf].buf) { + genfifo_entry |= GQSPI_GEN_FIFO_RX_EN_MASK; + if ((genfifo_entry & GQSPI_GEN_FIFO_TX_EN_MASK) && + (ctx->config->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { + LOG_ERR("Cannot have both RX and TX buffers for dual/quad " + "mode"); + ret = -ENOTSUP; + goto out; + } + } + } + transfer_bytes = Z_MAX(tx_bytes, rx_bytes); + + if (ctx->config->slave == 1) { + genfifo_entry |= GQSPI_GEN_FIFO_CS_UPPER_MASK; + genfifo_entry |= config->shared_data_bus ? GQSPI_GEN_FIFO_BUS_LOWER_MASK + : GQSPI_GEN_FIFO_BUS_UPPER_MASK; + } else { + genfifo_entry |= + GQSPI_GEN_FIFO_CS_LOWER_MASK | GQSPI_GEN_FIFO_BUS_LOWER_MASK; + } + switch (ctx->config->operation & SPI_LINES_MASK) { + case SPI_LINES_DUAL: + genfifo_entry |= GQSPI_GEN_FIFO_SPI_MODE_DUAL + << GQSPI_GEN_FIFO_SPI_MODE_SHIFT; + break; + case SPI_LINES_QUAD: + genfifo_entry |= GQSPI_GEN_FIFO_SPI_MODE_QUAD + << GQSPI_GEN_FIFO_SPI_MODE_SHIFT; + break; + case SPI_LINES_SINGLE: + default: + genfifo_entry |= GQSPI_GEN_FIFO_SPI_MODE_SINGLE + << GQSPI_GEN_FIFO_SPI_MODE_SHIFT; + break; + } + + if (transfer_bytes < 256) { + genfifo_entry |= transfer_bytes; + } else { + uint32_t exponent = GQSPI_MAX_EXPONENT; + size_t exponent_len = 1 << exponent; + + while (exponent_len > transfer_bytes) { + exponent_len >>= 1; + exponent--; + } + if (exponent_len != transfer_bytes) { + /* Could do this by breaking buffer into multiple transfer requests, + * but not implemented yet + */ + LOG_ERR("Unsupported buffer size %zu", transfer_bytes); + ret = -ENOTSUP; + goto out; + } + genfifo_entry |= GQSPI_GEN_FIFO_EXPONENT_MASK | exponent; + } + LOG_DBG("Buffer %zu, TX bytes: %zu, RX bytes: %zu, transfer bytes: %zu, " + "genfifo_entry: 0x%08x", + buf, tx_bytes, rx_bytes, transfer_bytes, genfifo_entry); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_GEN_FIFO, genfifo_entry); + } + + xlnx_zynqmp_gqspi_cs_control(dev, false); + + while (true) { + bool complete = xlnx_zynqmp_gqspi_service_fifos(dev); + uint32_t wait_events = 0; + + if (complete || async) { + break; + } + + if (spi_context_rx_buf_on(ctx)) { + wait_events |= GQSPI_INT_RX_FIFO_NOT_EMPTY; + } + if (spi_context_tx_buf_on(ctx)) { + wait_events |= GQSPI_INT_TX_FIFO_NOT_FULL; + } + if (!spi_context_tx_buf_on(ctx) && !spi_context_rx_buf_on(ctx)) { + wait_events |= GQSPI_INT_GEN_FIFO_EMPTY; + } + LOG_DBG("Waiting for events: 0x%08x", wait_events); + k_event_clear(&data->event, wait_events); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_IER, wait_events); + if (xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR) & wait_events) { + /* Already have the event */ + continue; + } + /** + * 20ms should be long enough for 256 byte FIFO at any + * reasonable clock speed. + */ + if (!k_event_wait(&data->event, wait_events, false, + K_MSEC(20 + CONFIG_SPI_COMPLETION_TIMEOUT_TOLERANCE))) { + LOG_ERR("Timeout, wait_events 0x%08x, ISR: 0x%08x", wait_events, + xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR)); + spi_context_complete(ctx, dev, -ETIMEDOUT); + break; + } + } + + ret = spi_context_wait_for_completion(ctx); +out: + spi_context_release(ctx, ret); + + return ret; +} + +static int xlnx_zynqmp_gqspi_transceive_blocking(const struct device *dev, + const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + return xlnx_zynqmp_gqspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL); +} + +#ifdef CONFIG_SPI_ASYNC +static int xlnx_zynqmp_gqspi_transceive_async(const struct device *dev, + const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, spi_callback_t cb, + void *userdata) +{ + return xlnx_zynqmp_gqspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata); +} +#endif /* CONFIG_SPI_ASYNC */ + +static int xlnx_zynqmp_gqspi_release(const struct device *dev, const struct spi_config *spi_cfg) +{ + struct xlnx_zynqmp_gqspi_data *data = dev->data; + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static void xlnx_zynqmp_gqspi_isr(const struct device *dev) +{ + struct xlnx_zynqmp_gqspi_data *data = dev->data; + const uint32_t isr = xlnx_zynqmp_gqspi_read32(dev, GQSPI_ISR); + const uint32_t masked = isr & ~xlnx_zynqmp_gqspi_read32(dev, GQSPI_IMASK); + + if (masked) { + LOG_DBG("ISR: 0x%08x, masked: 0x%08x", isr, masked); + /* Disable any interrupts that were just posted */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_IDR, masked); + + /** + * For async mode, we need to read the RX FIFO and refill the TX FIFO + * if needed here. + * For sync mode, we do this in the caller's context to avoid doing too much + * work in the ISR, so just post the event. + */ +#ifdef CONFIG_SPI_ASYNC + struct spi_context *ctx = &data->ctx; + + if (ctx->asynchronous) { + xlnx_zynqmp_gqspi_service_fifos(dev); + return; + } +#endif + k_event_post(&data->event, masked); + } else { + LOG_WRN("unhandled interrupt, isr = 0x%08x", isr); + } +} + +static int xlnx_zynqmp_gqspi_init(const struct device *dev) +{ + int err; + const struct xlnx_zynqmp_gqspi_config *config = dev->config; + struct xlnx_zynqmp_gqspi_data *data = dev->data; + + k_event_init(&data->event); + + /* Ensure that GQSPI (vs. LQSPI) mode is active */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_SEL, GQSPI_SEL_GQSPI_MASK); + + /* Ensure that poll timer interrupt is cleared */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_ISR, GQSPI_INT_POLL_TIME_EXPIRE); + + /* Disable all interrupts*/ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_IDR, GQSPI_INT_ALL_MASK); + xlnx_zynqmp_gqspi_write32(dev, GQSPIDMA_DST_I_DIS, GQSPIDMA_INT_ALL_MASK); + + /* Disable controller */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_EN, 0); + + xlnx_zynqmp_gqspi_write32(dev, GQSPI_FIFO_CTRL, + GQSPI_FIFO_CTRL_RST_RX_FIFO_MASK | + GQSPI_FIFO_CTRL_RST_TX_FIFO_MASK | + GQSPI_FIFO_CTRL_RST_GEN_FIFO_MASK); + + /* Set TX not full and RX not empty thresholds */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_TX_THRESH, 32); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_RX_THRESH, 1); + + data->spi_cfg = (GQSPI_CFG_MODE_IO << GQSPI_CFG_MODE_EN_SHIFT) | + GQSPI_CFG_GEN_FIFO_START_MANUAL_MASK | GQSPI_CFG_WP_HOLD_MASK; + + LOG_DBG("GQSPI_CFG: 0x%08x", data->spi_cfg); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_CFG, data->spi_cfg); + + /* Enable loopback */ + xlnx_zynqmp_gqspi_write32(dev, GQSPI_LPBK_DLY_ADJ, GQSPI_LPBK_DLY_ADJ_LOOPBACK_ENABLE); + xlnx_zynqmp_gqspi_write32(dev, GQSPI_DATA_DLY_ADJ, GQSPI_DATA_DLY_ADJ_LOOPBACK_ENABLE); + + xlnx_zynqmp_gqspi_write32(dev, GQSPI_EN, GQSPI_EN_ENABLE_MASK); + + config->irq_config_func(dev); + + err = spi_context_cs_configure_all(&data->ctx); + if (err < 0) { + return err; + } + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static DEVICE_API(spi, xlnx_zynqmp_gqspi_driver_api) = { + .transceive = xlnx_zynqmp_gqspi_transceive_blocking, +#ifdef CONFIG_SPI_ASYNC + .transceive_async = xlnx_zynqmp_gqspi_transceive_async, +#endif /* CONFIG_SPI_ASYNC */ +#ifdef CONFIG_SPI_RTIO + .iodev_submit = spi_rtio_iodev_default_submit, +#endif + .release = xlnx_zynqmp_gqspi_release, +}; + +#define XLNX_ZYNQMP_GQSPI_INIT(n) \ + static void xlnx_zynqmp_gqspi_config_func_##n(const struct device *dev); \ + \ + static const struct xlnx_zynqmp_gqspi_config xlnx_zynqmp_gqspi_config_##n = { \ + .base = DT_INST_REG_ADDR(n), \ + .irq_config_func = xlnx_zynqmp_gqspi_config_func_##n, \ + .ref_clock_freq = DT_INST_PROP(n, clock_frequency), \ + .shared_data_bus = DT_INST_NODE_HAS_PROP(n, shared_data_bus)}; \ + \ + static struct xlnx_zynqmp_gqspi_data xlnx_zynqmp_gqspi_data_##n = { \ + SPI_CONTEXT_INIT_LOCK(xlnx_zynqmp_gqspi_data_##n, ctx), \ + SPI_CONTEXT_INIT_SYNC(xlnx_zynqmp_gqspi_data_##n, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \ + \ + SPI_DEVICE_DT_INST_DEFINE(n, &xlnx_zynqmp_gqspi_init, NULL, &xlnx_zynqmp_gqspi_data_##n, \ + &xlnx_zynqmp_gqspi_config_##n, POST_KERNEL, \ + CONFIG_SPI_INIT_PRIORITY, &xlnx_zynqmp_gqspi_driver_api); \ + \ + static void xlnx_zynqmp_gqspi_config_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), xlnx_zynqmp_gqspi_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQN(n)); \ + } + +DT_INST_FOREACH_STATUS_OKAY(XLNX_ZYNQMP_GQSPI_INIT) diff --git a/dts/arm/xilinx/zynqmp.dtsi b/dts/arm/xilinx/zynqmp.dtsi index a524c2c271b25..05105c2cf53ff 100644 --- a/dts/arm/xilinx/zynqmp.dtsi +++ b/dts/arm/xilinx/zynqmp.dtsi @@ -304,6 +304,15 @@ IRQ_DEFAULT_PRIORITY>; reg = <0xfd070000 0x30000>; }; - }; + qspi: spi@ff0f0000 { + compatible = "xlnx,zynqmp-qspi-1.0"; + reg = <0xff0f0000 0x1000>; + status = "disabled"; + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; + }; + }; }; diff --git a/dts/bindings/spi/xlnx,zynqmp-qspi-1.0.yaml b/dts/bindings/spi/xlnx,zynqmp-qspi-1.0.yaml new file mode 100644 index 0000000000000..54ca7e1ac7649 --- /dev/null +++ b/dts/bindings/spi/xlnx,zynqmp-qspi-1.0.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Calian Ltd +# SPDX-License-Identifier: Apache-2.0 + +description: Xilinx ZynqMP Quad SPI interface + +compatible: "xlnx,zynqmp-qspi-1.0" + +include: spi-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clock-frequency: + description: SPI reference clock frequency in Hz + type: int + required: true + + shared-data-bus: + type: boolean + description: | + Indicates that both "lower" and "upper" chip selects are used with + the "lower" data bus, rather than each using a dedicated data bus. From 950f1105f1d4ab31063ea82d033df9ce9bd0df4b Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 10 Apr 2025 14:59:30 -0600 Subject: [PATCH 2/3] boards: qemu: cortex_r5: Added QSPI settings Added device tree entries for the QSPI device and the flash devices which are present behind it on this emulated board. Signed-off-by: Robert Hancock --- boards/qemu/cortex_r5/Kconfig.defconfig | 8 ++++++ boards/qemu/cortex_r5/qemu_cortex_r5.dts | 33 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/boards/qemu/cortex_r5/Kconfig.defconfig b/boards/qemu/cortex_r5/Kconfig.defconfig index a6ea313a4ab88..4ae0f3b605777 100644 --- a/boards/qemu/cortex_r5/Kconfig.defconfig +++ b/boards/qemu/cortex_r5/Kconfig.defconfig @@ -13,4 +13,12 @@ config COMPILER_ISA_THUMB2 endif +if SPI_NOR + +choice SPI_NOR_SFDP + default SPI_NOR_SFDP_DEVICETREE +endchoice + +endif + endif # BOARD_QEMU_CORTEX_R5 diff --git a/boards/qemu/cortex_r5/qemu_cortex_r5.dts b/boards/qemu/cortex_r5/qemu_cortex_r5.dts index b23e9534ed498..3324db5268ab9 100644 --- a/boards/qemu/cortex_r5/qemu_cortex_r5.dts +++ b/boards/qemu/cortex_r5/qemu_cortex_r5.dts @@ -31,3 +31,36 @@ status = "okay"; clock-frequency = <5000000>; }; + +&qspi { + status = "okay"; + clock-frequency = <124987511>; + + flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x0>; + spi-max-frequency = <166000000>; + jedec-id = [20 bb 20]; + sfdp-bfp = [e5 20 fb ff ff ff ff 1f 29 eb 27 6b 27 3b 27 bb + ff ff ff ff ff ff 27 bb ff ff 29 eb 0c 20 10 d8 + 00 00 00 00 35 8a 01 00 82 a3 03 da 6c c1 04 2e + 7a 75 7a 75 fb bd d5 5c 08 0f 82 ff 81 bd 35 36]; + size = <0x20000000>; + }; + + flash@1 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x1>; + spi-max-frequency = <166000000>; + jedec-id = [20 bb 20]; + sfdp-bfp = [e5 20 fb ff ff ff ff 1f 29 eb 27 6b 27 3b 27 bb + ff ff ff ff ff ff 27 bb ff ff 29 eb 0c 20 10 d8 + 00 00 00 00 35 8a 01 00 82 a3 03 da 6c c1 04 2e + 7a 75 7a 75 fb bd d5 5c 08 0f 82 ff 81 bd 35 36]; + size = <0x20000000>; + }; +}; From c876ea7adfc7b97a65a8bea8640c009f8f588a77 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Mon, 16 Jun 2025 17:47:18 -0600 Subject: [PATCH 3/3] dts: arm: xilinx: zynqmp: mark flash0 node as disabled Currently ZynqMP board setups do not appear to be using the flash0 node which is used for running in XIP mode from QSPI flash storage. Mark this node as disabled to avoid tripping up the drivers.flash.common.disable_spi_nor tests, which would otherwise try to build with FLASH enabled and SPI_NOR disabled. Signed-off-by: Robert Hancock --- dts/arm/xilinx/zynqmp.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/dts/arm/xilinx/zynqmp.dtsi b/dts/arm/xilinx/zynqmp.dtsi index 05105c2cf53ff..c729d8cffba78 100644 --- a/dts/arm/xilinx/zynqmp.dtsi +++ b/dts/arm/xilinx/zynqmp.dtsi @@ -18,6 +18,7 @@ flash0: flash@c0000000 { compatible = "soc-nv-flash"; reg = <0xc0000000 DT_SIZE_M(32)>; + status = "disabled"; }; sram0: memory@0 {