From dd045b9aa42f6965907d26e3f7d5e4ffbf1f0906 Mon Sep 17 00:00:00 2001
From: Alexander Graf <agraf@suse.de>
Date: Wed, 23 May 2018 22:24:51 +0200
Subject: [PATCH] mmc: Unirqify bcm2835_sdhost and fix writes

The bcm2835 sdhost driver has a problem with "write multiple" commands.
It seems to boil down to the fact that the controller dislikes its FIFO
to get drained at the end of a block when a write multiple blocks command
is in flight.

The easy fix is to simply get rid of all the IRQ driven logic and make
the driver push as much data into the FIFO as it can. That way we never
drain and we never run into the problem.

Reported-by: Jan Leonhardt <jan@cyberdesigner.net>
Signed-off-by: Alexander Graf <agraf@suse.de>
---
 drivers/mmc/bcm2835_sdhost.c | 265 +++++++----------------------------
 1 file changed, 47 insertions(+), 218 deletions(-)

diff --git a/drivers/mmc/bcm2835_sdhost.c b/drivers/mmc/bcm2835_sdhost.c
index bccd182e50..3c9f799948 100644
--- a/drivers/mmc/bcm2835_sdhost.c
+++ b/drivers/mmc/bcm2835_sdhost.c
@@ -166,7 +166,6 @@ struct bcm2835_host {
 	int			clock;		/* Current clock speed */
 	unsigned int		max_clk;	/* Max possible freq */
 	unsigned int		blocks;		/* remaining PIO blocks */
-	int			irq;		/* Device IRQ */
 
 	u32			ns_per_fifo_word;
 
@@ -176,14 +175,7 @@ struct bcm2835_host {
 
 	struct mmc_cmd	*cmd;		/* Current command */
 	struct mmc_data		*data;		/* Current data request */
-	bool			data_complete:1;/* Data finished before cmd */
 	bool			use_busy:1;	/* Wait for busy interrupt */
-	bool			wait_data_complete:1;	/* Wait for data */
-
-	/* for threaded irq handler */
-	bool			irq_block;
-	bool			irq_busy;
-	bool			irq_data;
 
 	struct udevice		*dev;
 	struct mmc		*mmc;
@@ -243,17 +235,9 @@ static void bcm2835_reset_internal(struct bcm2835_host *host)
 	writel(host->cdiv, host->ioaddr + SDCDIV);
 }
 
-static int bcm2835_finish_command(struct bcm2835_host *host);
-
-static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
+static int bcm2835_wait_transfer_complete(struct bcm2835_host *host)
 {
-	int timediff;
-	u32 alternate_idle;
-
-	alternate_idle = (host->data->flags & MMC_DATA_READ) ?
-		SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1;
-
-	timediff = 0;
+	int timediff = 0;
 
 	while (1) {
 		u32 edm, fsm;
@@ -264,7 +248,10 @@ static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
 		if ((fsm == SDEDM_FSM_IDENTMODE) ||
 		    (fsm == SDEDM_FSM_DATAMODE))
 			break;
-		if (fsm == alternate_idle) {
+
+		if ((fsm == SDEDM_FSM_READWAIT) ||
+		    (fsm == SDEDM_FSM_WRITESTART1) ||
+		    (fsm == SDEDM_FSM_READDATA)) {
 			writel(edm | SDEDM_FORCE_DATA_MODE,
 			       host->ioaddr + SDEDM);
 			break;
@@ -276,9 +263,11 @@ static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
 				"wait_transfer_complete - still waiting after %d retries\n",
 				timediff);
 			bcm2835_dumpregs(host);
-			return;
+			return -ETIMEDOUT;
 		}
 	}
+
+	return 0;
 }
 
 static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read)
