]> git.karo-electronics.de Git - karo-tx-uboot.git/blobdiff - drivers/mtd/nand/omap_gpmc.c
mtd: nand: omap_gpmc: support reading metadata from OOB
[karo-tx-uboot.git] / drivers / mtd / nand / omap_gpmc.c
index cdfa6bc1c9f96cf33cfbf99353cdeac2e745cee4..94c18c97d030919f2d87325c028c1bbe1e028cfc 100644 (file)
 static u8  bch8_polynomial[] = {0xef, 0x51, 0x2e, 0x09, 0xed, 0x93, 0x9a, 0xc2,
                                0x97, 0x79, 0xe5, 0x24, 0xb5};
 #endif
-static uint8_t cs;
+static uint8_t cs_next;
 static __maybe_unused struct nand_ecclayout omap_ecclayout;
 
+/*
+ * Driver configurations
+ */
+struct omap_nand_info {
+       struct bch_control *control;
+       enum omap_ecc ecc_scheme;
+       int cs;
+};
+
+/* We are wasting a bit of memory but al least we are safe */
+static struct omap_nand_info omap_nand_info[GPMC_MAX_CS];
+
+static struct gpmc __iomem *gpmc_cfg = (void __iomem *)GPMC_BASE;
+
+#ifdef CONFIG_SYS_NAND_USE_FLASH_BBT
+static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' };
+static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+       .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+               NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+       .offs = 0, /* may be overwritten depending on ECC layout */
+       .len = 4,
+       .veroffs = 4, /* may be overwritten depending on ECC layout */
+       .maxblocks = 4,
+       .pattern = bbt_pattern,
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+       .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
+               NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+       .offs = 0, /* may be overwritten depending on ECC layout */
+       .len = 4,
+       .veroffs = 4, /* may be overwritten depending on ECC layout */
+       .maxblocks = 4,
+       .pattern = mirror_pattern,
+};
+#endif
+
+#define PREFETCH_FIFOTHRESHOLD_MAX             0x40
+#define PREFETCH_FIFOTHRESHOLD(val)            ((val) << 8)
+
+#define PREFETCH_ENABLEOPTIMIZEDACCESS         (0x1 << 27)
+
+#define GPMC_PREFETCH_STATUS_FIFO_CNT(val)     (((val) >> 24) & 0x7F)
+#define GPMC_PREFETCH_STATUS_COUNT(val)                ((val) & 0x00003fff)
+
+#define CS_NUM_SHIFT                           24
+#define ENABLE_PREFETCH                                (0x1 << 7)
+#define DMA_MPU_MODE                           2
+
+#define OMAP_NAND_TIMEOUT_MS                   5000
+
+#define PRINT_REG(x) debug("+++ %.15s (0x%08x)=0x%08x\n", #x, &gpmc_cfg->x, readl(&gpmc_cfg->x))
+
+#ifdef CONFIG_SYS_GPMC_PREFETCH_ENABLE
+/**
+ * gpmc_prefetch_enable - configures and starts prefetch transfer
+ * @cs: cs (chip select) number
+ * @fifo_th: fifo threshold to be used for read/ write
+ * @count: number of bytes to be transferred
+ * @is_write: prefetch read(0) or write post(1) mode
+ */
+static inline void gpmc_prefetch_enable(int cs, int fifo_th,
+                                       unsigned int count, int is_write)
+{
+       writel(count, &gpmc_cfg->pref_config2);
+
+       /* Set the prefetch read / post write and enable the engine.
+        * Set which cs is has requested for.
+        */
+       uint32_t val = (cs << CS_NUM_SHIFT) |
+               PREFETCH_ENABLEOPTIMIZEDACCESS |
+               PREFETCH_FIFOTHRESHOLD(fifo_th) |
+               ENABLE_PREFETCH |
+               !!is_write;
+       writel(val, &gpmc_cfg->pref_config1);
+
+       /*  Start the prefetch engine */
+       writel(0x1, &gpmc_cfg->pref_control);
+}
+
+/**
+ * gpmc_prefetch_reset - disables and stops the prefetch engine
+ */
+static inline void gpmc_prefetch_reset(void)
+{
+       /* Stop the PFPW engine */
+       writel(0x0, &gpmc_cfg->pref_control);
+
+       /* Reset/disable the PFPW engine */
+       writel(0x0, &gpmc_cfg->pref_config1);
+}
+
+//#define FIFO_IOADDR          (nand->IO_ADDR_R)
+#define FIFO_IOADDR            PISMO1_NAND_BASE
+
+/**
+ * read_buf_pref - read data from NAND controller into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ */
+static void read_buf_pref(struct mtd_info *mtd, u_char *buf, int len)
+{
+       gpmc_prefetch_enable(cs, PREFETCH_FIFOTHRESHOLD_MAX, len, 0);
+       do {
+               // Get number of bytes waiting in the FIFO
+               uint32_t read_bytes = GPMC_PREFETCH_STATUS_FIFO_CNT(readl(&gpmc_cfg->pref_status));
+
+               if (read_bytes == 0)
+                       continue;
+               // Alignment of Destination Buffer
+               while (read_bytes && ((unsigned int)buf & 3)) {
+                       *buf++ = readb(FIFO_IOADDR);
+                       read_bytes--;
+                       len--;
+               }
+               // Use maximum word size (32bit) inside this loop, because speed is limited by
+               // GPMC bus arbitration with a maximum transfer rate of 3.000.000/sec.
+               len -= read_bytes & ~3;
+               while (read_bytes >= 4) {
+                       *((uint32_t*)buf) = readl(FIFO_IOADDR);
+                       buf += 4;
+                       read_bytes -= 4;
+               }
+               // Transfer the last (non-aligned) bytes only at the last iteration,
+               // to maintain full speed up to the end of the transfer.
+               if (read_bytes == len) {
+                       while (read_bytes) {
+                               *buf++ = readb(FIFO_IOADDR);
+                               read_bytes--;
+                       }
+                       len = 0;
+               }
+       } while (len > 0);
+       gpmc_prefetch_reset();
+}
+
+/*
+ * write_buf_pref - write buffer to NAND controller
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ */
+static void write_buf_pref(struct mtd_info *mtd, const u_char *buf, int len)
+{
+       /*  configure and start prefetch transfer */
+       gpmc_prefetch_enable(cs, PREFETCH_FIFOTHRESHOLD_MAX, len, 1);
+
+       while (len) {
+               // Get number of free bytes in the FIFO
+               uint32_t write_bytes = GPMC_PREFETCH_STATUS_FIFO_CNT(readl(&gpmc_cfg->pref_status));
+
+               // don't write more bytes than requested
+               if (write_bytes > len)
+                       write_bytes = len;
+
+               // Alignment of Source Buffer
+               while (write_bytes && ((unsigned int)buf & 3)) {
+                       writeb(*buf++, FIFO_IOADDR);
+                       write_bytes--;
+                       len--;
+               }
+
+               // Use maximum word size (32bit) inside this loop, because speed is limited by
+               // GPMC bus arbitration with a maximum transfer rate of 3.000.000/sec.
+               len -= write_bytes & ~3;
+               while (write_bytes >= 4) {
+                       writel(*((uint32_t*)buf), FIFO_IOADDR);
+                       buf += 4;
+                       write_bytes -= 4;
+               }
+
+               // Transfer the last (non-aligned) bytes only at the last iteration,
+               // to maintain full speed up to the end of the transfer.
+               if (write_bytes == len) {
+                       while (write_bytes) {
+                               writeb(*buf++, FIFO_IOADDR);
+                               write_bytes--;
+                       }
+                       len = 0;
+               }
+       }
+
+       /* wait for data to be flushed out before resetting the prefetch */
+       while ((len = GPMC_PREFETCH_STATUS_COUNT(readl(&gpmc_cfg->pref_status)))) {
+               debug("%u bytes still in FIFO\n", PREFETCH_FIFOTHRESHOLD_MAX - len);
+               ndelay(1);
+       }
+
+       /* disable and stop the PFPW engine */
+       gpmc_prefetch_reset();
+}
+#endif /* CONFIG_SYS_GPMC_PREFETCH_ENABLE */
+
 /*
  * omap_nand_hwcontrol - Set the address pointers corretly for the
  *                     following address/data/command operation
@@ -38,6 +234,8 @@ static void omap_nand_hwcontrol(struct mtd_info *mtd, int32_t cmd,
                                uint32_t ctrl)
 {
        register struct nand_chip *this = mtd->priv;
+       struct omap_nand_info *info = this->priv;
+       int cs = info->cs;
 
        /*
         * Point the IO_ADDR to DATA and ADDRESS registers instead
@@ -59,14 +257,11 @@ static void omap_nand_hwcontrol(struct mtd_info *mtd, int32_t cmd,
                writeb(cmd, this->IO_ADDR_W);
 }
 
-#ifdef CONFIG_SPL_BUILD
 /* Check wait pin as dev ready indicator */
