From 46840f66caf564866d191886d2bd86742f982010 Mon Sep 17 00:00:00 2001 From: pekon gupta Date: Mon, 2 Jun 2014 17:14:42 +0530 Subject: [PATCH] mtd: nand: omap: add support for BCH16_ECC - NAND driver updates This patch add support for BCH16_ECC to omap_gpmc driver. *need to BCH16 ECC scheme* With newer SLC Flash technologies and MLC NAND, and large densities, pagesizes Flash devices have become more suspectible to bit-flips. Thus stronger ECC schemes are required for protecting the data. But stronger ECC schemes have come with larger-sized ECC syndromes which require more space in OOB/Spare. This puts constrains like; (a) BCH16_ECC can correct 16 bit-flips per 512Bytes of data. (b) BCH16_ECC generates 26-bytes of ECC syndrome / 512B. Due to (b) this scheme can only be used with NAND devices which have enough OOB to satisfy following equation: OOBsize per page >= 26 * (page-size / 512) Signed-off-by: Pekon Gupta --- drivers/mtd/nand/omap_gpmc.c | 79 ++++++++++++++++++++++++++++++++++- include/linux/mtd/omap_gpmc.h | 8 ++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c index cdfa6bc1c9..1acf06b237 100644 --- a/drivers/mtd/nand/omap_gpmc.c +++ b/drivers/mtd/nand/omap_gpmc.c @@ -224,6 +224,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 +303,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 +344,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; } @@ -333,7 +371,7 @@ 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; @@ -365,6 +403,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 +423,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; } @@ -666,6 +711,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; diff --git a/include/linux/mtd/omap_gpmc.h b/include/linux/mtd/omap_gpmc.h index d55fe328e6..9a8658257f 100644 --- a/include/linux/mtd/omap_gpmc.h +++ b/include/linux/mtd/omap_gpmc.h @@ -27,6 +27,8 @@ enum omap_ecc { OMAP_ECC_BCH8_CODE_HW_DETECTION_SW, /* 8-bit ECC calculation by GPMC, Error detection by ELM */ OMAP_ECC_BCH8_CODE_HW, + /* 16-bit ECC calculation by GPMC, Error detection by ELM */ + OMAP_ECC_BCH16_CODE_HW, }; struct gpmc_cs { @@ -47,6 +49,10 @@ struct bch_res_0_3 { u32 bch_result_x[4]; }; +struct bch_res_4_6 { + u32 bch_result_x[3]; +}; + struct gpmc { u8 res1[0x10]; u32 sysconfig; /* 0x10 */ @@ -77,6 +83,8 @@ struct gpmc { u32 testmomde_ctrl; /* 0x230 */ u8 res8[12]; /* 0x234 */ struct bch_res_0_3 bch_result_0_3[GPMC_MAX_SECTORS]; /* 0x240,0x250, */ + u8 res9[16 * 4]; /* 0x2C0 - 0x2FF */ + struct bch_res_4_6 bch_result_4_6[GPMC_MAX_SECTORS]; /* 0x300,0x310, */ }; /* Used for board specific gpmc initialization */