|
@@ -148,35 +148,20 @@ static int __maybe_unused omap_correct_data(struct mtd_info *mtd, uint8_t *dat,
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Generic BCH interface
|
|
|
+ * Driver configurations
|
|
|
*/
|
|
|
-struct nand_bch_priv {
|
|
|
- uint8_t mode;
|
|
|
- uint8_t type;
|
|
|
- uint8_t nibbles;
|
|
|
+struct omap_nand_info {
|
|
|
struct bch_control *control;
|
|
|
enum omap_ecc ecc_scheme;
|
|
|
};
|
|
|
|
|
|
-/* bch types */
|
|
|
-#define ECC_BCH4 0
|
|
|
-#define ECC_BCH8 1
|
|
|
-#define ECC_BCH16 2
|
|
|
-
|
|
|
-/* BCH nibbles for diff bch levels */
|
|
|
-#define ECC_BCH4_NIBBLES 13
|
|
|
-#define ECC_BCH8_NIBBLES 26
|
|
|
-#define ECC_BCH16_NIBBLES 52
|
|
|
-
|
|
|
/*
|
|
|
* 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 = {
|
|
|
- .type = ECC_BCH8,
|
|
|
- .nibbles = ECC_BCH8_NIBBLES,
|
|
|
+static __maybe_unused struct omap_nand_info omap_nand_info = {
|
|
|
.control = NULL
|
|
|
};
|
|
|
|
|
@@ -206,7 +191,7 @@ __maybe_unused
|
|
|
static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode)
|
|
|
{
|
|
|
struct nand_chip *nand = mtd->priv;
|
|
|
- struct nand_bch_priv *bch = nand->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;
|
|
@@ -215,7 +200,7 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode)
|
|
|
u32 ecc_config_val = 0;
|
|
|
|
|
|
/* configure GPMC for specific ecc-scheme */
|
|
|
- switch (bch->ecc_scheme) {
|
|
|
+ switch (info->ecc_scheme) {
|
|
|
case OMAP_ECC_HAM1_CODE_SW:
|
|
|
return;
|
|
|
case OMAP_ECC_HAM1_CODE_HW:
|
|
@@ -239,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;
|
|
|
}
|
|
@@ -277,11 +275,11 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
|
|
|
uint8_t *ecc_code)
|
|
|
{
|
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
- struct nand_bch_priv *bch = chip->priv;
|
|
|
+ struct omap_nand_info *info = chip->priv;
|
|
|
uint32_t *ptr, val = 0;
|
|
|
int8_t i = 0, j;
|
|
|
|
|
|
- switch (bch->ecc_scheme) {
|
|
|
+ switch (info->ecc_scheme) {
|
|
|
case OMAP_ECC_HAM1_CODE_HW:
|
|
|
val = readl(&gpmc_cfg->ecc1_result);
|
|
|
ecc_code[0] = val & 0xFF;
|
|
@@ -305,11 +303,34 @@ 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;
|
|
|
}
|
|
|
/* ECC scheme specific syndrome customizations */
|
|
|
- switch (bch->ecc_scheme) {
|
|
|
+ switch (info->ecc_scheme) {
|
|
|
case OMAP_ECC_HAM1_CODE_HW:
|
|
|
break;
|
|
|
#ifdef CONFIG_BCH
|
|
@@ -323,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;
|
|
|
}
|
|
@@ -345,16 +368,17 @@ 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 nand_bch_priv *bch = chip->priv;
|
|
|
- uint32_t eccbytes = chip->ecc.bytes;
|
|
|
+ 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;
|
|
|
uint32_t byte_pos, bit_pos;
|
|
|
|
|
|
/* check calculated ecc */
|
|
|
- for (i = 0; i < chip->ecc.bytes && !ecc_flag; i++) {
|
|
|
+ for (i = 0; i < ecc->bytes && !ecc_flag; i++) {
|
|
|
if (calc_ecc[i] != 0x00)
|
|
|
ecc_flag = 1;
|
|
|
}
|
|
@@ -363,7 +387,7 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
|
|
|
|
|
|
/* check for whether its a erased-page */
|
|
|
ecc_flag = 0;
|
|
|
- for (i = 0; i < chip->ecc.bytes && !ecc_flag; i++) {
|
|
|
+ for (i = 0; i < ecc->bytes && !ecc_flag; i++) {
|
|
|
if (read_ecc[i] != 0xff)
|
|
|
ecc_flag = 1;
|
|
|
}
|
|
@@ -374,25 +398,33 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
|
|
|
* while reading ECC result we read it in big endian.
|
|
|
* Hence while loading to ELM we have rotate to get the right endian.
|
|
|
*/
|
|
|
- switch (bch->ecc_scheme) {
|
|
|
+ switch (info->ecc_scheme) {
|
|
|
case OMAP_ECC_BCH8_CODE_HW:
|
|
|
- omap_reverse_list(calc_ecc, eccbytes - 1);
|
|
|
+ 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;
|
|
|
}
|
|
|
/* use elm module to check for errors */
|
|
|
- elm_config((enum bch_level)(bch->type));
|
|
|
- if (elm_check_error(calc_ecc, bch->nibbles, &error_count, error_loc)) {
|
|
|
- printf("nand: error: uncorrectable ECC errors\n");
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
+ elm_config(bch_type);
|
|
|
+ err = elm_check_error(calc_ecc, bch_type, &error_count, error_loc);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
/* correct bch error */
|
|
|
for (count = 0; count < error_count; count++) {
|
|
|
- switch (bch->type) {
|
|
|
- case ECC_BCH8:
|
|
|
+ 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);
|
|
|
+ error_max = SECTOR_BYTES + (ecc->bytes - 1);
|
|
|
+ break;
|
|
|
+ case OMAP_ECC_BCH16_CODE_HW:
|
|
|
+ error_max = SECTOR_BYTES + ecc->bytes;
|
|
|
break;
|
|
|
default:
|
|
|
return -EINVAL;
|
|
@@ -496,10 +528,10 @@ static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data,
|
|
|
/* 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;
|
|
|
+ struct omap_nand_info *info = chip->priv;
|
|
|
|
|
|
- count = decode_bch(bch, NULL, 512, read_ecc, calc_ecc, NULL, errloc);
|
|
|
+ count = decode_bch(info->control, NULL, 512, read_ecc, calc_ecc,
|
|
|
+ NULL, errloc);
|
|
|
if (count > 0) {
|
|
|
/* correct errors */
|
|
|
for (i = 0; i < count; i++) {
|
|
@@ -535,15 +567,11 @@ static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data,
|
|
|
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;
|
|
|
+ struct omap_nand_info *info = chip->priv;
|
|
|
|
|
|
- if (bch) {
|
|
|
- free_bch(bch);
|
|
|
- chip_priv->control = NULL;
|
|
|
+ if (info->control) {
|
|
|
+ free_bch(info->control);
|
|
|
+ info->control = NULL;
|
|
|
}
|
|
|
}
|
|
|
#endif /* CONFIG_BCH */
|
|
@@ -557,7 +585,7 @@ static void __maybe_unused omap_free_bch(struct mtd_info *mtd)
|
|
|
*/
|
|
|
static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
enum omap_ecc ecc_scheme, unsigned int pagesize, unsigned int oobsize) {
|
|
|
- struct nand_bch_priv *bch = nand->priv;
|
|
|
+ struct omap_nand_info *info = nand->priv;
|
|
|
struct nand_ecclayout *ecclayout = &omap_ecclayout;
|
|
|
int eccsteps = pagesize / SECTOR_BYTES;
|
|
|
int i;
|
|
@@ -567,12 +595,10 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
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 */
|
|
|
- bch_priv.control = NULL;
|
|
|
- bch_priv.type = 0;
|
|
|
+ info->control = NULL;
|
|
|
nand->ecc.mode = NAND_ECC_SOFT;
|
|
|
nand->ecc.layout = NULL;
|
|
|
nand->ecc.size = 0;
|
|
|
- bch->ecc_scheme = OMAP_ECC_HAM1_CODE_SW;
|
|
|
break;
|
|
|
|
|
|
case OMAP_ECC_HAM1_CODE_HW:
|
|
@@ -583,8 +609,7 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
(3 * eccsteps) + BADBLOCK_MARKER_LENGTH));
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
- bch_priv.control = NULL;
|
|
|
- bch_priv.type = 0;
|
|
|
+ info->control = NULL;
|
|
|
/* populate ecc specific fields */
|
|
|
memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl));
|
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
@@ -605,7 +630,6 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
|
|
|
ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
|
|
|
BADBLOCK_MARKER_LENGTH;
|
|
|
- bch->ecc_scheme = OMAP_ECC_HAM1_CODE_HW;
|
|
|
break;
|
|
|
|
|
|
case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW:
|
|
@@ -618,12 +642,11 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
/* check if BCH S/W library can be used for error detection */
|
|
|
- bch_priv.control = init_bch(13, 8, 0x201b);
|
|
|
- if (!bch_priv.control) {
|
|
|
+ info->control = init_bch(13, 8, 0x201b);
|
|
|
+ if (!info->control) {
|
|
|
printf("nand: error: could not init_bch()\n");
|
|
|
return -ENODEV;
|
|
|
}
|
|
|
- bch_priv.type = ECC_BCH8;
|
|
|
/* populate ecc specific fields */
|
|
|
memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl));
|
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
@@ -647,7 +670,6 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
|
|
|
ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
|
|
|
BADBLOCK_MARKER_LENGTH;
|
|
|
- bch->ecc_scheme = OMAP_ECC_BCH8_CODE_HW_DETECTION_SW;
|
|
|
break;
|
|
|
#else
|
|
|
printf("nand: error: CONFIG_BCH required for ECC\n");
|
|
@@ -665,7 +687,7 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
}
|
|
|
/* intialize ELM for ECC error detection */
|
|
|
elm_init();
|
|
|
- bch_priv.type = ECC_BCH8;
|
|
|
+ info->control = NULL;
|
|
|
/* populate ecc specific fields */
|
|
|
memset(&nand->ecc, 0, sizeof(struct nand_ecc_ctrl));
|
|
|
nand->ecc.mode = NAND_ECC_HW;
|
|
@@ -683,13 +705,44 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
|
|
|
ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
|
|
|
BADBLOCK_MARKER_LENGTH;
|
|
|
- bch->ecc_scheme = OMAP_ECC_BCH8_CODE_HW;
|
|
|
break;
|
|
|
#else
|
|
|
printf("nand: error: CONFIG_NAND_OMAP_ELM required for ECC\n");
|
|
|
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;
|
|
@@ -699,6 +752,7 @@ static int omap_select_ecc_scheme(struct nand_chip *nand,
|
|
|
if (ecc_scheme != OMAP_ECC_HAM1_CODE_SW)
|
|
|
nand->ecc.layout = ecclayout;
|
|
|
|
|
|
+ info->ecc_scheme = ecc_scheme;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -802,16 +856,21 @@ 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->priv = &bch_priv;
|
|
|
+ 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;
|
|
|
nand->ecc.layout = &omap_ecclayout;
|
|
|
|
|
|
+ /* configure driver and controller based on NAND device bus-width */
|
|
|
+ gpmc_config = readl(&gpmc_cfg->cs[cs].config1);
|
|
|
+#if defined(CONFIG_SYS_NAND_BUSWIDTH_16BIT)
|
|
|
+ nand->options |= NAND_BUSWIDTH_16;
|
|
|
+ writel(gpmc_config | (0x1 << 12), &gpmc_cfg->cs[cs].config1);
|
|
|
+#else
|
|
|
+ nand->options &= ~NAND_BUSWIDTH_16;
|
|
|
+ writel(gpmc_config & ~(0x1 << 12), &gpmc_cfg->cs[cs].config1);
|
|
|
+#endif
|
|
|
/* select ECC scheme */
|
|
|
#if defined(CONFIG_NAND_OMAP_ECCSCHEME)
|
|
|
err = omap_select_ecc_scheme(nand, CONFIG_NAND_OMAP_ECCSCHEME,
|