-int omap_spl_dev_ready(struct mtd_info *mtd)
+static int omap_dev_ready(struct mtd_info *mtd)
 {
-       return gpmc_cfg->status & (1 << 8);
+       return readl(&gpmc_cfg->status) & (1 << 8);
 }
-#endif
-
 
 /*
  * gen_true_ecc - This function will generate true ECC value, which
@@ -147,41 +342,6 @@ static int __maybe_unused omap_correct_data(struct mtd_info *mtd, uint8_t *dat,
        return 0;
 }
 
-/*
- * Driver configurations
- */
-struct omap_nand_info {
-       struct bch_control *control;
-       enum omap_ecc ecc_scheme;
-};
-
-/*
- * This can be a single instance cause all current users have only one NAND
- * with nearly the same setup (BCH8, some with ELM and others with sw BCH
- * library).
- * When some users with other BCH strength will exists this have to change!
- */
-static __maybe_unused struct omap_nand_info omap_nand_info = {
-       .control = NULL
-};
-
-/*
- * omap_reverse_list - re-orders list elements in reverse order [internal]
- * @list:      pointer to start of list
- * @length:    length of list
-*/
-void omap_reverse_list(u8 *list, unsigned int length)
-{
-       unsigned int i, j;
-       unsigned int half_length = length / 2;
-       u8 tmp;
-       for (i = 0, j = length - 1; i < half_length; i++, j--) {
-               tmp = list[i];
-               list[i] = list[j];
-               list[j] = tmp;
-       }
-}
-
 /*
  * omap_enable_hwecc - configures GPMC as per ECC scheme before read/write
  * @mtd:       MTD device structure
@@ -198,6 +358,7 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode)
        unsigned int eccsize1 = 0x00, eccsize0 = 0x00, bch_wrapmode = 0x00;
        u32 ecc_size_config_val = 0;
        u32 ecc_config_val = 0;
+       int cs = info->cs;
 
        /* configure GPMC for specific ecc-scheme */
        switch (info->ecc_scheme) {
@@ -224,6 +385,19 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode)
                        eccsize1 = 2;  /* non-ECC bits in nibbles per sector */
                }
                break;
