mirror of https://github.com/torvalds/linux.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 <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:
parent
6022eacdda
commit
380fd29d57
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue