OSDN Git Service

spi: sun6i: fix race between DMA RX transfer completion and RX FIFO drain
[tomoyo/tomoyo-test1.git] / drivers / spi / spi-sun6i.c
index 8fcb269..57c828e 100644 (file)
@@ -102,6 +102,7 @@ struct sun6i_spi {
        struct reset_control    *rstc;
 
        struct completion       done;
+       struct completion       dma_rx_done;
 
        const u8                *tx_buf;
        u8                      *rx_buf;
@@ -196,6 +197,13 @@ static size_t sun6i_spi_max_transfer_size(struct spi_device *spi)
        return SUN6I_MAX_XFER_SIZE - 1;
 }
 
+static void sun6i_spi_dma_rx_cb(void *param)
+{
+       struct sun6i_spi *sspi = param;
+
+       complete(&sspi->dma_rx_done);
+}
+
 static int sun6i_spi_prepare_dma(struct sun6i_spi *sspi,
                                 struct spi_transfer *tfr)
 {
@@ -220,6 +228,8 @@ static int sun6i_spi_prepare_dma(struct sun6i_spi *sspi,
                                                 DMA_PREP_INTERRUPT);
                if (!rxdesc)
                        return -EINVAL;
+               rxdesc->callback_param = sspi;
+               rxdesc->callback = sun6i_spi_dma_rx_cb;
        }
 
        txdesc = NULL;
@@ -275,6 +285,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
                return -EINVAL;
 
        reinit_completion(&sspi->done);
+       reinit_completion(&sspi->dma_rx_done);
        sspi->tx_buf = tfr->tx_buf;
        sspi->rx_buf = tfr->rx_buf;
        sspi->len = tfr->len;
@@ -459,6 +470,22 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
        start = jiffies;
        timeout = wait_for_completion_timeout(&sspi->done,
                                              msecs_to_jiffies(tx_time));
+
+       if (!use_dma) {
+               sun6i_spi_drain_fifo(sspi);
+       } else {
+               if (timeout && rx_len) {
+                       /*
+                        * Even though RX on the peripheral side has finished
+                        * RX DMA might still be in flight
+                        */
+                       timeout = wait_for_completion_timeout(&sspi->dma_rx_done,
+                                                             timeout);
+                       if (!timeout)
+                               dev_warn(&master->dev, "RX DMA timeout\n");
+               }
+       }
+
        end = jiffies;
        if (!timeout) {
                dev_warn(&master->dev,
@@ -486,7 +513,6 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
        /* Transfer complete */
        if (status & SUN6I_INT_CTL_TC) {
                sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC);
-               sun6i_spi_drain_fifo(sspi);
                complete(&sspi->done);
                return IRQ_HANDLED;
        }
@@ -644,6 +670,7 @@ static int sun6i_spi_probe(struct platform_device *pdev)
        }
 
        init_completion(&sspi->done);
+       init_completion(&sspi->dma_rx_done);
 
        sspi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
        if (IS_ERR(sspi->rstc)) {