mmc: meson-gx: make sure the descriptor is stopped on errors
authorJerome Brunet <jbrunet@baylibre.com>
Thu, 6 Dec 2018 15:18:25 +0000 (16:18 +0100)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 17 Dec 2018 07:26:24 +0000 (08:26 +0100)
On errors, if we don't stop the descriptor chain, it may continue to
run and raise IRQ after we have called mmc_request_done(). This is bad
because we won't be able to get cmd anymore and properly deal with the
IRQ.

This patch makes sure the descriptor chain is stopped before
calling mmc_request_done()

Fixes: 79ed05e329c3 ("mmc: meson-gx: add support for descriptor chain mode")
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/meson-gx-mmc.c

index c201c37..fcb5d69 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/init.h>
+#include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #define   CFG_CLK_ALWAYS_ON BIT(18)
 #define   CFG_CHK_DS BIT(20)
 #define   CFG_AUTO_CLK BIT(23)
+#define   CFG_ERR_ABORT BIT(27)
 
 #define SD_EMMC_STATUS 0x48
 #define   STATUS_BUSY BIT(31)
+#define   STATUS_DESC_BUSY BIT(30)
 #define   STATUS_DATI GENMASK(23, 16)
 
 #define SD_EMMC_IRQ_EN 0x4c
@@ -928,6 +931,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd)
 
        cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode);
        cmd_cfg |= CMD_CFG_OWNER;  /* owned by CPU */
+       cmd_cfg |= CMD_CFG_ERROR; /* stop in case of error */
 
        meson_mmc_set_response_bits(cmd, &cmd_cfg);
 
@@ -1022,6 +1026,17 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
        u32 irq_en, status, raw_status;
        irqreturn_t ret = IRQ_NONE;
 
+       irq_en = readl(host->regs + SD_EMMC_IRQ_EN);
+       raw_status = readl(host->regs + SD_EMMC_STATUS);
+       status = raw_status & irq_en;
+
+       if (!status) {
+               dev_dbg(host->dev,
+                       "Unexpected IRQ! irq_en 0x%08x - status 0x%08x\n",
+                        irq_en, raw_status);
+               return IRQ_NONE;
+       }
+
        if (WARN_ON(!host) || WARN_ON(!host->cmd))
                return IRQ_NONE;
 
@@ -1029,22 +1044,18 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
 
        cmd = host->cmd;
        data = cmd->data;
-       irq_en = readl(host->regs + SD_EMMC_IRQ_EN);
-       raw_status = readl(host->regs + SD_EMMC_STATUS);
-       status = raw_status & irq_en;
-
        cmd->error = 0;
        if (status & IRQ_CRC_ERR) {
                dev_dbg(host->dev, "CRC Error - status 0x%08x\n", status);
                cmd->error = -EILSEQ;
-               ret = IRQ_HANDLED;
+               ret = IRQ_WAKE_THREAD;
                goto out;
        }
 
        if (status & IRQ_TIMEOUTS) {
                dev_dbg(host->dev, "Timeout - status 0x%08x\n", status);
                cmd->error = -ETIMEDOUT;
-               ret = IRQ_HANDLED;
+               ret = IRQ_WAKE_THREAD;
                goto out;
        }
 
@@ -1069,17 +1080,49 @@ out:
        /* ack all enabled interrupts */
        writel(irq_en, host->regs + SD_EMMC_STATUS);
 
+       if (cmd->error) {
+               /* Stop desc in case of errors */
+               u32 start = readl(host->regs + SD_EMMC_START);
+
+               start &= ~START_DESC_BUSY;
+               writel(start, host->regs + SD_EMMC_START);
+       }
+
        if (ret == IRQ_HANDLED)
                meson_mmc_request_done(host->mmc, cmd->mrq);
-       else if (ret == IRQ_NONE)
-               dev_warn(host->dev,
-                        "Unexpected IRQ! status=0x%08x, irq_en=0x%08x\n",
-                        raw_status, irq_en);
 
        spin_unlock(&host->lock);
        return ret;
 }
 
+static int meson_mmc_wait_desc_stop(struct meson_host *host)
+{
+       int loop;
+       u32 status;
+
+       /*
+        * It may sometimes take a while for it to actually halt. Here, we
+        * are giving it 5ms to comply
+        *
+        * If we don't confirm the descriptor is stopped, it might raise new
+        * IRQs after we have called mmc_request_done() which is bad.
+        */
+       for (loop = 50; loop; loop--) {
+               status = readl(host->regs + SD_EMMC_STATUS);
+               if (status & (STATUS_BUSY | STATUS_DESC_BUSY))
+                       udelay(100);
+               else
+                       break;
+       }
+
+       if (status & (STATUS_BUSY | STATUS_DESC_BUSY)) {
+               dev_err(host->dev, "Timed out waiting for host to stop\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
 static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
 {
        struct meson_host *host = dev_id;
@@ -1090,6 +1133,13 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
        if (WARN_ON(!cmd))
                return IRQ_NONE;
 
+       if (cmd->error) {
+               meson_mmc_wait_desc_stop(host);
+               meson_mmc_request_done(host->mmc, cmd->mrq);
+
+               return IRQ_HANDLED;
+       }
+
        data = cmd->data;
        if (meson_mmc_bounce_buf_read(data)) {
                xfer_bytes = data->blksz * data->blocks;
@@ -1130,6 +1180,9 @@ static void meson_mmc_cfg_init(struct meson_host *host)
        cfg |= FIELD_PREP(CFG_RC_CC_MASK, ilog2(SD_EMMC_CFG_CMD_GAP));
        cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, ilog2(SD_EMMC_CFG_BLK_SIZE));
 
+       /* abort chain on R/W errors */
+       cfg |= CFG_ERR_ABORT;
+
        writel(cfg, host->regs + SD_EMMC_CFG);
 }