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>
This commit is contained in:
Andreas Bießmann 2013-04-05 04:55:21 +00:00 committed by Tom Rini
parent da634ae356
commit 4a0930069b
3 changed files with 305 additions and 92 deletions

View File

@ -145,6 +145,34 @@ int omap3_dma_wait_for_transfer(uint32_t chan)
int omap3_dma_get_revision(uint32_t *minor, uint32_t *major)
Read silicon Revision of the DMA module
NAND
====
There are some OMAP3 devices out there with NAND attached. Due to the fact that
OMAP3 ROM code can only handle 1-bit hamming ECC for accessing first page
(place where SPL lives) we require this setup for u-boot at least when reading
the second progam within SPL. A lot of newer NAND chips however require more
than 1-bit ECC for the pages, some can live with 1-bit for the first page. To
handle this we can switch to another ECC algorithm after reading the payload
within SPL.
BCH8
----
To enable hardware assisted BCH8 (8-bit BCH [Bose, Chaudhuri, Hocquenghem]) on
OMAP3 devices we can use the BCH library in lib/bch.c. To do so add CONFIG_BCH
to enable the library and CONFIG_NAND_OMAP_BCH8 to to enable hardware assisted
syndrom generation to your board config.
The NAND OOB layout is the same as in linux kernel, if the linux kernel BCH8
implementation for OMAP3 works for you so the u-boot version should also.
When you require the SPL to read with BCH8 there are two more configs to
change:
* CONFIG_SYS_NAND_ECCPOS (must be the same as .eccpos in
GPMC_NAND_HW_BCH8_ECC_LAYOUT defined in
arch/arm/include/asm/arch-omap3/omap_gpmc.h)
* CONFIG_SYS_NAND_ECCSIZE must be 512
* CONFIG_SYS_NAND_ECCBYTES must be 13 for this BCH8 setup
Acknowledgements
================

View File

@ -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,20 +256,145 @@ 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
*
@ -305,18 +433,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
*
@ -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)

View File

@ -27,7 +27,6 @@ LIB = $(obj)libgeneric.o
ifndef CONFIG_SPL_BUILD
COBJS-$(CONFIG_ADDR_MAP) += addr_map.o
COBJS-$(CONFIG_BCH) += bch.o
COBJS-$(CONFIG_AES) += aes.o
COBJS-$(CONFIG_BZIP2) += bzlib.o
COBJS-$(CONFIG_BZIP2) += bzlib_crctable.o
@ -67,6 +66,7 @@ COBJS-$(CONFIG_SPL_NET_SUPPORT) += errno.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += hashtable.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += net_utils.o
endif
COBJS-$(CONFIG_BCH) += bch.o
COBJS-y += crc32.o
COBJS-y += ctype.o
COBJS-y += div64.o