Mutex to avoid concurrent NAND, NOR access

The QorIQ LS1024A (aka Comcerto 2000) appears to not properly arbitrate between
NOR and NAND access. On the scope, we see two chip select lines active at the
same time. This patch introduces a global mutex to coordinate between the NOR
and the NAND driver.

This patch has been provided by Freescale.

Change-Id: I7d8a6f8067a47606e280277c8d71223f4f66466a
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
index 6561b14..590fb26 100644
--- a/drivers/mtd/chips/cfi_cmdset_0002.c
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -37,6 +37,7 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/cfi.h>
 #include <linux/mtd/xip.h>
+#include <linux/mtd/exp_lock.h>
 
 #define AMD_BOOTLOC_BUG
 #define FORCE_WORD_WRITE 0
@@ -670,8 +671,12 @@
 				return -EIO;
 			}
 			mutex_unlock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_unlock(&exp_bus_lock);
 			cfi_udelay(1);
 			mutex_lock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_lock(&exp_bus_lock);
 			/* Someone else might have been playing with it. */
 			goto retry;
 		}
@@ -714,8 +719,12 @@
 			}
 
 			mutex_unlock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_unlock(&exp_bus_lock);
 			cfi_udelay(1);
 			mutex_lock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_lock(&exp_bus_lock);
 			/* Nobody will touch it while it's in state FL_ERASE_SUSPENDING.
 			   So we can just loop here. */
 		}
@@ -744,9 +753,13 @@
 		set_current_state(TASK_UNINTERRUPTIBLE);
 		add_wait_queue(&chip->wq, &wait);
 		mutex_unlock(&chip->mutex);
+		/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+		mutex_unlock(&exp_bus_lock);
 		schedule();
 		remove_wait_queue(&chip->wq, &wait);
 		mutex_lock(&chip->mutex);
+		/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+		mutex_lock(&exp_bus_lock);
 		goto resettime;
 	}
 }
@@ -878,6 +891,8 @@
 			xip_iprefetch();
 			local_irq_enable();
 			mutex_unlock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_unlock(&exp_bus_lock);
 			xip_iprefetch();
 			cond_resched();
 
@@ -888,14 +903,20 @@
 			 * until it's done.
 			 */
 			mutex_lock(&chip->mutex);
+			/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+			mutex_lock(&exp_bus_lock);
 			while (chip->state != FL_XIP_WHILE_ERASING) {
 				DECLARE_WAITQUEUE(wait, current);
 				set_current_state(TASK_UNINTERRUPTIBLE);
 				add_wait_queue(&chip->wq, &wait);
 				mutex_unlock(&chip->mutex);
+				/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+				mutex_unlock(&exp_bus_lock);
 				schedule();
 				remove_wait_queue(&chip->wq, &wait);
 				mutex_lock(&chip->mutex);
+				/* mutex to prevent concurrent NOR/NAND access on the EXP bus */
+				mutex_lock(&exp_bus_lock);
 			}
 			/* Disallow XIP again */
 			local_irq_disable();
@@ -958,16 +979,20 @@
 #define UDELAY(map, chip, adr, usec)  \
 do {  \
 	mutex_unlock(&chip->mutex);  \
+	mutex_unlock(&exp_bus_lock); \
 	cfi_udelay(usec);  \
 	mutex_lock(&chip->mutex);  \
+	mutex_lock(&exp_bus_lock); \
 } while (0)
 
 #define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec)  \
 do {  \
 	mutex_unlock(&chip->mutex);  \
+	mutex_unlock(&exp_bus_lock); \
 	INVALIDATE_CACHED_RANGE(map, adr, len);  \
 	cfi_udelay(usec);  \
 	mutex_lock(&chip->mutex);  \
+	mutex_lock(&exp_bus_lock); \
 } while (0)
 
 #endif
@@ -984,9 +1009,14 @@
 	cmd_addr = adr & ~(map_bankwidth(map)-1);
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, cmd_addr, FL_READY);
 	if (ret) {
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 		return ret;
 	}
 
@@ -1000,6 +1030,8 @@
 	put_chip(map, chip, cmd_addr);
 
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 	return 0;
 }
 
@@ -1054,12 +1086,16 @@
 
  retry:
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
 
 	if (chip->state != FL_READY){
 		set_current_state(TASK_UNINTERRUPTIBLE);
 		add_wait_queue(&chip->wq, &wait);
 
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 
 		schedule();
 		remove_wait_queue(&chip->wq, &wait);
@@ -1085,6 +1121,8 @@
 
 	wake_up(&chip->wq);
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 
 	return 0;
 }
@@ -1154,9 +1192,15 @@
 	adr += chip->start;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
+
 	ret = get_chip(map, chip, adr, FL_WRITING);
 	if (ret) {
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 		return ret;
 	}
 
@@ -1200,10 +1244,14 @@
 			set_current_state(TASK_UNINTERRUPTIBLE);
 			add_wait_queue(&chip->wq, &wait);
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 			schedule();
 			remove_wait_queue(&chip->wq, &wait);
 			timeo = jiffies + (HZ / 2); /* FIXME */
 			mutex_lock(&chip->mutex);
