[MTD] Pre-CFI Sharp chip driver: Some speedups and cleanups
[safe/jmp/linux-2.6] / drivers / mtd / chips / cfi_cmdset_0001.c
index b3f5acf..1e99dff 100644 (file)
@@ -4,7 +4,7 @@
  *
  * (C) 2000 Red Hat. GPL'd
  *
- * $Id: cfi_cmdset_0001.c,v 1.167 2005/02/08 17:11:15 nico Exp $
+ * $Id: cfi_cmdset_0001.c,v 1.180 2005/07/20 21:01:13 tpoynor Exp $
  *
  * 
  * 10/10/2000  Nicolas Pitre <nico@cam.org>
@@ -29,6 +29,7 @@
 #include <linux/slab.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
+#include <linux/reboot.h>
 #include <linux/mtd/xip.h>
 #include <linux/mtd/map.h>
 #include <linux/mtd/mtd.h>
@@ -54,6 +55,7 @@ static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *);
 static void cfi_intelext_sync (struct mtd_info *);
 static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
 static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
+#ifdef CONFIG_MTD_OTP
 static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
 static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
 static int cfi_intelext_write_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
@@ -62,8 +64,10 @@ static int cfi_intelext_get_fact_prot_info (struct mtd_info *,
                                            struct otp_info *, size_t);
 static int cfi_intelext_get_user_prot_info (struct mtd_info *,
                                            struct otp_info *, size_t);
+#endif
 static int cfi_intelext_suspend (struct mtd_info *);
 static void cfi_intelext_resume (struct mtd_info *);
+static int cfi_intelext_reboot (struct notifier_block *, unsigned long, void *);
 
 static void cfi_intelext_destroy(struct mtd_info *);
 
@@ -248,6 +252,15 @@ read_pri_intelext(struct map_info *map, __u16 adr)
        if (!extp)
                return NULL;
 
+       if (extp->MajorVersion != '1' ||
+           (extp->MinorVersion < '0' || extp->MinorVersion > '3')) {
+               printk(KERN_ERR "  Unknown Intel/Sharp Extended Query "
+                      "version %c.%c.\n",  extp->MajorVersion,
+                      extp->MinorVersion);
+               kfree(extp);
+               return NULL;
+       }
+
        /* Do some byteswapping if necessary */
        extp->FeatureSupport = le32_to_cpu(extp->FeatureSupport);
        extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask);
@@ -331,7 +344,9 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary)
        mtd->resume  = cfi_intelext_resume;
        mtd->flags   = MTD_CAP_NORFLASH;
        mtd->name    = map->name;