@@ -325,6 +314,9 @@ static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read)
 			      fsm_state != SDEDM_FSM_READCRC)) ||
 			    (!is_read &&
 			     (fsm_state != SDEDM_FSM_WRITEDATA &&
+			      fsm_state != SDEDM_FSM_WRITEWAIT1 &&
+			      fsm_state != SDEDM_FSM_WRITEWAIT2 &&
+			      fsm_state != SDEDM_FSM_WRITECRC &&
 			      fsm_state != SDEDM_FSM_WRITESTART1 &&
 			      fsm_state != SDEDM_FSM_WRITESTART2))) {
 				hsts = readl(host->ioaddr + SDHSTS);
@@ -361,9 +353,8 @@ static int bcm2835_transfer_pio(struct bcm2835_host *host)
 
 	is_read = (host->data->flags & MMC_DATA_READ) != 0;
 	ret = bcm2835_transfer_block_pio(host, is_read);
-
-	if (host->wait_data_complete)
-		bcm2835_wait_transfer_complete(host);
+	if (ret)
+		return ret;
 
 	sdhsts = readl(host->ioaddr + SDHSTS);
 	if (sdhsts & (SDHSTS_CRC16_ERROR |
@@ -382,21 +373,8 @@ static int bcm2835_transfer_pio(struct bcm2835_host *host)
 	return ret;
 }
 
-static void bcm2835_set_transfer_irqs(struct bcm2835_host *host)
-{
-	u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN |
-		SDHCFG_BUSY_IRPT_EN;
-
-	host->hcfg = (host->hcfg & ~all_irqs) |
-		SDHCFG_DATA_IRPT_EN |
-		SDHCFG_BUSY_IRPT_EN;
-
-	writel(host->hcfg, host->ioaddr + SDHCFG);
-}
-
-static
-void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
-			  struct mmc_data *data)
+static void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
+				 struct mmc_data *data)
 {
 	WARN_ON(host->data);
 
@@ -404,14 +382,9 @@ void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
 	if (!data)
 		return;
 
-	host->wait_data_complete = cmd->cmdidx != MMC_CMD_READ_MULTIPLE_BLOCK;
-	host->data_complete = false;
-
 	/* Use PIO */
 	host->blocks = data->blocks;
 
-	bcm2835_set_transfer_irqs(host);
-
 	writel(data->blocksize, host->ioaddr + SDHBCT);
 	writel(data->blocks, host->ioaddr + SDHBLC);
 }
@@ -486,36 +459,6 @@ static int bcm2835_send_command(struct bcm2835_host *host, struct mmc_cmd *cmd,
 	return 0;
 }
 
-static int bcm2835_transfer_complete(struct bcm2835_host *host)
-{
-	int ret = 0;
-
-	WARN_ON(!host->data_complete);
-
-	host->data = NULL;
-
-	return ret;
-}
-
-static void bcm2835_finish_data(struct bcm2835_host *host)
-{
-	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
-	writel(host->hcfg, host->ioaddr + SDHCFG);
-
-	host->data_complete = true;
-
-	if (host->cmd) {
-		/* Data managed to finish before the
-		 * command completed. Make sure we do
-		 * things in the proper order.
-		 */
-		dev_dbg(dev, "Finished early - HSTS %08x\n",
-			readl(host->ioaddr + SDHSTS));
-	} else {
-		bcm2835_transfer_complete(host);
-	}
-}
-
 static int bcm2835_finish_command(struct bcm2835_host *host)
 {
 	struct mmc_cmd *cmd = host->cmd;
@@ -565,8 +508,6 @@ static int bcm2835_finish_command(struct bcm2835_host *host)
 
 	/* Processed actual command. */
 	host->cmd = NULL;
-	if (host->data && host->data_complete)
-		ret = bcm2835_transfer_complete(host);
 
 	return ret;
 }
@@ -611,159 +552,44 @@ static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask)
 	return ret;
 }
 
-static void bcm2835_busy_irq(struct bcm2835_host *host)
-{
-	if (WARN_ON(!host->cmd)) {
-		bcm2835_dumpregs(host);
-		return;
-	}
-
-	if (WARN_ON(!host->use_busy)) {
-		bcm2835_dumpregs(host);
-		return;
-	}
-	host->use_busy = false;
-
-	bcm2835_finish_command(host);
-}
-
-static void bcm2835_data_irq(struct bcm2835_host *host, u32 intmask)
+static int bcm2835_transmit(struct bcm2835_host *host)
 {
+	u32 intmask = readl(host->ioaddr + SDHSTS);
 	int ret;
 
-	/*
-	 * There are no dedicated data/space available interrupt
-	 * status bits, so it is necessary to use the single shared
-	 * data/space available FIFO status bits. It is therefore not
-	 * an error to get here when there is no data transfer in
-	 * progress.
-	 */
-	if (!host->data)
-		return;
-
+	/* Check for errors */
 	ret = bcm2835_check_data_error(host, intmask);
 	if (ret)
-		goto finished;
-
-	if (host->data->flags & MMC_DATA_WRITE) {
-		/* Use the block interrupt for writes after the first block */
-		host->hcfg &= ~(SDHCFG_DATA_IRPT_EN);
-		host->hcfg |= SDHCFG_BLOCK_IRPT_EN;
-		writel(host->hcfg, host->ioaddr + SDHCFG);
-		bcm2835_transfer_pio(host);
-	} else {
-		bcm2835_transfer_pio(host);
-		host->blocks--;
-		if ((host->blocks == 0))
-			goto finished;
-	}
-	return;
+		return ret;
 
-finished:
-	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
-	writel(host->hcfg, host->ioaddr + SDHCFG);
-}
-
-static void bcm2835_data_threaded_irq(struct bcm2835_host *host)
-{
-	if (!host->data)
-		return;
-	if ((host->blocks == 0))
-		bcm2835_finish_data(host);
-}
-
-static void bcm2835_block_irq(struct bcm2835_host *host)
-{
-	if (WARN_ON(!host->data)) {
-		bcm2835_dumpregs(host);
-		return;
-	}
-
-	WARN_ON(!host->blocks);
-	if ((--host->blocks == 0))
-		bcm2835_finish_data(host);
-	else
-		bcm2835_transfer_pio(host);
-}
+	ret = bcm2835_check_cmd_error(host, intmask);
+	if (ret)
+		return ret;
 
