]> git.karo-electronics.de Git - linux-beck.git/blobdiff - drivers/mtd/chips/cfi_cmdset_0002.c
Merge l2-mtd/next into l2-mtd/master
[linux-beck.git] / drivers / mtd / chips / cfi_cmdset_0002.c
index e21fde9d4d7e2d07ff8bc5d86bae597e69d1da17..c50d8cf0f60dd01b0b6c25822788b53dd2884bfd 100644 (file)
@@ -58,7 +58,18 @@ static void cfi_amdstd_sync (struct mtd_info *);
 static int cfi_amdstd_suspend (struct mtd_info *);
 static void cfi_amdstd_resume (struct mtd_info *);
 static int cfi_amdstd_reboot(struct notifier_block *, unsigned long, void *);
+static int cfi_amdstd_get_fact_prot_info(struct mtd_info *, size_t,
+                                        size_t *, struct otp_info *);
+static int cfi_amdstd_get_user_prot_info(struct mtd_info *, size_t,
+                                        size_t *, struct otp_info *);
 static int cfi_amdstd_secsi_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_amdstd_read_fact_prot_reg(struct mtd_info *, loff_t, size_t,
+                                        size_t *, u_char *);
+static int cfi_amdstd_read_user_prot_reg(struct mtd_info *, loff_t, size_t,
+                                        size_t *, u_char *);
+static int cfi_amdstd_write_user_prot_reg(struct mtd_info *, loff_t, size_t,
+                                         size_t *, u_char *);
+static int cfi_amdstd_lock_user_prot_reg(struct mtd_info *, loff_t, size_t);
 
 static int cfi_amdstd_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
                                  size_t *retlen, const u_char *buf);
@@ -518,6 +529,12 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
        mtd->_sync    = cfi_amdstd_sync;
        mtd->_suspend = cfi_amdstd_suspend;
        mtd->_resume  = cfi_amdstd_resume;
+       mtd->_read_user_prot_reg = cfi_amdstd_read_user_prot_reg;
+       mtd->_read_fact_prot_reg = cfi_amdstd_read_fact_prot_reg;
+       mtd->_get_fact_prot_info = cfi_amdstd_get_fact_prot_info;
+       mtd->_get_user_prot_info = cfi_amdstd_get_user_prot_info;
+       mtd->_write_user_prot_reg = cfi_amdstd_write_user_prot_reg;
+       mtd->_lock_user_prot_reg = cfi_amdstd_lock_user_prot_reg;
        mtd->flags   = MTD_CAP_NORFLASH;
        mtd->name    = map->name;
        mtd->writesize = 1;
@@ -628,6 +645,23 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
                cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp;
                cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp;
                cfi->chips[i].erase_time = 1<<cfi->cfiq->BlockEraseTimeoutTyp;
+               /*
+                * First calculate the timeout max according to timeout field
+                * of struct cfi_ident that probed from chip's CFI aera, if
+                * available. Specify a minimum of 2000us, in case the CFI data
+                * is wrong.
+                */
+               if (cfi->cfiq->BufWriteTimeoutTyp &&
+                   cfi->cfiq->BufWriteTimeoutMax)
+                       cfi->chips[i].buffer_write_time_max =
+                               1 << (cfi->cfiq->BufWriteTimeoutTyp +
+                                     cfi->cfiq->BufWriteTimeoutMax);
+               else
+                       cfi->chips[i].buffer_write_time_max = 0;
+
+               cfi->chips[i].buffer_write_time_max =
+                       max(cfi->chips[i].buffer_write_time_max, 2000);
+
                cfi->chips[i].ref_point_counter = 0;
                init_waitqueue_head(&(cfi->chips[i].wq));
        }
@@ -1137,12 +1171,48 @@ static int cfi_amdstd_read (struct mtd_info *mtd, loff_t from, size_t len, size_
        return ret;
 }
 
+typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip,
+                       loff_t adr, size_t len, u_char *buf, size_t grouplen);
+
+static inline void otp_enter(struct map_info *map, struct flchip *chip,
+                            loff_t adr, size_t len)
+{
+       struct cfi_private *cfi = map->fldrv_priv;
+
+       cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x88, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+
+       INVALIDATE_CACHED_RANGE(map, chip->start + adr, len);
+}
 
