Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 310234
b: refs/heads/master
c: 0e618ef
h: refs/heads/master
v: v3
  • Loading branch information
Ivan Djelic authored and David Woodhouse committed May 14, 2012
1 parent b3eea82 commit ac87ce7
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 1 deletion.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 8d602cf50d3bba864bc1438f486b626df69c87b3
refs/heads/master: 0e618ef0a6a33cf7ef96c2c824402088dd8ef48c
40 changes: 40 additions & 0 deletions trunk/drivers/mtd/nand/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,46 @@ config MTD_NAND_OMAP2
Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4
platforms.

config MTD_NAND_OMAP_BCH
depends on MTD_NAND && MTD_NAND_OMAP2 && ARCH_OMAP3
bool "Enable support for hardware BCH error correction"
default n
select BCH
select BCH_CONST_PARAMS
help
Support for hardware BCH error correction.

choice
prompt "BCH error correction capability"
depends on MTD_NAND_OMAP_BCH

config MTD_NAND_OMAP_BCH8
bool "8 bits / 512 bytes (recommended)"
help
Support correcting up to 8 bitflips per 512-byte block.
This will use 13 bytes of spare area per 512 bytes of page data.
This is the recommended mode, as 4-bit mode does not work
on some OMAP3 revisions, due to a hardware bug.

config MTD_NAND_OMAP_BCH4
bool "4 bits / 512 bytes"
help
Support correcting up to 4 bitflips per 512-byte block.
This will use 7 bytes of spare area per 512 bytes of page data.
Note that this mode does not work on some OMAP3 revisions, due to a
hardware bug. Please check your OMAP datasheet before selecting this
mode.

endchoice

if MTD_NAND_OMAP_BCH
config BCH_CONST_M
default 13
config BCH_CONST_T
default 4 if MTD_NAND_OMAP_BCH4
default 8 if MTD_NAND_OMAP_BCH8
endif

config MTD_NAND_IDS
tristate

Expand Down
245 changes: 245 additions & 0 deletions trunk/drivers/mtd/nand/omap2.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include <linux/io.h>
#include <linux/slab.h>

#ifdef CONFIG_MTD_NAND_OMAP_BCH
#include <linux/bch.h>
#endif

#include <plat/dma.h>
#include <plat/gpmc.h>
#include <plat/nand.h>
Expand Down Expand Up @@ -127,6 +131,11 @@ struct omap_nand_info {
} iomode;
u_char *buf;
int buf_len;

#ifdef CONFIG_MTD_NAND_OMAP_BCH
struct bch_control *bch;
struct nand_ecclayout ecclayout;
#endif
};

/**
Expand Down Expand Up @@ -929,6 +938,226 @@ static int omap_dev_ready(struct mtd_info *mtd)
return 1;
}

#ifdef CONFIG_MTD_NAND_OMAP_BCH

/**
* omap3_enable_hwecc_bch - Program OMAP3 GPMC to perform BCH ECC correction
* @mtd: MTD device structure
* @mode: Read/Write mode
*/
static void omap3_enable_hwecc_bch(struct mtd_info *mtd, int mode)
{
int nerrors;
unsigned int dev_width;
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
struct nand_chip *chip = mtd->priv;

nerrors = (info->nand.ecc.bytes == 13) ? 8 : 4;
dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0;
/*
* Program GPMC to perform correction on one 512-byte sector at a time.
* Using 4 sectors at a time (i.e. ecc.size = 2048) is also possible and
* gives a slight (5%) performance gain (but requires additional code).
*/
(void)gpmc_enable_hwecc_bch(info->gpmc_cs, mode, dev_width, 1, nerrors);
}

/**
* omap3_calculate_ecc_bch4 - Generate 7 bytes of ECC bytes
* @mtd: MTD device structure
* @dat: The pointer to data on which ecc is computed
* @ecc_code: The ecc_code buffer
*/
static int omap3_calculate_ecc_bch4(struct mtd_info *mtd, const u_char *dat,
u_char *ecc_code)
{
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
return gpmc_calculate_ecc_bch4(info->gpmc_cs, dat, ecc_code);
}

/**
* omap3_calculate_ecc_bch8 - Generate 13 bytes of ECC bytes
* @mtd: MTD device structure
* @dat: The pointer to data on which ecc is computed
* @ecc_code: The ecc_code buffer
*/
static int omap3_calculate_ecc_bch8(struct mtd_info *mtd, const u_char *dat,
u_char *ecc_code)
{
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
return gpmc_calculate_ecc_bch8(info->gpmc_cs, dat, ecc_code);
}

/**
* omap3_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 omap3_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 omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);

count = decode_bch(info->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);
pr_debug("corrected bitflip %u\n", errloc[i]);
}
} else if (count < 0) {
pr_err("ecc unrecoverable error\n");
}
return count;
}

/**
* omap3_free_bch - Release BCH ecc resources
* @mtd: MTD device structure
*/
static void omap3_free_bch(struct mtd_info *mtd)
{
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
if (info->bch) {
free_bch(info->bch);
info->bch = NULL;
}
}

