From 13fd20d063051cddd8b9ab4065a9984218debc13 Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Fri, 17 Apr 2026 16:30:00 +0800 Subject: [PATCH 01/14] anolis: Revert "NVIDIA: SAUCE: spi: tegra210-quad: Check hardware status on timeout" ANBZ: #33323 This reverts commit 1839c043d9dc82e517067fa5083833dafd2a663e. Prepare to use the mainline version. --- drivers/spi/spi-tegra210-quad.c | 165 ++++++++++---------------------- 1 file changed, 50 insertions(+), 115 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index e2118bd7efc3..b372a511f794 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -994,20 +994,15 @@ static void tegra_qspi_dump_regs(struct tegra_qspi *tqspi) tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS)); } -static void tegra_qspi_reset(struct tegra_qspi *tqspi) -{ - if (device_reset(tqspi->dev) < 0) { - dev_warn_once(tqspi->dev, "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } -} - static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) { dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - tegra_qspi_reset(tqspi); + if (device_reset(tqspi->dev) < 0) { + dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1023,49 +1018,6 @@ static void tegra_qspi_transfer_end(struct spi_device *spi) tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); } -static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi); -static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); - -/** - * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check - * @tqspi: QSPI controller instance - * - * When a timeout occurs but hardware has completed the transfer (interrupt - * was lost or delayed), manually trigger transfer completion processing. - * This avoids failing transfers that actually succeeded. - * - * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout - */ -static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) -{ - irqreturn_t ret; - u32 status; - - /* Check if hardware actually completed the transfer */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - if (!(status & QSPI_RDY)) - return -ETIMEDOUT; - - /* - * Hardware completed but interrupt was lost/delayed. Manually - * process the completion by calling the appropriate handler. - */ - dev_warn_ratelimited(tqspi->dev, - "QSPI interrupt timeout, but transfer complete\n"); - - /* Clear the transfer status */ - status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); - tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); - - /* Manually trigger completion handler */ - if (!tqspi->is_curr_dma_xfer) - ret = handle_cpu_based_xfer(tqspi); - else - ret = handle_dma_based_xfer(tqspi); - - return (ret == IRQ_HANDLED) ? 0 : -EIO; -} - static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) { u32 cmd_config = 0; @@ -1097,30 +1049,6 @@ static u32 tegra_qspi_addr_config(bool is_ddr, u8 bus_width, u8 len) return addr_config; } -static void tegra_qspi_dma_stop(struct tegra_qspi *tqspi) -{ - u32 value; - - if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) - dmaengine_terminate_all(tqspi->tx_dma_chan); - - if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) - dmaengine_terminate_all(tqspi->rx_dma_chan); - - value = tegra_qspi_readl(tqspi, QSPI_DMA_CTL); - value &= ~QSPI_DMA_EN; - tegra_qspi_writel(tqspi, value, QSPI_DMA_CTL); -} - -static void tegra_qspi_pio_stop(struct tegra_qspi *tqspi) -{ - u32 value; - - value = tegra_qspi_readl(tqspi, QSPI_COMMAND1); - value &= ~QSPI_PIO; - tegra_qspi_writel(tqspi, value, QSPI_COMMAND1); -} - static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_message *msg) { @@ -1128,7 +1056,7 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_transfer *xfer; struct spi_device *spi = msg->spi; u8 transfer_phase = 0; - u32 cmd1 = 0; + u32 cmd1 = 0, dma_ctl = 0; int ret = 0; u32 address_value = 0; u32 cmd_config = 0, addr_config = 0; @@ -1188,28 +1116,44 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON_ONCE(ret == 0)) { - /* - * Check if hardware completed the transfer - * even though interrupt was lost or delayed. - * If so, process the completion and continue. - */ - ret = tegra_qspi_handle_timeout(tqspi); - if (ret < 0) { - /* Real timeout - clean up and fail */ - dev_err(tqspi->dev, "transfer timeout\n"); - - /* Abort transfer by resetting pio/dma bit */ - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); - else - tegra_qspi_pio_stop(tqspi); - - /* Reset controller if timeout happens */ - tegra_qspi_reset(tqspi); - - ret = -EIO; - goto exit; + dev_err_ratelimited(tqspi->dev, + "QSPI Transfer failed with timeout\n"); + if (tqspi->is_curr_dma_xfer && + (tqspi->cur_direction & DATA_DIR_TX)) + dmaengine_terminate_all + (tqspi->tx_dma_chan); + + if (tqspi->is_curr_dma_xfer && + (tqspi->cur_direction & DATA_DIR_RX)) + dmaengine_terminate_all + (tqspi->rx_dma_chan); + + /* Abort transfer by resetting pio/dma bit */ + if (!tqspi->is_curr_dma_xfer) { + cmd1 = tegra_qspi_readl + (tqspi, + QSPI_COMMAND1); + cmd1 &= ~QSPI_PIO; + tegra_qspi_writel + (tqspi, cmd1, + QSPI_COMMAND1); + } else { + dma_ctl = tegra_qspi_readl + (tqspi, + QSPI_DMA_CTL); + dma_ctl &= ~QSPI_DMA_EN; + tegra_qspi_writel(tqspi, dma_ctl, + QSPI_DMA_CTL); } + + /* Reset controller if timeout happens */ + if (device_reset(tqspi->dev) < 0) { + dev_warn_once(tqspi->dev, + "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } + ret = -EIO; + goto exit; } if (tqspi->tx_status || tqspi->rx_status) { @@ -1300,23 +1244,14 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, ret = wait_for_completion_timeout(&tqspi->xfer_completion, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { - /* - * Check if hardware completed the transfer even though - * interrupt was lost or delayed. If so, process the - * completion and continue. - */ - ret = tegra_qspi_handle_timeout(tqspi); - if (ret < 0) { - /* Real timeout - clean up and fail */ - dev_err(tqspi->dev, "transfer timeout\n"); - - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); - - tegra_qspi_handle_error(tqspi); - ret = -EIO; - goto complete_xfer; - } + dev_err(tqspi->dev, "transfer timeout\n"); + if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_TX)) + dmaengine_terminate_all(tqspi->tx_dma_chan); + if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_RX)) + dmaengine_terminate_all(tqspi->rx_dma_chan); + tegra_qspi_handle_error(tqspi); + ret = -EIO; + goto complete_xfer; } if (tqspi->tx_status || tqspi->rx_status) { -- Gitee From f087c62b1ce0cb0081b8dfe4953f0eb20728e72e Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Fri, 17 Apr 2026 16:30:13 +0800 Subject: [PATCH 02/14] anolis: Revert "NVIDIA: SAUCE: spi: tegra210-quad: Fix timeout handling" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ANBZ: #33323 The SAUCE implementation of tegra_qspi_handle_timeout() directly calls handle_cpu_based_xfer() to manually process a timed-out but hardware-completed transfer. However, the backport was based on an older version of the code that predates upstream commit b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling"), which added a critical `tqspi->curr_xfer = NULL` assignment at the exit path of handle_cpu_based_xfer(). As a result, after tegra_qspi_handle_timeout() successfully invokes handle_cpu_based_xfer(), curr_xfer is left pointing to a completed (and potentially freed) transfer. Any subsequent access to curr_xfer — for example in the ISR thread or the spinlock-protected handlers added by later fixes — may dereference a stale pointer. The correct fix is the upstream patch 380fd29d57ab ("spi: tegra210-quad: Check hardware status on timeout"), which was developed on top of both b4e002d8a7ce and 6022eacdda8b and properly handles curr_xfer lifetime. Revert the SAUCE patch and replace it with the correct upstream version. This reverts commit 7ecf6847bddc1f6badab1ada9530f5cbb288fbec. --- drivers/spi/spi-tegra210-quad.c | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index b372a511f794..e9afebd72423 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -999,10 +999,8 @@ static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - if (device_reset(tqspi->dev) < 0) { + if (device_reset(tqspi->dev) < 0) dev_warn_once(tqspi->dev, "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1146,14 +1144,12 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_CTL); } - /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) { - dev_warn_once(tqspi->dev, - "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } - ret = -EIO; - goto exit; + /* Reset controller if timeout happens */ + if (device_reset(tqspi->dev) < 0) + dev_warn_once(tqspi->dev, + "device reset failed\n"); + ret = -EIO; + goto exit; } if (tqspi->tx_status || tqspi->rx_status) { @@ -1169,7 +1165,6 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, goto exit; } msg->actual_length += xfer->len; - tqspi->curr_xfer = NULL; if (!xfer->cs_change && transfer_phase == DATA_TRANSFER) { tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); @@ -1179,7 +1174,6 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, ret = 0; exit: - tqspi->curr_xfer = NULL; msg->status = ret; return ret; @@ -1263,8 +1257,6 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: - tqspi->curr_xfer = NULL; - if (ret < 0) { tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); @@ -1444,15 +1436,6 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; - /* - * Occasionally the IRQ thread takes a long time to wake up (usually - * when the CPU that it's running on is excessively busy) and we have - * already reached the timeout before and cleaned up the timed out - * transfer. Avoid any processing in that case and bail out early. - */ - if (tqspi->curr_xfer == NULL) - return IRQ_NONE; - tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) -- Gitee From d56403584b199fed1bbd442886e6b93b91c0ef32 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Wed, 16 Apr 2025 11:06:05 +0000 Subject: [PATCH 03/14] spi: tegra210-quad: Update dummy sequence configuration ANBZ: #33323 commit c283fcdc4e2b89678c171691fd26f576139fc256 upstream. Adding support for the dummy sequence configuration. The dummy sequence introduces a delay between the command and the data phases of a transfer. This delay, measured in clock cycles, allows the slave device to prepare for data transmission, ensuring data integrity and proper synchronization. Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20250416110606.2737315-6-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index e9afebd72423..fbb895855687 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -22,6 +22,7 @@ #include #include #include +#include #define QSPI_COMMAND1 0x000 #define QSPI_BIT_LENGTH(x) (((x) & 0x1f) << 0) @@ -156,10 +157,14 @@ #define DATA_DIR_RX BIT(1) #define QSPI_DMA_TIMEOUT (msecs_to_jiffies(1000)) -#define DEFAULT_QSPI_DMA_BUF_LEN (64 * 1024) -#define CMD_TRANSFER 0 -#define ADDR_TRANSFER 1 -#define DATA_TRANSFER 2 +#define DEFAULT_QSPI_DMA_BUF_LEN SZ_64K + +enum tegra_qspi_transfer_type { + CMD_TRANSFER = 0, + ADDR_TRANSFER = 1, + DUMMY_TRANSFER = 2, + DATA_TRANSFER = 3 +}; struct tegra_qspi_soc_data { bool has_dma; @@ -1085,6 +1090,13 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, xfer->len); address_value = *((const u32 *)(xfer->tx_buf)); break; + case DUMMY_TRANSFER: + if (xfer->dummy_data) { + tqspi->dummy_cycles = xfer->len * 8 / xfer->tx_nbits; + break; + } + transfer_phase++; + fallthrough; case DATA_TRANSFER: /* Program Command, Address value in register */ tegra_qspi_writel(tqspi, cmd_value, QSPI_CMB_SEQ_CMD); @@ -1292,7 +1304,9 @@ static bool tegra_qspi_validate_cmb_seq(struct tegra_qspi *tqspi, list_for_each_entry(xfer, &msg->transfers, transfer_list) { transfer_count++; } - if (!tqspi->soc_data->cmb_xfer_capable || transfer_count != 3) + if (!tqspi->soc_data->cmb_xfer_capable) + return false; + if (transfer_count > 4 || transfer_count < 3) return false; xfer = list_first_entry(&msg->transfers, typeof(*xfer), transfer_list); @@ -1302,6 +1316,13 @@ static bool tegra_qspi_validate_cmb_seq(struct tegra_qspi *tqspi, if (xfer->len > 4 || xfer->len < 3) return false; xfer = list_next_entry(xfer, transfer_list); + if (transfer_count == 4) { + if (xfer->dummy_data != 1) + return false; + if ((xfer->len * 8 / xfer->tx_nbits) > QSPI_DUMMY_CYCLES_MAX) + return false; + xfer = list_next_entry(xfer, transfer_list); + } if (!tqspi->soc_data->has_dma && xfer->len > (QSPI_FIFO_DEPTH << 2)) return false; -- Gitee From 9224451f327f3176bfb158609fc722d42ffe3e33 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 13 May 2025 20:00:43 +0000 Subject: [PATCH 04/14] spi: tegra210-quad: Add support for internal DMA ANBZ: #33323 commit 017f1b0bae08e8b456cf35cbdaae93ec19b50f0a upstream. Add support for internal DMA in Tegra234 devices. Tegra234 has an internal DMA controller, while Tegra241 continues to use an external DMA controller (GPCDMA). This patch adds support for both internal and external DMA controllers. Signed-off-by: Vishwaroop A Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20250513200043.608292-2-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 225 +++++++++++++++++++------------- 1 file changed, 131 insertions(+), 94 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index fbb895855687..e7393f8611f0 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -111,6 +111,9 @@ #define QSPI_DMA_BLK 0x024 #define QSPI_DMA_BLK_SET(x) (((x) & 0xffff) << 0) +#define QSPI_DMA_MEM_ADDRESS 0x028 +#define QSPI_DMA_HI_ADDRESS 0x02c + #define QSPI_TX_FIFO 0x108 #define QSPI_RX_FIFO 0x188 @@ -167,9 +170,9 @@ enum tegra_qspi_transfer_type { }; struct tegra_qspi_soc_data { - bool has_dma; bool cmb_xfer_capable; bool supports_tpm; + bool has_ext_dma; unsigned int cs_count; }; @@ -605,13 +608,16 @@ static void tegra_qspi_dma_unmap_xfer(struct tegra_qspi *tqspi, struct spi_trans len = DIV_ROUND_UP(tqspi->curr_dma_words * tqspi->bytes_per_word, 4) * 4; - dma_unmap_single(tqspi->dev, t->tx_dma, len, DMA_TO_DEVICE); - dma_unmap_single(tqspi->dev, t->rx_dma, len, DMA_FROM_DEVICE); + if (t->tx_buf) + dma_unmap_single(tqspi->dev, t->tx_dma, len, DMA_TO_DEVICE); + if (t->rx_buf) + dma_unmap_single(tqspi->dev, t->rx_dma, len, DMA_FROM_DEVICE); } static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct spi_transfer *t) { struct dma_slave_config dma_sconfig = { 0 }; + dma_addr_t rx_dma_phys, tx_dma_phys; unsigned int len; u8 dma_burst; int ret = 0; @@ -634,60 +640,86 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct len = tqspi->curr_dma_words * 4; /* set attention level based on length of transfer */ - val = 0; - if (len & 0xf) { - val |= QSPI_TX_TRIG_1 | QSPI_RX_TRIG_1; - dma_burst = 1; - } else if (((len) >> 4) & 0x1) { - val |= QSPI_TX_TRIG_4 | QSPI_RX_TRIG_4; - dma_burst = 4; - } else { - val |= QSPI_TX_TRIG_8 | QSPI_RX_TRIG_8; - dma_burst = 8; + if (tqspi->soc_data->has_ext_dma) { + val = 0; + if (len & 0xf) { + val |= QSPI_TX_TRIG_1 | QSPI_RX_TRIG_1; + dma_burst = 1; + } else if (((len) >> 4) & 0x1) { + val |= QSPI_TX_TRIG_4 | QSPI_RX_TRIG_4; + dma_burst = 4; + } else { + val |= QSPI_TX_TRIG_8 | QSPI_RX_TRIG_8; + dma_burst = 8; + } + + tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); } - tegra_qspi_writel(tqspi, val, QSPI_DMA_CTL); tqspi->dma_control_reg = val; dma_sconfig.device_fc = true; + if (tqspi->cur_direction & DATA_DIR_TX) { - dma_sconfig.dst_addr = tqspi->phys + QSPI_TX_FIFO; - dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - dma_sconfig.dst_maxburst = dma_burst; - ret = dmaengine_slave_config(tqspi->tx_dma_chan, &dma_sconfig); - if (ret < 0) { - dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); - return ret; - } + if (tqspi->tx_dma_chan) { + dma_sconfig.dst_addr = tqspi->phys + QSPI_TX_FIFO; + dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.dst_maxburst = dma_burst; + ret = dmaengine_slave_config(tqspi->tx_dma_chan, &dma_sconfig); + if (ret < 0) { + dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); + return ret; + } - tegra_qspi_copy_client_txbuf_to_qspi_txbuf(tqspi, t); - ret = tegra_qspi_start_tx_dma(tqspi, t, len); - if (ret < 0) { - dev_err(tqspi->dev, "failed to starting TX DMA: %d\n", ret); - return ret; + tegra_qspi_copy_client_txbuf_to_qspi_txbuf(tqspi, t); + ret = tegra_qspi_start_tx_dma(tqspi, t, len); + if (ret < 0) { + dev_err(tqspi->dev, "failed to starting TX DMA: %d\n", ret); + return ret; + } + } else { + if (tqspi->is_packed) + tx_dma_phys = t->tx_dma; + else + tx_dma_phys = tqspi->tx_dma_phys; + tegra_qspi_copy_client_txbuf_to_qspi_txbuf(tqspi, t); + tegra_qspi_writel(tqspi, lower_32_bits(tx_dma_phys), + QSPI_DMA_MEM_ADDRESS); + tegra_qspi_writel(tqspi, (upper_32_bits(tx_dma_phys) & 0xff), + QSPI_DMA_HI_ADDRESS); } } if (tqspi->cur_direction & DATA_DIR_RX) { - dma_sconfig.src_addr = tqspi->phys + QSPI_RX_FIFO; - dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - dma_sconfig.src_maxburst = dma_burst; - ret = dmaengine_slave_config(tqspi->rx_dma_chan, &dma_sconfig); - if (ret < 0) { - dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); - return ret; - } - - dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, - tqspi->dma_buf_size, - DMA_FROM_DEVICE); + if (tqspi->rx_dma_chan) { + dma_sconfig.src_addr = tqspi->phys + QSPI_RX_FIFO; + dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.src_maxburst = dma_burst; + ret = dmaengine_slave_config(tqspi->rx_dma_chan, &dma_sconfig); + if (ret < 0) { + dev_err(tqspi->dev, "failed DMA slave config: %d\n", ret); + return ret; + } - ret = tegra_qspi_start_rx_dma(tqspi, t, len); - if (ret < 0) { - dev_err(tqspi->dev, "failed to start RX DMA: %d\n", ret); - if (tqspi->cur_direction & DATA_DIR_TX) - dmaengine_terminate_all(tqspi->tx_dma_chan); - return ret; + dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, + tqspi->dma_buf_size, DMA_FROM_DEVICE); + ret = tegra_qspi_start_rx_dma(tqspi, t, len); + if (ret < 0) { + dev_err(tqspi->dev, "failed to start RX DMA: %d\n", ret); + if (tqspi->cur_direction & DATA_DIR_TX) + dmaengine_terminate_all(tqspi->tx_dma_chan); + return ret; + } + } else { + if (tqspi->is_packed) + rx_dma_phys = t->rx_dma; + else + rx_dma_phys = tqspi->rx_dma_phys; + + tegra_qspi_writel(tqspi, lower_32_bits(rx_dma_phys), + QSPI_DMA_MEM_ADDRESS); + tegra_qspi_writel(tqspi, (upper_32_bits(rx_dma_phys) & 0xff), + QSPI_DMA_HI_ADDRESS); } } @@ -726,9 +758,6 @@ static int tegra_qspi_start_cpu_based_transfer(struct tegra_qspi *qspi, struct s static void tegra_qspi_deinit_dma(struct tegra_qspi *tqspi) { - if (!tqspi->soc_data->has_dma) - return; - if (tqspi->tx_dma_buf) { dma_free_coherent(tqspi->dev, tqspi->dma_buf_size, tqspi->tx_dma_buf, tqspi->tx_dma_phys); @@ -759,16 +788,29 @@ static int tegra_qspi_init_dma(struct tegra_qspi *tqspi) u32 *dma_buf; int err; - if (!tqspi->soc_data->has_dma) - return 0; + if (tqspi->soc_data->has_ext_dma) { + dma_chan = dma_request_chan(tqspi->dev, "rx"); + if (IS_ERR(dma_chan)) { + err = PTR_ERR(dma_chan); + goto err_out; + } - dma_chan = dma_request_chan(tqspi->dev, "rx"); - if (IS_ERR(dma_chan)) { - err = PTR_ERR(dma_chan); - goto err_out; - } + tqspi->rx_dma_chan = dma_chan; - tqspi->rx_dma_chan = dma_chan; + dma_chan = dma_request_chan(tqspi->dev, "tx"); + if (IS_ERR(dma_chan)) { + err = PTR_ERR(dma_chan); + goto err_out; + } + + tqspi->tx_dma_chan = dma_chan; + } else { + if (!device_iommu_mapped(tqspi->dev)) { + dev_warn(tqspi->dev, + "IOMMU not enabled in device-tree, falling back to PIO mode\n"); + return 0; + } + } dma_buf = dma_alloc_coherent(tqspi->dev, tqspi->dma_buf_size, &dma_phys, GFP_KERNEL); if (!dma_buf) { @@ -779,14 +821,6 @@ static int tegra_qspi_init_dma(struct tegra_qspi *tqspi) tqspi->rx_dma_buf = dma_buf; tqspi->rx_dma_phys = dma_phys; - dma_chan = dma_request_chan(tqspi->dev, "tx"); - if (IS_ERR(dma_chan)) { - err = PTR_ERR(dma_chan); - goto err_out; - } - - tqspi->tx_dma_chan = dma_chan; - dma_buf = dma_alloc_coherent(tqspi->dev, tqspi->dma_buf_size, &dma_phys, GFP_KERNEL); if (!dma_buf) { err = -ENOMEM; @@ -1128,15 +1162,14 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (WARN_ON_ONCE(ret == 0)) { dev_err_ratelimited(tqspi->dev, "QSPI Transfer failed with timeout\n"); - if (tqspi->is_curr_dma_xfer && - (tqspi->cur_direction & DATA_DIR_TX)) - dmaengine_terminate_all - (tqspi->tx_dma_chan); - - if (tqspi->is_curr_dma_xfer && - (tqspi->cur_direction & DATA_DIR_RX)) - dmaengine_terminate_all - (tqspi->rx_dma_chan); + if (tqspi->is_curr_dma_xfer) { + if ((tqspi->cur_direction & DATA_DIR_TX) && + tqspi->tx_dma_chan) + dmaengine_terminate_all(tqspi->tx_dma_chan); + if ((tqspi->cur_direction & DATA_DIR_RX) && + tqspi->rx_dma_chan) + dmaengine_terminate_all(tqspi->rx_dma_chan); + } /* Abort transfer by resetting pio/dma bit */ if (!tqspi->is_curr_dma_xfer) { @@ -1251,10 +1284,12 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { dev_err(tqspi->dev, "transfer timeout\n"); - if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_TX)) - dmaengine_terminate_all(tqspi->tx_dma_chan); - if (tqspi->is_curr_dma_xfer && (tqspi->cur_direction & DATA_DIR_RX)) - dmaengine_terminate_all(tqspi->rx_dma_chan); + if (tqspi->is_curr_dma_xfer) { + if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) + dmaengine_terminate_all(tqspi->tx_dma_chan); + if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) + dmaengine_terminate_all(tqspi->rx_dma_chan); + } tegra_qspi_handle_error(tqspi); ret = -EIO; goto complete_xfer; @@ -1323,7 +1358,7 @@ static bool tegra_qspi_validate_cmb_seq(struct tegra_qspi *tqspi, return false; xfer = list_next_entry(xfer, transfer_list); } - if (!tqspi->soc_data->has_dma && xfer->len > (QSPI_FIFO_DEPTH << 2)) + if (!tqspi->soc_data->has_ext_dma && xfer->len > (QSPI_FIFO_DEPTH << 2)) return false; return true; @@ -1384,41 +1419,43 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) unsigned int total_fifo_words; unsigned long flags; long wait_status; - int err = 0; + int num_errors = 0; if (tqspi->cur_direction & DATA_DIR_TX) { if (tqspi->tx_status) { - dmaengine_terminate_all(tqspi->tx_dma_chan); - err += 1; - } else { + if (tqspi->tx_dma_chan) + dmaengine_terminate_all(tqspi->tx_dma_chan); + num_errors++; + } else if (tqspi->tx_dma_chan) { wait_status = wait_for_completion_interruptible_timeout( &tqspi->tx_dma_complete, QSPI_DMA_TIMEOUT); if (wait_status <= 0) { dmaengine_terminate_all(tqspi->tx_dma_chan); dev_err(tqspi->dev, "failed TX DMA transfer\n"); - err += 1; + num_errors++; } } } if (tqspi->cur_direction & DATA_DIR_RX) { if (tqspi->rx_status) { - dmaengine_terminate_all(tqspi->rx_dma_chan); - err += 2; - } else { + if (tqspi->rx_dma_chan) + dmaengine_terminate_all(tqspi->rx_dma_chan); + num_errors++; + } else if (tqspi->rx_dma_chan) { wait_status = wait_for_completion_interruptible_timeout( &tqspi->rx_dma_complete, QSPI_DMA_TIMEOUT); if (wait_status <= 0) { dmaengine_terminate_all(tqspi->rx_dma_chan); dev_err(tqspi->dev, "failed RX DMA transfer\n"); - err += 2; + num_errors++; } } } spin_lock_irqsave(&tqspi->lock, flags); - if (err) { + if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); tegra_qspi_handle_error(tqspi); complete(&tqspi->xfer_completion); @@ -1444,9 +1481,9 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) /* continue transfer in current message */ total_fifo_words = tegra_qspi_calculate_curr_xfer_param(tqspi, t); if (total_fifo_words > QSPI_FIFO_DEPTH) - err = tegra_qspi_start_dma_based_transfer(tqspi, t); + num_errors = tegra_qspi_start_dma_based_transfer(tqspi, t); else - err = tegra_qspi_start_cpu_based_transfer(tqspi, t); + num_errors = tegra_qspi_start_cpu_based_transfer(tqspi, t); exit: spin_unlock_irqrestore(&tqspi->lock, flags); @@ -1474,28 +1511,28 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) } static struct tegra_qspi_soc_data tegra210_qspi_soc_data = { - .has_dma = true, + .has_ext_dma = true, .cmb_xfer_capable = false, .supports_tpm = false, .cs_count = 1, }; static struct tegra_qspi_soc_data tegra186_qspi_soc_data = { - .has_dma = true, + .has_ext_dma = true, .cmb_xfer_capable = true, .supports_tpm = false, .cs_count = 1, }; static struct tegra_qspi_soc_data tegra234_qspi_soc_data = { - .has_dma = false, + .has_ext_dma = false, .cmb_xfer_capable = true, .supports_tpm = true, .cs_count = 1, }; static struct tegra_qspi_soc_data tegra241_qspi_soc_data = { - .has_dma = false, + .has_ext_dma = true, .cmb_xfer_capable = true, .supports_tpm = true, .cs_count = 4, -- Gitee From ba0e51c531b693b4bd8e146a3dfcb7641b4c2ed1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 13 Jun 2025 14:30:37 +0200 Subject: [PATCH 05/14] spi: tegra210-qspi: Remove cache operations ANBZ: #33323 commit d57e92dd660014ccac884eda616cafc7b04601e0 upstream. The DMA memory for this driver is allocated using dma_alloc_coherent(), which ends up mapping the allocated memory as uncached. Performing the various dma_sync_*() operations on this memory causes issues during SPI flashing: [ 7.818017] pc : dcache_inval_poc+0x40/0x58 [ 7.822128] lr : arch_sync_dma_for_cpu+0x2c/0x4c [ 7.826854] sp : ffff80008193bcf0 [ 7.830267] x29: ffff80008193bcf0 x28: ffffa3fe5ff1e908 x27: ffffa3fe627bb130 [ 7.837528] x26: ffff000086952180 x25: ffff00008015c8ac x24: ffff000086c9b480 [ 7.844878] x23: ffff00008015c800 x22: 0000000000000002 x21: 0000000000010000 [ 7.852229] x20: 0000000106dae000 x19: ffff000080112410 x18: 0000000000000001 [ 7.859580] x17: ffff000080159400 x16: ffffa3fe607a9bd8 x15: ffff0000eac1b180 [ 7.866753] x14: 000000000000000c x13: 0000000000000001 x12: 000000000000025a [ 7.874104] x11: 0000000000000000 x10: 7f73e96357f6a07f x9 : db1fc8072a7f5e3a [ 7.881365] x8 : ffff000086c9c588 x7 : ffffa3fe607a9bd8 x6 : ffff80008193bc28 [ 7.888630] x5 : 000000000000ffff x4 : 0000000000000009 x3 : 000000000000003f [ 7.895892] x2 : 0000000000000040 x1 : ffff000086dbe000 x0 : ffff000086db0000 [ 7.903155] Call trace: [ 7.905606] dcache_inval_poc+0x40/0x58 (P) [ 7.909804] iommu_dma_sync_single_for_cpu+0xb4/0xb8 [ 7.914617] __dma_sync_single_for_cpu+0x158/0x194 [ 7.919428] __this_module+0x5b020/0x5baf8 [spi_tegra210_quad] [ 7.925291] irq_thread_fn+0x2c/0xc0 [ 7.928966] irq_thread+0x16c/0x318 [ 7.932467] kthread+0x12c/0x214 Fix this by removing all calls to the dma_sync_*() functions. This isn't ideal because DMA is used only for relatively large (> 64 words or 256 bytes) and using uncached memory for this might be slow. Reworking this to use cached memory for faster access and reintroducing the cache maintenance calls is probably worth a follow-up patch. Reported-by: Brad Griffis Fixes: 017f1b0bae08 ("spi: tegra210-quad: Add support for internal DMA") Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20250613123037.2082788-1-thierry.reding@gmail.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index e7393f8611f0..92d236b17e08 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -407,9 +407,6 @@ tegra_qspi_read_rx_fifo_to_client_rxbuf(struct tegra_qspi *tqspi, struct spi_tra static void tegra_qspi_copy_client_txbuf_to_qspi_txbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) { - dma_sync_single_for_cpu(tqspi->dev, tqspi->tx_dma_phys, - tqspi->dma_buf_size, DMA_TO_DEVICE); - /* * In packed mode, each word in FIFO may contain multiple packets * based on bits per word. So all bytes in each FIFO word are valid. @@ -442,17 +439,11 @@ tegra_qspi_copy_client_txbuf_to_qspi_txbuf(struct tegra_qspi *tqspi, struct spi_ tqspi->cur_tx_pos += write_bytes; } - - dma_sync_single_for_device(tqspi->dev, tqspi->tx_dma_phys, - tqspi->dma_buf_size, DMA_TO_DEVICE); } static void tegra_qspi_copy_qspi_rxbuf_to_client_rxbuf(struct tegra_qspi *tqspi, struct spi_transfer *t) { - dma_sync_single_for_cpu(tqspi->dev, tqspi->rx_dma_phys, - tqspi->dma_buf_size, DMA_FROM_DEVICE); - if (tqspi->is_packed) { tqspi->cur_rx_pos += tqspi->curr_dma_words * tqspi->bytes_per_word; } else { @@ -478,9 +469,6 @@ tegra_qspi_copy_qspi_rxbuf_to_client_rxbuf(struct tegra_qspi *tqspi, struct spi_ tqspi->cur_rx_pos += read_bytes; } - - dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, - tqspi->dma_buf_size, DMA_FROM_DEVICE); } static void tegra_qspi_dma_complete(void *args) @@ -701,8 +689,6 @@ static int tegra_qspi_start_dma_based_transfer(struct tegra_qspi *tqspi, struct return ret; } - dma_sync_single_for_device(tqspi->dev, tqspi->rx_dma_phys, - tqspi->dma_buf_size, DMA_FROM_DEVICE); ret = tegra_qspi_start_rx_dma(tqspi, t, len); if (ret < 0) { dev_err(tqspi->dev, "failed to start RX DMA: %d\n", ret); -- Gitee From 10a5ddb0f138b3e737b89ae9c150cd9609f89ff2 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:01 +0000 Subject: [PATCH 06/14] spi: tegra210-quad: Fix timeout handling ANBZ: #33323 commit b4e002d8a7cee3b1d70efad0e222567f92a73000 upstream. When the CPU that the QSPI interrupt handler runs on (typically CPU 0) is excessively busy, it can lead to rare cases of the IRQ thread not running before the transfer timeout is reached. While handling the timeouts, any pending transfers are cleaned up and the message that they correspond to is marked as failed, which leaves the curr_xfer field pointing at stale memory. To avoid this, clear curr_xfer to NULL upon timeout and check for this condition when the IRQ thread is finally run. While at it, also make sure to clear interrupts on failure so that new interrupts can be run. A better, more involved, fix would move the interrupt clearing into a hard IRQ handler. Ideally we would also want to signal that the IRQ thread no longer needs to be run after the timeout is hit to avoid the extra check for a valid transfer. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Thierry Reding Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20251028155703.4151791-2-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 92d236b17e08..b69b691fee66 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1024,8 +1024,10 @@ static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); tegra_qspi_dump_regs(tqspi); tegra_qspi_flush_fifos(tqspi, true); - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1176,9 +1178,11 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, } /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) + if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_mask_clear_irq(tqspi); + } ret = -EIO; goto exit; } @@ -1200,11 +1204,13 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); } + tqspi->curr_xfer = NULL; transfer_phase++; } ret = 0; exit: + tqspi->curr_xfer = NULL; msg->status = ret; return ret; @@ -1290,6 +1296,8 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: + tqspi->curr_xfer = NULL; + if (ret < 0) { tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); @@ -1395,6 +1403,7 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) tegra_qspi_calculate_curr_xfer_param(tqspi, t); tegra_qspi_start_cpu_based_transfer(tqspi, t); exit: + tqspi->curr_xfer = NULL; spin_unlock_irqrestore(&tqspi->lock, flags); return IRQ_HANDLED; } @@ -1480,6 +1489,15 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + /* + * Occasionally the IRQ thread takes a long time to wake up (usually + * when the CPU that it's running on is excessively busy) and we have + * already reached the timeout before and cleaned up the timed out + * transfer. Avoid any processing in that case and bail out early. + */ + if (!tqspi->curr_xfer) + return IRQ_NONE; + tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); if (tqspi->cur_direction & DATA_DIR_TX) -- Gitee From 3f3691f3e8da12a5bb2c9c53ee525b94beeaf622 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:02 +0000 Subject: [PATCH 07/14] spi: tegra210-quad: Refactor error handling into helper functions ANBZ: #33323 commit 6022eacdda8b0b06a2e1d4122e5268099b62ff5d upstream. Extract common cleanup code into dedicated helper functions to simplify the code and improve readability. This refactoring includes: - tegra_qspi_reset(): Device reset and interrupt cleanup - tegra_qspi_dma_stop(): DMA termination and disable - tegra_qspi_pio_stop(): PIO mode disable No functional changes. This is purely a code reorganization to prepare for improved timeout handling in subsequent patches. Signed-off-by: Vishwaroop A Acked-by: Thierry Reding Link: https://patch.msgid.link/20251028155703.4151791-3-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 84 +++++++++++++++++---------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index b69b691fee66..17067dc1349e 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1019,17 +1019,22 @@ static void tegra_qspi_dump_regs(struct tegra_qspi *tqspi) tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS)); } -static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) +static void tegra_qspi_reset(struct tegra_qspi *tqspi) { - dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); - tegra_qspi_dump_regs(tqspi); - tegra_qspi_flush_fifos(tqspi, true); if (device_reset(tqspi->dev) < 0) { dev_warn_once(tqspi->dev, "device reset failed\n"); tegra_qspi_mask_clear_irq(tqspi); } } +static void tegra_qspi_handle_error(struct tegra_qspi *tqspi) +{ + dev_err(tqspi->dev, "error in transfer, fifo status 0x%08x\n", tqspi->status_reg); + tegra_qspi_dump_regs(tqspi); + tegra_qspi_flush_fifos(tqspi, true); + tegra_qspi_reset(tqspi); +} + static void tegra_qspi_transfer_end(struct spi_device *spi) { struct tegra_qspi *tqspi = spi_master_get_devdata(spi->master); @@ -1074,6 +1079,30 @@ static u32 tegra_qspi_addr_config(bool is_ddr, u8 bus_width, u8 len) return addr_config; } +static void tegra_qspi_dma_stop(struct tegra_qspi *tqspi) +{ + u32 value; + + if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) + dmaengine_terminate_all(tqspi->tx_dma_chan); + + if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) + dmaengine_terminate_all(tqspi->rx_dma_chan); + + value = tegra_qspi_readl(tqspi, QSPI_DMA_CTL); + value &= ~QSPI_DMA_EN; + tegra_qspi_writel(tqspi, value, QSPI_DMA_CTL); +} + +static void tegra_qspi_pio_stop(struct tegra_qspi *tqspi) +{ + u32 value; + + value = tegra_qspi_readl(tqspi, QSPI_COMMAND1); + value &= ~QSPI_PIO; + tegra_qspi_writel(tqspi, value, QSPI_COMMAND1); +} + static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_message *msg) { @@ -1081,7 +1110,7 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_transfer *xfer; struct spi_device *spi = msg->spi; u8 transfer_phase = 0; - u32 cmd1 = 0, dma_ctl = 0; + u32 cmd1 = 0; int ret = 0; u32 address_value = 0; u32 cmd_config = 0, addr_config = 0; @@ -1150,39 +1179,16 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, if (WARN_ON_ONCE(ret == 0)) { dev_err_ratelimited(tqspi->dev, "QSPI Transfer failed with timeout\n"); - if (tqspi->is_curr_dma_xfer) { - if ((tqspi->cur_direction & DATA_DIR_TX) && - tqspi->tx_dma_chan) - dmaengine_terminate_all(tqspi->tx_dma_chan); - if ((tqspi->cur_direction & DATA_DIR_RX) && - tqspi->rx_dma_chan) - dmaengine_terminate_all(tqspi->rx_dma_chan); - } /* Abort transfer by resetting pio/dma bit */ - if (!tqspi->is_curr_dma_xfer) { - cmd1 = tegra_qspi_readl - (tqspi, - QSPI_COMMAND1); - cmd1 &= ~QSPI_PIO; - tegra_qspi_writel - (tqspi, cmd1, - QSPI_COMMAND1); - } else { - dma_ctl = tegra_qspi_readl - (tqspi, - QSPI_DMA_CTL); - dma_ctl &= ~QSPI_DMA_EN; - tegra_qspi_writel(tqspi, dma_ctl, - QSPI_DMA_CTL); - } + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + else + tegra_qspi_pio_stop(tqspi); /* Reset controller if timeout happens */ - if (device_reset(tqspi->dev) < 0) { - dev_warn_once(tqspi->dev, - "device reset failed\n"); - tegra_qspi_mask_clear_irq(tqspi); - } + tegra_qspi_reset(tqspi); + ret = -EIO; goto exit; } @@ -1276,12 +1282,10 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { dev_err(tqspi->dev, "transfer timeout\n"); - if (tqspi->is_curr_dma_xfer) { - if ((tqspi->cur_direction & DATA_DIR_TX) && tqspi->tx_dma_chan) - dmaengine_terminate_all(tqspi->tx_dma_chan); - if ((tqspi->cur_direction & DATA_DIR_RX) && tqspi->rx_dma_chan) - dmaengine_terminate_all(tqspi->rx_dma_chan); - } + + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + tegra_qspi_handle_error(tqspi); ret = -EIO; goto complete_xfer; -- Gitee From 81398f3fb481f7562bceb73a8faa0d710a4adf64 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:03 +0000 Subject: [PATCH 08/14] spi: tegra210-quad: Check hardware status on timeout ANBZ: #33323 commit 380fd29d57abe6679d87ec56babe65ddc5873a37 upstream. Under high system load, QSPI interrupts can be delayed or blocked on the target CPU, causing wait_for_completion_timeout() to report failure even though the hardware successfully completed the transfer. When a timeout occurs, check the QSPI_RDY bit in QSPI_TRANS_STATUS to determine if the hardware actually completed the transfer. If so, manually invoke the completion handler to process the transfer successfully instead of failing it. This distinguishes lost/delayed interrupts from real hardware timeouts, preventing unnecessary failures of transfers that completed successfully. Signed-off-by: Vishwaroop A Acked-by: Thierry Reding Link: https://patch.msgid.link/20251028155703.4151791-4-va@nvidia.com Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 100 +++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 17067dc1349e..5d2dcd1be880 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1048,6 +1048,49 @@ static void tegra_qspi_transfer_end(struct spi_device *spi) tegra_qspi_writel(tqspi, tqspi->def_command1_reg, QSPI_COMMAND1); } +static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi); +static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi); + +/** + * tegra_qspi_handle_timeout - Handle transfer timeout with hardware check + * @tqspi: QSPI controller instance + * + * When a timeout occurs but hardware has completed the transfer (interrupt + * was lost or delayed), manually trigger transfer completion processing. + * This avoids failing transfers that actually succeeded. + * + * Returns: 0 if transfer was completed, -ETIMEDOUT if real timeout + */ +static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi) +{ + irqreturn_t ret; + u32 status; + + /* Check if hardware actually completed the transfer */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + if (!(status & QSPI_RDY)) + return -ETIMEDOUT; + + /* + * Hardware completed but interrupt was lost/delayed. Manually + * process the completion by calling the appropriate handler. + */ + dev_warn_ratelimited(tqspi->dev, + "QSPI interrupt timeout, but transfer complete\n"); + + /* Clear the transfer status */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); + tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS); + + /* Manually trigger completion handler */ + if (!tqspi->is_curr_dma_xfer) + ret = handle_cpu_based_xfer(tqspi); + else + ret = handle_dma_based_xfer(tqspi); + + return (ret == IRQ_HANDLED) ? 0 : -EIO; +} + static u32 tegra_qspi_cmd_config(bool is_ddr, u8 bus_width, u8 len) { u32 cmd_config = 0; @@ -1177,20 +1220,28 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, QSPI_DMA_TIMEOUT); if (WARN_ON_ONCE(ret == 0)) { - dev_err_ratelimited(tqspi->dev, - "QSPI Transfer failed with timeout\n"); - - /* Abort transfer by resetting pio/dma bit */ - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); - else - tegra_qspi_pio_stop(tqspi); - - /* Reset controller if timeout happens */ - tegra_qspi_reset(tqspi); - - ret = -EIO; - goto exit; + /* + * Check if hardware completed the transfer + * even though interrupt was lost or delayed. + * If so, process the completion and continue. + */ + ret = tegra_qspi_handle_timeout(tqspi); + if (ret < 0) { + /* Real timeout - clean up and fail */ + dev_err(tqspi->dev, "transfer timeout\n"); + + /* Abort transfer by resetting pio/dma bit */ + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); + else + tegra_qspi_pio_stop(tqspi); + + /* Reset controller if timeout happens */ + tegra_qspi_reset(tqspi); + + ret = -EIO; + goto exit; + } } if (tqspi->tx_status || tqspi->rx_status) { @@ -1281,14 +1332,23 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, ret = wait_for_completion_timeout(&tqspi->xfer_completion, QSPI_DMA_TIMEOUT); if (WARN_ON(ret == 0)) { - dev_err(tqspi->dev, "transfer timeout\n"); + /* + * Check if hardware completed the transfer even though + * interrupt was lost or delayed. If so, process the + * completion and continue. + */ + ret = tegra_qspi_handle_timeout(tqspi); + if (ret < 0) { + /* Real timeout - clean up and fail */ + dev_err(tqspi->dev, "transfer timeout\n"); - if (tqspi->is_curr_dma_xfer) - tegra_qspi_dma_stop(tqspi); + if (tqspi->is_curr_dma_xfer) + tegra_qspi_dma_stop(tqspi); - tegra_qspi_handle_error(tqspi); - ret = -EIO; - goto complete_xfer; + tegra_qspi_handle_error(tqspi); + ret = -EIO; + goto complete_xfer; + } } if (tqspi->tx_status || tqspi->rx_status) { -- Gitee From d666b45378e0f64a32cfee9c168df088985b5755 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:26 -0800 Subject: [PATCH 09/14] spi: tegra210-quad: Return IRQ_HANDLED when timeout already processed transfer ANBZ: #33323 commit aabd8ea0aa253d40cf5f20a609fc3d6f61e38299 upstream. When the ISR thread wakes up late and finds that the timeout handler has already processed the transfer (curr_xfer is NULL), return IRQ_HANDLED instead of IRQ_NONE. Use a similar approach to tegra_qspi_handle_timeout() by reading QSPI_TRANS_STATUS and checking the QSPI_RDY bit to determine if the hardware actually completed the transfer. If QSPI_RDY is set, the interrupt was legitimate and triggered by real hardware activity. The fact that the timeout path handled it first doesn't make it spurious. Returning IRQ_NONE incorrectly suggests the interrupt wasn't for this device, which can cause issues with shared interrupt lines and interrupt accounting. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Signed-off-by: Usama Arif Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-1-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 5d2dcd1be880..719da0ed6a05 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1552,15 +1552,30 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + u32 status; + + /* + * Read transfer status to check if interrupt was triggered by transfer + * completion + */ + status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS); /* * Occasionally the IRQ thread takes a long time to wake up (usually * when the CPU that it's running on is excessively busy) and we have * already reached the timeout before and cleaned up the timed out * transfer. Avoid any processing in that case and bail out early. + * + * If no transfer is in progress, check if this was a real interrupt + * that the timeout handler already processed, or a spurious one. */ - if (!tqspi->curr_xfer) - return IRQ_NONE; + if (!tqspi->curr_xfer) { + /* Spurious interrupt - transfer not ready */ + if (!(status & QSPI_RDY)) + return IRQ_NONE; + /* Real interrupt, already handled by timeout path */ + return IRQ_HANDLED; + } tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS); -- Gitee From 341e5e9174a5204cc995e500ea921d16c313a8da Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:27 -0800 Subject: [PATCH 10/14] spi: tegra210-quad: Move curr_xfer read inside spinlock ANBZ: #33323 commit ef13ba357656451d6371940d8414e3e271df97e3 upstream. Move the assignment of the transfer pointer from curr_xfer inside the spinlock critical section in both handle_cpu_based_xfer() and handle_dma_based_xfer(). Previously, curr_xfer was read before acquiring the lock, creating a window where the timeout path could clear curr_xfer between reading it and using it. By moving the read inside the lock, the handlers are guaranteed to see a consistent value that cannot be modified by the timeout path. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Breno Leitao Acked-by: Thierry Reding Tested-by: Jon Hunter Acked-by: Jon Hunter Link: https://patch.msgid.link/20260126-tegra_xfer-v2-2-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 719da0ed6a05..5fc49e4e95ce 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1440,10 +1440,11 @@ static int tegra_qspi_transfer_one_message(struct spi_master *master, static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) { - struct spi_transfer *t = tqspi->curr_xfer; + struct spi_transfer *t; unsigned long flags; spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; if (tqspi->tx_status || tqspi->rx_status) { tegra_qspi_handle_error(tqspi); @@ -1474,7 +1475,7 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) { - struct spi_transfer *t = tqspi->curr_xfer; + struct spi_transfer *t; unsigned int total_fifo_words; unsigned long flags; long wait_status; @@ -1513,6 +1514,7 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) } spin_lock_irqsave(&tqspi->lock, flags); + t = tqspi->curr_xfer; if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); -- Gitee From 5bd2df6c1bfefe90751db21399059616d78e42d7 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:28 -0800 Subject: [PATCH 11/14] spi: tegra210-quad: Protect curr_xfer assignment in tegra_qspi_setup_transfer_one ANBZ: #33323 commit f5a4d7f5e32ba163cff893493ec1cbb0fd2fb0d5 upstream. When the timeout handler processes a completed transfer and signals completion, the transfer thread can immediately set up the next transfer and assign curr_xfer to point to it. If a delayed ISR from the previous transfer then runs, it checks if (!tqspi->curr_xfer) (currently without the lock also -- to be fixed soon) to detect stale interrupts, but this check passes because curr_xfer now points to the new transfer. The ISR then incorrectly processes the new transfer's context. Protect the curr_xfer assignment with the spinlock to ensure the ISR either sees NULL (and bails out) or sees the new value only after the assignment is complete. Fixes: 921fc1838fb0 ("spi: tegra210-quad: Add support for Tegra210 QSPI controller") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-3-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 5fc49e4e95ce..b9d85c62d7b2 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -839,6 +839,7 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran u32 command1, command2, speed = t->speed_hz; u8 bits_per_word = t->bits_per_word; u32 tx_tap = 0, rx_tap = 0; + unsigned long flags; int req_mode; if (!has_acpi_companion(tqspi->dev) && speed != tqspi->cur_speed) { @@ -846,10 +847,12 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran tqspi->cur_speed = speed; } + spin_lock_irqsave(&tqspi->lock, flags); tqspi->cur_pos = 0; tqspi->cur_rx_pos = 0; tqspi->cur_tx_pos = 0; tqspi->curr_xfer = t; + spin_unlock_irqrestore(&tqspi->lock, flags); if (is_first_of_msg) { tegra_qspi_mask_clear_irq(tqspi); -- Gitee From 9af1aaec830cce0fd9b21663668f777a80ec08d8 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:29 -0800 Subject: [PATCH 12/14] spi: tegra210-quad: Protect curr_xfer in tegra_qspi_combined_seq_xfer ANBZ: #33323 commit bf4528ab28e2bf112c3a2cdef44fd13f007781cd upstream. The curr_xfer field is read by the IRQ handler without holding the lock to check if a transfer is in progress. When clearing curr_xfer in the combined sequence transfer loop, protect it with the spinlock to prevent a race with the interrupt handler. Protect the curr_xfer clearing at the exit path of tegra_qspi_combined_seq_xfer() with the spinlock to prevent a race with the interrupt handler that reads this field. Without this protection, the IRQ handler could read a partially updated curr_xfer value, leading to NULL pointer dereference or use-after-free. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-4-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index b9d85c62d7b2..1bf376ab46ec 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1161,6 +1161,7 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, u32 address_value = 0; u32 cmd_config = 0, addr_config = 0; u8 cmd_value = 0, val = 0; + unsigned long flags; /* Enable Combined sequence mode */ val = tegra_qspi_readl(tqspi, QSPI_GLOBAL_CONFIG); @@ -1264,13 +1265,17 @@ static int tegra_qspi_combined_seq_xfer(struct tegra_qspi *tqspi, tegra_qspi_transfer_end(spi); spi_transfer_delay_exec(xfer); } + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); transfer_phase++; } ret = 0; exit: + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); msg->status = ret; return ret; -- Gitee From 1a86e75f84651eeadf16ef4fea529d865599292c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:30 -0800 Subject: [PATCH 13/14] spi: tegra210-quad: Protect curr_xfer clearing in tegra_qspi_non_combined_seq_xfer ANBZ: #33323 commit 6d7723e8161f3c3f14125557e19dd080e9d882be upstream. Protect the curr_xfer clearing in tegra_qspi_non_combined_seq_xfer() with the spinlock to prevent a race with the interrupt handler that reads this field to check if a transfer is in progress. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-5-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 1bf376ab46ec..58f3217fd316 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1288,6 +1288,7 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, struct spi_transfer *transfer; bool is_first_msg = true; int ret = 0, val = 0; + unsigned long flags; msg->status = 0; msg->actual_length = 0; @@ -1368,7 +1369,9 @@ static int tegra_qspi_non_combined_seq_xfer(struct tegra_qspi *tqspi, msg->actual_length += xfer->len + dummy_bytes; complete_xfer: + spin_lock_irqsave(&tqspi->lock, flags); tqspi->curr_xfer = NULL; + spin_unlock_irqrestore(&tqspi->lock, flags); if (ret < 0) { tegra_qspi_transfer_end(spi); -- Gitee From dc7bc29cda5abae4f49515d4c27368306a0b77e9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 26 Jan 2026 09:50:31 -0800 Subject: [PATCH 14/14] spi: tegra210-quad: Protect curr_xfer check in IRQ handler ANBZ: #33323 commit edf9088b6e1d6d88982db7eb5e736a0e4fbcc09e upstream. Now that all other accesses to curr_xfer are done under the lock, protect the curr_xfer NULL check in tegra_qspi_isr_thread() with the spinlock. Without this protection, the following race can occur: CPU0 (ISR thread) CPU1 (timeout path) ---------------- ------------------- if (!tqspi->curr_xfer) // sees non-NULL spin_lock() tqspi->curr_xfer = NULL spin_unlock() handle_*_xfer() spin_lock() t = tqspi->curr_xfer // NULL! ... t->len ... // NULL dereference! With this patch, all curr_xfer accesses are now properly synchronized. Although all accesses to curr_xfer are done under the lock, in tegra_qspi_isr_thread() it checks for NULL, releases the lock and reacquires it later in handle_cpu_based_xfer()/handle_dma_based_xfer(). There is a potential for an update in between, which could cause a NULL pointer dereference. To handle this, add a NULL check inside the handlers after acquiring the lock. This ensures that if the timeout path has already cleared curr_xfer, the handler will safely return without dereferencing the NULL pointer. Fixes: b4e002d8a7ce ("spi: tegra210-quad: Fix timeout handling") Signed-off-by: Breno Leitao Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Thierry Reding Link: https://patch.msgid.link/20260126-tegra_xfer-v2-6-6d2115e4f387@debian.org Signed-off-by: Mark Brown Signed-off-by: Shuai Xue --- drivers/spi/spi-tegra210-quad.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 58f3217fd316..cbd3107dd570 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1457,6 +1457,11 @@ static irqreturn_t handle_cpu_based_xfer(struct tegra_qspi *tqspi) spin_lock_irqsave(&tqspi->lock, flags); t = tqspi->curr_xfer; + if (!t) { + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; + } + if (tqspi->tx_status || tqspi->rx_status) { tegra_qspi_handle_error(tqspi); complete(&tqspi->xfer_completion); @@ -1527,6 +1532,11 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) spin_lock_irqsave(&tqspi->lock, flags); t = tqspi->curr_xfer; + if (!t) { + spin_unlock_irqrestore(&tqspi->lock, flags); + return IRQ_HANDLED; + } + if (num_errors) { tegra_qspi_dma_unmap_xfer(tqspi, t); tegra_qspi_handle_error(tqspi); @@ -1565,6 +1575,7 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi) static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) { struct tegra_qspi *tqspi = context_data; + unsigned long flags; u32 status; /* @@ -1582,7 +1593,9 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) * If no transfer is in progress, check if this was a real interrupt * that the timeout handler already processed, or a spurious one. */ + spin_lock_irqsave(&tqspi->lock, flags); if (!tqspi->curr_xfer) { + spin_unlock_irqrestore(&tqspi->lock, flags); /* Spurious interrupt - transfer not ready */ if (!(status & QSPI_RDY)) return IRQ_NONE; @@ -1599,7 +1612,14 @@ static irqreturn_t tegra_qspi_isr_thread(int irq, void *context_data) tqspi->rx_status = tqspi->status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF); tegra_qspi_mask_clear_irq(tqspi); + spin_unlock_irqrestore(&tqspi->lock, flags); + /* + * Lock is released here but handlers safely re-check curr_xfer under + * lock before dereferencing. + * DMA handler also needs to sleep in wait_for_completion_*(), which + * cannot be done while holding spinlock. + */ if (!tqspi->is_curr_dma_xfer) return handle_cpu_based_xfer(tqspi); -- Gitee