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 <va@nvidia.com>
Acked-by: Thierry Reding <treding@nvidia.com>
Link: https://patch.msgid.link/20251028155703.4151791-4-va@nvidia.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Vishwaroop A 2025-10-28 15:57:03 +00:00 committed by Mark Brown
parent 6022eacdda
commit 380fd29d57
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
1 changed files with 77 additions and 17 deletions

View File

@ -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");
/*
* 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);
/* 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);
/* Reset controller if timeout happens */
tegra_qspi_reset(tqspi);
ret = -EIO;
goto exit;
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) {