In commit 2ee471a1e28e ("spi: spi-geni-qcom: Mo' betta locking") we
added a dance in setup_fifo_xfer() to make sure that the previous
transfer was really done before we setup the next one.  However, it
wasn't enough.  Specifically, if we had a timeout it's possible that
the previous transfer could still be pending.  This could happen if
our interrupt handler was blocked for a long while (interrupt storm or
someone disablng IRQs for a while).  This pending interrupt could
throw off our logic.

Let's really make sure that the previous interrupt isn't still pending
before we start the next transfer.

Fixes: 561de45f72bd ("spi: spi-geni-qcom: Add SPI driver support for GENI based 
QUP")
Signed-off-by: Douglas Anderson <diand...@chromium.org>
---

 drivers/spi/spi-geni-qcom.c | 69 ++++++++++++++++++++++++++++---------
 1 file changed, 53 insertions(+), 16 deletions(-)

diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
index 6f736e94e9f4..5ef2e9f38ac9 100644
--- a/drivers/spi/spi-geni-qcom.c
+++ b/drivers/spi/spi-geni-qcom.c
@@ -145,12 +145,49 @@ static void handle_fifo_timeout(struct spi_master *spi,
                dev_err(mas->dev, "Failed to cancel/abort m_cmd\n");
 }
 
+static int spi_geni_check_busy(struct spi_geni_master *mas)
+{
+       struct geni_se *se = &mas->se;
+       u32 m_irq, m_irq_en;
+
+       /*
+        * We grab the spinlock so that if we raced really fast and the IRQ
+        * handler is still actually running we'll wait for it to exit.  This
+        * can happen because the IRQ handler may signal in the middle of the
+        * function and the next transfer can kick off right away.
+        *
+        * Once we have the spinlock, if we're starting a new transfer we
+        * expect nothing is pending.  We check this to handle the case where
+        * the previous transfer timed out and then handle_fifo_timeout() timed
+        * out.  This can happen if the interrupt handler was blocked for
+        * a long time and we don't want to start any new transfers until it's
+        * all done.
+        *
+        * We are OK releasing the spinlock after we're done here since (if
+        * we're returning 0 and going ahead with the transfer) we know that
+        * the SPI controller must be in a quiet state.
+        */
+       spin_lock_irq(&mas->lock);
+       m_irq = readl(se->base + SE_GENI_M_IRQ_STATUS);
+       m_irq_en = readl(se->base + SE_GENI_M_IRQ_EN);
+       spin_unlock_irq(&mas->lock);
+
+       if (m_irq & m_irq_en) {
+               dev_err(mas->dev, "Busy, IRQs pending %#010x\n",
+                       m_irq & m_irq_en);
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
 static void spi_geni_set_cs(struct spi_device *slv, bool set_flag)
 {
        struct spi_geni_master *mas = spi_master_get_devdata(slv->master);
        struct spi_master *spi = dev_get_drvdata(mas->dev);
        struct geni_se *se = &mas->se;
        unsigned long time_left;
+       int ret;
 
        if (!(slv->mode & SPI_CS_HIGH))
                set_flag = !set_flag;
@@ -158,6 +195,12 @@ static void spi_geni_set_cs(struct spi_device *slv, bool 
set_flag)
        if (set_flag == mas->cs_flag)
                return;
 
+       ret = spi_geni_check_busy(mas);
+       if (ret) {
+               dev_err(mas->dev, "Can't set chip select\n");
+               return;
+       }
+
        mas->cs_flag = set_flag;
 
        pm_runtime_get_sync(mas->dev);
@@ -277,8 +320,12 @@ static int setup_fifo_params(struct spi_device *spi_slv,
 static int spi_geni_prepare_message(struct spi_master *spi,
                                        struct spi_message *spi_msg)
 {
-       int ret;
        struct spi_geni_master *mas = spi_master_get_devdata(spi);
+       int ret;
+
+       ret = spi_geni_check_busy(mas);
+       if (ret)
+               return ret;
 
        ret = setup_fifo_params(spi_msg->spi, spi);
        if (ret)
@@ -440,21 +487,6 @@ static void setup_fifo_xfer(struct spi_transfer *xfer,
        struct geni_se *se = &mas->se;
        int ret;
 
-       /*
-        * Ensure that our interrupt handler isn't still running from some
-        * prior command before we start messing with the hardware behind
-        * its back.  We don't need to _keep_ the lock here since we're only
-        * worried about racing with out interrupt handler.  The SPI core
-        * already handles making sure that we're not trying to do two
-        * transfers at once or setting a chip select and doing a transfer
-        * concurrently.
-        *
-        * NOTE: we actually _can't_ hold the lock here because possibly we
-        * might call clk_set_rate() which needs to be able to sleep.
-        */
-       spin_lock_irq(&mas->lock);
-       spin_unlock_irq(&mas->lock);
-
        if (xfer->bits_per_word != mas->cur_bits_per_word) {
                spi_setup_word_len(mas, mode, xfer->bits_per_word);
                mas->cur_bits_per_word = xfer->bits_per_word;
@@ -511,6 +543,11 @@ static int spi_geni_transfer_one(struct spi_master *spi,
                                struct spi_transfer *xfer)
 {
        struct spi_geni_master *mas = spi_master_get_devdata(spi);
+       int ret;
+
+       ret = spi_geni_check_busy(mas);
+       if (ret)
+               return ret;
 
        /* Terminate and return success for 0 byte length transfer */
        if (!xfer->len)
-- 
2.29.2.684.gfbc64c5ab5-goog

Reply via email to