X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=drivers%2Fmtd%2Fnand%2Fomap_gpmc.c;h=391a26858cd8302d3a7d80f14f548c1f04cf1131;hb=9233279f8e33d1b3d21958d8afe10fbc3e46b3f2;hp=f1469d11050a04705bc8fa89f91df153359f0bac;hpb=5fb29f3c48d26981b117b08286bc16ec99d4ca0b;p=u-boot diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c index f1469d1105..391a26858c 100644 --- a/drivers/mtd/nand/omap_gpmc.c +++ b/drivers/mtd/nand/omap_gpmc.c @@ -2,37 +2,33 @@ * (C) Copyright 2004-2008 Texas Instruments, * Rohit Choraria * - * See file CREDITS for list of people who contributed to this - * project. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA + * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include -#include +#include #include +#include #include #include - +#include + +#define BADBLOCK_MARKER_LENGTH 2 +#define SECTOR_BYTES 512 +#define ECCCLEAR (0x1 << 8) +#define ECCRESULTREG1 (0x1 << 0) +/* 4 bit padding to make byte aligned, 56 = 52 + 4 */ +#define BCH4_BIT_PAD 4 + +#ifdef CONFIG_BCH +static u8 bch8_polynomial[] = {0xef, 0x51, 0x2e, 0x09, 0xed, 0x93, 0x9a, 0xc2, + 0x97, 0x79, 0xe5, 0x24, 0xb5}; +#endif static uint8_t cs; -static __maybe_unused struct nand_ecclayout hw_nand_oob = - GPMC_NAND_HW_ECC_LAYOUT; +static __maybe_unused struct nand_ecclayout omap_ecclayout; /* * omap_nand_hwcontrol - Set the address pointers corretly for the @@ -71,21 +67,6 @@ int omap_spl_dev_ready(struct mtd_info *mtd) } #endif -/* - * omap_hwecc_init - Initialize the Hardware ECC for NAND flash in - * GPMC controller - * @mtd: MTD device structure - * - */ -static void __maybe_unused omap_hwecc_init(struct nand_chip *chip) -{ - /* - * Init ECC Control Register - * Clear all ECC | Enable Reg1 - */ - writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); - writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL, &gpmc_cfg->ecc_size_config); -} /* * gen_true_ecc - This function will generate true ECC value, which @@ -167,8 +148,107 @@ static int __maybe_unused omap_correct_data(struct mtd_info *mtd, uint8_t *dat, } /* - * omap_calculate_ecc - Generate non-inverted ECC bytes. - * + * 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 + * @mode: Read/Write mode + */ +__maybe_unused +static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode) +{ + struct nand_chip *nand = mtd->priv; + struct omap_nand_info *info = nand->priv; + unsigned int dev_width = (nand->options & NAND_BUSWIDTH_16) ? 1 : 0; + unsigned int ecc_algo = 0; + unsigned int bch_type = 0; + unsigned int eccsize1 = 0x00, eccsize0 = 0x00, bch_wrapmode = 0x00; + u32 ecc_size_config_val = 0; + u32 ecc_config_val = 0; + + /* configure GPMC for specific ecc-scheme */ + switch (info->ecc_scheme) { + case OMAP_ECC_HAM1_CODE_SW: + return; + case OMAP_ECC_HAM1_CODE_HW: + ecc_algo = 0x0; + bch_type = 0x0; + bch_wrapmode = 0x00; + eccsize0 = 0xFF; + eccsize1 = 0xFF; + break; + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH8_CODE_HW: + ecc_algo = 0x1; + bch_type = 0x1; + if (mode == NAND_ECC_WRITE) { + bch_wrapmode = 0x01; + eccsize0 = 0; /* extra bits in nibbles per sector */ + eccsize1 = 28; /* OOB bits in nibbles per sector */ + } else { + bch_wrapmode = 0x01; + eccsize0 = 26; /* ECC bits in nibbles per sector */ + eccsize1 = 2; /* non-ECC bits in nibbles per sector */ + } + break; + default: + return; + } + /* Clear ecc and enable bits */ + writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); + /* Configure ecc size for BCH */ + ecc_size_config_val = (eccsize1 << 22) | (eccsize0 << 12); + writel(ecc_size_config_val, &gpmc_cfg->ecc_size_config); + + /* Configure device details for BCH engine */ + ecc_config_val = ((ecc_algo << 16) | /* HAM1 | BCHx */ + (bch_type << 12) | /* BCH4/BCH8/BCH16 */ + (bch_wrapmode << 8) | /* wrap mode */ + (dev_width << 7) | /* bus width */ + (0x0 << 4) | /* number of sectors */ + (cs << 1) | /* ECC CS */ + (0x1)); /* enable ECC */ + writel(ecc_config_val, &gpmc_cfg->ecc_config); +} + +/* + * omap_calculate_ecc - Read ECC result + * @mtd: MTD structure + * @dat: unused + * @ecc_code: ecc_code buffer * Using noninverted ECC can be considered ugly since writing a blank * page ie. padding will clear the ECC bytes. This is no problem as * long nobody is trying to write data on the seemingly unused page. @@ -177,120 +257,477 @@ static int __maybe_unused omap_correct_data(struct mtd_info *mtd, uint8_t *dat, * E.g. if page is 0xFF (fresh erased), and if HW ECC engine within GPMC * is used, the result of read will be 0x0 while the ECC offsets of the * spare area will be 0xFF which will result in an ECC mismatch. - * @mtd: MTD structure - * @dat: unused - * @ecc_code: ecc_code buffer */ -static int __maybe_unused omap_calculate_ecc(struct mtd_info *mtd, - const uint8_t *dat, uint8_t *ecc_code) +static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) { - u_int32_t val; + struct nand_chip *chip = mtd->priv; + struct omap_nand_info *info = chip->priv; + uint32_t *ptr, val = 0; + int8_t i = 0, j; + + switch (info->ecc_scheme) { + case OMAP_ECC_HAM1_CODE_HW: + val = readl(&gpmc_cfg->ecc1_result); + ecc_code[0] = val & 0xFF; + ecc_code[1] = (val >> 16) & 0xFF; + ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0); + break; +#ifdef CONFIG_BCH + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: +#endif + case OMAP_ECC_BCH8_CODE_HW: + ptr = &gpmc_cfg->bch_result_0_3[0].bch_result_x[3]; + val = readl(ptr); + ecc_code[i++] = (val >> 0) & 0xFF; + ptr--; + for (j = 0; j < 3; j++) { + val = readl(ptr); + ecc_code[i++] = (val >> 24) & 0xFF; + ecc_code[i++] = (val >> 16) & 0xFF; + ecc_code[i++] = (val >> 8) & 0xFF; + ecc_code[i++] = (val >> 0) & 0xFF; + ptr--; + } + break; + default: + return -EINVAL; + } + /* ECC scheme specific syndrome customizations */ + switch (info->ecc_scheme) { + case OMAP_ECC_HAM1_CODE_HW: + break; +#ifdef CONFIG_BCH + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: - /* Start Reading from HW ECC1_Result = 0x200 */ - val = readl(&gpmc_cfg->ecc1_result); + for (i = 0; i < chip->ecc.bytes; i++) + *(ecc_code + i) = *(ecc_code + i) ^ + bch8_polynomial[i]; + break; +#endif + case OMAP_ECC_BCH8_CODE_HW: + ecc_code[chip->ecc.bytes - 1] = 0x00; + break; + default: + return -EINVAL; + } + return 0; +} - ecc_code[0] = val & 0xFF; - ecc_code[1] = (val >> 16) & 0xFF; - ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0); +#ifdef CONFIG_NAND_OMAP_ELM +/* + * 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 + * + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash (ignored) + * @calc_ecc: ecc read from ECC registers + * + * @return 0 if data is OK or corrected, else returns -1 + */ +static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct nand_chip *chip = mtd->priv; + struct omap_nand_info *info = chip->priv; + uint32_t eccbytes = chip->ecc.bytes; + uint32_t error_count = 0, error_max; + uint32_t error_loc[8]; + enum bch_level bch_type; + uint32_t i, ecc_flag = 0; + uint8_t count, err = 0; + uint32_t byte_pos, bit_pos; + + /* check calculated ecc */ + for (i = 0; i < chip->ecc.bytes && !ecc_flag; i++) { + if (calc_ecc[i] != 0x00) + ecc_flag = 1; + } + if (!ecc_flag) + return 0; + + /* check for whether its a erased-page */ + ecc_flag = 0; + for (i = 0; i < chip->ecc.bytes && !ecc_flag; i++) { + if (read_ecc[i] != 0xff) + ecc_flag = 1; + } + if (!ecc_flag) + return 0; /* - * Stop reading anymore ECC vals and clear old results - * enable will be called if more reads are required + * while reading ECC result we read it in big endian. + * Hence while loading to ELM we have rotate to get the right endian. */ - writel(0x000, &gpmc_cfg->ecc_config); + switch (info->ecc_scheme) { + case OMAP_ECC_BCH8_CODE_HW: + bch_type = BCH_8_BIT; + omap_reverse_list(calc_ecc, eccbytes - 1); + break; + default: + return -EINVAL; + } + /* use elm module to check for errors */ + elm_config(bch_type); + if (elm_check_error(calc_ecc, bch_type, &error_count, error_loc)) { + printf("nand: error: uncorrectable ECC errors\n"); + return -EINVAL; + } + /* correct bch error */ + for (count = 0; count < error_count; count++) { + switch (info->ecc_scheme) { + case OMAP_ECC_BCH8_CODE_HW: + /* 14th byte in ECC is reserved to match ROM layout */ + error_max = SECTOR_BYTES + (eccbytes - 1); + break; + default: + return -EINVAL; + } + byte_pos = error_max - (error_loc[count] / 8) - 1; + bit_pos = error_loc[count] % 8; + if (byte_pos < SECTOR_BYTES) { + dat[byte_pos] ^= 1 << bit_pos; + printf("nand: bit-flip corrected @data=%d\n", byte_pos); + } else if (byte_pos < error_max) { + read_ecc[byte_pos - SECTOR_BYTES] ^= 1 << bit_pos; + printf("nand: bit-flip corrected @oob=%d\n", byte_pos - + SECTOR_BYTES); + } else { + err = -EBADMSG; + printf("nand: error: invalid bit-flip location\n"); + } + } + return (err) ? err : error_count; +} + +/** + * omap_read_page_bch - hardware ecc based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller expects OOB data read to chip->oob_poi + * @page: page number to read + * + */ +static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + 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; + 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]; + + 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->read_buf(mtd, p, eccsize); + + /* read respective ecc from oob area */ + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_pos, page); + chip->read_buf(mtd, oob, eccbytes); + /* read syndrome */ + chip->ecc.calculate(mtd, p, &ecc_calc[i]); + + data_pos += eccsize; + oob_pos += eccbytes; + } + + for (i = 0; i < chip->ecc.total; i++) + ecc_code[i] = chip->oob_poi[eccpos[i]]; + eccsteps = chip->ecc.steps; + p = buf; + + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } return 0; } +#endif /* CONFIG_NAND_OMAP_ELM */ /* - * omap_enable_ecc - This function enables the hardware ecc functionality - * @mtd: MTD device structure - * @mode: Read/Write mode + * OMAP3 BCH8 support (with BCH library) */ -static void __maybe_unused omap_enable_hwecc(struct mtd_info *mtd, int32_t mode) +#ifdef CONFIG_BCH +/** + * omap_correct_data_bch_sw - Decode received data and correct errors + * @mtd: MTD device structure + * @data: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + */ +static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data, + u_char *read_ecc, u_char *calc_ecc) { + int i, count; + /* cannot correct more than 8 errors */ + unsigned int errloc[8]; struct nand_chip *chip = mtd->priv; - uint32_t val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; - - switch (mode) { - case NAND_ECC_READ: - case NAND_ECC_WRITE: - /* Clear the ecc result registers, select ecc reg as 1 */ - writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); - - /* - * Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes - * tell all regs to generate size0 sized regs - * we just have a single ECC engine for all CS - */ - writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL, - &gpmc_cfg->ecc_size_config); - val = (dev_width << 7) | (cs << 1) | (0x1); - writel(val, &gpmc_cfg->ecc_config); + struct omap_nand_info *info = chip->priv; + + count = decode_bch(info->control, NULL, 512, read_ecc, calc_ecc, + NULL, errloc); + if (count > 0) { + /* correct errors */ + for (i = 0; i < count; i++) { + /* correct data only, not ecc bytes */ + if (errloc[i] < 8*512) + data[errloc[i]/8] ^= 1 << (errloc[i] & 7); + printf("corrected bitflip %u\n", errloc[i]); +#ifdef DEBUG + puts("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: "); + for (i = 0; i < 13; i++) + printf("%02x ", calc_ecc[i]); + puts("\n"); +#endif + } + } else if (count < 0) { + puts("ecc unrecoverable error\n"); + } + return count; +} + +/** + * omap_free_bch - Release BCH ecc resources + * @mtd: MTD device structure + */ +static void __maybe_unused omap_free_bch(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct omap_nand_info *info = chip->priv; + + if (info->control) { + free_bch(info->control); + info->control = NULL; + } +} +#endif /* CONFIG_BCH */ + +/** + * omap_select_ecc_scheme - configures driver for particular ecc-scheme + * @nand: NAND chip device structure + * @ecc_scheme: ecc scheme to configure + * @pagesize: number of main-area bytes per page of NAND device + * @oobsize: number of OOB/spare bytes per page of NAND device + */ +static int omap_select_ecc_scheme(struct nand_chip *nand, + enum omap_ecc ecc_scheme, unsigned int pagesize, unsigned int oobsize) { + struct omap_nand_info *info = nand->priv; + struct nand_ecclayout *ecclayout = &omap_ecclayout; + int eccsteps = pagesize / SECTOR_BYTES; + int i; + + switch (ecc_scheme) { + case OMAP_ECC_HAM1_CODE_SW: + debug("nand: selected OMAP_ECC_HAM1_CODE_SW\n"); + /* For this ecc-scheme, ecc.bytes, ecc.layout, ... are + * initialized in nand_scan_tail(), so just set ecc.mode */ + info->control = NULL; + nand->ecc.mode = NAND_ECC_SOFT; + nand->ecc.layout = NULL; + nand->ecc.size = 0; break; - default: - printf("Error: Unrecognized Mode[%d]!\n", mode); + + case OMAP_ECC_HAM1_CODE_HW: + debug("nand: selected OMAP_ECC_HAM1_CODE_HW\n"); + /* check ecc-scheme requirements before updating ecc info */ + if ((3 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { + printf("nand: error: insufficient OOB: require=%d\n", ( + (3 * eccsteps) + BADBLOCK_MARKER_LENGTH)); + return -EINVAL; + } + info->control = NULL; + /* populate ecc specific fields */ + memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl)); + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.strength = 1; + nand->ecc.size = SECTOR_BYTES; + nand->ecc.bytes = 3; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.correct = omap_correct_data; + nand->ecc.calculate = omap_calculate_ecc; + /* define ecc-layout */ + ecclayout->eccbytes = nand->ecc.bytes * eccsteps; + for (i = 0; i < ecclayout->eccbytes; i++) { + if (nand->options & NAND_BUSWIDTH_16) + ecclayout->eccpos[i] = i + 2; + else + ecclayout->eccpos[i] = i + 1; + } + ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH; + ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes - + BADBLOCK_MARKER_LENGTH; break; + + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: +#ifdef CONFIG_BCH + debug("nand: selected OMAP_ECC_BCH8_CODE_HW_DETECTION_SW\n"); + /* check ecc-scheme requirements before updating ecc info */ + if ((13 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { + printf("nand: error: insufficient OOB: require=%d\n", ( + (13 * eccsteps) + BADBLOCK_MARKER_LENGTH)); + return -EINVAL; + } + /* check if BCH S/W library can be used for error detection */ + info->control = init_bch(13, 8, 0x201b); + if (!info->control) { + printf("nand: error: could not init_bch()\n"); + return -ENODEV; + } + /* populate ecc specific fields */ + memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl)); + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.strength = 8; + nand->ecc.size = SECTOR_BYTES; + nand->ecc.bytes = 13; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.correct = omap_correct_data_bch_sw; + nand->ecc.calculate = omap_calculate_ecc; + /* define ecc-layout */ + ecclayout->eccbytes = nand->ecc.bytes * eccsteps; + ecclayout->eccpos[0] = BADBLOCK_MARKER_LENGTH; + for (i = 1; i < ecclayout->eccbytes; i++) { + if (i % nand->ecc.bytes) + ecclayout->eccpos[i] = + ecclayout->eccpos[i - 1] + 1; + else + ecclayout->eccpos[i] = + ecclayout->eccpos[i - 1] + 2; + } + ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH; + ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes - + BADBLOCK_MARKER_LENGTH; + break; +#else + printf("nand: error: CONFIG_BCH required for ECC\n"); + return -EINVAL; +#endif + + case OMAP_ECC_BCH8_CODE_HW: +#ifdef CONFIG_NAND_OMAP_ELM + debug("nand: selected OMAP_ECC_BCH8_CODE_HW\n"); + /* check ecc-scheme requirements before updating ecc info */ + if ((14 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { + printf("nand: error: insufficient OOB: require=%d\n", ( + (14 * eccsteps) + BADBLOCK_MARKER_LENGTH)); + return -EINVAL; + } + /* intialize ELM for ECC error detection */ + elm_init(); + info->control = NULL; + /* populate ecc specific fields */ + memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl)); + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.strength = 8; + nand->ecc.size = SECTOR_BYTES; + nand->ecc.bytes = 14; + 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 - ecclayout->eccbytes - + 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; } + + /* nand_scan_tail() sets ham1 sw ecc; hw ecc layout is set by driver */ + if (ecc_scheme != OMAP_ECC_HAM1_CODE_SW) + nand->ecc.layout = ecclayout; + + info->ecc_scheme = ecc_scheme; + return 0; } #ifndef CONFIG_SPL_BUILD /* - * omap_nand_switch_ecc - switch the ECC operation b/w h/w ecc and s/w ecc. - * The default is to come up on s/w ecc - * - * @hardware - 1 -switch to h/w ecc, 0 - s/w ecc + * omap_nand_switch_ecc - switch the ECC operation between different engines + * (h/w and s/w) and different algorithms (hamming and BCHx) * + * @hardware - true if one of the HW engines should be used + * @eccstrength - the number of bits that could be corrected + * (1 - hamming, 4 - BCH4, 8 - BCH8, 16 - BCH16) */ -void omap_nand_switch_ecc(int32_t hardware) +int __maybe_unused omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength) { struct nand_chip *nand; struct mtd_info *mtd; + int err = 0; if (nand_curr_device < 0 || nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || !nand_info[nand_curr_device].name) { - printf("Error: Can't switch ecc, no devices available\n"); - return; + printf("nand: error: no NAND devices found\n"); + return -ENODEV; } mtd = &nand_info[nand_curr_device]; nand = mtd->priv; - nand->options |= NAND_OWN_BUFFERS; - - /* Reset ecc interface */ - nand->ecc.read_page = NULL; - nand->ecc.write_page = NULL; - nand->ecc.read_oob = NULL; - nand->ecc.write_oob = NULL; - nand->ecc.hwctl = NULL; - nand->ecc.correct = NULL; - nand->ecc.calculate = NULL; - + nand->options &= ~NAND_SUBPAGE_READ; /* Setup the ecc configurations again */ if (hardware) { - nand->ecc.mode = NAND_ECC_HW; - nand->ecc.layout = &hw_nand_oob; - nand->ecc.size = 512; - nand->ecc.bytes = 3; - nand->ecc.hwctl = omap_enable_hwecc; - nand->ecc.correct = omap_correct_data; - nand->ecc.calculate = omap_calculate_ecc; - omap_hwecc_init(nand); - printf("HW ECC selected\n"); + if (eccstrength == 1) { + err = omap_select_ecc_scheme(nand, + OMAP_ECC_HAM1_CODE_HW, + mtd->writesize, mtd->oobsize); + } else if (eccstrength == 8) { + err = omap_select_ecc_scheme(nand, + OMAP_ECC_BCH8_CODE_HW, + mtd->writesize, mtd->oobsize); + } else { + printf("nand: error: unsupported ECC scheme\n"); + return -EINVAL; + } } else { - nand->ecc.mode = NAND_ECC_SOFT; - /* Use mtd default settings */ - nand->ecc.layout = NULL; - nand->ecc.size = 0; - printf("SW ECC selected\n"); + err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW, + mtd->writesize, mtd->oobsize); } /* Update NAND handling after ECC mode switch */ - nand_scan_tail(mtd); - - nand->options &= ~NAND_OWN_BUFFERS; + if (!err) + err = nand_scan_tail(mtd); + return err; } #endif /* CONFIG_SPL_BUILD */ @@ -313,7 +750,7 @@ int board_nand_init(struct nand_chip *nand) { int32_t gpmc_config = 0; cs = 0; - + int err = 0; /* * xloader/Uboot's gpmc configuration would have configured GPMC for * nand type of memory. The following logic scans and latches on to the @@ -330,7 +767,7 @@ int board_nand_init(struct nand_chip *nand) cs++; } if (cs >= GPMC_MAX_CS) { - printf("NAND: Unable to find NAND settings in " + printf("nand: error: Unable to find NAND settings in " "GPMC Configuration - quitting\n"); return -ENODEV; } @@ -342,27 +779,27 @@ 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->cmd_ctrl = omap_nand_hwcontrol; - nand->options = NAND_NO_PADDING | NAND_CACHEPRG | NAND_NO_AUTOINCR; + nand->priv = &omap_nand_info; + nand->cmd_ctrl = omap_nand_hwcontrol; + nand->options |= NAND_NO_PADDING | NAND_CACHEPRG; /* If we are 16 bit dev, our gpmc config tells us that */ if ((readl(&gpmc_cfg->cs[cs].config1) & 0x3000) == 0x1000) nand->options |= NAND_BUSWIDTH_16; nand->chip_delay = 100; - /* Default ECC mode */ -#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC) - nand->ecc.mode = NAND_ECC_SOFT; + nand->ecc.layout = &omap_ecclayout; + + /* select ECC scheme */ +#if defined(CONFIG_NAND_OMAP_ECCSCHEME) + err = omap_select_ecc_scheme(nand, CONFIG_NAND_OMAP_ECCSCHEME, + CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE); #else - nand->ecc.mode = NAND_ECC_HW; - nand->ecc.layout = &hw_nand_oob; - nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE; - nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES; - nand->ecc.hwctl = omap_enable_hwecc; - nand->ecc.correct = omap_correct_data; - nand->ecc.calculate = omap_calculate_ecc; - omap_hwecc_init(nand); + /* pagesize and oobsize are not required to configure sw ecc-scheme */ + err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW, + 0, 0); #endif + if (err) + return err; #ifdef CONFIG_SPL_BUILD if (nand->options & NAND_BUSWIDTH_16)