-       
+
+       mtd->reboot_notifier.notifier_call = cfi_intelext_reboot;
+
        if (cfi->cfi_mode == CFI_MODE_CFI) {
                /* 
                 * It's a real CFI chip, not one for which the probe
@@ -444,6 +459,7 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
                goto setup_err;
 
        __module_get(THIS_MODULE);
+       register_reboot_notifier(&mtd->reboot_notifier);
        return mtd;
 
  setup_err:
@@ -819,10 +835,6 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad
  * assembly to make sure inline functions were actually inlined and that gcc
  * didn't emit calls to its own support functions). Also configuring MTD CFI
  * support to a single buswidth and a single interleave is also recommended.
- * Note that not only IRQs are disabled but the preemption count is also
- * increased to prevent other locking primitives (namely spin_unlock) from
- * decrementing the preempt count to zero and scheduling the CPU away while
- * not in array mode.
  */
 
 static void xip_disable(struct map_info *map, struct flchip *chip,
@@ -830,7 +842,6 @@ static void xip_disable(struct map_info *map, struct flchip *chip,
 {
        /* TODO: chips with no XIP use should ignore and return */
        (void) map_read(map, adr); /* ensure mmu mapping is up to date */
-       preempt_disable();
        local_irq_disable();
 }
 
@@ -843,9 +854,8 @@ static void __xipram xip_enable(struct map_info *map, struct flchip *chip,
                chip->state = FL_READY;
        }
        (void) map_read(map, adr);
-       asm volatile (".rep 8; nop; .endr"); /* fill instruction prefetch */
+       xip_iprefetch();
        local_irq_enable();
-       preempt_enable();
 }
 
 /*
@@ -921,7 +931,7 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
                        (void) map_read(map, adr);
                        asm volatile (".rep 8; nop; .endr");
                        local_irq_enable();
-                       preempt_enable();
+                       spin_unlock(chip->mutex);
                        asm volatile (".rep 8; nop; .endr");
                        cond_resched();
 
@@ -931,15 +941,15 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
                         * a suspended erase state.  If so let's wait
                         * until it's done.
                         */
-                       preempt_disable();
+                       spin_lock(chip->mutex);
                        while (chip->state != newstate) {
                                DECLARE_WAITQUEUE(wait, current);
                                set_current_state(TASK_UNINTERRUPTIBLE);
                                add_wait_queue(&chip->wq, &wait);
-                               preempt_enable();
+                               spin_unlock(chip->mutex);
                                schedule();
                                remove_wait_queue(&chip->wq, &wait);
-                               preempt_disable();
+                               spin_lock(chip->mutex);
                        }
                        /* Disallow XIP again */
                        local_irq_disable();
@@ -968,12 +978,14 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
  * The INVALIDATE_CACHED_RANGE() macro is normally used in parallel while
  * the flash is actively programming or erasing since we have to poll for
  * the operation to complete anyway.  We can't do that in a generic way with
- * a XIP setup so do it before the actual flash operation in this case.
+ * a XIP setup so do it before the actual flash operation in this case
+ * and stub it out from INVALIDATE_CACHE_UDELAY.
  */
-#undef INVALIDATE_CACHED_RANGE
-#define INVALIDATE_CACHED_RANGE(x...)
-#define XIP_INVAL_CACHED_RANGE(map, from, size) \
-       do { if(map->inval_cache) map->inval_cache(map, from, size); } while(0)
+#define XIP_INVAL_CACHED_RANGE(map, from, size)  \
+       INVALIDATE_CACHED_RANGE(map, from, size)
+
+#define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec)  \
+       UDELAY(map, chip, adr, usec)
 
 /*
  * Extra notes:
@@ -996,11 +1008,23 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
 
 #define xip_disable(map, chip, adr)
 #define xip_enable(map, chip, adr)
-
-#define UDELAY(map, chip, adr, usec)  cfi_udelay(usec)
-
 #define XIP_INVAL_CACHED_RANGE(x...)
 
+#define UDELAY(map, chip, adr, usec)  \
+do {  \
+       spin_unlock(chip->mutex);  \
+       cfi_udelay(usec);  \
+       spin_lock(chip->mutex);  \
+} while (0)
+
+#define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec)  \
+do {  \
+       spin_unlock(chip->mutex);  \
+       INVALIDATE_CACHED_RANGE(map, adr, len);  \
+       cfi_udelay(usec);  \
+       spin_lock(chip->mutex);  \
+} while (0)
+
 #endif
 
 static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len)
@@ -1220,10 +1244,9 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        map_write(map, datum, adr);
        chip->state = mode;
 
-       spin_unlock(chip->mutex);
-       INVALIDATE_CACHED_RANGE(map, adr, map_bankwidth(map));
-       UDELAY(map, chip, adr, chip->word_write_time);
-       spin_lock(chip->mutex);
+       INVALIDATE_CACHE_UDELAY(map, chip,
+                               adr, map_bankwidth(map),
+                               chip->word_write_time);
 
        timeo = jiffies + (HZ/2);
        z = 0;
@@ -1256,10 +1279,8 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
                }
 
                /* Latency issues. Drop the lock, wait a while and retry */
-               spin_unlock(chip->mutex);
                z++;
                UDELAY(map, chip, adr, 1);
-               spin_lock(chip->mutex);
        }
        if (!z) {
                chip->word_write_time--;
@@ -1423,9 +1444,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
                if (map_word_andequal(map, status, status_OK, status_OK))
                        break;
 
-               spin_unlock(chip->mutex);
                UDELAY(map, chip, cmd_adr, 1);
-               spin_lock(chip->mutex);
 
                if (++z > 20) {
                        /* Argh. Not ready for write to buffer */
@@ -1471,10 +1490,9 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
        map_write(map, CMD(0xd0), cmd_adr);
        chip->state = FL_WRITING;
 
-       spin_unlock(chip->mutex);
-       INVALIDATE_CACHED_RANGE(map, adr, len);
-       UDELAY(map, chip, cmd_adr, chip->buffer_write_time);
-       spin_lock(chip->mutex);
+       INVALIDATE_CACHE_UDELAY(map, chip, 
+                               cmd_adr, len,
+                               chip->buffer_write_time);
 
        timeo = jiffies + (HZ/2);
        z = 0;
@@ -1506,10 +1524,8 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
                }
                
                /* Latency issues. Drop the lock, wait a while and retry */
-               spin_unlock(chip->mutex);
-               UDELAY(map, chip, cmd_adr, 1);
                z++;
-               spin_lock(chip->mutex);
+               UDELAY(map, chip, cmd_adr, 1);
        }
        if (!z) {
                chip->buffer_write_time--;
@@ -1637,10 +1653,9 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
        chip->state = FL_ERASING;
        chip->erase_suspended = 0;
 
-       spin_unlock(chip->mutex);
-       INVALIDATE_CACHED_RANGE(map, adr, len);
-       UDELAY(map, chip, adr, chip->erase_time*1000/2);
-       spin_lock(chip->mutex);
+       INVALIDATE_CACHE_UDELAY(map, chip,
+                               adr, len,
+                               chip->erase_time*1000/2);
 
        /* FIXME. Use a timer to check this, and return immediately. */
        /* Once the state machine's known to be working I'll do that */
@@ -1685,9 +1700,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
                }
                
                /* Latency issues. Drop the lock, wait a while and retry */
-               spin_unlock(chip->mutex);
                UDELAY(map, chip, adr, 1000000/HZ);
-               spin_lock(chip->mutex);
        }
 
        /* We've broken this before. It doesn't hurt to be safe */
