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>
diff --git a/drivers/mmc/bcm2835_sdhost.c b/drivers/mmc/bcm2835_sdhost.c
index 9642833..1ce019a 100644
--- a/drivers/mmc/bcm2835_sdhost.c
+++ b/drivers/mmc/bcm2835_sdhost.c
@@ -163,7 +163,6 @@
 	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;
 
@@ -173,14 +172,7 @@
 
 	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;
@@ -240,17 +232,9 @@
 	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;
@@ -261,7 +245,10 @@
 		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;
@@ -273,9 +260,11 @@
 				"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)
@@ -322,6 +311,9 @@
 			      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);
@@ -358,9 +350,8 @@
 
 	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 |
@@ -379,21 +370,8 @@
 	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);
 
@@ -401,14 +379,9 @@
 	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);
 }
@@ -483,36 +456,6 @@
 	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;
@@ -562,8 +505,6 @@
 
 	/* Processed actual command. */
 	host->cmd = NULL;
-	if (host->data && host->data_complete)
-		ret = bcm2835_transfer_complete(host);
 
 	return ret;
 }
@@ -608,159 +549,44 @@
 	return ret;
 }
 
-static void bcm2835_busy_irq(struct bcm2835_host *host)
+static int bcm2835_transmit(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)
-{
+	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;
+		return ret;
 
-	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);
+	ret = bcm2835_check_cmd_error(host, intmask);
+	if (ret)
+		return ret;
+
+	/* 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);
+	}
+
+	/* Handle PIO data transfer */
+	if (host->data) {
+		ret = bcm2835_transfer_pio(host);
+		if (ret)
+			return ret;
 		host->blocks--;
-		if ((host->blocks == 0))
-			goto finished;
-	}
-	return;
-
-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);
-}
-
-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;
-	}
-
-	if (intmask & SDHSTS_BUSY_IRPT) {
-		if (!bcm2835_check_cmd_error(host, intmask)) {
-			host->irq_busy = true;
-			result = IRQ_WAKE_THREAD;
-		} else {
-			result = IRQ_HANDLED;
+		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)
@@ -864,8 +690,11 @@
 	}
 
 	/* 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;
 }