瀏覽代碼

mmc: at91_mci: avoid timeouts

The at91 mci controller internal state machine seems to often crash. This can
be fixed by resetting the controller after each command for at91rm9200 and by
setting the MCI_BLKR register on at91sam926*.

Signed-off-by: Marc Pignat <marc.pignat@hevs.ch>
Signed-off-by: Hans J Koch <hjk@linutronix.de>
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
Marc Pignat 17 年之前
父節點
當前提交
c5a89c6c08
共有 2 個文件被更改,包括 52 次插入0 次删除
  1. 48 0
      drivers/mmc/host/at91_mci.c
  2. 4 0
      include/asm-arm/arch-at91/at91_mci.h

+ 48 - 0
drivers/mmc/host/at91_mci.c

@@ -130,6 +130,43 @@ struct at91mci_host
 	struct timer_list timer;
 	struct timer_list timer;
 };
 };
 
 
+/*
+ * Reset the controller and restore most of the state
+ */
+static void at91_reset_host(struct at91mci_host *host)
+{
+	unsigned long flags;
+	u32 mr;
+	u32 sdcr;
+	u32 dtor;
+	u32 imr;
+
+	local_irq_save(flags);
+	imr = at91_mci_read(host, AT91_MCI_IMR);
+
+	at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+
+	/* save current state */
+	mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+	sdcr = at91_mci_read(host, AT91_MCI_SDCR);
+	dtor = at91_mci_read(host, AT91_MCI_DTOR);
+
+	/* reset the controller */
+	at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
+
+	/* restore state */
+	at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+	at91_mci_write(host, AT91_MCI_MR, mr);
+	at91_mci_write(host, AT91_MCI_SDCR, sdcr);
+	at91_mci_write(host, AT91_MCI_DTOR, dtor);
+	at91_mci_write(host, AT91_MCI_IER, imr);
+
+	/* make sure sdio interrupts will fire */
+	at91_mci_read(host, AT91_MCI_SR);
+
+	local_irq_restore(flags);
+}
+
 static void at91_timeout_timer(unsigned long data)
 static void at91_timeout_timer(unsigned long data)
 {
 {
 	struct at91mci_host *host;
 	struct at91mci_host *host;
@@ -148,6 +185,7 @@ static void at91_timeout_timer(unsigned long data)
 				host->request->cmd->error = -ETIMEDOUT;
 				host->request->cmd->error = -ETIMEDOUT;
 		}
 		}
 
 
+		at91_reset_host(host);
 		mmc_request_done(host->mmc, host->request);
 		mmc_request_done(host->mmc, host->request);
 	}
 	}
 }
 }
@@ -512,6 +550,11 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
 		mr |= AT91_MCI_PDCMODE;
 		mr |= AT91_MCI_PDCMODE;
 		at91_mci_write(host, AT91_MCI_MR, mr);
 		at91_mci_write(host, AT91_MCI_MR, mr);
 
 
+		if (!cpu_is_at91rm9200())
+			at91_mci_write(host, AT91_MCI_BLKR,
+				AT91_MCI_BLKR_BCNT(blocks) |
+				AT91_MCI_BLKR_BLKLEN(block_length));
+
 		/*
 		/*
 		 * Disable the PDC controller
 		 * Disable the PDC controller
 		 */
 		 */
@@ -584,6 +627,11 @@ static void at91_mci_process_next(struct at91mci_host *host)
 		at91_mci_send_command(host, host->request->stop);
 		at91_mci_send_command(host, host->request->stop);
 	} else {
 	} else {
 		del_timer(&host->timer);
 		del_timer(&host->timer);
+		/* the at91rm9200 mci controller hangs after some transfers,
+		 * and the workaround is to reset it after each transfer.
+		 */
+		if (cpu_is_at91rm9200())
+			at91_reset_host(host);
 		mmc_request_done(host->mmc, host->request);
 		mmc_request_done(host->mmc, host->request);
 	}
 	}
 }
 }

+ 4 - 0
include/asm-arm/arch-at91/at91_mci.h

@@ -75,6 +75,10 @@
 #define			AT91_MCI_TRTYP_MULTIPLE	(1 << 19)
 #define			AT91_MCI_TRTYP_MULTIPLE	(1 << 19)
 #define			AT91_MCI_TRTYP_STREAM	(2 << 19)
 #define			AT91_MCI_TRTYP_STREAM	(2 << 19)
 
 
+#define AT91_MCI_BLKR		0x18		/* Block Register */
+#define		AT91_MCI_BLKR_BCNT(n)	((0xffff & (n)) << 0)	/* Block count */
+#define		AT91_MCI_BLKR_BLKLEN(n)	((0xffff & (n)) << 16)	/* Block lenght */
+
 #define AT91_MCI_RSPR(n)	(0x20 + ((n) * 4))	/* Response Registers 0-3 */
 #define AT91_MCI_RSPR(n)	(0x20 + ((n) * 4))	/* Response Registers 0-3 */
 #define AT91_MCR_RDR		0x30		/* Receive Data Register */
 #define AT91_MCR_RDR		0x30		/* Receive Data Register */
 #define AT91_MCR_TDR		0x34		/* Transmit Data Register */
 #define AT91_MCR_TDR		0x34		/* Transmit Data Register */