mtd/drivers/mtd/chips cfi_cmdset_0002.c,1.87,1.88

Thayne Harbaugh tharbaugh at lnxi.com
Fri Oct 17 17:46:28 EDT 2003


Update of /home/cvs/mtd/drivers/mtd/chips
In directory phoenix.infradead.org:/tmp/cvs-serv7518

Modified Files:
	cfi_cmdset_0002.c 
Log Message:
Buffered writes similar to cfi_cmdset_0001.
Patches from Stephane Fillod - thanks!

Index: cfi_cmdset_0002.c
===================================================================
RCS file: /home/cvs/mtd/drivers/mtd/chips/cfi_cmdset_0002.c,v
retrieving revision 1.87
retrieving revision 1.88
diff -u -r1.87 -r1.88
--- cfi_cmdset_0002.c	17 Oct 2003 20:57:13 -0000	1.87
+++ cfi_cmdset_0002.c	17 Oct 2003 21:46:25 -0000	1.88
@@ -83,7 +83,8 @@
 
 
 static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
-static int cfi_amdstd_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
 static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *);
 static int cfi_amdstd_erase_onesize(struct mtd_info *, struct erase_info *);
 static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *);
@@ -365,7 +366,14 @@
 		else
 			mtd->erase = cfi_amdstd_erase_onesize;
 		mtd->read = cfi_amdstd_read;
-		mtd->write = cfi_amdstd_write;
+		if ( cfi->cfiq->BufWriteTimeoutTyp) {
+			DEBUG(MTD_DEBUG_LEVEL1 "Using buffer write method\n" );
+			mtd->write = cfi_amdstd_write_buffers;
+		} else {
+			DEBUG(MTD_DEBUG_LEVEL1 "Using word write method\n" );
+			mtd->write = cfi_amdstd_write_words;
+		}
+
 		break;
 
 	default:
@@ -375,7 +383,7 @@
 		break;
 	}
 	if (cfi->fast_prog) {
-		/* In cfi_amdstd_write() we frob the protection stuff
+		/* In cfi_amdstd_write_words() we frob the protection stuff
 		   without paying any attention to the state machine.
 		   This upsets in-progress erases. So we turn this flag
 		   off for now till the code gets fixed. */
@@ -819,7 +827,9 @@
 	return ret;
 }
 