+       case OMAP_ECC_BCH16_CODE_HW:
+               ecc_algo = 0x1;
+               bch_type = 0x2;
+               if (mode == NAND_ECC_WRITE) {
+                       bch_wrapmode = 0x01;
+                       eccsize0 = 0;  /* extra bits in nibbles per sector */
+                       eccsize1 = 52; /* OOB bits in nibbles per sector */
+               } else {
+                       bch_wrapmode = 0x01;
+                       eccsize0 = 52; /* ECC bits in nibbles per sector */
+                       eccsize1 = 0;  /* non-ECC bits in nibbles per sector */
+               }
+               break;
        default:
                return;
        }
@@ -290,6 +464,29 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
                        ptr--;
                }
                break;
+       case OMAP_ECC_BCH16_CODE_HW:
+               val = readl(&gpmc_cfg->bch_result_4_6[0].bch_result_x[2]);
+               ecc_code[i++] = (val >>  8) & 0xFF;
+               ecc_code[i++] = (val >>  0) & 0xFF;
+               val = readl(&gpmc_cfg->bch_result_4_6[0].bch_result_x[1]);
+               ecc_code[i++] = (val >> 24) & 0xFF;
+               ecc_code[i++] = (val >> 16) & 0xFF;
+               ecc_code[i++] = (val >>  8) & 0xFF;
+               ecc_code[i++] = (val >>  0) & 0xFF;
+               val = readl(&gpmc_cfg->bch_result_4_6[0].bch_result_x[0]);
+               ecc_code[i++] = (val >> 24) & 0xFF;
+               ecc_code[i++] = (val >> 16) & 0xFF;
+               ecc_code[i++] = (val >>  8) & 0xFF;
+               ecc_code[i++] = (val >>  0) & 0xFF;
+               for (j = 3; j >= 0; j--) {
+                       val = readl(&gpmc_cfg->bch_result_0_3[0].bch_result_x[j]
+                                                                       );
+                       ecc_code[i++] = (val >> 24) & 0xFF;
+                       ecc_code[i++] = (val >> 16) & 0xFF;
+                       ecc_code[i++] = (val >>  8) & 0xFF;
+                       ecc_code[i++] = (val >>  0) & 0xFF;
+               }
+               break;
        default:
                return -EINVAL;
        }