/**
* omap3_init_bch - Initialize BCH ECC
* @mtd: MTD device structure
* @ecc_opt: OMAP ECC mode (OMAP_ECC_BCH4_CODE_HW or OMAP_ECC_BCH8_CODE_HW)
*/
static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
{
int ret, max_errors;
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
#ifdef CONFIG_MTD_NAND_OMAP_BCH8
const int hw_errors = 8;
#else
const int hw_errors = 4;
#endif
info->bch = NULL;

max_errors = (ecc_opt == OMAP_ECC_BCH8_CODE_HW) ? 8 : 4;
if (max_errors != hw_errors) {
pr_err("cannot configure %d-bit BCH ecc, only %d-bit supported",
max_errors, hw_errors);
goto fail;
}

/* initialize GPMC BCH engine */
ret = gpmc_init_hwecc_bch(info->gpmc_cs, 1, max_errors);
if (ret)
goto fail;

/* software bch library is only used to detect and locate errors */
info->bch = init_bch(13, max_errors, 0x201b /* hw polynomial */);
if (!info->bch)
goto fail;

info->nand.ecc.size = 512;
info->nand.ecc.hwctl = omap3_enable_hwecc_bch;
info->nand.ecc.correct = omap3_correct_data_bch;
info->nand.ecc.mode = NAND_ECC_HW;

/*
* The number of corrected errors in an ecc block that will trigger
* block scrubbing defaults to the ecc strength (4 or 8).
* Set mtd->bitflip_threshold here to define a custom threshold.
*/

if (max_errors == 8) {
info->nand.ecc.strength = 8;
info->nand.ecc.bytes = 13;
info->nand.ecc.calculate = omap3_calculate_ecc_bch8;
} else {
info->nand.ecc.strength = 4;
info->nand.ecc.bytes = 7;
info->nand.ecc.calculate = omap3_calculate_ecc_bch4;
}

pr_info("enabling NAND BCH ecc with %d-bit correction\n", max_errors);
return 0;
fail:
omap3_free_bch(mtd);
return -1;
}

/**
* omap3_init_bch_tail - Build an oob layout for BCH ECC correction.
* @mtd: MTD device structure
*/
static int omap3_init_bch_tail(struct mtd_info *mtd)
{
int i, steps;
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
struct nand_ecclayout *layout = &info->ecclayout;

/* build oob layout */
steps = mtd->writesize/info->nand.ecc.size;
layout->eccbytes = steps*info->nand.ecc.bytes;

/* do not bother creating special oob layouts for small page devices */
if (mtd->oobsize < 64) {
pr_err("BCH ecc is not supported on small page devices\n");
goto fail;
}

/* reserve 2 bytes for bad block marker */
if (layout->eccbytes+2 > mtd->oobsize) {
pr_err("no oob layout available for oobsize %d eccbytes %u\n",
mtd->oobsize, layout->eccbytes);
goto fail;
}

/* put ecc bytes at oob tail */
for (i = 0; i < layout->eccbytes; i++)
layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i;

layout->oobfree[0].offset = 2;
layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes;
info->nand.ecc.layout = layout;

if (!(info->nand.options & NAND_BUSWIDTH_16))
info->nand.badblock_pattern = &bb_descrip_flashbased;
return 0;
fail:
omap3_free_bch(mtd);
return -1;
}

#else
static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
{
pr_err("CONFIG_MTD_NAND_OMAP_BCH is not enabled\n");
return -1;
}
static int omap3_init_bch_tail(struct mtd_info *mtd)
{
return -1;
}
static void omap3_free_bch(struct mtd_info *mtd)
{
}
#endif /* CONFIG_MTD_NAND_OMAP_BCH */

static int __devinit omap_nand_probe(struct platform_device *pdev)
{
struct omap_nand_info *info;
Expand Down Expand Up @@ -1067,6 +1296,13 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
info->nand.ecc.hwctl = omap_enable_hwecc;
info->nand.ecc.correct = omap_correct_data;
info->nand.ecc.mode = NAND_ECC_HW;
} else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
(pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
err = omap3_init_bch(&info->mtd, pdata->ecc_opt);
if (err) {
err = -EINVAL;
goto out_release_mem_region;
}
}

/* DIP switches on some boards change between 8 and 16 bit
Expand Down Expand Up @@ -1098,6 +1334,14 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
(offset + omap_oobinfo.eccbytes);

info->nand.ecc.layout = &omap_oobinfo;
} else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
(pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
/* build OOB layout for BCH ECC correction */
err = omap3_init_bch_tail(&info->mtd);
if (err) {
err = -EINVAL;
goto out_release_mem_region;
}
}

/* second phase scan */
Expand Down Expand Up @@ -1126,6 +1370,7 @@ static int omap_nand_remove(struct platform_device *pdev)
struct mtd_info *mtd = platform_get_drvdata(pdev);
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
mtd);
omap3_free_bch(&info->mtd);

platform_set_drvdata(pdev, NULL);
if (info->dma_ch != -1)
Expand Down

0 comments on commit ac87ce7

Please sign in to comment.