+			// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_lock(&exp_bus_lock);
 			continue;
 		}
 
@@ -1236,6 +1284,8 @@
 	chip->state = FL_READY;
 	put_chip(map, chip, adr);
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 
 	return ret;
 }
@@ -1389,9 +1439,14 @@
 	cmd_adr = adr;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, adr, FL_WRITING);
 	if (ret) {
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 		return ret;
 	}
 
@@ -1410,6 +1465,9 @@
 	/* Write Buffer Load */
 	map_write(map, CMD(0x25), cmd_adr);
 
+	/* patch from https://dev.openwrt.org/browser/trunk/target/linux/generic/patches-3.2/475-mtd_cfi_cmdset_0002-add-buffer-write-cmd-timeout.patch?rev=31000 */
+	(void) map_read(map, cmd_adr);
+
 	chip->state = FL_WRITING_TO_BUFFER;
 
 	/* Write length of data to come */
@@ -1446,10 +1504,14 @@
 			set_current_state(TASK_UNINTERRUPTIBLE);
 			add_wait_queue(&chip->wq, &wait);
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 			schedule();
 			remove_wait_queue(&chip->wq, &wait);
 			timeo = jiffies + (HZ / 2); /* FIXME */
 			mutex_lock(&chip->mutex);
+			// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_lock(&exp_bus_lock);
 			continue;
 		}
 
@@ -1490,6 +1552,8 @@
 	chip->state = FL_READY;
 	put_chip(map, chip, adr);
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 
 	return ret;
 }
@@ -1590,9 +1654,14 @@
 	adr = cfi->addr_unlock1;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, adr, FL_WRITING);
 	if (ret) {
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 		return ret;
 	}
 
@@ -1626,9 +1695,13 @@
 			set_current_state(TASK_UNINTERRUPTIBLE);
 			add_wait_queue(&chip->wq, &wait);
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 			schedule();
 			remove_wait_queue(&chip->wq, &wait);
 			mutex_lock(&chip->mutex);
+			// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_lock(&exp_bus_lock);
 			continue;
 		}
 		if (chip->erase_suspended) {
@@ -1663,6 +1736,8 @@
 	xip_enable(map, chip, adr);
 	put_chip(map, chip, adr);
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 
 	return ret;
 }
@@ -1678,9 +1753,14 @@
 	adr += chip->start;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, adr, FL_ERASING);
 	if (ret) {
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 		return ret;
 	}
 
@@ -1714,9 +1794,13 @@
 			set_current_state(TASK_UNINTERRUPTIBLE);
 			add_wait_queue(&chip->wq, &wait);
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 			schedule();
 			remove_wait_queue(&chip->wq, &wait);
 			mutex_lock(&chip->mutex);
+			// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_lock(&exp_bus_lock);
 			continue;
 		}
 		if (chip->erase_suspended) {
@@ -1753,6 +1837,9 @@
 	chip->state = FL_READY;
 	put_chip(map, chip, adr);
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
+
 	return ret;
 }
 
@@ -1805,6 +1892,9 @@
 	int ret;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, adr + chip->start, FL_LOCKING);
 	if (ret)
 		goto out_unlock;
@@ -1830,6 +1920,8 @@
 
 out_unlock:
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 	return ret;
 }
 
@@ -1840,6 +1932,9 @@
 	int ret;
 
 	mutex_lock(&chip->mutex);
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	ret = get_chip(map, chip, adr + chip->start, FL_UNLOCKING);
 	if (ret)
 		goto out_unlock;
@@ -1857,6 +1952,8 @@
 
 out_unlock:
 	mutex_unlock(&chip->mutex);
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 	return ret;
 }
 
@@ -1885,6 +1982,8 @@
 
 	retry:
 		mutex_lock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_lock(&exp_bus_lock);
 
 		switch(chip->state) {
 		case FL_READY:
@@ -1899,6 +1998,8 @@
 			 */
 		case FL_SYNCING:
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 			break;
 
 		default:
@@ -1907,6 +2008,8 @@
 			add_wait_queue(&chip->wq, &wait);
 
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 
 			schedule();
 
@@ -1922,12 +2025,16 @@
 		chip = &cfi->chips[i];
 
 		mutex_lock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_lock(&exp_bus_lock);
 
 		if (chip->state == FL_SYNCING) {
 			chip->state = chip->oldstate;
 			wake_up(&chip->wq);
 		}
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 	}
 }
 
@@ -1944,6 +2051,8 @@
 		chip = &cfi->chips[i];
 
 		mutex_lock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_lock(&exp_bus_lock);
 
 		switch(chip->state) {
 		case FL_READY:
@@ -1964,6 +2073,9 @@
 			break;
 		}
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
+
 	}
 
 	/* Unlock the chips again */
@@ -1973,12 +2085,16 @@
 			chip = &cfi->chips[i];
 
 			mutex_lock(&chip->mutex);