@@ -308,6 +505,8 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
        case OMAP_ECC_BCH8_CODE_HW:
                ecc_code[chip->ecc.bytes - 1] = 0x00;
                break;
+       case OMAP_ECC_BCH16_CODE_HW:
+               break;
        default:
                return -EINVAL;
        }
@@ -315,6 +514,23 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
 }
 
 #ifdef CONFIG_NAND_OMAP_ELM
+/*
+ * omap_reverse_list - re-orders list elements in reverse order [internal]
+ * @list:      pointer to start of list
+ * @length:    length of list
+*/
+static void omap_reverse_list(u8 *list, unsigned int length)
+{
+       unsigned int i, j;
+       unsigned int half_length = length / 2;
+       u8 tmp;
+       for (i = 0, j = length - 1; i < half_length; i++, j--) {
+               tmp = list[i];
+               list[i] = list[j];
+               list[j] = tmp;
+       }
+}
+
 /*
  * omap_correct_data_bch - Compares the ecc read from nand spare area
  * with ECC registers values and corrects one bit error if it has occured
@@ -333,11 +549,12 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
        struct omap_nand_info *info = chip->priv;
        struct nand_ecc_ctrl *ecc = &chip->ecc;
        uint32_t error_count = 0, error_max;
-       uint32_t error_loc[8];
+       uint32_t error_loc[ELM_MAX_ERROR_COUNT];
        enum bch_level bch_type;
        uint32_t i, ecc_flag = 0;
-       uint8_t count, err = 0;
+       uint8_t count;
        uint32_t byte_pos, bit_pos;
+       int err = 0;
 
        /* check calculated ecc */
        for (i = 0; i < ecc->bytes && !ecc_flag; i++) {
@@ -365,6 +582,10 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
                bch_type = BCH_8_BIT;
                omap_reverse_list(calc_ecc, ecc->bytes - 1);
                break;
+       case OMAP_ECC_BCH16_CODE_HW:
+               bch_type = BCH_16_BIT;
+               omap_reverse_list(calc_ecc, ecc->bytes);
+               break;
        default:
                return -EINVAL;
        }
@@ -381,6 +602,9 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
                        /* 14th byte in ECC is reserved to match ROM layout */
                        error_max = SECTOR_BYTES + (ecc->bytes - 1);
                        break;
+               case OMAP_ECC_BCH16_CODE_HW:
+                       error_max = SECTOR_BYTES + ecc->bytes;
+                       break;
                default:
                        return -EINVAL;
                }
@@ -420,28 +644,33 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip,
        uint8_t *ecc_calc = chip->buffers->ecccalc;
        uint8_t *ecc_code = chip->buffers->ecccode;
        uint32_t *eccpos = chip->ecc.layout->eccpos;
-       uint8_t *oob = chip->oob_poi;
+       uint8_t *oob = &chip->oob_poi[eccpos[0]];
        uint32_t data_pos;
        uint32_t oob_pos;
 
        data_pos = 0;
        /* oob area start */
-       oob_pos = (eccsize * eccsteps) + chip->ecc.layout->eccpos[0];
-       oob += chip->ecc.layout->eccpos[0];
+       oob_pos = (eccsize * eccsteps) + eccpos[0];
 
        for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize,
                                oob += eccbytes) {
                chip->ecc.hwctl(mtd, NAND_ECC_READ);
                /* read data */
-               chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_pos, page);
+               chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_pos, -1);
                chip->read_buf(mtd, p, eccsize);
 
                /* read respective ecc from oob area */
-               chip->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_pos, page);
+               chip->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_pos, -1);
                chip->read_buf(mtd, oob, eccbytes);
                /* read syndrome */
                chip->ecc.calculate(mtd, p, &ecc_calc[i]);
 
+               if (oob_required) {
+                       /* reread the OOB area to get the metadata */
+                       chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, page);
+                       chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+               }
+
                data_pos += eccsize;
                oob_pos += eccbytes;
        }
@@ -495,22 +724,22 @@ static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data,
                                data[errloc[i]/8] ^= 1 << (errloc[i] & 7);
                        printf("corrected bitflip %u\n", errloc[i]);
 #ifdef DEBUG
-                       puts("read_ecc: ");
+                       printf("read_ecc: ");
                        /*
                         * BCH8 have 13 bytes of ECC; BCH4 needs adoption
                         * here!
                         */
                        for (i = 0; i < 13; i++)
                                printf("%02x ", read_ecc[i]);