@@ -1697,44 +1710,34 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
 
        /* check for lock bit */
        if (map_word_bitsset(map, status, CMD(0x3a))) {
-               unsigned char chipstatus;
+               unsigned long chipstatus;
 
                /* Reset the error bits */
                map_write(map, CMD(0x50), adr);
                map_write(map, CMD(0x70), adr);
                xip_enable(map, chip, adr);
 
-               chipstatus = status.x[0];
-               if (!map_word_equal(map, status, CMD(chipstatus))) {
-                       int i, w;
-                       for (w=0; w<map_words(map); w++) {
-                               for (i = 0; i<cfi_interleave(cfi); i++) {
-                                       chipstatus |= status.x[w] >> (cfi->device_type * 8);
-                               }
-                       }
-                       printk(KERN_WARNING "Status is not identical for all chips: 0x%lx. Merging to give 0x%02x\n",
-                              status.x[0], chipstatus);
-               }
+               chipstatus = MERGESTATUS(status);
 
                if ((chipstatus & 0x30) == 0x30) {
-                       printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%x\n", chipstatus);
+                       printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%lx\n", chipstatus);
                        ret = -EIO;
                } else if (chipstatus & 0x02) {
                        /* Protection bit set */
                        ret = -EROFS;
                } else if (chipstatus & 0x8) {
                        /* Voltage */
-                       printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%x\n", chipstatus);
+                       printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%lx\n", chipstatus);
                        ret = -EIO;
                } else if (chipstatus & 0x20) {
                        if (retries--) {
-                               printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x. Retrying...\n", adr, chipstatus);
+                               printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%lx. Retrying...\n", adr, chipstatus);
                                timeo = jiffies + HZ;
                                put_chip(map, chip, adr);
                                spin_unlock(chip->mutex);
                                goto retry;
                        }
-                       printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x\n", adr, chipstatus);
+                       printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%lx\n", adr, chipstatus);
                        ret = -EIO;
                }
        } else {
@@ -1799,6 +1802,7 @@ static void cfi_intelext_sync (struct mtd_info *mtd)
                
                if (chip->state == FL_SYNCING) {
                        chip->state = chip->oldstate;
+                       chip->oldstate = FL_READY;
                        wake_up(&chip->wq);
                }
                spin_unlock(chip->mutex);
@@ -1814,8 +1818,9 @@ static int __xipram do_printlockstatus_oneblock(struct map_info *map,
        struct cfi_private *cfi = map->fldrv_priv;
        int status, ofs_factor = cfi->interleave * cfi->device_type;
 
+       adr += chip->start;
        xip_disable(map, chip, adr+(2*ofs_factor));
-       cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+       map_write(map, CMD(0x90), adr+(2*ofs_factor));
        chip->state = FL_JEDEC_QUERY;
        status = cfi_read_query(map, adr+(2*ofs_factor));
        xip_enable(map, chip, 0);
@@ -1832,6 +1837,7 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip
                                       unsigned long adr, int len, void *thunk)
 {
        struct cfi_private *cfi = map->fldrv_priv;
+       struct cfi_pri_intelext *extp = cfi->cmdset_priv;
        map_word status, status_OK;
        unsigned long timeo = jiffies + HZ;
        int ret;
@@ -1861,9 +1867,13 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip
        } else
                BUG();
 
-       spin_unlock(chip->mutex);
-       UDELAY(map, chip, adr, 1000000/HZ);
-       spin_lock(chip->mutex);
+       /*
+        * If Instant Individual Block Locking supported then no need
+        * to delay.
+        */
+
+       if (!extp || !(extp->FeatureSupport & (1 << 5)))
+               UDELAY(map, chip, adr, 1000000/HZ);
 
        /* FIXME. Use a timer to check this, and return immediately. */
        /* Once the state machine's known to be working I'll do that */
@@ -1890,9 +1900,7 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip
                }
                
                /* Latency issues. Drop the lock, wait a while and retry */
-               spin_unlock(chip->mutex);
                UDELAY(map, chip, adr, 1);
