diff options
Diffstat (limited to 'drivers/spi/spi-tegra210-quad.c')
| -rw-r--r-- | drivers/spi/spi-tegra210-quad.c | 174 |
1 files changed, 128 insertions, 46 deletions
diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c index 3be7499db21e..cdc3cb7c01f9 100644 --- a/drivers/spi/spi-tegra210-quad.c +++ b/drivers/spi/spi-tegra210-quad.c @@ -1019,13 +1019,20 @@ 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); - if (device_reset(tqspi->dev) < 0) - dev_warn_once(tqspi->dev, "device reset failed\n"); + tegra_qspi_reset(tqspi); } static void tegra_qspi_transfer_end(struct spi_device *spi) @@ -1041,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; @@ -1072,6 +1122,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) { @@ -1079,7 +1153,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; @@ -1146,41 +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"); - 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); + /* + * 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; } - - /* 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) { @@ -1200,11 +1261,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; @@ -1269,16 +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"); - 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); + /* + * 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; } - tegra_qspi_handle_error(tqspi); - ret = -EIO; - goto complete_xfer; } if (tqspi->tx_status || tqspi->rx_status) { @@ -1290,6 +1360,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 +1467,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 +1553,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) |
