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