[PATCHv3 4/5] mtd: mxc_nand fixups

John Ogness john.ogness at linutronix.de
Sat Jun 26 05:17:35 EDT 2010


On 2010-06-25, Ivo Clarysse <ivo.clarysse at gmail.com> wrote:
> Tested on an MX21ADS board, and it still works.
>
> [...]
>
> Naming is not very consistent; I'd suggest nfc_set_interrupt /
> nfc_clear_interrupt

Actually, the set function is a query, not an action. I've renamed it
nfc_isset_interrupt() so that it is clearer. I also fixed a typo in one
of the comments.

This patch is based on linux-next 20100618.

Signed-off-by: John Ogness <john.ogness at linutronix.de>
---
 drivers/mtd/nand/mxc_nand.c |   81 +++++++++++++++++++++++++---------
 1 file changed, 61 insertions(+), 20 deletions(-)

Index: linux-next-20100618/drivers/mtd/nand/mxc_nand.c
===================================================================
--- linux-next-20100618.orig/drivers/mtd/nand/mxc_nand.c
+++ linux-next-20100618/drivers/mtd/nand/mxc_nand.c
@@ -30,6 +30,8 @@
 #include <linux/clk.h>
 #include <linux/err.h>
 #include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/completion.h>
 
 #include <asm/mach/flash.h>
 #include <mach/mxc_nand.h>
@@ -40,6 +42,13 @@
 #define nfc_is_v21()		(cpu_is_mx25() || cpu_is_mx35())
 #define nfc_is_v1()		(cpu_is_mx31() || cpu_is_mx27() || cpu_is_mx21())
 
+/* It has been observed that the i.MX21 cannot read the CONFIG2:INT bit
+ * if interrupts are masked (CONFIG1:INT_MSK is set). To handle this, the
+ * driver can enable/disable the irq line rather than simply masking the
+ * interrupts. The nfc_avoid_masking() macro identifies the systems that
+ * should use this workaround. */
+#define nfc_avoid_masking()	(cpu_is_mx21())
+
 /* Addresses for NFC registers */
 #define NFC_BUF_SIZE		0xE00
 #define NFC_BUF_ADDR		0xE04
@@ -100,6 +109,18 @@
 
 #define NFC_RSLTSPARE_AREA_MASK  0xff
 
+#define nfc_isset_interrupt(_regs) \
+		(readw(_regs + NFC_CONFIG2) & NFC_INT)
+#define nfc_clear_interrupt(_regs) \
+		writew(readw(_regs + NFC_CONFIG2) & ~NFC_INT, \
+		       _regs + NFC_CONFIG2)
+#define nfc_mask_irq(_regs) \
+		writew(readw(_regs + NFC_CONFIG1) | NFC_INT_MSK, \
+		       _regs + NFC_CONFIG1)
+#define nfc_unmask_irq(_regs) \
+		writew(readw(_regs + NFC_CONFIG1) & ~NFC_INT_MSK, \
+		       _regs + NFC_CONFIG1)
+
 struct mxc_nand_host {
 	struct mtd_info		mtd;
 	struct nand_chip	nand;
@@ -117,7 +138,7 @@ struct mxc_nand_host {
 	int			clk_act;
 	int			irq;
 
-	wait_queue_head_t	irq_waitq;
+	struct completion	op_completion;
 
 	uint8_t			*data_buf;
 	unsigned int		buf_start;
@@ -174,9 +195,18 @@ static irqreturn_t mxc_nfc_irq(int irq, 
 {
 	struct mxc_nand_host *host = dev_id;
 
-	disable_irq_nosync(irq);
-
-	wake_up(&host->irq_waitq);
+	/* If NFC_INT is not set, we have a spurious interrupt! */
+	if (!nfc_isset_interrupt(host->regs))
+		return IRQ_NONE;
+
+	/* Even with nfc_avoid_masking() we mask the interrupt
+	 * here to avoid an interrupt flood until the irq line
+	 * is disabled by the driver thread. */
+	nfc_mask_irq(host->regs);
+
+	/* Notify the driver thread that an interrupt has occurred.
+	 * The driver thread will clear the interrupt. */
+	complete(&host->op_completion);
 
 	return IRQ_HANDLED;
 }
@@ -186,27 +216,32 @@ static irqreturn_t mxc_nfc_irq(int irq, 
  */
 static void wait_op_done(struct mxc_nand_host *host, int useirq)
 {
-	uint16_t tmp;
 	int max_retries = 8000;
 
 	if (useirq) {
-		if ((readw(host->regs + NFC_CONFIG2) & NFC_INT) == 0) {
+		if (!nfc_isset_interrupt(host->regs)) {
+
+			INIT_COMPLETION(host->op_completion);
 
-			enable_irq(host->irq);
+			if (nfc_avoid_masking())
+				enable_irq(host->irq);
+			else
+				nfc_unmask_irq(host->regs);
 
-			wait_event(host->irq_waitq,
-				readw(host->regs + NFC_CONFIG2) & NFC_INT);
+			/* wait for the interrupt */
+			wait_for_completion(&host->op_completion);
 
-			tmp = readw(host->regs + NFC_CONFIG2);
-			tmp  &= ~NFC_INT;
-			writew(tmp, host->regs + NFC_CONFIG2);
+			if (nfc_avoid_masking()) {
+				disable_irq(host->irq);
+				nfc_unmask_irq(host->regs);
+			}
 		}
+
+		nfc_clear_interrupt(host->regs);
 	} else {
 		while (max_retries-- > 0) {
-			if (readw(host->regs + NFC_CONFIG2) & NFC_INT) {
-				tmp = readw(host->regs + NFC_CONFIG2);
-				tmp  &= ~NFC_INT;
-				writew(tmp, host->regs + NFC_CONFIG2);
+			if (nfc_isset_interrupt(host->regs)) {
+				nfc_clear_interrupt(host->regs);
 				break;
 			}
 			udelay(1);
@@ -582,9 +617,8 @@ static void preset(struct mtd_info *mtd)
 	struct mxc_nand_host *host = nand_chip->priv;
 	uint16_t tmp;
 
-	/* enable interrupt, disable spare enable, setup ECC */
+	/* disable spare-only, setup ECC */
 	tmp = readw(host->regs + NFC_CONFIG1);
-	tmp &= ~NFC_INT_MSK;
 	tmp &= ~NFC_SP_EN;
 	if (nand_chip->ecc.mode == NAND_ECC_HW) {
 		tmp |= NFC_ECC_EN;
@@ -842,11 +876,18 @@ static int __init mxcnd_probe(struct pla
 		this->options |= NAND_USE_FLASH_BBT;
 	}
 
-	init_waitqueue_head(&host->irq_waitq);
+	init_completion(&host->op_completion);
 
 	host->irq = platform_get_irq(pdev, 0);
 
-	err = request_irq(host->irq, mxc_nfc_irq, IRQF_DISABLED, DRIVER_NAME, host);
+	if (nfc_avoid_masking()) {
+		set_irq_flags(host->irq, IRQF_VALID | IRQF_NOAUTOEN);
+		nfc_unmask_irq(host->regs);
+	} else {
+		nfc_mask_irq(host->regs);
+	}
+
+	err = request_irq(host->irq, mxc_nfc_irq, 0, DRIVER_NAME, host);
 	if (err)
 		goto eirq;
 



More information about the linux-arm-kernel mailing list