diff options
author | Andreas Bießmann <andreas.devel@googlemail.com> | 2013-04-05 04:55:21 +0000 |
---|---|---|
committer | Tom Rini <trini@ti.com> | 2013-04-08 11:29:05 -0400 |
commit | 4a0930069b596ae27267a0e7cd44199e2270afa5 (patch) | |
tree | 06242fd84a0a21e8316b6040ff530ed43a674116 /drivers/mtd/nand/omap_gpmc.c | |
parent | da634ae3567cc2df435f8617dbc95db2d079bf11 (diff) |
omap_gpmc: add support for hw assisted BCH8
The kernel states:
---8<---
The OMAP3 GPMC hardware BCH engine computes remainder polynomials, it does not
provide automatic error location and correction: this step is implemented using
the BCH library.
--->8---
And we do so in u-boot.
This implementation uses the same layout for BCH8 but it is fix. The current
provided layout does only work with 64 Byte OOB.
Signed-off-by: Andreas Bießmann <andreas.devel@googlemail.com>
Cc: Tom Rini <trini@ti.com>
Cc: Ilya Yanok <ilya.yanok@cogentembedded.com>
Cc: Scott Wood <scottwood@freescale.com>
Cc: Mansoor Ahamed <mansoor.ahamed@ti.com>
Cc: Thomas Weber <thomas.weber.linux@googlemail.com>
Diffstat (limited to 'drivers/mtd/nand/omap_gpmc.c')
-rw-r--r-- | drivers/mtd/nand/omap_gpmc.c | 367 |
1 files changed, 276 insertions, 91 deletions
diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c index 3468c7805e3..bc1bcad3bad 100644 --- a/drivers/mtd/nand/omap_gpmc.c +++ b/drivers/mtd/nand/omap_gpmc.c @@ -28,6 +28,7 @@ #include <asm/arch/cpu.h> #include <asm/omap_gpmc.h> #include <linux/mtd/nand_ecc.h> +#include <linux/bch.h> #include <linux/compiler.h> #include <nand.h> #ifdef CONFIG_AM33XX @@ -37,6 +38,8 @@ static uint8_t cs; static __maybe_unused struct nand_ecclayout hw_nand_oob = GPMC_NAND_HW_ECC_LAYOUT; +static __maybe_unused struct nand_ecclayout hw_bch8_nand_oob = + GPMC_NAND_HW_BCH8_ECC_LAYOUT; /* * omap_nand_hwcontrol - Set the address pointers corretly for the @@ -239,13 +242,13 @@ static void __maybe_unused omap_enable_hwecc(struct mtd_info *mtd, int32_t mode) } /* - * BCH8 support (needs ELM and thus AM33xx-only) + * Generic BCH interface */ -#ifdef CONFIG_AM33XX struct nand_bch_priv { uint8_t mode; uint8_t type; uint8_t nibbles; + struct bch_control *control; }; /* bch types */ @@ -253,21 +256,146 @@ struct nand_bch_priv { #define ECC_BCH8 1 #define ECC_BCH16 2 +/* GPMC ecc engine settings */ +#define BCH_WRAPMODE_1 1 /* BCH wrap mode 1 */ +#define BCH_WRAPMODE_6 6 /* BCH wrap mode 6 */ + /* BCH nibbles for diff bch levels */ #define NAND_ECC_HW_BCH ((uint8_t)(NAND_ECC_HW_OOB_FIRST) + 1) #define ECC_BCH4_NIBBLES 13 #define ECC_BCH8_NIBBLES 26 #define ECC_BCH16_NIBBLES 52 -static struct nand_ecclayout hw_bch8_nand_oob = GPMC_NAND_HW_BCH8_ECC_LAYOUT; - -static struct nand_bch_priv bch_priv = { +/* + * 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 nand_bch_priv bch_priv = { .mode = NAND_ECC_HW_BCH, .type = ECC_BCH8, - .nibbles = ECC_BCH8_NIBBLES + .nibbles = ECC_BCH8_NIBBLES, + .control = NULL }; /* + * omap_hwecc_init_bch - Initialize the BCH Hardware ECC for NAND flash in + * GPMC controller + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +__maybe_unused +static void omap_hwecc_init_bch(struct nand_chip *chip, int32_t mode) +{ + uint32_t val; + uint32_t dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; +#ifdef CONFIG_AM33XX + uint32_t unused_length = 0; +#endif + uint32_t wr_mode = BCH_WRAPMODE_6; + struct nand_bch_priv *bch = chip->priv; + + /* Clear the ecc result registers, select ecc reg as 1 */ + writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); + +#ifdef CONFIG_AM33XX + wr_mode = BCH_WRAPMODE_1; + + switch (bch->nibbles) { + case ECC_BCH4_NIBBLES: + unused_length = 3; + break; + case ECC_BCH8_NIBBLES: + unused_length = 2; + break; + case ECC_BCH16_NIBBLES: + unused_length = 0; + break; + } + + /* + * This is ecc_size_config for ELM mode. + * Here we are using different settings for read and write access and + * also depending on BCH strength. + */ + switch (mode) { + case NAND_ECC_WRITE: + /* write access only setup eccsize1 config */ + val = ((unused_length + bch->nibbles) << 22); + break; + + case NAND_ECC_READ: + default: + /* + * by default eccsize0 selected for ecc1resultsize + * eccsize0 config. + */ + val = (bch->nibbles << 12); + /* eccsize1 config */ + val |= (unused_length << 22); + break; + } +#else + /* + * This ecc_size_config setting is for BCH sw library. + * + * Note: we only support BCH8 currently with BCH sw library! + * Should be really easy to adobt to BCH4, however some omap3 have + * flaws with BCH4. + * + * Here we are using wrapping mode 6 both for reading and writing, with: + * size0 = 0 (no additional protected byte in spare area) + * size1 = 32 (skip 32 nibbles = 16 bytes per sector in spare area) + */ + val = (32 << 22) | (0 << 12); +#endif + /* ecc size configuration */ + writel(val, &gpmc_cfg->ecc_size_config); + + /* + * Configure the ecc engine in gpmc + * We assume 512 Byte sector pages for access to NAND. + */ + val = (1 << 16); /* enable BCH mode */ + val |= (bch->type << 12); /* setup BCH type */ + val |= (wr_mode << 8); /* setup wrapping mode */ + val |= (dev_width << 7); /* setup device width (16 or 8 bit) */ + val |= (cs << 1); /* setup chip select to work on */ + debug("set ECC_CONFIG=0x%08x\n", val); + writel(val, &gpmc_cfg->ecc_config); +} + +/* + * omap_enable_ecc_bch - This function enables the bch h/w ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +__maybe_unused +static void omap_enable_ecc_bch(struct mtd_info *mtd, int32_t mode) +{ + struct nand_chip *chip = mtd->priv; + + omap_hwecc_init_bch(chip, mode); + /* enable ecc */ + writel((readl(&gpmc_cfg->ecc_config) | 0x1), &gpmc_cfg->ecc_config); +} + +/* + * omap_ecc_disable - Disable H/W ECC calculation + * + * @mtd: MTD device structure + */ +static void __maybe_unused omap_ecc_disable(struct mtd_info *mtd) +{ + writel((readl(&gpmc_cfg->ecc_config) & ~0x1), &gpmc_cfg->ecc_config); +} + +/* + * BCH8 support (needs ELM and thus AM33xx-only) + */ +#ifdef CONFIG_AM33XX +/* * omap_read_bch8_result - Read BCH result for BCH8 level * * @mtd: MTD device structure @@ -306,18 +434,6 @@ static void omap_read_bch8_result(struct mtd_info *mtd, uint8_t big_endian, } /* - * omap_ecc_disable - Disable H/W ECC calculation - * - * @mtd: MTD device structure - * - */ -static void omap_ecc_disable(struct mtd_info *mtd) -{ - writel((readl(&gpmc_cfg->ecc_config) & ~0x1), - &gpmc_cfg->ecc_config); -} - -/* * omap_rotate_ecc_bch - Rotate the syndrome bytes * * @mtd: MTD device structure @@ -468,76 +584,6 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat, return 0; } -/* - * omap_hwecc_init_bch - Initialize the BCH Hardware ECC for NAND flash in - * GPMC controller - * @mtd: MTD device structure - * @mode: Read/Write mode - */ -static void omap_hwecc_init_bch(struct nand_chip *chip, int32_t mode) -{ - uint32_t val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; - uint32_t unused_length = 0; - struct nand_bch_priv *bch = chip->priv; - - switch (bch->nibbles) { - case ECC_BCH4_NIBBLES: - unused_length = 3; - break; - case ECC_BCH8_NIBBLES: - unused_length = 2; - break; - case ECC_BCH16_NIBBLES: - unused_length = 0; - break; - } - - /* Clear the ecc result registers, select ecc reg as 1 */ - writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); - - switch (mode) { - case NAND_ECC_WRITE: - /* eccsize1 config */ - val = ((unused_length + bch->nibbles) << 22); - break; - - case NAND_ECC_READ: - default: - /* by default eccsize0 selected for ecc1resultsize */ - /* eccsize0 config */ - val = (bch->nibbles << 12); - /* eccsize1 config */ - val |= (unused_length << 22); - break; - } - /* ecc size configuration */ - writel(val, &gpmc_cfg->ecc_size_config); - /* by default 512bytes sector page is selected */ - /* set bch mode */ - val = (1 << 16); - /* bch4 / bch8 / bch16 */ - val |= (bch->type << 12); - /* set wrap mode to 1 */ - val |= (1 << 8); - val |= (dev_width << 7); - val |= (cs << 1); - writel(val, &gpmc_cfg->ecc_config); -} - -/* - * omap_enable_ecc_bch- This function enables the bch h/w ecc functionality - * @mtd: MTD device structure - * @mode: Read/Write mode - * - */ -static void omap_enable_ecc_bch(struct mtd_info *mtd, int32_t mode) -{ - struct nand_chip *chip = mtd->priv; - - omap_hwecc_init_bch(chip, mode); - /* enable ecc */ - writel((readl(&gpmc_cfg->ecc_config) | 0x1), &gpmc_cfg->ecc_config); -} /** * omap_read_page_bch - hardware ecc based page read function @@ -602,6 +648,127 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip, } #endif /* CONFIG_AM33XX */ +/* + * OMAP3 BCH8 support (with BCH library) + */ +#ifdef CONFIG_NAND_OMAP_BCH8 +/* + * omap_calculate_ecc_bch - Read BCH ECC result + * + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed (unused here) + * @ecc: The ECC output buffer + */ +static int omap_calculate_ecc_bch(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc) +{ + int ret = 0; + size_t i; + unsigned long nsectors, val1, val2, val3, val4; + + nsectors = ((readl(&gpmc_cfg->ecc_config) >> 4) & 0x7) + 1; + + for (i = 0; i < nsectors; i++) { + /* Read hw-computed remainder */ + val1 = readl(&gpmc_cfg->bch_result_0_3[i].bch_result_x[0]); + val2 = readl(&gpmc_cfg->bch_result_0_3[i].bch_result_x[1]); + val3 = readl(&gpmc_cfg->bch_result_0_3[i].bch_result_x[2]); + val4 = readl(&gpmc_cfg->bch_result_0_3[i].bch_result_x[3]); + + /* + * Add constant polynomial to remainder, in order to get an ecc + * sequence of 0xFFs for a buffer filled with 0xFFs. + */ + *ecc++ = 0xef ^ (val4 & 0xFF); + *ecc++ = 0x51 ^ ((val3 >> 24) & 0xFF); + *ecc++ = 0x2e ^ ((val3 >> 16) & 0xFF); + *ecc++ = 0x09 ^ ((val3 >> 8) & 0xFF); + *ecc++ = 0xed ^ (val3 & 0xFF); + *ecc++ = 0x93 ^ ((val2 >> 24) & 0xFF); + *ecc++ = 0x9a ^ ((val2 >> 16) & 0xFF); + *ecc++ = 0xc2 ^ ((val2 >> 8) & 0xFF); + *ecc++ = 0x97 ^ (val2 & 0xFF); + *ecc++ = 0x79 ^ ((val1 >> 24) & 0xFF); + *ecc++ = 0xe5 ^ ((val1 >> 16) & 0xFF); + *ecc++ = 0x24 ^ ((val1 >> 8) & 0xFF); + *ecc++ = 0xb5 ^ (val1 & 0xFF); + } + + /* + * Stop reading anymore ECC vals and clear old results + * enable will be called if more reads are required + */ + omap_ecc_disable(mtd); + + return ret; +} + +/** + * omap_correct_data_bch - 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(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; + struct nand_bch_priv *chip_priv = chip->priv; + struct bch_control *bch = chip_priv->control; + + count = decode_bch(bch, 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 nand_bch_priv *chip_priv = chip->priv; + struct bch_control *bch = NULL; + + if (chip_priv) + bch = chip_priv->control; + + if (bch) { + free_bch(bch); + chip_priv->control = NULL; + } +} +#endif /* CONFIG_NAND_OMAP_BCH8 */ + #ifndef CONFIG_SPL_BUILD /* * omap_nand_switch_ecc - switch the ECC operation between different engines @@ -651,13 +818,17 @@ void omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength) omap_hwecc_init(nand); printf("1-bit hamming HW ECC selected\n"); } -#ifdef CONFIG_AM33XX +#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) else if (eccstrength == 8) { nand->ecc.mode = NAND_ECC_HW; nand->ecc.layout = &hw_bch8_nand_oob; nand->ecc.size = 512; +#ifdef CONFIG_AM33XX nand->ecc.bytes = 14; nand->ecc.read_page = omap_read_page_bch; +#else + nand->ecc.bytes = 13; +#endif nand->ecc.hwctl = omap_enable_ecc_bch; nand->ecc.correct = omap_correct_data_bch; nand->ecc.calculate = omap_calculate_ecc_bch; @@ -737,16 +908,28 @@ int board_nand_init(struct nand_chip *nand) nand->chip_delay = 100; +#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) #ifdef CONFIG_AM33XX + /* AM33xx uses the ELM */ /* required in case of BCH */ elm_init(); - +#else + /* + * Whereas other OMAP based SoC do not have the ELM, they use the BCH + * SW library. + */ + bch_priv.control = init_bch(13, 8, 0x201b /* hw polynominal */); + if (!bch_priv.control) { + puts("Could not init_bch()\n"); + return -ENODEV; + } +#endif /* BCH info that will be correct for SPL or overridden otherwise. */ nand->priv = &bch_priv; #endif /* Default ECC mode */ -#ifdef CONFIG_AM33XX +#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) nand->ecc.mode = NAND_ECC_HW; nand->ecc.layout = &hw_bch8_nand_oob; nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE; @@ -754,7 +937,9 @@ int board_nand_init(struct nand_chip *nand) nand->ecc.hwctl = omap_enable_ecc_bch; nand->ecc.correct = omap_correct_data_bch; nand->ecc.calculate = omap_calculate_ecc_bch; +#ifdef CONFIG_AM33XX nand->ecc.read_page = omap_read_page_bch; +#endif omap_hwecc_init_bch(nand, NAND_ECC_READ); #else #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC) |