-static irqreturn_t bcm2835_irq(int irq, void *dev_id)
-{
-	irqreturn_t result = IRQ_NONE;
-	struct bcm2835_host *host = dev_id;
-	u32 intmask;
-
-	intmask = readl(host->ioaddr + SDHSTS);
-
-	writel(SDHSTS_BUSY_IRPT |
-	       SDHSTS_BLOCK_IRPT |
-	       SDHSTS_SDIO_IRPT |
-	       SDHSTS_DATA_FLAG,
-	       host->ioaddr + SDHSTS);
-
-	if (intmask & SDHSTS_BLOCK_IRPT) {
-		bcm2835_check_data_error(host, intmask);
-		host->irq_block = true;
-		result = IRQ_WAKE_THREAD;
+	/* Handle wait for busy end */
+	if (host->use_busy && (intmask & SDHSTS_BUSY_IRPT)) {
+		writel(SDHSTS_BUSY_IRPT, host->ioaddr + SDHSTS);
+		host->use_busy = false;
+		bcm2835_finish_command(host);
 	}
 
-	if (intmask & SDHSTS_BUSY_IRPT) {
-		if (!bcm2835_check_cmd_error(host, intmask)) {
-			host->irq_busy = true;
-			result = IRQ_WAKE_THREAD;
-		} else {
-			result = IRQ_HANDLED;
+	/* Handle PIO data transfer */
+	if (host->data) {
+		ret = bcm2835_transfer_pio(host);
+		if (ret)
+			return ret;
+		host->blocks--;
+		if (host->blocks == 0) {
+			/* Wait for command to complete for real */
+			ret = bcm2835_wait_transfer_complete(host);
+			if (ret)
+				return ret;
+			/* Transfer complete */
+			host->data = NULL;
 		}
 	}
 
-	/* There is no true data interrupt status bit, so it is
-	 * necessary to qualify the data flag with the interrupt
-	 * enable bit.
-	 */
-	if ((intmask & SDHSTS_DATA_FLAG) &&
-	    (host->hcfg & SDHCFG_DATA_IRPT_EN)) {
-		bcm2835_data_irq(host, intmask);
-		host->irq_data = true;
-		result = IRQ_WAKE_THREAD;
-	}
-
-	return result;
-}
-
-static irqreturn_t bcm2835_threaded_irq(int irq, void *dev_id)
-{
-	struct bcm2835_host *host = dev_id;
-
-	if (host->irq_block) {
-		host->irq_block = false;
-		bcm2835_block_irq(host);
-	}
-
-	if (host->irq_busy) {
-		host->irq_busy = false;
-		bcm2835_busy_irq(host);
-	}
-
-	if (host->irq_data) {
-		host->irq_data = false;
-		bcm2835_data_threaded_irq(host);
-	}
-
-	return IRQ_HANDLED;
-}
-
-static void bcm2835_irq_poll(struct bcm2835_host *host)
-{
-	u32 intmask;
-
-	while (1) {
-		intmask = readl(host->ioaddr + SDHSTS);
-		if (intmask & (SDHSTS_BUSY_IRPT | SDHSTS_BLOCK_IRPT |
-			       SDHSTS_SDIO_IRPT | SDHSTS_DATA_FLAG)) {
-			bcm2835_irq(0, host);
-			bcm2835_threaded_irq(0, host);
-			return;
-		}
-	}
+	return 0;
 }
 
 static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
@@ -867,8 +693,11 @@ static int bcm2835_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
 	}
 
 	/* Wait for completion of busy signal or data transfer */
-	while (host->use_busy || host->data)
-		bcm2835_irq_poll(host);
+	while (host->use_busy || host->data) {
+		ret = bcm2835_transmit(host);
+		if (ret)
+			break;
+	}
 
 	return ret;
 }
