[PATCH] cafe_nand: remove busy-wait loop

Daniel Drake dsd at laptop.org
Tue Dec 9 11:05:08 EST 2008


This patch enables interrupts for DMA and command completion events, and
uses them to determine when commands and transfers have completed.

This avoids a busy-wait loop which was a waste of CPU time.

Signed-off-by: Daniel Drake <dsd at laptop.org>

---
Where do we go with this? Who's adventurous enough to test it? Dare we
put it in joyride? Any suggestions for how to measure performance
difference?

It works for me.

Index: linux-2.6.27-gentoo-r4/drivers/mtd/nand/cafe_nand.c
===================================================================
--- linux-2.6.27-gentoo-r4.orig/drivers/mtd/nand/cafe_nand.c
+++ linux-2.6.27-gentoo-r4/drivers/mtd/nand/cafe_nand.c
@@ -17,6 +17,8 @@
 #include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
 #include <asm/io.h>
 
 #define CAFE_NAND_CTRL1		0x00
@@ -51,12 +53,23 @@
 /* Missing from the datasheet: bit 19 of CTRL1 sets CE0 vs. CE1 */
 #define CTRL1_CHIPSELECT	(1<<19)
 
+/* Enable command done and DMA done interrupts */
+static const uint32_t cafe_interrupt_mask = 0x6fffffff;
+
 struct cafe_priv {
 	struct nand_chip nand;
 	struct mtd_partition *parts;
 	struct pci_dev *pdev;
 	void __iomem *mmio;
 	struct rs_control *rs;
+
+	/* waitqueue for interrupt arrival notification */
+	wait_queue_head_t wq;
+
+	/* when an interrupt arrives, the IRQ bits are set in the irqs member */
+	spinlock_t irqs_lock;
+	uint32_t irqs;
+
 	uint32_t ctl1;
 	uint32_t ctl2;
 	int datalen;
@@ -153,6 +166,19 @@ static uint8_t cafe_read_byte(struct mtd
 	return d;
 }
 
+static uint32_t check_irq(struct cafe_priv *cafe, uint32_t doneint)
+{
+	uint32_t val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&cafe->irqs_lock, flags);
+	val = cafe->irqs;
+	spin_unlock_irqrestore(&cafe->irqs_lock, flags);
+
+	cafe_dev_dbg(&cafe->pdev->dev, "check_irq %x\n", val);
+	return val & doneint;
+}
+
 static void cafe_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
 			      int column, int page_addr)
 {
@@ -160,6 +186,7 @@ static void cafe_nand_cmdfunc(struct mtd
 	int adrbytes = 0;
 	uint32_t ctl1;
 	uint32_t doneint = 0x80000000;
+	unsigned long flags;
 
 	cafe_dev_dbg(&cafe->pdev->dev, "cmdfunc %02x, 0x%x, 0x%x\n",
 		command, column, page_addr);
@@ -243,6 +270,10 @@ static void cafe_nand_cmdfunc(struct mtd
 	cafe_dev_dbg(&cafe->pdev->dev, "dlen %x, ctl1 %x, ctl2 %x\n",
 		cafe->datalen, ctl1, cafe_readl(cafe, NAND_CTRL2));
 
+	spin_lock_irqsave(&cafe->irqs_lock, flags);
+	cafe->irqs = 0;
+	spin_unlock_irqrestore(&cafe->irqs_lock, flags);
+
 	/* NB: The datasheet lies -- we really should be subtracting 1 here */
 	cafe_writel(cafe, cafe->datalen, NAND_DATA_LEN);
 	cafe_writel(cafe, 0x90000000, NAND_IRQ);
@@ -273,21 +304,14 @@ static void cafe_nand_cmdfunc(struct mtd
 	ndelay(100);
 
 	if (1) {
-		int c;
-		uint32_t irqs;
+		long ret;
+		cafe_dev_dbg(&cafe->pdev->dev, "Entering wait_event\n");
+		ret = wait_event_timeout(cafe->wq, check_irq(cafe, doneint), HZ / 2);
+		if (unlikely(ret == 0))
+			dev_warn(&cafe->pdev->dev, "Command wait IRQ timeout\n");
 
-		for (c = 500000; c != 0; c--) {
-			irqs = cafe_readl(cafe, NAND_IRQ);
-			if (irqs & doneint)
-				break;
-			udelay(1);
-			if (!(c % 100000))
-				cafe_dev_dbg(&cafe->pdev->dev, "Wait for ready, IRQ %x\n", irqs);
-			cpu_relax();
-		}
-		cafe_writel(cafe, doneint, NAND_IRQ);
-		cafe_dev_dbg(&cafe->pdev->dev, "Command %x completed after %d usec, irqs %x (%x)\n",
-			     command, 500000-c, irqs, cafe_readl(cafe, NAND_IRQ));
+		cafe_dev_dbg(&cafe->pdev->dev, "Command %x completed in %ld jiffies, irqs %x\n",
+			     command, (HZ / 2) - ret, cafe_readl(cafe, NAND_IRQ));
 	}
 
 	WARN_ON(cafe->ctl2 & (1<<30));
@@ -334,11 +358,19 @@ static int cafe_nand_interrupt(int irq, 
 	struct mtd_info *mtd = id;
 	struct cafe_priv *cafe = mtd->priv;
 	uint32_t irqs = cafe_readl(cafe, NAND_IRQ);
-	cafe_writel(cafe, irqs & ~0x90000000, NAND_IRQ);
 	if (!irqs)
 		return IRQ_NONE;
 
+	/* clear interrupts */
+	cafe_writel(cafe, irqs, NAND_IRQ);
+
 	cafe_dev_dbg(&cafe->pdev->dev, "irq, bits %x (%x)\n", irqs, cafe_readl(cafe, NAND_IRQ));
+
+	spin_lock(&cafe->irqs_lock);
+	cafe->irqs |= irqs;
+	spin_unlock(&cafe->irqs_lock);
+
+	wake_up(&cafe->wq);
 	return IRQ_HANDLED;
 }
 
@@ -655,6 +687,8 @@ static int __devinit cafe_nand_probe(str
 	mtd->owner = THIS_MODULE;
 
 	cafe->pdev = pdev;
+	init_waitqueue_head(&cafe->wq);
+	spin_lock_init(&cafe->irqs_lock);
 	cafe->mmio = pci_iomap(pdev, 0, 0);
 	if (!cafe->mmio) {
 		dev_warn(&pdev->dev, "failed to iomap\n");
@@ -721,7 +755,7 @@ static int __devinit cafe_nand_probe(str
 	cafe_writel(cafe, timing[1], NAND_TIMING2);
 	cafe_writel(cafe, timing[2], NAND_TIMING3);
 
-	cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK);
+	cafe_writel(cafe, cafe_interrupt_mask, NAND_IRQ_MASK);
 	err = request_irq(pdev->irq, &cafe_nand_interrupt, IRQF_SHARED,
 			  "CAFE NAND", mtd);
 	if (err) {
@@ -857,7 +891,7 @@ static int cafe_nand_resume(struct pci_d
        /* Start off by resetting the NAND controller completely */
 	cafe_writel(cafe, 1, NAND_RESET);
 	cafe_writel(cafe, 0, NAND_RESET);
-	cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK);
+	cafe_writel(cafe, cafe_interrupt_mask, NAND_IRQ_MASK);
 
 	/* Restore timing configuration */
 	cafe_writel(cafe, timing[0], NAND_TIMING1);



More information about the Devel mailing list