-                       puts("\n");
-                       puts("calc_ecc: ");
+                       printf("\n");
+                       printf("calc_ecc: ");
                        for (i = 0; i < 13; i++)
                                printf("%02x ", calc_ecc[i]);
-                       puts("\n");
+                       printf("\n");
 #endif
                }
        } else if (count < 0) {
-               puts("ecc unrecoverable error\n");
+               printf("ecc unrecoverable error\n");
        }
        return count;
 }
@@ -666,6 +895,38 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
                return -EINVAL;
 #endif
 
+       case OMAP_ECC_BCH16_CODE_HW:
+#ifdef CONFIG_NAND_OMAP_ELM
+               debug("nand: using OMAP_ECC_BCH16_CODE_HW\n");
+               /* check ecc-scheme requirements before updating ecc info */
+               if ((26 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) {
+                       printf("nand: error: insufficient OOB: require=%d\n", (
+                               (26 * eccsteps) + BADBLOCK_MARKER_LENGTH));
+                       return -EINVAL;
+               }
+               /* intialize ELM for ECC error detection */
+               elm_init();
+               /* populate ecc specific fields */
+               nand->ecc.mode          = NAND_ECC_HW;
+               nand->ecc.size          = SECTOR_BYTES;
+               nand->ecc.bytes         = 26;
+               nand->ecc.strength      = 16;
+               nand->ecc.hwctl         = omap_enable_hwecc;
+               nand->ecc.correct       = omap_correct_data_bch;
+               nand->ecc.calculate     = omap_calculate_ecc;
+               nand->ecc.read_page     = omap_read_page_bch;
+               /* define ecc-layout */
+               ecclayout->eccbytes     = nand->ecc.bytes * eccsteps;
+               for (i = 0; i < ecclayout->eccbytes; i++)
+                       ecclayout->eccpos[i] = i + BADBLOCK_MARKER_LENGTH;
+               ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
+               ecclayout->oobfree[0].length = oobsize - nand->ecc.bytes -
+                                               BADBLOCK_MARKER_LENGTH;
+               break;
+#else
+               printf("nand: error: CONFIG_NAND_OMAP_ELM required for ECC\n");
+               return -EINVAL;
+#endif
        default:
                debug("nand: error: ecc scheme not enabled or supported\n");
                return -EINVAL;
@@ -749,7 +1010,7 @@ int __maybe_unused omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength)
 int board_nand_init(struct nand_chip *nand)
 {
        int32_t gpmc_config = 0;
-       cs = 0;
+       int cs = cs_next++;
        int err = 0;
        /*
         * xloader/Uboot's gpmc configuration would have configured GPMC for
@@ -779,7 +1040,9 @@ int board_nand_init(struct nand_chip *nand)
 
        nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;
        nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd;
-       nand->priv      = &omap_nand_info;
+       omap_nand_info[cs].control = NULL;
+       omap_nand_info[cs].cs = cs;
+       nand->priv      = &omap_nand_info[cs];
        nand->cmd_ctrl  = omap_nand_hwcontrol;
        nand->options   |= NAND_NO_PADDING | NAND_CACHEPRG;
        nand->chip_delay = 100;
@@ -805,14 +1068,30 @@ int board_nand_init(struct nand_chip *nand)
 #endif
        if (err)
                return err;
+#ifdef CONFIG_SYS_NAND_USE_FLASH_BBT
+       if (nand->ecc.layout) {
+               bbt_main_descr.offs = nand->ecc.layout->oobfree[0].offset;
+               bbt_main_descr.veroffs = bbt_main_descr.offs +
+                       sizeof(bbt_pattern);
+
+               bbt_mirror_descr.offs = nand->ecc.layout->oobfree[0].offset;
+               bbt_mirror_descr.veroffs = bbt_mirror_descr.offs +
+                       sizeof(mirror_pattern);
+       }
+
+       nand->bbt_options |= NAND_BBT_USE_FLASH;
+       nand->bbt_td = &bbt_main_descr;
+       nand->bbt_md = &bbt_mirror_descr;
+#endif
 
 #ifdef CONFIG_SPL_BUILD
        if (nand->options & NAND_BUSWIDTH_16)
                nand->read_buf = nand_read_buf16;
        else
                nand->read_buf = nand_read_buf;
-       nand->dev_ready = omap_spl_dev_ready;
 #endif
 
+       nand->dev_ready = omap_dev_ready;
+
        return 0;
 }