From: Vishwaroop A Date: Tue, 28 Oct 2025 15:57:03 +0000 (+0000) Subject: spi: tegra210-quad: Check hardware status on timeout X-Git-Tag: v6.19-rc1~150^2~23^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=380fd29d57abe6679d87ec56babe65ddc5873a37;p=thirdparty%2Fkernel%2Flinux.git spi: tegra210-quad: Check hardware status on timeout 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 --- diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 69defb4ffe498..cdc3cb7c01f9b 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) {