-               spin_lock(chip->mutex);
        }
        
        /* Done and happy. */
@@ -1972,8 +1980,7 @@ do_otp_read(struct map_info *map, struct flchip *chip, u_long offset,
        }
 
        /* let's ensure we're not reading back cached data from array mode */
-       if (map->inval_cache)
-               map->inval_cache(map, chip->start + offset, size);
+       INVALIDATE_CACHED_RANGE(map, chip->start + offset, size);
 
        xip_disable(map, chip, chip->start);
        if (chip->state != FL_JEDEC_QUERY) {
@@ -1984,8 +1991,7 @@ do_otp_read(struct map_info *map, struct flchip *chip, u_long offset,
        xip_enable(map, chip, chip->start);
 
        /* then ensure we don't keep OTP data in the cache */
-       if (map->inval_cache)
-               map->inval_cache(map, chip->start + offset, size);
+       INVALIDATE_CACHED_RANGE(map, chip->start + offset, size);
 
        put_chip(map, chip, chip->start);
        spin_unlock(chip->mutex);
@@ -2025,7 +2031,7 @@ do_otp_lock(struct map_info *map, struct flchip *chip, u_long offset,
        map_word datum;
 
        /* make sure area matches group boundaries */
-       if (offset != 0 || size != grpsz)
+       if (size != grpsz)
                return -EXDEV;
 
        datum = map_word_ff(map);
@@ -2056,8 +2062,20 @@ static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
        /* we need real chips here not virtual ones */
        devsize = (1 << cfi->cfiq->DevSize) * cfi->interleave;
        chip_step = devsize >> cfi->chipshift;
+       chip_num = 0;
+
+       /* Some chips have OTP located in the _top_ partition only.
+          For example: Intel 28F256L18T (T means top-parameter device) */
+       if (cfi->mfr == MANUFACTURER_INTEL) {
+               switch (cfi->id) {
+               case 0x880b:
+               case 0x880c:
+               case 0x880d:
+                       chip_num = chip_step - 1;
+               }
+       }
 
-       for (chip_num = 0; chip_num < cfi->numchips; chip_num += chip_step) {
+       for ( ; chip_num < cfi->numchips; chip_num += chip_step) {
                chip = &cfi->chips[chip_num];
                otp = (struct cfi_intelext_otpinfo *)&extp->extra[0];
 
@@ -2089,7 +2107,7 @@ static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
                                groupno = 0;
                        }
 
-                       while (groups > 0) {
+                       while (len > 0 && groups > 0) {
                                if (!action) {
                                        /*
                                         * Special case: if action is NULL
@@ -2118,6 +2136,7 @@ static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
                                        *retlen += sizeof(*otpinfo);
                                } else if (from >= groupsize) {
                                        from -= groupsize;
+                                       data_offset += groupsize;
                                } else {
                                        int size = groupsize;
                                        data_offset += from;
@@ -2133,6 +2152,7 @@ static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
                                        buf += size;
                                        len -= size;
                                        *retlen += size;
+                                       data_offset += size;
                                }
                                groupno++;
                                groups--;
@@ -2298,10 +2318,46 @@ static void cfi_intelext_resume(struct mtd_info *mtd)
        }
 }
 
+static int cfi_intelext_reset(struct mtd_info *mtd)
+{
+       struct map_info *map = mtd->priv;
+       struct cfi_private *cfi = map->fldrv_priv;
+       int i, ret;
+
+       for (i=0; i < cfi->numchips; i++) {
+               struct flchip *chip = &cfi->chips[i];
+
+               /* force the completion of any ongoing operation
+                  and switch to array mode so any bootloader in 
+                  flash is accessible for soft reboot. */
+               spin_lock(chip->mutex);
+               ret = get_chip(map, chip, chip->start, FL_SYNCING);
+               if (!ret) {
+                       map_write(map, CMD(0xff), chip->start);
+                       chip->state = FL_READY;
+               }
+               spin_unlock(chip->mutex);
+       }
+
+       return 0;
+}
+
+static int cfi_intelext_reboot(struct notifier_block *nb, unsigned long val,
+                              void *v)
+{
+       struct mtd_info *mtd;
+
+       mtd = container_of(nb, struct mtd_info, reboot_notifier);
+       cfi_intelext_reset(mtd);
+       return NOTIFY_DONE;
+}
+
 static void cfi_intelext_destroy(struct mtd_info *mtd)
 {
        struct map_info *map = mtd->priv;
        struct cfi_private *cfi = map->fldrv_priv;
+       cfi_intelext_reset(mtd);
+       unregister_reboot_notifier(&mtd->reboot_notifier);
        kfree(cfi->cmdset_priv);
        kfree(cfi->cfiq);
        kfree(cfi->chips[0].priv);