drivers: spi_xlnx_axi_quadspi: Reduce IRQ work
This driver could end up doing a great deal of work inside the ISR when
large SPI transfers were in use, which could cause significant IRQ
latency. For the normal, non-async SPI transfer case, use events to
signal the calling thread to complete the work rather than performing
FIFO transfers inside the ISR.
Signed-off-by: Robert Hancock <robert.hancock@calian.com>
diff --git a/drivers/spi/Kconfig.xlnx b/drivers/spi/Kconfig.xlnx
index 5d3f454..4ce0b67 100644
--- a/drivers/spi/Kconfig.xlnx
+++ b/drivers/spi/Kconfig.xlnx
@@ -7,5 +7,6 @@
bool "Xilinx AXI Quad SPI driver"
default y
depends on DT_HAS_XLNX_XPS_SPI_2_00_A_ENABLED
+ select EVENTS
help
Enable Xilinx AXI Quad SPI v3.2 driver.
diff --git a/drivers/spi/spi_xlnx_axi_quadspi.c b/drivers/spi/spi_xlnx_axi_quadspi.c
index c42166c..4c551b6 100644
--- a/drivers/spi/spi_xlnx_axi_quadspi.c
+++ b/drivers/spi/spi_xlnx_axi_quadspi.c
@@ -11,6 +11,7 @@
#include <zephyr/sys/sys_io.h>
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
+#include <zephyr/kernel.h>
LOG_MODULE_REGISTER(xlnx_quadspi, CONFIG_SPI_LOG_LEVEL);
#include "spi_context.h"
@@ -94,6 +95,7 @@
struct xlnx_quadspi_data {
struct spi_context ctx;
+ struct k_event dtr_empty;
};
static inline uint32_t xlnx_quadspi_read32(const struct device *dev,
@@ -227,7 +229,7 @@
return 0;
}
-static void xlnx_quadspi_start_tx(const struct device *dev)
+static bool xlnx_quadspi_start_tx(const struct device *dev)
{
const struct xlnx_quadspi_config *config = dev->config;
struct xlnx_quadspi_data *data = dev->data;
@@ -237,6 +239,7 @@
uint32_t spisr;
uint32_t dtr = 0U;
uint32_t fifo_avail_words = config->fifo_size ? config->fifo_size : 1;
+ bool complete = false;
if (!spi_context_tx_on(ctx) && !spi_context_rx_on(ctx)) {
/* All done, de-assert slave select */
@@ -250,7 +253,8 @@
}
spi_context_complete(ctx, dev, 0);
- return;
+ complete = true;
+ return complete;
}
if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) {
@@ -318,6 +322,7 @@
SPICR_OFFSET);
spi_context_complete(ctx, dev, -ENOTSUP);
+ complete = true;
}
if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) {
@@ -325,6 +330,47 @@
spicr &= ~(SPICR_MASTER_XFER_INH);
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
}
+ return complete;
+}
+
+static void xlnx_quadspi_read_fifo(const struct device *dev)
+{
+ const struct xlnx_quadspi_config *config = dev->config;
+ struct xlnx_quadspi_data *data = dev->data;
+ struct spi_context *ctx = &data->ctx;
+ uint32_t spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
+ /* RX FIFO occupancy register only exists if FIFO is implemented */
+ uint32_t rx_fifo_words = config->fifo_size ?
+ xlnx_quadspi_read32(dev, SPI_RX_FIFO_OCR_OFFSET) + 1 : 1;
+
+ /* Read RX data */
+ while (!(spisr & SPISR_RX_EMPTY)) {
+ uint32_t drr = xlnx_quadspi_read32(dev, SPI_DRR_OFFSET);
+
+ if (spi_context_rx_buf_on(ctx)) {
+ switch (config->num_xfer_bytes) {
+ case 1:
+ UNALIGNED_PUT(drr, (uint8_t *)ctx->rx_buf);
+ break;
+ case 2:
+ UNALIGNED_PUT(drr, (uint16_t *)ctx->rx_buf);
+ break;
+ case 4:
+ UNALIGNED_PUT(drr, (uint32_t *)ctx->rx_buf);
+ break;
+ default:
+ __ASSERT(0, "unsupported num_xfer_bytes");
+ }
+ }
+
+ spi_context_update_rx(ctx, config->num_xfer_bytes, 1);
+
+ if (--rx_fifo_words == 0) {
+ spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
+ rx_fifo_words = config->fifo_size ?
+ xlnx_quadspi_read32(dev, SPI_RX_FIFO_OCR_OFFSET) + 1 : 1;
+ }
+ }
}
static int xlnx_quadspi_transceive(const struct device *dev,
@@ -352,7 +398,27 @@
xlnx_quadspi_cs_control(dev, true);
- xlnx_quadspi_start_tx(dev);
+ while (true) {
+ k_event_clear(&data->dtr_empty, 1);
+ bool complete = xlnx_quadspi_start_tx(dev);
+
+ if (complete || async) {
+ break;
+ }
+
+ /**
+ * 20ms should be long enough for 256 byte FIFO at any
+ * reasonable clock speed.
+ */
+ if (!k_event_wait(&data->dtr_empty, 1, false,
+ K_MSEC(20 + CONFIG_SPI_COMPLETION_TIMEOUT_TOLERANCE))) {
+ /* Timeout */
+ LOG_ERR("DTR empty timeout");
+ spi_context_complete(ctx, dev, -ETIMEDOUT);
+ break;
+ }
+ xlnx_quadspi_read_fifo(dev);
+ }
ret = spi_context_wait_for_completion(ctx);
out:
@@ -405,9 +471,7 @@
static void xlnx_quadspi_isr(const struct device *dev)
{
- const struct xlnx_quadspi_config *config = dev->config;
struct xlnx_quadspi_data *data = dev->data;
- struct spi_context *ctx = &data->ctx;
uint32_t ipisr;
/* Acknowledge interrupt */
@@ -415,46 +479,20 @@
xlnx_quadspi_write32(dev, ipisr, IPISR_OFFSET);
if (ipisr & IPIXR_DTR_EMPTY) {
- uint32_t spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
- /* RX FIFO occupancy register only exists if FIFO is implemented */
- uint32_t rx_fifo_words = config->fifo_size ?
- xlnx_quadspi_read32(dev, SPI_RX_FIFO_OCR_OFFSET) + 1 : 1;
-
- /* Read RX data */
- while (!(spisr & SPISR_RX_EMPTY)) {
- uint32_t drr = xlnx_quadspi_read32(dev, SPI_DRR_OFFSET);
-
- if (spi_context_rx_buf_on(ctx)) {
- switch (config->num_xfer_bytes) {
- case 1:
- UNALIGNED_PUT(drr,
- (uint8_t *)ctx->rx_buf);
- break;
- case 2:
- UNALIGNED_PUT(drr,
- (uint16_t *)ctx->rx_buf);
- break;
- case 4:
- UNALIGNED_PUT(drr,
- (uint32_t *)ctx->rx_buf);
- break;
- default:
- __ASSERT(0,
- "unsupported num_xfer_bytes");
- }
- }
-
- spi_context_update_rx(ctx, config->num_xfer_bytes, 1);
-
- if (--rx_fifo_words == 0) {
- spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
- rx_fifo_words = config->fifo_size ?
- xlnx_quadspi_read32(dev, SPI_RX_FIFO_OCR_OFFSET) + 1 : 1;
- }
+ /**
+ * 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
+ if (ctx->asynchronous) {
+ xlnx_quadspi_read_fifo(dev);
+ xlnx_quadspi_start_tx(dev);
+ return;
}
-
- /* Start next TX */
- xlnx_quadspi_start_tx(dev);
+#endif
+ k_event_post(&data->dtr_empty, 1);
} else {
LOG_WRN("unhandled interrupt, ipisr = 0x%08x", ipisr);
}
@@ -514,6 +552,8 @@
const struct xlnx_quadspi_config *config = dev->config;
struct xlnx_quadspi_data *data = dev->data;
+ k_event_init(&data->dtr_empty);
+
/* Reset controller */
xlnx_quadspi_write32(dev, SRR_SOFTRESET_MAGIC, SRR_OFFSET);