-static inline int do_read_secsi_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
+static inline void otp_exit(struct map_info *map, struct flchip *chip,
+                           loff_t adr, size_t len)
+{
+       struct cfi_private *cfi = map->fldrv_priv;
+
+       cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x90, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x00, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+
+       INVALIDATE_CACHED_RANGE(map, chip->start + adr, len);
+}
+
+static inline int do_read_secsi_onechip(struct map_info *map,
+                                       struct flchip *chip, loff_t adr,
+                                       size_t len, u_char *buf,
+                                       size_t grouplen)
 {
        DECLARE_WAITQUEUE(wait, current);
        unsigned long timeo = jiffies + HZ;
-       struct cfi_private *cfi = map->fldrv_priv;
 
  retry:
        mutex_lock(&chip->mutex);
@@ -1164,16 +1234,9 @@ static inline int do_read_secsi_onechip(struct map_info *map, struct flchip *chi
 
        chip->state = FL_READY;
 
-       cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
-       cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
-       cfi_send_gen_cmd(0x88, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
-
+       otp_enter(map, chip, adr, len);
        map_copy_from(map, buf, adr, len);
-
-       cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
-       cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
-       cfi_send_gen_cmd(0x90, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
-       cfi_send_gen_cmd(0x00, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+       otp_exit(map, chip, adr, len);
 
        wake_up(&chip->wq);
        mutex_unlock(&chip->mutex);
@@ -1205,7 +1268,8 @@ static int cfi_amdstd_secsi_read (struct mtd_info *mtd, loff_t from, size_t len,
                else
                        thislen = len;
 
-               ret = do_read_secsi_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf);
+               ret = do_read_secsi_onechip(map, &cfi->chips[chipnum], ofs,
+                                           thislen, buf, 0);
                if (ret)
                        break;
 
@@ -1219,8 +1283,271 @@ static int cfi_amdstd_secsi_read (struct mtd_info *mtd, loff_t from, size_t len,
        return ret;
 }
 
+static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
+                                    unsigned long adr, map_word datum,
+                                    int mode);
 
-static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, unsigned long adr, map_word datum)
+static int do_otp_write(struct map_info *map, struct flchip *chip, loff_t adr,
+                       size_t len, u_char *buf, size_t grouplen)
+{
+       int ret;
+       while (len) {
+               unsigned long bus_ofs = adr & ~(map_bankwidth(map)-1);
+               int gap = adr - bus_ofs;
+               int n = min_t(int, len, map_bankwidth(map) - gap);
+               map_word datum;
+
+               if (n != map_bankwidth(map)) {
+                       /* partial write of a word, load old contents */
+                       otp_enter(map, chip, bus_ofs, map_bankwidth(map));
+                       datum = map_read(map, bus_ofs);
+                       otp_exit(map, chip, bus_ofs, map_bankwidth(map));
+               }
+
+               datum = map_word_load_partial(map, datum, buf, gap, n);
+               ret = do_write_oneword(map, chip, bus_ofs, datum, FL_OTP_WRITE);
+               if (ret)
+                       return ret;
+
+               adr += n;
+               buf += n;
+               len -= n;
+       }
+
+       return 0;
+}
+
+static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr,
+                      size_t len, u_char *buf, size_t grouplen)
+{
+       struct cfi_private *cfi = map->fldrv_priv;
+       uint8_t lockreg;
+       unsigned long timeo;
+       int ret;
+
+       /* make sure area matches group boundaries */
+       if ((adr != 0) || (len != grouplen))
+               return -EINVAL;
+
+       mutex_lock(&chip->mutex);
+       ret = get_chip(map, chip, chip->start, FL_LOCKING);
+       if (ret) {
+               mutex_unlock(&chip->mutex);
+               return ret;
+       }
+       chip->state = FL_LOCKING;
+
+       /* Enter lock register command */
+       cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+       cfi_send_gen_cmd(0x40, cfi->addr_unlock1, chip->start, map, cfi,
+                        cfi->device_type, NULL);
+
+       /* read lock register */
+       lockreg = cfi_read_query(map, 0);
+
+       /* set bit 0 to protect extended memory block */
+       lockreg &= ~0x01;
+
+       /* set bit 0 to protect extended memory block */
+       /* write lock register */
+       map_write(map, CMD(0xA0), chip->start);
+       map_write(map, CMD(lockreg), chip->start);
+
+       /* wait for chip to become ready */
+       timeo = jiffies + msecs_to_jiffies(2);
+       for (;;) {
+               if (chip_ready(map, adr))
+                       break;
+
+               if (time_after(jiffies, timeo)) {
+                       pr_err("Waiting for chip to be ready timed out.\n");
+                       ret = -EIO;
+                       break;
+               }
+               UDELAY(map, chip, 0, 1);
+       }
+
+       /* exit protection commands */
+       map_write(map, CMD(0x90), chip->start);
+       map_write(map, CMD(0x00), chip->start);
+
+       chip->state = FL_READY;
+       put_chip(map, chip, chip->start);
+       mutex_unlock(&chip->mutex);
+
+       return ret;
+}
+
+static int cfi_amdstd_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
+                              size_t *retlen, u_char *buf,
+                              otp_op_t action, int user_regs)
+{
+       struct map_info *map = mtd->priv;
+       struct cfi_private *cfi = map->fldrv_priv;
+       int ofs_factor = cfi->interleave * cfi->device_type;
+       unsigned long base;
+       int chipnum;
+       struct flchip *chip;
+       uint8_t otp, lockreg;
+       int ret;
+
+       size_t user_size, factory_size, otpsize;
+       loff_t user_offset, factory_offset, otpoffset;
+       int user_locked = 0, otplocked;
+
+       *retlen = 0;
+
+       for (chipnum = 0; chipnum < cfi->numchips; chipnum++) {
+               chip = &cfi->chips[chipnum];
+               factory_size = 0;
+               user_size = 0;
+
+               /* Micron M29EW family */
+               if (is_m29ew(cfi)) {
+                       base = chip->start;
+
+                       /* check whether secsi area is factory locked
+                          or user lockable */
+                       mutex_lock(&chip->mutex);
+                       ret = get_chip(map, chip, base, FL_CFI_QUERY);
+                       if (ret) {
+                               mutex_unlock(&chip->mutex);
+                               return ret;
+                       }
+                       cfi_qry_mode_on(base, map, cfi);
+                       otp = cfi_read_query(map, base + 0x3 * ofs_factor);
+                       cfi_qry_mode_off(base, map, cfi);
+                       put_chip(map, chip, base);
+                       mutex_unlock(&chip->mutex);
+
+                       if (otp & 0x80) {
+                               /* factory locked */
+                               factory_offset = 0;
+                               factory_size = 0x100;
+                       } else {
+                               /* customer lockable */
+                               user_offset = 0;
+                               user_size = 0x100;
+
+                               mutex_lock(&chip->mutex);
+                               ret = get_chip(map, chip, base, FL_LOCKING);
+                               if (ret) {
+                                       mutex_unlock(&chip->mutex);
+                                       return ret;
+                               }
+
+                               /* Enter lock register command */
+                               cfi_send_gen_cmd(0xAA, cfi->addr_unlock1,
+                                                chip->start, map, cfi,
+                                                cfi->device_type, NULL);
+                               cfi_send_gen_cmd(0x55, cfi->addr_unlock2,
+                                                chip->start, map, cfi,
+                                                cfi->device_type, NULL);
+                               cfi_send_gen_cmd(0x40, cfi->addr_unlock1,
+                                                chip->start, map, cfi,
+                                                cfi->device_type, NULL);
+                               /* read lock register */
+                               lockreg = cfi_read_query(map, 0);
+                               /* exit protection commands */
+                               map_write(map, CMD(0x90), chip->start);
+                               map_write(map, CMD(0x00), chip->start);
+                               put_chip(map, chip, chip->start);
+                               mutex_unlock(&chip->mutex);
+
+                               user_locked = ((lockreg & 0x01) == 0x00);
+                       }
+               }
+
+               otpsize = user_regs ? user_size : factory_size;
+               if (!otpsize)
+                       continue;
+               otpoffset = user_regs ? user_offset : factory_offset;
+               otplocked = user_regs ? user_locked : 1;
+
+               if (!action) {
+                       /* return otpinfo */
+                       struct otp_info *otpinfo;
+                       len -= sizeof(*otpinfo);
+                       if (len <= 0)
+                               return -ENOSPC;
+                       otpinfo = (struct otp_info *)buf;
+                       otpinfo->start = from;
+                       otpinfo->length = otpsize;
+                       otpinfo->locked = otplocked;
+                       buf += sizeof(*otpinfo);
+                       *retlen += sizeof(*otpinfo);
+                       from += otpsize;
+               } else if ((from < otpsize) && (len > 0)) {
+                       size_t size;
+                       size = (len < otpsize - from) ? len : otpsize - from;
+                       ret = action(map, chip, otpoffset + from, size, buf,
+                                    otpsize);
+                       if (ret < 0)
+                               return ret;
+
+                       buf += size;
+                       len -= size;
+                       *retlen += size;
+                       from = 0;
+               } else {
+                       from -= otpsize;
+               }
+       }
+       return 0;
+}
+
+static int cfi_amdstd_get_fact_prot_info(struct mtd_info *mtd, size_t len,
+                                        size_t *retlen, struct otp_info *buf)
+{
+       return cfi_amdstd_otp_walk(mtd, 0, len, retlen, (u_char *)buf,
+                                  NULL, 0);
+}
+
+static int cfi_amdstd_get_user_prot_info(struct mtd_info *mtd, size_t len,
+                                        size_t *retlen, struct otp_info *buf)
+{
+       return cfi_amdstd_otp_walk(mtd, 0, len, retlen, (u_char *)buf,
+                                  NULL, 1);
+}
+
+static int cfi_amdstd_read_fact_prot_reg(struct mtd_info *mtd, loff_t from,
+                                        size_t len, size_t *retlen,
+                                        u_char *buf)
+{
+       return cfi_amdstd_otp_walk(mtd, from, len, retlen,
+                                  buf, do_read_secsi_onechip, 0);
+}
+
+static int cfi_amdstd_read_user_prot_reg(struct mtd_info *mtd, loff_t from,
+                                        size_t len, size_t *retlen,
+                                        u_char *buf)
+{
+       return cfi_amdstd_otp_walk(mtd, from, len, retlen,
+                                  buf, do_read_secsi_onechip, 1);
+}
+
+static int cfi_amdstd_write_user_prot_reg(struct mtd_info *mtd, loff_t from,
+                                         size_t len, size_t *retlen,
+                                         u_char *buf)
+{
+       return cfi_amdstd_otp_walk(mtd, from, len, retlen, buf,
+                                  do_otp_write, 1);
+}
+
+static int cfi_amdstd_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
+                                        size_t len)
+{
+       size_t retlen;
+       return cfi_amdstd_otp_walk(mtd, from, len, &retlen, NULL,
+                                  do_otp_lock, 1);
+}
+
+static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
+                                    unsigned long adr, map_word datum,
+                                    int mode)
 {
        struct cfi_private *cfi = map->fldrv_priv;
        unsigned long timeo = jiffies + HZ;
@@ -1241,7 +1568,7 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        adr += chip->start;
 
        mutex_lock(&chip->mutex);
-       ret = get_chip(map, chip, adr, FL_WRITING);
+       ret = get_chip(map, chip, adr, mode);
        if (ret) {
                mutex_unlock(&chip->mutex);
                return ret;
@@ -1250,6 +1577,9 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n",
               __func__, adr, datum.x[0] );
 
+       if (mode == FL_OTP_WRITE)
+               otp_enter(map, chip, adr, map_bankwidth(map));
+
        /*
         * Check for a NOP for the case when the datum to write is already
         * present - it saves time and works around buggy chips that corrupt
@@ -1266,12 +1596,13 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map));
        ENABLE_VPP(map);
        xip_disable(map, chip, adr);
+
  retry:
        cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
        cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
        cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
        map_write(map, datum, adr);
-       chip->state = FL_WRITING;
+       chip->state = mode;
 
        INVALIDATE_CACHE_UDELAY(map, chip,
                                adr, map_bankwidth(map),
@@ -1280,7 +1611,7 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        /* See comment above for timeout value. */
        timeo = jiffies + uWriteTimeout;
        for (;;) {
-               if (chip->state != FL_WRITING) {
+               if (chip->state != mode) {
                        /* Someone's suspended the write. Sleep */
                        DECLARE_WAITQUEUE(wait, current);
 
@@ -1320,6 +1651,8 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
        }
        xip_enable(map, chip, adr);
  op_done:
+       if (mode == FL_OTP_WRITE)
+               otp_exit(map, chip, adr, map_bankwidth(map));
        chip->state = FL_READY;
        DISABLE_VPP(map);
        put_chip(map, chip, adr);
@@ -1375,7 +1708,7 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len,
                tmp_buf = map_word_load_partial(map, tmp_buf, buf, i, n);
 
                ret = do_write_oneword(map, &cfi->chips[chipnum],
-                                      bus_ofs, tmp_buf);
+                                      bus_ofs, tmp_buf, FL_WRITING);
                if (ret)
                        return ret;
 
@@ -1399,7 +1732,7 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len,
                datum = map_word_load(map, buf);
 
                ret = do_write_oneword(map, &cfi->chips[chipnum],
-                                      ofs, datum);
+                                      ofs, datum, FL_WRITING);
                if (ret)
                        return ret;
 
@@ -1442,7 +1775,7 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len,
                tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len);
 
                ret = do_write_oneword(map, &cfi->chips[chipnum],
-                               ofs, tmp_buf);
+                                      ofs, tmp_buf, FL_WRITING);
                if (ret)
                        return ret;
 
@@ -1462,8 +1795,12 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
 {
        struct cfi_private *cfi = map->fldrv_priv;
        unsigned long timeo = jiffies + HZ;
-       /* see comments in do_write_oneword() regarding uWriteTimeo. */
-       unsigned long uWriteTimeout = ( HZ / 1000 ) + 1;
+       /*
+        * Timeout is calculated according to CFI data, if available.
+        * See more comments in cfi_cmdset_0002().
+        */
+       unsigned long uWriteTimeout =
+                               usecs_to_jiffies(chip->buffer_write_time_max);
        int ret = -EIO;
        unsigned long cmd_adr;
        int z, words;
@@ -1696,6 +2033,8 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
 
                        udelay(1);
                }
+
+               retries--;
        }
 
        /* the chip never became ready */