Re: [PATCH] mmc: mmci: Add support for SW busy-end timeouts

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 6/20/23 11:11, Ulf Hansson wrote:
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 work gets to run, let's kick the IRQ handler to
completed the currently running request/command.

Reviewed-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
Tested-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
Signed-off-by: Ulf Hansson <ulf.hansson@xxxxxxxxxx>
---
  drivers/mmc/host/mmci.c             | 50 ++++++++++++++++++++++++++---
  drivers/mmc/host/mmci.h             |  3 +-
  drivers/mmc/host/mmci_stm32_sdmmc.c |  3 +-
  3 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 8a661ea1a2d1..61d761646462 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -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>
@@ -682,7 +683,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;
@@ -726,6 +728,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--;
@@ -753,6 +757,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;
@@ -761,6 +766,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),
@@ -1277,6 +1283,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",
@@ -1304,10 +1311,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
@@ -1448,7 +1456,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;
@@ -1495,6 +1503,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);
@@ -2309,6 +2345,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);

Hi Ulf,

STM32 variants also have busy_detect = true.
Could that be an issue to initialize this work, which seem dedicated to ux500?
I haven't tested the patch yet. Will do that soon.


Yann

+
  	writel(MCI_IRQENABLE | variant->start_err, host->base + MMCIMASK0);
amba_set_drvdata(dev, mmc);
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index 12a7bbd3ce26..69b2439842dd 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -393,7 +393,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);
  };
@@ -451,6 +451,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)
diff --git a/drivers/mmc/host/mmci_stm32_sdmmc.c b/drivers/mmc/host/mmci_stm32_sdmmc.c
index 953d1be4e379..f07cfbeafe2e 100644
--- a/drivers/mmc/host/mmci_stm32_sdmmc.c
+++ b/drivers/mmc/host/mmci_stm32_sdmmc.c
@@ -372,7 +372,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;




[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux