]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
spi: tegra210-quad: Check hardware status on timeout
authorVishwaroop A <va@nvidia.com>
Tue, 28 Oct 2025 15:57:03 +0000 (15:57 +0000)
committerMark Brown <broonie@kernel.org>
Tue, 4 Nov 2025 16:48:55 +0000 (16:48 +0000)
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>
drivers/spi/spi-tegra210-quad.c

index 69defb4ffe498d34f0dc938a042b9912ef27ccab..cdc3cb7c01f9b7dbe2f1a47ae6c764822d585152 100644 (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");
-
-                               /* 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) {