+			// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_lock(&exp_bus_lock);
 
 			if (chip->state == FL_PM_SUSPENDED) {
 				chip->state = chip->oldstate;
 				wake_up(&chip->wq);
 			}
 			mutex_unlock(&chip->mutex);
+			// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+			mutex_unlock(&exp_bus_lock);
 		}
 	}
 
@@ -1998,6 +2114,8 @@
 		chip = &cfi->chips[i];
 
 		mutex_lock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_lock(&exp_bus_lock);
 
 		if (chip->state == FL_PM_SUSPENDED) {
 			chip->state = FL_READY;
@@ -2008,6 +2126,8 @@
 			printk(KERN_ERR "Argh. Chip not in PM_SUSPENDED state upon resume()\n");
 
 		mutex_unlock(&chip->mutex);
+		// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 	}
 }
 
@@ -2030,6 +2150,8 @@
 		chip = &cfi->chips[i];
 
 		mutex_lock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_lock(&exp_bus_lock);
 
 		ret = get_chip(map, chip, chip->start, FL_SHUTDOWN);
 		if (!ret) {
@@ -2039,6 +2161,8 @@
 		}
 
 		mutex_unlock(&chip->mutex);
+		// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+		mutex_unlock(&exp_bus_lock);
 	}
 
 	return 0;
diff --git a/drivers/mtd/chips/gen_probe.c b/drivers/mtd/chips/gen_probe.c
index 3b9a284..1a37000 100644
--- a/drivers/mtd/chips/gen_probe.c
+++ b/drivers/mtd/chips/gen_probe.c
@@ -11,6 +11,12 @@
 #include <linux/mtd/map.h>
 #include <linux/mtd/cfi.h>
 #include <linux/mtd/gen_probe.h>
+#include <linux/mtd/exp_lock.h>
+
+/* Define a mutex available to both comcerto NAND driver and cfi NOR flash driver */
+DEFINE_MUTEX(exp_bus_lock);
+EXPORT_SYMBOL(exp_bus_lock);
+
 
 static struct mtd_info *check_cmd_set(struct map_info *, int);
 static struct cfi_private *genprobe_ident_chips(struct map_info *map,
@@ -29,6 +35,9 @@
 	if (!cfi)
 		return NULL;
 
+	/* Init the mutex which prevents concurrent NOR and NAND accesses to C2k EXP_BUS */
+	mutex_init(&exp_bus_lock);
+
 	map->fldrv_priv = cfi;
 	/* OK we liked it. Now find a driver for the command set it talks */
 
diff --git a/drivers/mtd/nand/comcerto_nand.c b/drivers/mtd/nand/comcerto_nand.c
index 7a1c161..6c840ac 100644
--- a/drivers/mtd/nand/comcerto_nand.c
+++ b/drivers/mtd/nand/comcerto_nand.c
@@ -26,6 +26,7 @@
 #include <linux/ratelimit.h>
 #include <linux/platform_device.h>
 #include <mach/ecc.h>
+#include <linux/mtd/exp_lock.h>
 
 /*
  * MTD structure for Comcerto board
@@ -285,7 +286,7 @@
 	if (likely(!((readl_relaxed(ecc_base_addr + ECC_POLY_STAT)) & ECC_CORR_REQ))) {
 		return 0;
 	}
- 
+
 	/* Error found! Correction required */
 #if defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) || defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH)
 	/* Initiate correction operation */
@@ -389,6 +390,8 @@
 	const uint8_t *p = buf;
 	uint8_t *oob = chip->oob_poi;
 
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
 	/* CS4 will have the option for ECC calculation */
 	writel_relaxed(ECC_CS4_SEL, ecc_base_addr + ECC_CS_SEL_CFG);
 
@@ -410,6 +413,9 @@
 	i = mtd->oobsize - (oob - chip->oob_poi);
 	if (i)
 		chip->write_buf(mtd, oob, i);
+
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 }
 
 /** reads single page from the NAND device and will read ECC bytes from flash. A
@@ -433,6 +439,9 @@
 	uint8_t stat;
 	uint8_t *oob = nand_device->oob_poi;
 
+	// lock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_lock(&exp_bus_lock);
+
 	for (; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
 
 		chip->ecc.hwctl(mtd, NAND_ECC_READ);
@@ -464,6 +473,8 @@
 	if (i)
 		chip->read_buf(mtd, oob, i);
 
+	// unlock mutex to prevent simultaneous NAND and NOR access to Comcerto2000 EXP bus
+	mutex_unlock(&exp_bus_lock);
 	return 0;
 }
 
diff --git a/include/linux/mtd/exp_lock.h b/include/linux/mtd/exp_lock.h
new file mode 100644
index 0000000..8360bc6
--- /dev/null
+++ b/include/linux/mtd/exp_lock.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __MTD_EXP_LOCK_H__
+#define __MTD_EXP_LOCK_H__
+
+/* Define a mutex available to both comcerto NAND driver and cfi NOR flash driver */
+extern struct mutex exp_bus_lock;
+
+#endif /* __MTD_MTD_H__ */