mmc: mmci: Add support for SW busy-end timeouts
authorUlf Hansson <ulf.hansson@linaro.org>
Tue, 20 Jun 2023 09:11:13 +0000 (11:11 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Thu, 22 Jun 2023 09:06:37 +0000 (11:06 +0200)
The ux500 variant doesn't have a HW based timeout to use for busy-end IRQs.
To avoid hanging and waiting for the card to stop signaling busy, let's
schedule a delayed work, according to the corresponding cmd->busy_timeout
for the command. If the work gets to run, let's kick the IRQ handler to
complete the currently running request/command.

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Tested-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Link: https://lore.kernel.org/r/20230620091113.33393-1-ulf.hansson@linaro.org
drivers/mmc/host/mmci.c
drivers/mmc/host/mmci.h
drivers/mmc/host/mmci_stm32_sdmmc.c

index f7e9f07..769b34a 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/pinctrl/consumer.h>
 #include <linux/reset.h>
 #include <linux/gpio/consumer.h>
+#include <linux/workqueue.h>
 
 #include <asm/div64.h>
 #include <asm/io.h>
@@ -712,7 +713,8 @@ static void ux500_busy_clear_mask_done(struct mmci_host *host)
  *                     |                 |
  *                    IRQ1              IRQ2
  */
-static bool ux500_busy_complete(struct mmci_host *host, u32 status, u32 err_msk)
+static bool ux500_busy_complete(struct mmci_host *host, struct mmc_command *cmd,
+                               u32 status, u32 err_msk)
 {
        void __iomem *base = host->base;
        int retries = 10;
@@ -756,6 +758,8 @@ static bool ux500_busy_complete(struct mmci_host *host, u32 status, u32 err_msk)
                                       host->variant->busy_detect_mask,
                                       base + MMCIMASK0);
                                host->busy_state = MMCI_BUSY_WAITING_FOR_START_IRQ;
+                               schedule_delayed_work(&host->ux500_busy_timeout_work,
+                                     msecs_to_jiffies(cmd->busy_timeout));
                                goto out_ret_state;
                        }
                        retries--;
@@ -783,6 +787,7 @@ static bool ux500_busy_complete(struct mmci_host *host, u32 status, u32 err_msk)
                } else {
                        dev_dbg(mmc_dev(host->mmc),
                                "lost busy status when waiting for busy start IRQ\n");
+                       cancel_delayed_work(&host->ux500_busy_timeout_work);
                        ux500_busy_clear_mask_done(host);
                }
                break;
@@ -791,6 +796,7 @@ static bool ux500_busy_complete(struct mmci_host *host, u32 status, u32 err_msk)
                if (!(status & host->variant->busy_detect_flag)) {
                        host->busy_status |= status & (MCI_CMDSENT | MCI_CMDRESPEND);
                        writel(host->variant->busy_detect_mask, base + MMCICLEAR);
+                       cancel_delayed_work(&host->ux500_busy_timeout_work);
                        ux500_busy_clear_mask_done(host);
                } else {
                        dev_dbg(mmc_dev(host->mmc),
@@ -1307,6 +1313,7 @@ static void
 mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
 {
        void __iomem *base = host->base;
+       bool busy_resp = cmd->flags & MMC_RSP_BUSY;
        unsigned long long clks;
 
        dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n",
@@ -1334,10 +1341,11 @@ mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
        host->busy_status = 0;
        host->busy_state = MMCI_BUSY_DONE;
 
-       if (host->variant->busy_timeout && cmd->flags & MMC_RSP_BUSY) {
-               if (!cmd->busy_timeout)
-                       cmd->busy_timeout = 10 * MSEC_PER_SEC;
+       /* Assign a default timeout if the core does not provide one */
+       if (busy_resp && !cmd->busy_timeout)
+               cmd->busy_timeout = 10 * MSEC_PER_SEC;
 
+       if (busy_resp && host->variant->busy_timeout) {
                if (cmd->busy_timeout > host->mmc->max_busy_timeout)
                        clks = (unsigned long long)host->mmc->max_busy_timeout * host->cclk;
                else
@@ -1478,7 +1486,7 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
 
        /* Handle busy detection on DAT0 if the variant supports it. */
        if (busy_resp && host->variant->busy_detect)
-               if (!host->ops->busy_complete(host, status, err_msk))
+               if (!host->ops->busy_complete(host, cmd, status, err_msk))
                        return;
 
        host->cmd = NULL;
@@ -1525,6 +1533,34 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
        }
 }
 
+/*
+ * This busy timeout worker is used to "kick" the command IRQ if a
+ * busy detect IRQ fails to appear in reasonable time. Only used on
+ * variants with busy detection IRQ delivery.
+ */
+static void ux500_busy_timeout_work(struct work_struct *work)
+{
+       struct mmci_host *host = container_of(work, struct mmci_host,
+                                       ux500_busy_timeout_work.work);
+       unsigned long flags;
+       u32 status;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       if (host->cmd) {
+               dev_dbg(mmc_dev(host->mmc), "timeout waiting for busy IRQ\n");
+
+               /* If we are still busy let's tag on a cmd-timeout error. */
+               status = readl(host->base + MMCISTATUS);
+               if (status & host->variant->busy_detect_flag)
+                       status |= MCI_CMDTIMEOUT;
+
+               mmci_cmd_irq(host, host->cmd, status);
+       }
+
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+
 static int mmci_get_rx_fifocnt(struct mmci_host *host, u32 status, int remain)
 {
        return remain - (readl(host->base + MMCIFIFOCNT) << 2);
@@ -2339,6 +2375,10 @@ static int mmci_probe(struct amba_device *dev,
                        goto clk_disable;
        }
 
+       if (host->variant->busy_detect)
+               INIT_DELAYED_WORK(&host->ux500_busy_timeout_work,
+                                 ux500_busy_timeout_work);
+
        writel(MCI_IRQENABLE | variant->start_err, host->base + MMCIMASK0);
 
        amba_set_drvdata(dev, mmc);
index 3619542..253197f 100644 (file)
@@ -397,7 +397,7 @@ struct mmci_host_ops {
        void (*dma_error)(struct mmci_host *host);
        void (*set_clkreg)(struct mmci_host *host, unsigned int desired);
        void (*set_pwrreg)(struct mmci_host *host, unsigned int pwr);
-       bool (*busy_complete)(struct mmci_host *host, u32 status, u32 err_msk);
+       bool (*busy_complete)(struct mmci_host *host, struct mmc_command *cmd, u32 status, u32 err_msk);
        void (*pre_sig_volt_switch)(struct mmci_host *host);
        int (*post_sig_volt_switch)(struct mmci_host *host, struct mmc_ios *ios);
 };
@@ -455,6 +455,7 @@ struct mmci_host {
        void                    *dma_priv;
 
        s32                     next_cookie;
+       struct delayed_work     ux500_busy_timeout_work;
 };
 
 #define dma_inprogress(host)   ((host)->dma_in_progress)
index 5f95410..35067e1 100644 (file)
@@ -411,7 +411,8 @@ static u32 sdmmc_get_dctrl_cfg(struct mmci_host *host)
        return datactrl;
 }
 
-static bool sdmmc_busy_complete(struct mmci_host *host, u32 status, u32 err_msk)
+static bool sdmmc_busy_complete(struct mmci_host *host, struct mmc_command *cmd,
+                               u32 status, u32 err_msk)
 {
        void __iomem *base = host->base;
        u32 busy_d0, busy_d0end, mask, sdmmc_status;