summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorHan Xu <b45815@freescale.com>2015-10-09 13:29:41 -0500
committerMarcel Ziswiler <marcel.ziswiler@toradex.com>2017-01-11 21:25:08 +0100
commitc7ed23d26cacc7255b4ebc062dd947b0ae28868f (patch)
tree023b70c90be54ce21686f827cb49419a1eda49df /drivers
parent6544295794fe7cf1862b22fdc2b74a78ce7dc904 (diff)
MLK-11718-2: mtd: nand: change the BCH layout setting for large oob NAND
The cod change updated the NAND driver BCH ECC layout algorithm to support large oob size NAND chips(oob > 1024 bytes). Current implementation requires each chunk size larger than oob size so the bad block marker (BBM) can be guaranteed located in data chunk. The ECC layout always using the unbalanced layout(Ecc for both meta and Data0 chunk), but for the NAND chips with oob larger than 1k, the driver cannot support because BCH doesn’t support GF 15 for 2K chunk. The change keeps the data chunk no larger than 1k and adjust the ECC strength or ECC layout to locate the BBM in data chunk. General idea for large oob NAND chips is 1.Try all ECC strength from the minimum value required by NAND spec to the maximum one that works, any ECC makes the BBM locate in data chunk can be chosen. 2.If none of them works, using separate ECC for meta, which will add one extra ecc with the same ECC strength as other data chunks. This extra ECC can guarantee BBM located in data chunk, of course, we need to check if oob can afford it. Signed-off-by: Han Xu <b45815@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mtd/nand/mxs_nand.c156
1 files changed, 118 insertions, 38 deletions
diff --git a/drivers/mtd/nand/mxs_nand.c b/drivers/mtd/nand/mxs_nand.c
index d3c420afde..b10539e674 100644
--- a/drivers/mtd/nand/mxs_nand.c
+++ b/drivers/mtd/nand/mxs_nand.c
@@ -7,7 +7,7 @@
* Based on code from LTIB:
* Freescale GPMI NFC NAND Flash Driver
*
- * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
*
* SPDX-License-Identifier: GPL-2.0+
@@ -46,6 +46,11 @@
#define MXS_NAND_BCH_TIMEOUT 10000
+int bbm_chunk;
+int ecc_strength;
+bool large_oob_flag;
+bool ecc_for_meta;
+
struct mxs_nand_info {
int cur_chip;
@@ -137,7 +142,8 @@ static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
static uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
{
- return page_data_size / chunk_data_size;
+ int tmp = page_data_size / chunk_data_size;
+ return ecc_for_meta ? tmp + 1 : tmp;
}
static uint32_t mxs_nand_ecc_size_in_bits(uint32_t ecc_strength)
@@ -150,10 +156,50 @@ static uint32_t mxs_nand_aux_status_offset(void)
return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3;
}
-static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
- uint32_t page_oob_size)
+/*
+ * For some large oob NAND chip( the oob larger than data chunk), combined meta
+ * with chunk0 style bch layout might override the bbm with ecc data. The
+ * function checked if bbm can be in the data chunk. If it is true, chunk_num
+ * indicate the chunk number that bbm located.
+ *
+ */
+static bool mxs_nand_bbm_in_data_chunk(struct mtd_info *mtd, int gf_len,
+ int *chunk_num)
{
- int ecc_strength;
+ int i, j;
+ int meta = MXS_NAND_METADATA_SIZE;
+
+ i = (mtd->writesize * 8 - meta * 8) /
+ (gf_len * ecc_strength +
+ chunk_data_size * 8);
+
+ j = (mtd->writesize * 8 - meta * 8) %
+ (gf_len * ecc_strength +
+ chunk_data_size * 8);
+
+ if (j < chunk_data_size * 8) {
+ *chunk_num = i+1;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * the work flow about how to set the ecc layout
+ *
+ * 1. if ecc_strength_ds>max_soc_ecc, quit
+ * 2. if ecc_strength_ds>0 and ecc_stride_ds>0,
+ * if ecc_stride_ds > oob, go to large_oob branch
+ * else go to normal branch
+ * 3. if either ecc_stride_ds<=0 or ecc_stride_ds<=0, quit
+ *
+ */
+static int mxs_nand_get_ecc_strength(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd->priv;
+ uint32_t page_oob_size = mtd->oobsize;
+ int meta = MXS_NAND_METADATA_SIZE;
int max_ecc_strength_supported;
/* Refer to Chapter 17 for i.MX6DQ, Chapter 18 for i.MX6SX */
@@ -162,20 +208,55 @@ static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
else
max_ecc_strength_supported = 40;
- /*
- * Determine the ECC layout with the formula:
- * ECC bits per chunk = (total page spare data bits) /
- * (bits per ECC level) / (chunks per page)
- * where:
- * total page spare data bits =
- * (page oob size - meta data size) * (bits per byte)
- */
- ecc_strength = ((page_oob_size - MXS_NAND_METADATA_SIZE) * 8)
- / (galois_field *
- mxs_nand_ecc_chunk_cnt(page_data_size));
+ if (chip->ecc_strength_ds > max_ecc_strength_supported) {
+ printf("cannot support the NAND, ecc too weak\n");
+ return -EINVAL;
+ }
- return min(round_down(ecc_strength, 2), max_ecc_strength_supported);
-}
+ if (!(chip->ecc_strength_ds > 0 && chip->ecc_step_ds > 0) &&
+ !(page_oob_size > 1024)) {
+ printf("cannot support the NAND, missing necessary info\n");
+ return -EINVAL;
+ }
+
+ /* set some parameters according to NAND chip parameters */
+ chunk_data_size = chip->ecc_step_ds;
+ if (1024 == chunk_data_size)
+ galois_field = 14;
+ if (chunk_data_size < page_oob_size)
+ large_oob_flag = true;
+
+ if (large_oob_flag) {
+ /* start from the minimum ecc NAND chips required */
+ ecc_strength = chip->ecc_strength_ds;
+ while (!(ecc_strength > max_ecc_strength_supported)) {
+ if (mxs_nand_bbm_in_data_chunk(mtd,
+ galois_field,
+ &bbm_chunk))
+ break;
+ ecc_strength += 2;
+ }
+ /*
+ * if all supported ecc cannot satisfy the bbm
+ * requirement, change * the ecc layout to meta
+ * with ecc type.
+ *
+ */
+ if (ecc_strength > max_ecc_strength_supported) {
+ ecc_strength = chip->ecc_strength_ds;
+ ecc_for_meta = true;
+ /* calculate in which chunk bbm located */
+ bbm_chunk = (mtd->writesize * 8 - meta * 8 -
+ galois_field * ecc_strength) /
+ (galois_field * ecc_strength +
+ chunk_data_size * 8) + 1;
+ }
+ } else {
+ ecc_strength = chip->ecc_strength_ds;
+ ecc_strength += ecc_strength & 1;
+ }
+ return 0;
+};
static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size,
uint32_t ecc_strength)
@@ -196,8 +277,13 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size,
/* Compute the bit offset of the block mark within the physical page. */
block_mark_bit_offset = page_data_size * 8;
- /* Subtract the metadata bits. */
- block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8;
+ if (ecc_for_meta)
+ /* Subtract the metadata bits and ecc bits. */
+ block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8
+ + chunk_ecc_size_in_bits;
+ else
+ /* Subtract the metadata bits. */
+ block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8;
/*
* Compute the chunk number (starting at zero) in which the block mark
@@ -228,15 +314,11 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size,
static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd)
{
- uint32_t ecc_strength;
- ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3;
}
static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd)
{
- uint32_t ecc_strength;
- ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) & 0x7;
}
@@ -992,15 +1074,8 @@ static int mxs_nand_scan_bbt(struct mtd_info *mtd)
struct mxs_bch_regs *bch_regs = (struct mxs_bch_regs *)MXS_BCH_BASE;
uint32_t tmp;
- if (mtd->oobsize > MXS_NAND_CHUNK_DATA_CHUNK_SIZE) {
- galois_field = 14;
- chunk_data_size = MXS_NAND_CHUNK_DATA_CHUNK_SIZE * 2;
- }
-
- if (mtd->oobsize > chunk_data_size) {
- printf("Not support the NAND chips whose oob size is larger then %d bytes!\n", chunk_data_size);
- return -EINVAL;
- }
+ /* calculate ecc_strength, bbm_chunk, eec_for meta, if necessary */
+ mxs_nand_get_ecc_strength(mtd);
/* Configure BCH and set NFC geometry */
mxs_reset_block(&bch_regs->hw_bch_ctrl_reg);
@@ -1009,16 +1084,21 @@ static int mxs_nand_scan_bbt(struct mtd_info *mtd)
tmp = (mxs_nand_ecc_chunk_cnt(mtd->writesize) - 1)
<< BCH_FLASHLAYOUT0_NBLOCKS_OFFSET;
tmp |= MXS_NAND_METADATA_SIZE << BCH_FLASHLAYOUT0_META_SIZE_OFFSET;
- tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+ tmp |= (ecc_strength >> 1)
<< BCH_FLASHLAYOUT0_ECC0_OFFSET;
- tmp |= chunk_data_size >> MXS_NAND_CHUNK_DATA_CHUNK_SIZE_SHIFT;
- tmp |= (14 == galois_field ? 1 : 0) <<
- BCH_FLASHLAYOUT0_GF13_0_GF14_1_OFFSET;
+ if (!ecc_for_meta)
+ tmp |= chunk_data_size
+ >> MXS_NAND_CHUNK_DATA_CHUNK_SIZE_SHIFT;
+ else
+ /* set data0 size as 0 */
+ tmp &= ~BCH_FLASHLAYOUT0_DATA0_SIZE_MASK;
+ tmp |= (14 == galois_field ? 1 : 0)
+ << BCH_FLASHLAYOUT0_GF13_0_GF14_1_OFFSET;
writel(tmp, &bch_regs->hw_bch_flash0layout0);
tmp = (mtd->writesize + mtd->oobsize)
<< BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET;
- tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+ tmp |= (ecc_strength >> 1)
<< BCH_FLASHLAYOUT1_ECCN_OFFSET;
tmp |= chunk_data_size >> MXS_NAND_CHUNK_DATA_CHUNK_SIZE_SHIFT;
tmp |= (14 == galois_field ? 1 : 0) <<