-static int cfi_amdstd_write (struct mtd_info *mtd, loff_t to , size_t len, size_t *retlen, const u_char *buf)
+
+static int cfi_amdstd_write_words (struct mtd_info *mtd, loff_t to, size_t len,
+				   size_t *retlen, const u_char *buf)
 {
 	struct map_info *map = mtd->priv;
 	struct cfi_private *cfi = map->fldrv_priv;
@@ -1029,6 +1039,372 @@
 			return ret;
 		
 		(*retlen) += n;
+	}
+
+	return 0;
+}
+
+
+/*
+ * FIXME: interleaved mode not tested, and probably not supported!
+ */
+static inline int do_write_buffer(struct map_info *map, struct flchip *chip, 
+				  unsigned long adr, const u_char *buf, int len)
+{
+	unsigned long timeo = jiffies + HZ;
+	cfi_word oldstatus, status, prev_oldstatus, prev_status;
+	cfi_word dq6;
+	struct cfi_private *cfi = map->fldrv_priv;
+    /* We use a 1ms + 1 jiffies generic timeout for writes (most devices have
+       a max write time of a few hundreds usec). However, we should use the
+       maximum timeout value given by the chip at probe time instead. 
+       Unfortunately, struct flchip does have a field for maximum timeout, 
+       only for typical which can be far too short depending of the conditions.
+       The ' + 1' is to avoid having a timeout of 0 jiffies if HZ is smaller
+       than 1000. Using a static variable allows makes us save the costly
+       divide operation at each word write.*/ 
+    static unsigned long uWriteTimeout = ( HZ / 1000 ) + 1;
+	DECLARE_WAITQUEUE(wait, current);
+	int ret = -EIO;
+	int ta = 0;
+	unsigned long cmd_adr;
+	int z, bytes, words;
+	cfi_word datum;
+        
+ retry:
+	cfi_spin_lock(chip->mutex);
+
+	if (chip->state != FL_READY) {
+#if 1
+	        printk(KERN_DEBUG "Waiting for chip to write, status = %d\n", chip->state);
+#endif
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		add_wait_queue(&chip->wq, &wait);
+                
+		cfi_spin_unlock(chip->mutex);
+
+		schedule();
+		remove_wait_queue(&chip->wq, &wait);
+#if 1
+		printk(KERN_DEBUG "Wake up to write:\n");
+		if(signal_pending(current))
+			return -EINTR;
+#endif
+		timeo = jiffies + HZ;
+
+		goto retry;
+	}	
+
+	chip->state = FL_WRITING_TO_BUFFER;
+
+	adr += chip->start;
+	cmd_adr = adr;
+
+
+	if (cfi_buswidth_is_1()) {
+		datum = *(__u8*)buf;
+	} else if (cfi_buswidth_is_2()) {
+		datum = *(__u16*)buf;
+	} else if (cfi_buswidth_is_4()) {
+		datum = *(__u32*)buf;
+#ifdef CFI_WORD_64
+	} else if (cfi_buswidth_is_8()) {
+		datum = *(__u64*)buf;
+#endif
+	} else {
+		printk(KERN_WARNING "MTD %s(): Unsupported buswidth %d\n",
+		       __func__, CFIDEV_BUSWIDTH);
+		return -EINVAL;
+	}
+
+#if 1
+        status = cfi_read(map, cmd_adr);
+
+	DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): READ 0x%.8lx(0x%.8x)\n",
+	       __func__, adr, status );
+
+      	DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): WRITE 0x%.8lx(0x%.8x)\n",
+	       __func__, adr, datum );
+#endif
+
+	ENABLE_VPP(map);
+	cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, CFI_DEVICETYPE_X8, NULL);
+	cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, CFI_DEVICETYPE_X8, NULL);
+	//cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, CFI_DEVICETYPE_X8, NULL);
+
+	/* Write Buffer Load */
+	cfi_write(map, CMD(0x25), cmd_adr);
+
+	/* Write length of data to come */
+	bytes = len & (CFIDEV_BUSWIDTH-1);
+	words = len / CFIDEV_BUSWIDTH;
+	cfi_write(map, CMD(words - !bytes), cmd_adr );
+	/* Write data */
+	z = 0;
+	while(z < words * CFIDEV_BUSWIDTH) {
+		if (cfi_buswidth_is_1()) {
+			datum = *((__u8*)buf);
+			map_write8 (map, *((__u8*)buf)++, adr+z);
+		} else if (cfi_buswidth_is_2()) {
+			datum = *((__u16*)buf);
+			map_write16 (map, *((__u16*)buf)++, adr+z);
+		} else if (cfi_buswidth_is_4()) {
+			datum = *((__u32*)buf);
+			map_write32 (map, *((__u32*)buf)++, adr+z);
+#ifdef CFI_WORD_64
+		} else if (cfi_buswidth_is_8()) {
+			datum = *((__u64*)buf);
+			map_write64 (map, *((__u64*)buf)++, adr+z);
+#endif
+		} else {
+			printk(KERN_WARNING "MTD %s(): Unsupported buswidth %d\n",
+			       __func__, CFIDEV_BUSWIDTH);
+			ret = -EINVAL;
+			goto write_failed;
+		}
+		z += CFIDEV_BUSWIDTH;
+	}
+	if (bytes) {
+		int i = 0, n = 0;
+		u_char tmp_buf[8], *tmp_p = tmp_buf;
+
+		while (bytes--)
+			tmp_buf[i++] = buf[n++];
+		while (i < CFIDEV_BUSWIDTH)
+			tmp_buf[i++] = 0xff;
+		if (cfi_buswidth_is_2()) {
+			datum = *((__u16*)tmp_p);
+			map_write16 (map, *((__u16*)tmp_p)++, adr+z);
+		} else if (cfi_buswidth_is_4()) {
+			datum = *((__u32*)tmp_p);
+			map_write32 (map, *((__u32*)tmp_p)++, adr+z);
+#ifdef CFI_WORD_64
+		} else if (cfi_buswidth_is_8()) {
+			datum = *((__u64*)tmp_p);
+			map_write64 (map, *((__u64*)tmp_p)++, adr+z);
+#endif
+		} else {
+			printk(KERN_WARNING "MTD %s(): Unsupported buswidth %d\n",
+			       __func__, CFIDEV_BUSWIDTH);
+			ret = -EINVAL;
+			goto write_failed;
+		}
+	} else if (words > 0) {
+		z -= CFIDEV_BUSWIDTH;
+	}
+
+	adr += z;
+
+	/* Write Buffer Program Confirm: GO GO GO */
+	cfi_write(map, CMD(0x29), cmd_adr);
+	chip->state = FL_WRITING;
+
+	cfi_spin_unlock(chip->mutex);
+	cfi_udelay(chip->buffer_write_time);
+	cfi_spin_lock(chip->mutex);
+
+	/*
+	 * Polling toggle bits instead of reading back many times
+	 * This ensures that write operation is really completed,
+	 * or tells us why it failed.
+	 *
+	 * It appears that the polling and decoding of error state might
+	 * be simplified.  Don't do it unless you really know what you
+	 * are doing.  You must remember that JESD21-C 3.5.3 states that
+	 * the status must be read back an _additional_ two times before
+	 * a failure is determined.  This is because these devices have
+	 * internal state machines that are asynchronous to the external
+	 * data bus.  During an erase or write the read-back status of the
+	 * polling bits might be transitioning internaly when the external
+	 * read-back occurs.  This means that the bits aren't in the final
+	 * state and they might appear to report an error as they transition
+	 * and are in a weird state.  This will produce infrequent errors
+	 * that will usually disappear the next time an erase or write
+	 * happens (Try tracking those errors down!).  To ensure that
+	 * the bits are not in transition the location must be read-back
+	 * two more times and compared against what was written - BOTH reads
+	 * MUST match what was written - don't think this can be simplified
+	 * to only the last read matching.  If the comparison fails, error
+	 * state can then be decoded.
+	 *
+	 * - Thayne Harbaugh
+	 */
+	dq6 = CMD(1<<6);
+
+	/* See comment above for timeout value. */
+	timeo = jiffies + uWriteTimeout; 
+		
+	oldstatus = cfi_read(map, adr);
+	status = cfi_read(map, adr);
+	DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): Check 0x%.8x 0x%.8x\n",
+	       __func__, oldstatus, status );
+
+	/*
+	 * This only checks if dq6 is still toggling and that our
+	 * timer hasn't expired.  We purposefully ignore the chips
+	 * internal timer that will assert dq5 and leave dq6 toggling.
+	 * This is done for a variety of reasons:
+	 * 1) Not all chips support dq5.
+	 * 2) Dealing with asynchronous status bit and data updates
+	 *    and reading a device two more times creates _messy_
+	 *    logic when trying to deal with interleaved devices -
+	 *    some may be changing while others are still busy.
+	 * 3) Checking dq5 only helps to optimize an error case that
+	 *    should at worst be infrequent and at best non-existent.
+	 *
+	 * If our timeout occurs _then_ we will check dq5 to see
+	 * if the device also had an internal timeout.
+	 */
+	while( ( ( status ^ oldstatus ) & dq6 )
+	       && ! ( ta = time_after(jiffies, timeo) ) ) {
+
+		if (need_resched()) {
+			cfi_spin_unlock(chip->mutex);
+			yield();
+			cfi_spin_lock(chip->mutex);
+		} else 
+			udelay(1);
+
+
+		oldstatus = cfi_read( map, adr );
+		status = cfi_read( map, adr );
+		DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): Check 0x%.8x 0x%.8x\n",
+		       __func__, oldstatus, status );
+	}
+
+	/*
+	 * Something kicked us out of the read-back loop.  We'll
+	 * check success before checking failure.
+	 * Even though dq6 might be true data, it is unkown if
+	 * all of the other bits have changed to true data due to
+	 * the asynchronous nature of the internal state machine.
+	 * We will read two more times and use this to either
+	 * verify that the write completed successfully or
+	 * that something really went wrong.  BOTH reads
+	 * must match what was written - this certifies that
+	 * bits aren't still changing  and that the status
+	 * bits erroneously match the datum that was written.
+	 */
+	prev_oldstatus = oldstatus;
+	prev_status = status;
+	oldstatus = cfi_read(map, adr);
+	status = cfi_read(map, adr);
+	DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): Check 0x%.8x 0x%.8x\n",
+	       __func__, oldstatus, status );
+
+	if ( oldstatus == datum && status == datum ) {
+		/* success - do nothing */
+		ret = 0;
+		goto write_done;
+	}
+
+	if ( ta ) {
+		cfi_word dq5mask = ( ( status ^ oldstatus ) & dq6 ) >> 1;
+		if ( status & dq5mask ) {
+			/* dq5 asserted - decode interleave chips */
+			printk( KERN_WARNING
+				"MTD %s(): FLASH internal timeout: 0x%.8x 0x%.8x 0x%.8x\n",
+				__func__,
+				status & dq5mask, status, datum );
+		} else {
+			printk( KERN_WARNING
+				"MTD %s(): Software timed out during write.\n",
+				__func__ );
+		}
+		goto write_failed;
+	}
+
+	/*
+	 * If we get to here then it means that something
+	 * is wrong and it's not a timeout.  Something
+	 * is seriously wacky!  Dump some debug info.
+	 */
+	printk(KERN_WARNING
+	       "MTD %s(): Wacky!  Unable to decode failure status\n",
+	       __func__ );
+
+	printk(KERN_WARNING
+	       "MTD %s(): 0x%.8lx(0x%.8x): 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
+	       __func__, adr, datum,
+	       prev_oldstatus, prev_status,
+	       oldstatus, status);
+
+ write_failed:
+	/* reset on all failures. */
+	cfi_write( map, CMD(0xF0), chip->start );
+	/* FIXME - should have reset delay before continuing */
+
+ write_done:
+	DISABLE_VPP(map);
+	chip->state = FL_READY;
+	wake_up(&chip->wq);
+	cfi_spin_unlock(chip->mutex);
+
+	return ret;
+}
+
+
+static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
+				    size_t *retlen, const u_char *buf)
+{
+	struct map_info *map = mtd->priv;
+	struct cfi_private *cfi = map->fldrv_priv;
+	int wbufsize = CFIDEV_INTERLEAVE << cfi->cfiq->MaxBufWriteSize;
+	int ret = 0;
+	int chipnum;
+	unsigned long ofs;
+
+	*retlen = 0;
+	if (!len)
+		return 0;
+
+	chipnum = to >> cfi->chipshift;
+	ofs = to  - (chipnum << cfi->chipshift);
+
+	/* If it's not bus-aligned, do the first word write */
+	if (ofs & (CFIDEV_BUSWIDTH-1)) {
+		size_t local_len = (-ofs)&(CFIDEV_BUSWIDTH-1);
+		if (local_len > len)
+			local_len = len;
+		ret = cfi_amdstd_write_words(mtd, to, local_len,
+					       retlen, buf);
+		if (ret)
+			return ret;
+		ofs += local_len;
+		buf += local_len;
+		len -= local_len;
+
+		if (ofs >> cfi->chipshift) {
+			chipnum ++;
+			ofs = 0;
+			if (chipnum == cfi->numchips)
+				return 0;
+		}
+	}
+
+	/* Write buffer is worth it only if more than one word to write... */
+	while (len) {
+		/* We must not cross write block boundaries */
+		int size = wbufsize - (ofs & (wbufsize-1));
+
+		if (size > len)
+			size = len;
+		ret = do_write_buffer(map, &cfi->chips[chipnum], 
+				      ofs, buf, size);
+		if (ret)
+			return ret;
+
+		ofs += size;
+		buf += size;
+		(*retlen) += size;
+		len -= size;
+
+		if (ofs >> cfi->chipshift) {
+			chipnum ++; 
+			ofs = 0;
+			if (chipnum == cfi->numchips)
+				return 0;
+		}
 	}
 
 	return 0;




More information about the linux-mtd-cvs mailing list