From 6bddff903bb875c02831cc312a66f99195a2cdf2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Lothar=20Wa=C3=9Fmann?= Date: Fri, 28 Jul 2017 12:51:14 +0200 Subject: [PATCH] mtd: nand: kirkwood: add RS-ECC encoding support The ROM code of the Kirkwood processors uses a for large page NAND devices. Currently all data in NAND is protected with 1-bit Hamming ECC protection. This is incompatible with the default behaviour of the ROM code for large page NAND devices, which expects a Reed-Solomon ECC scheme. Booting accidentally works, since the ROM code tries to read the NAND without ECC after several tries with ECC. But in case of a single bit error in the first page in NAND, booting fails, since the boot image header cannot be read by the CPU. Add support for writing to flash using the RS-ECC scheme found in the openocd source code supplied by Marvell. --- drivers/mtd/nand/Makefile | 3 + drivers/mtd/nand/kirkwood_nand.c | 27 +++++ drivers/mtd/nand/nand_base.c | 19 +++ drivers/mtd/nand/nand_ecc_rs.c | 191 +++++++++++++++++++++++++++++++ include/configs/tk71.h | 3 + include/linux/mtd/nand.h | 1 + include/linux/mtd/nand_ecc.h | 7 ++ 7 files changed, 251 insertions(+) create mode 100644 drivers/mtd/nand/nand_ecc_rs.c diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 1d1b628651..575ca9262f 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -52,6 +52,9 @@ COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o COBJS-$(CONFIG_NAND_JZ4740) += jz4740_nand.o COBJS-$(CONFIG_NAND_KB9202) += kb9202_nand.o COBJS-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o +ifneq ($(CONFIG_NAND_KIRKWOOD),) + COBJS-$(CONFIG_NAND_ECC_SOFT_RS) += nand_ecc_rs.o +endif COBJS-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o COBJS-$(CONFIG_NAND_MPC5121_NFC) += mpc5121_nfc.o COBJS-$(CONFIG_NAND_MXC) += mxc_nand.o diff --git a/drivers/mtd/nand/kirkwood_nand.c b/drivers/mtd/nand/kirkwood_nand.c index bdab5aa795..1cdf286f60 100644 --- a/drivers/mtd/nand/kirkwood_nand.c +++ b/drivers/mtd/nand/kirkwood_nand.c @@ -26,6 +26,11 @@ #include #include #include +#include + +#ifndef CONFIG_NAND_ECC_ALGO +#define CONFIG_NAND_ECC_ALGO NAND_ECC_SOFT +#endif /* NAND Flash Soc registers */ struct kwnandf_registers { @@ -71,10 +76,32 @@ void kw_nand_select_chip(struct mtd_info *mtd, int chip) writel(data, &nf_reg->ctrl); } +#ifdef CONFIG_NAND_ECC_SOFT_RS +static struct nand_ecclayout kw_nand_oob_rs = { + .eccbytes = 40, + .eccpos = { + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + }, + .oobfree = { + { .offset = 2, .length = 22, }, + }, +}; +#endif + int board_nand_init(struct nand_chip *nand) { nand->options = NAND_COPYBACK | NAND_CACHEPRG | NAND_NO_PADDING; +#ifndef CONFIG_NAND_ECC_SOFT_RS nand->ecc.mode = NAND_ECC_SOFT; +#else + nand->ecc.mode = NAND_ECC_SOFT_RS; + nand->ecc.layout = &kw_nand_oob_rs; + nand->ecc.size = 512; + nand->ecc.bytes = 10; +#endif nand->cmd_ctrl = kw_nand_hwcontrol; nand->chip_delay = 40; nand->select_chip = kw_nand_select_chip; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index bfd668fa0a..47755b1f25 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -3081,6 +3081,25 @@ int nand_scan_tail(struct mtd_info *mtd) break; +#ifdef CONFIG_NAND_ECC_SOFT_RS + case NAND_ECC_SOFT_RS: + chip->ecc.calculate = nand_rs_calculate_ecc; + chip->ecc.correct = nand_rs_correct_data; + chip->ecc.read_page = nand_read_page_swecc; + chip->ecc.read_subpage = nand_read_subpage; + chip->ecc.write_page = nand_write_page_swecc; + chip->ecc.read_page_raw = nand_read_page_raw; + chip->ecc.write_page_raw = nand_write_page_raw; + chip->ecc.read_oob = nand_read_oob_std; + chip->ecc.write_oob = nand_write_oob_std; + if (!chip->ecc.size && mtd->oobsize >= 64) { + chip->ecc.size = 512; + chip->ecc.bytes = 10; + printf("Using default ecc size %u and eccbytes %u for OOB size %u\n", + chip->ecc.size, chip->ecc.bytes, mtd->oobsize); + } + break; +#endif case NAND_ECC_NONE: printk(KERN_WARNING "NAND_ECC_NONE selected by board driver. " "This is not recommended !!\n"); diff --git a/drivers/mtd/nand/nand_ecc_rs.c b/drivers/mtd/nand/nand_ecc_rs.c new file mode 100644 index 0000000000..30a04067f9 --- /dev/null +++ b/drivers/mtd/nand/nand_ecc_rs.c @@ -0,0 +1,191 @@ +/* + * Reed-Solomon ECC handling for the Marvell Kirkwood SOC + * Copyright (C) 2017 Lothar Waßmann + * + * derived from openocd src/flash/nand/ecc_kw.c: + * Copyright (C) 2009 Marvell Semiconductor, Inc. + * + * Authors: Lennert Buytenhek + * Nicolas Pitre + * + * This file 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 or (at your option) any + * later version. + * + * This file 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. + */ +#include +#include +#include +#include + +/***************************************************************************** + * Arithmetic in GF(2^10) ("F") modulo x^10 + x^3 + 1. + * + * For multiplication, a discrete log/exponent table is used, with + * primitive element x (F is a primitive field, so x is primitive). + */ +#define MODPOLY 0x409 /* x^10 + x^3 + 1 in binary */ + +/* + * Maps an integer a [0..1022] to a polynomial b = gf_exp[a] in + * GF(2^10) mod x^10 + x^3 + 1 such that b = x ^ a. There's two + * identical copies of this array back-to-back so that we can save + * the mod 1023 operation when doing a GF multiplication. + */ +static uint16_t gf_exp[1023 + 1023]; + +/* + * Maps a polynomial b in GF(2^10) mod x^10 + x^3 + 1 to an index + * a = gf_log[b] in [0..1022] such that b = x ^ a. + */ +static uint16_t gf_log[1024]; + +static void gf_build_log_exp_table(void) +{ + int i; + int p_i; + + /* + * p_i = x ^ i + * + * Initialise to 1 for i = 0. + */ + p_i = 1; + + for (i = 0; i < 1023; i++) { + gf_exp[i] = p_i; + gf_exp[i + 1023] = p_i; + gf_log[p_i] = i; + + /* + * p_i = p_i * x + */ + p_i <<= 1; + if (p_i & (1 << 10)) + p_i ^= MODPOLY; + } +#ifdef DEBUG + for (i = 0; i < ARRAY_SIZE(gf_log); i++) { + printf("exp[%03x]=%4d log[%03x]=%4d\n", i, gf_exp[i], i, gf_log[i]); + } + for (; i < ARRAY_SIZE(gf_exp); i++) { + printf("exp[%03x]=%4d\n", i, gf_exp[i]); + } +#endif +} + +/***************************************************************************** + * Reed-Solomon code + * + * This implements a (1023,1015) Reed-Solomon ECC code over GF(2^10) + * mod x^10 + x^3 + 1, shortened to (520,512). The ECC data consists + * of 8 10-bit symbols, or 10 8-bit bytes. + * + * Given 512 bytes of data, computes 10 bytes of ECC. + * + * This is done by converting the 512 bytes to 512 10-bit symbols + * (elements of F), interpreting those symbols as a polynomial in F[X] + * by taking symbol 0 as the coefficient of X^8 and symbol 511 as the + * coefficient of X^519, and calculating the residue of that polynomial + * divided by the generator polynomial, which gives us the 8 ECC symbols + * as the remainder. Finally, we convert the 8 10-bit ECC symbols to 10 + * 8-bit bytes. + * + * The generator polynomial is hardcoded, as that is faster, but it + * can be computed by taking the primitive element a = x (in F), and + * constructing a polynomial in F[X] with roots a, a^2, a^3, ..., a^8 + * by multiplying the minimal polynomials for those roots (which are + * just 'x - a^i' for each i). + * + * Note: due to unfortunate circumstances, the bootrom in the Kirkwood SOC + * expects the ECC to be computed backward, i.e. from the last byte down + * to the first one. + */ +int nand_rs_calculate_ecc(struct mtd_info *mtd, const uint8_t *data, + uint8_t *ecc) +{ + unsigned int r7, r6, r5, r4, r3, r2, r1, r0; + int i; + static int tables_initialized; + + if (!tables_initialized) { + printf("Using RS-ECC\n"); + gf_build_log_exp_table(); + tables_initialized = 1; + } + + /* + * Load bytes 504..511 of the data into r. + */ + r0 = data[504]; + r1 = data[505]; + r2 = data[506]; + r3 = data[507]; + r4 = data[508]; + r5 = data[509]; + r6 = data[510]; + r7 = data[511]; + + /* + * Shift bytes 503..0 (in that order) into r0, followed + * by eight zero bytes, while reducing the polynomial by the + * generator polynomial in every step. + */ + for (i = 503; i >= -8; i--) { + unsigned int d = (i >= 0) ? data[i] : 0; + + if (r7) { + uint16_t *t = &gf_exp[gf_log[r7]]; + + r7 = r6 ^ t[0x21c]; // 540 + r6 = r5 ^ t[0x181]; // 385 + r5 = r4 ^ t[0x18e]; // 398 + r4 = r3 ^ t[0x25f]; // 607 + r3 = r2 ^ t[0x197]; // 407 + r2 = r1 ^ t[0x193]; // 403 + r1 = r0 ^ t[0x237]; // 567 + r0 = d ^ t[0x024]; // 36 + } else { + r7 = r6; + r6 = r5; + r5 = r4; + r4 = r3; + r3 = r2; + r2 = r1; + r1 = r0; + r0 = d; + } + } + + ecc[0] = r0; + ecc[1] = (r0 >> 8) | (r1 << 2); + ecc[2] = (r1 >> 6) | (r2 << 4); + ecc[3] = (r2 >> 4) | (r3 << 6); + ecc[4] = (r3 >> 2); + ecc[5] = r4; + ecc[6] = (r4 >> 8) | (r5 << 2); + ecc[7] = (r5 >> 6) | (r6 << 4); + ecc[8] = (r6 >> 4) | (r7 << 6); + ecc[9] = (r7 >> 2); + + debug("ECC: %03x %03x %03x %03x %03x %03x %03x %03x\n", + r0, r1, r2, r3, r4, r5, r6, r7); + + return 0; +} + +int nand_rs_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + static int once; + if (!once) { + printf("Reading NAND with RS-ECC not supported\n"); + once++; + } + return -EINVAL; +} diff --git a/include/configs/tk71.h b/include/configs/tk71.h index 88f49e7ddc..03fd994d6a 100644 --- a/include/configs/tk71.h +++ b/include/configs/tk71.h @@ -73,6 +73,9 @@ * NAND flash */ #ifdef CONFIG_CMD_NAND +#define CONFIG_NAND_ECC_SOFT_RS +#define CONFIG_NAND_ECC_BCH +#define CONFIG_BCH #define CONFIG_MTD_DEVICE #define CONFIG_MTD_PARTITIONS #define CONFIG_JFFS2_NAND diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 82704de083..e0ad9ae37d 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -131,6 +131,7 @@ typedef enum { NAND_ECC_HW_SYNDROME, NAND_ECC_HW_OOB_FIRST, NAND_ECC_SOFT_BCH, + NAND_ECC_SOFT_RS, } nand_ecc_modes_t; /* diff --git a/include/linux/mtd/nand_ecc.h b/include/linux/mtd/nand_ecc.h index 090da50542..dc1ddeda9a 100644 --- a/include/linux/mtd/nand_ecc.h +++ b/include/linux/mtd/nand_ecc.h @@ -25,4 +25,11 @@ int nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code */ int nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc); +#ifdef CONFIG_NAND_ECC_SOFT_RS +/* + * Calculate 10 byte RS ECC syndrome for 512 byte block + */ +int nand_rs_calculate_ecc(struct mtd_info *mtd, const uint8_t *data, uint8_t *ecc); +#endif + #endif /* __MTD_NAND_ECC_H__ */ -- 2.39.2