diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2019-09-12 07:53:06 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2019-09-12 07:53:06 +0200 |
commit | d8af9462c9040c2e222d90a6003581abc8fb040e (patch) | |
tree | e81bd1d44fd3ee7c8bd4af6904e023d0e28d775c /drivers/mtd/nand | |
parent | abc71c4cf97470728cc29175d69543cbba4d0d4a (diff) | |
parent | 63ea06d03e9f0ef574ff4d68b1bd3b3abff67e8e (diff) | |
download | barebox-d8af9462c9040c2e222d90a6003581abc8fb040e.tar.gz barebox-d8af9462c9040c2e222d90a6003581abc8fb040e.tar.xz |
Merge branch 'for-next/omap'
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r-- | drivers/mtd/nand/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mtd/nand/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_omap_gpmc.c | 333 | ||||
-rw-r--r-- | drivers/mtd/nand/omap_elm.c | 413 |
4 files changed, 749 insertions, 8 deletions
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 2b4a478a03..fadfe1f99b 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -90,6 +90,16 @@ config NAND_OMAP_GPMC Support for NAND flash using GPMC. GPMC is a common memory interface found on Texas Instrument's OMAP platforms +config MTD_NAND_OMAP_ELM + bool "Support for ELM (Error Location Module) on OMAP platforms" + depends on NAND_OMAP_GPMC + help + This config enables the ELM hardware engine, which can be used to + locate and correct errors when using BCH ECC scheme. This offloads + the cpu from doing ECC error searching and correction. However some + legacy OMAP families like OMAP2xxx, OMAP3xxx do not have ELM engine + so this is optional for them. + config NAND_ORION bool prompt "Marvell Orion NAND driver" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 6088512745..a4066ba778 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o obj-$(CONFIG_NAND_IMX) += nand_imx.o obj-$(CONFIG_NAND_IMX_BBM) += nand_imx_bbm.o obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o nand_omap_bch_decoder.o +obj-$(CONFIG_MTD_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_ORION) += nand_orion.o obj-$(CONFIG_NAND_MRVL_NFC) += nand_mrvl_nfc.o obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 9c7f5a02a1..c4d0cdfb57 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -70,6 +70,7 @@ #include <io.h> #include <mach/gpmc.h> #include <mach/gpmc_nand.h> +#include <platform_data/elm.h> #include "nand_omap_bch_decoder.h" @@ -91,6 +92,10 @@ static const uint8_t bch8_vector[] = {0xf3, 0xdb, 0x14, 0x16, 0x8b, 0xd2, 0xbe, 0xcc, 0xac, 0x6b, 0xff, 0x99, 0x7b}; +static u_char bch16_vector[] = {0xf5, 0x24, 0x1c, 0xd0, 0x61, 0xb3, 0xf1, 0x55, + 0x2e, 0x2c, 0x86, 0xa3, 0xed, 0x36, 0x1b, 0x78, + 0x48, 0x76, 0xa9, 0x3b, 0x97, 0xd1, 0x7a, 0x93, + 0x07, 0x0e}; static const char *ecc_mode_strings[] = { "software", @@ -232,7 +237,7 @@ static int __omap_calculate_ecc(struct mtd_info *mtd, uint8_t *ecc_code, { struct nand_chip *nand = mtd_to_nand(mtd); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); - unsigned int reg; + unsigned int reg, reg1, val; unsigned int val1 = 0x0, val2 = 0x0; unsigned int val3 = 0x0, val4 = 0x0; int ecc_size = 8; @@ -274,6 +279,45 @@ static int __omap_calculate_ecc(struct mtd_info *mtd, uint8_t *ecc_code, /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ *ecc_code++ = ((val1 >> 8) & 0x0f) | ((val1 >> 20) & 0xf0); break; + + case OMAP_ECC_BCH16_CODE_HW: + reg = GPMC_ECC_BCH_RESULT_0 + (0x10 * sblock); + reg1 = 0x300 + (0x10 * sblock); + + val = readl(oinfo->gpmc_base + reg1 + 0x8); + ecc_code[0] = ((val >> 8) & 0xFF); + ecc_code[1] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg1 + 0x4); + ecc_code[2] = ((val >> 24) & 0xFF); + ecc_code[3] = ((val >> 16) & 0xFF); + ecc_code[4] = ((val >> 8) & 0xFF); + ecc_code[5] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg1 + 0x0); + ecc_code[6] = ((val >> 24) & 0xFF); + ecc_code[7] = ((val >> 16) & 0xFF); + ecc_code[8] = ((val >> 8) & 0xFF); + ecc_code[9] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg + 0xc); + ecc_code[10] = ((val >> 24) & 0xFF); + ecc_code[11] = ((val >> 16) & 0xFF); + ecc_code[12] = ((val >> 8) & 0xFF); + ecc_code[13] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg + 0x8); + ecc_code[14] = ((val >> 24) & 0xFF); + ecc_code[15] = ((val >> 16) & 0xFF); + ecc_code[16] = ((val >> 8) & 0xFF); + ecc_code[17] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg + 0x4); + ecc_code[18] = ((val >> 24) & 0xFF); + ecc_code[19] = ((val >> 16) & 0xFF); + ecc_code[20] = ((val >> 8) & 0xFF); + ecc_code[21] = ((val >> 0) & 0xFF); + val = readl(oinfo->gpmc_base + reg + 0x0); + ecc_code[22] = ((val >> 24) & 0xFF); + ecc_code[23] = ((val >> 16) & 0xFF); + ecc_code[24] = ((val >> 8) & 0xFF); + ecc_code[25] = ((val >> 0) & 0xFF); + break; default: return -EINVAL; } @@ -294,7 +338,7 @@ static int omap_correct_bch(struct mtd_info *mtd, uint8_t *dat, struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); int j, actual_eccsize; const uint8_t *erased_ecc_vec; - unsigned int err_loc[8]; + unsigned int err_loc[16]; int bch_max_err; int bitflip_count = 0; bool eccflag = 0; @@ -311,6 +355,11 @@ static int omap_correct_bch(struct mtd_info *mtd, uint8_t *dat, erased_ecc_vec = bch8_vector; bch_max_err = BCH8_MAX_ERROR; break; + case OMAP_ECC_BCH16_CODE_HW: + actual_eccsize = eccsize; + erased_ecc_vec = bch16_vector; + bch_max_err = 16; + break; default: dev_err(oinfo->pdev, "invalid driver configuration\n"); return -EINVAL; @@ -441,13 +490,27 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) case OMAP_ECC_BCH8_CODE_HW: case OMAP_ECC_BCH8_CODE_HW_ROMCODE: if (mode == NAND_ECC_READ) { - eccsize1 = 0x1A; eccsize0 = 0x18; + eccsize0 = 24; + eccsize1 = 26; bch_mod = 1; - bch_wrapmode = 0x04; + bch_wrapmode = 4; } else { - eccsize1 = 0x20; eccsize0 = 0x00; + eccsize0 = 0; + eccsize1 = 32; bch_mod = 1; - bch_wrapmode = 0x06; + bch_wrapmode = 6; + } + break; + case OMAP_ECC_BCH16_CODE_HW: + bch_mod = 2; + if (mode == NAND_ECC_READ) { + bch_wrapmode = 4; + eccsize0 = 4; /* ECC bits in nibbles per sector */ + eccsize1 = 52; /* non-ECC bits in nibbles per sector */ + } else { + bch_wrapmode = 4; + eccsize0 = 4; /* extra bits in nibbles per sector */ + eccsize1 = 52; /* OOB bits in nibbles per sector */ } break; case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: @@ -483,7 +546,7 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) GPMC_ECC_CONFIG_ECCBCHTSEL(bch_mod) | GPMC_ECC_CONFIG_ECCWRAPMODE(bch_wrapmode) | dev_width | - GPMC_ECC_CONFIG_ECCTOPSECTOR(3) | + GPMC_ECC_CONFIG_ECCTOPSECTOR(nand->ecc.steps - 1) | GPMC_ECC_CONFIG_ECCCS(cs) | GPMC_ECC_CONFIG_ECCENABLE); } @@ -689,6 +752,234 @@ static int omap_gpmc_read_page_bch_rom_mode(struct mtd_info *mtd, return max_bitflips; } +/** + * erased_sector_bitflips - count bit flips + * @data: data sector buffer + * @oob: oob buffer + * oinfo: gpmc_nand_info + * + * Check the bit flips in erased page falls below correctable level. + * If falls below, report the page as erased with correctable bit + * flip, else report as uncorrectable page. + */ +static int erased_sector_bitflips(u_char *data, u_char *oob, + struct gpmc_nand_info *oinfo) +{ + int flip_bits = 0, i; + + for (i = 0; i < oinfo->nand.ecc.size; i++) { + flip_bits += hweight8(~data[i]); + if (flip_bits > oinfo->nand.ecc.strength) + return 0; + } + + for (i = 0; i < oinfo->nand.ecc.bytes - 1; i++) { + flip_bits += hweight8(~oob[i]); + if (flip_bits > oinfo->nand.ecc.strength) + return 0; + } + + /* + * Bit flips falls in correctable level. + * Fill data area with 0xFF + */ + if (flip_bits) { + memset(data, 0xFF, oinfo->nand.ecc.size); + memset(oob, 0xFF, oinfo->nand.ecc.bytes); + } + + return flip_bits; +} + +/** + * omap_elm_correct_data - corrects page data area in case error reported + * @chip: NAND chip object + * @data: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + * + * Calculated ecc vector reported as zero in case of non-error pages. + * In case of non-zero ecc vector, first filter out erased-pages, and + * then process data via ELM to detect bit-flips. + */ +static int omap_elm_correct_data(struct nand_chip *chip, u_char *data, + u_char *read_ecc, u_char *calc_ecc) +{ + struct gpmc_nand_info *oinfo = chip->priv; + struct nand_ecc_ctrl *ecc = &oinfo->nand.ecc; + int eccsteps = oinfo->nand.ecc.steps; + int i , j, stat = 0; + int eccflag, actual_eccbytes; + struct elm_errorvec err_vec[ERROR_VECTOR_MAX]; + u_char *ecc_vec = calc_ecc; + u_char *spare_ecc = read_ecc; + const u_char *erased_ecc_vec; + u_char *buf; + int bitflip_count; + bool is_error_reported = false; + u32 bit_pos, byte_pos, error_max, pos; + int err; + + switch (oinfo->ecc_mode) { + case OMAP_ECC_BCH8_CODE_HW: + /* omit 14th ECC byte reserved for ROM code compatibility */ + actual_eccbytes = ecc->bytes - 1; + erased_ecc_vec = bch8_vector; + break; + case OMAP_ECC_BCH16_CODE_HW: + actual_eccbytes = ecc->bytes; + erased_ecc_vec = bch16_vector; + break; + default: + dev_err(oinfo->pdev, "invalid driver configuration\n"); + return -EINVAL; + } + + /* Initialize elm error vector to zero */ + memset(err_vec, 0, sizeof(err_vec)); + + for (i = 0; i < eccsteps ; i++) { + eccflag = 0; /* initialize eccflag */ + + /* + * Check any error reported, + * In case of error, non zero ecc reported. + */ + for (j = 0; j < actual_eccbytes; j++) { + if (calc_ecc[j] != 0) { + eccflag = 1; /* non zero ecc, error present */ + break; + } + } + + if (eccflag == 1) { + if (memcmp(calc_ecc, erased_ecc_vec, + actual_eccbytes) == 0) { + /* + * calc_ecc[] matches pattern for ECC(all 0xff) + * so this is definitely an erased-page + */ + } else { + buf = &data[oinfo->nand.ecc.size * i]; + /* + * count number of 0-bits in read_buf. + * This check can be removed once a similar + * check is introduced in generic NAND driver + */ + bitflip_count = erased_sector_bitflips( + buf, read_ecc, oinfo); + if (bitflip_count) { + /* + * number of 0-bits within ECC limits + * So this may be an erased-page + */ + stat += bitflip_count; + } else { + /* + * Too many 0-bits. It may be a + * - programmed-page, OR + * - erased-page with many bit-flips + * So this page requires check by ELM + */ + err_vec[i].error_reported = true; + is_error_reported = true; + } + } + } + + /* Update the ecc vector */ + calc_ecc += ecc->bytes; + read_ecc += ecc->bytes; + } + + /* Check if any error reported */ + if (!is_error_reported) + return stat; + + /* Decode BCH error using ELM module */ + elm_decode_bch_error_page(ecc_vec, err_vec); + + err = 0; + for (i = 0; i < eccsteps; i++) { + if (err_vec[i].error_uncorrectable) { + dev_err(oinfo->pdev, + "uncorrectable bit-flips found\n"); + err = -EBADMSG; + } else if (err_vec[i].error_reported) { + for (j = 0; j < err_vec[i].error_count; j++) { + switch (oinfo->ecc_mode) { + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH16_CODE_HW: + pos = err_vec[i].error_loc[j]; + break; + default: + return -EINVAL; + } + error_max = (ecc->size + actual_eccbytes) * 8; + /* Calculate bit position of error */ + bit_pos = pos % 8; + + /* Calculate byte position of error */ + byte_pos = (error_max - pos - 1) / 8; + + if (pos < error_max) { + if (byte_pos < 512) { + pr_debug("bitflip@dat[%d]=%x\n", + byte_pos, data[byte_pos]); + data[byte_pos] ^= 1 << bit_pos; + } else { + pr_debug("bitflip@oob[%d]=%x\n", + (byte_pos - 512), + spare_ecc[byte_pos - 512]); + spare_ecc[byte_pos - 512] ^= + 1 << bit_pos; + } + } else { + dev_err(oinfo->pdev, + "invalid bit-flip @ %d:%d\n", + byte_pos, bit_pos); + err = -EBADMSG; + } + } + } + + /* Update number of correctable errors */ + stat = max_t(unsigned int, stat, err_vec[i].error_count); + + /* Update page data with sector size */ + data += ecc->size; + spare_ecc += ecc->bytes; + } + + return (err) ? err : stat; +} + +static int gpmc_read_page_hwecc_elm(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + int i; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + for (i = 0; i < chip->ecc.total; i++) + ecc_code[i] = chip->oob_poi[eccpos[i]]; + + eccsteps = chip->ecc.steps; + + for (i = 0; eccsteps; eccsteps--, i++) + __omap_calculate_ecc(mtd, &ecc_calc[i * eccbytes], i); + + return omap_elm_correct_data(chip, buf, ecc_code, ecc_calc); +} + static int gpmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) @@ -732,7 +1023,7 @@ static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, { struct mtd_info *minfo = &oinfo->minfo; struct nand_chip *nand = &oinfo->nand; - int offset; + int offset, err; int i, j; if (nand->options & NAND_BUSWIDTH_16) @@ -807,6 +1098,32 @@ static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, for (i = 2; i < 58; i++) omap_oobinfo.eccpos[j++] = i; break; + case OMAP_ECC_BCH16_CODE_HW: + oinfo->nand.ecc.size = 512; + oinfo->nand.ecc.bytes = 26; + oinfo->nand.ecc.strength = 16; + oinfo->nand.ecc.steps = + minfo->writesize / oinfo->nand.ecc.size; + oinfo->nand.ecc.total = + oinfo->nand.ecc.steps * oinfo->nand.ecc.bytes; + omap_oobinfo.eccbytes = oinfo->nand.ecc.total; + + for (i = 0; i < oinfo->nand.ecc.total; i++) + omap_oobinfo.eccpos[i] = i + 2; + + /* reserved marker already included in ecclayout->eccbytes */ + omap_oobinfo.oobfree->offset = + omap_oobinfo.eccpos[oinfo->nand.ecc.total - 1] + 1; + + err = elm_config(BCH16_ECC, + oinfo->minfo.writesize / nand->ecc.size, + nand->ecc.size, nand->ecc.bytes); + if (err < 0) + return err; + + nand->ecc.read_page = gpmc_read_page_hwecc_elm; + + break; case OMAP_ECC_SOFT: nand->ecc.layout = NULL; nand->ecc.mode = NAND_ECC_SOFT; diff --git a/drivers/mtd/nand/omap_elm.c b/drivers/mtd/nand/omap_elm.c new file mode 100644 index 0000000000..583235fc78 --- /dev/null +++ b/drivers/mtd/nand/omap_elm.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Error Location Module + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#define DRIVER_NAME "omap-elm" + +#include <common.h> +#include <driver.h> +#include <init.h> +#include <platform_data/elm.h> + +#define ELM_SYSCONFIG 0x010 +#define ELM_IRQSTATUS 0x018 +#define ELM_IRQENABLE 0x01c +#define ELM_LOCATION_CONFIG 0x020 +#define ELM_PAGE_CTRL 0x080 +#define ELM_SYNDROME_FRAGMENT_0 0x400 +#define ELM_SYNDROME_FRAGMENT_1 0x404 +#define ELM_SYNDROME_FRAGMENT_2 0x408 +#define ELM_SYNDROME_FRAGMENT_3 0x40c +#define ELM_SYNDROME_FRAGMENT_4 0x410 +#define ELM_SYNDROME_FRAGMENT_5 0x414 +#define ELM_SYNDROME_FRAGMENT_6 0x418 +#define ELM_LOCATION_STATUS 0x800 +#define ELM_ERROR_LOCATION_0 0x880 + +/* ELM Interrupt Status Register */ +#define INTR_STATUS_PAGE_VALID BIT(8) + +/* ELM Interrupt Enable Register */ +#define INTR_EN_PAGE_MASK BIT(8) + +/* ELM Location Configuration Register */ +#define ECC_BCH_LEVEL_MASK 0x3 + +/* ELM syndrome */ +#define ELM_SYNDROME_VALID BIT(16) + +/* ELM_LOCATION_STATUS Register */ +#define ECC_CORRECTABLE_MASK BIT(8) +#define ECC_NB_ERRORS_MASK 0x1f + +/* ELM_ERROR_LOCATION_0-15 Registers */ +#define ECC_ERROR_LOCATION_MASK 0x1fff + +#define ELM_ECC_SIZE 0x7ff + +#define SYNDROME_FRAGMENT_REG_SIZE 0x40 +#define ERROR_LOCATION_SIZE 0x100 + +struct elm_registers { + u32 elm_irqenable; + u32 elm_sysconfig; + u32 elm_location_config; + u32 elm_page_ctrl; + u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX]; +}; + +struct elm_info { + struct device_d *dev; + void __iomem *elm_base; + struct list_head list; + enum bch_ecc bch_type; + struct elm_registers elm_regs; + int ecc_steps; + int ecc_syndrome_size; +}; + +static struct elm_info *ginfo; + +static void elm_write_reg(struct elm_info *info, int offset, u32 val) +{ + writel(val, info->elm_base + offset); +} + +static u32 elm_read_reg(struct elm_info *info, int offset) +{ + return readl(info->elm_base + offset); +} + +/** + * elm_config - Configure ELM module + * @dev: ELM device + * @bch_type: Type of BCH ecc + */ +int elm_config(enum bch_ecc bch_type, int ecc_steps, int ecc_step_size, + int ecc_syndrome_size) +{ + struct elm_info *info = ginfo; + u32 reg_val; + + if (!info) + return -ENODEV; + /* ELM cannot detect ECC errors for chunks > 1KB */ + if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) { + dev_err(info->dev, "unsupported config ecc-size=%d\n", ecc_step_size); + return -EINVAL; + } + /* ELM support 8 error syndrome process */ + if (ecc_steps > ERROR_VECTOR_MAX) { + dev_err(info->dev, "unsupported config ecc-step=%d\n", ecc_steps); + return -EINVAL; + } + + reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16); + elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val); + info->bch_type = bch_type; + info->ecc_steps = ecc_steps; + info->ecc_syndrome_size = ecc_syndrome_size; + + return 0; +} + +/** + * elm_configure_page_mode - Enable/Disable page mode + * @info: elm info + * @index: index number of syndrome fragment vector + * @enable: enable/disable flag for page mode + * + * Enable page mode for syndrome fragment index + */ +static void elm_configure_page_mode(struct elm_info *info, int index, + bool enable) +{ + u32 reg_val; + + reg_val = elm_read_reg(info, ELM_PAGE_CTRL); + if (enable) + reg_val |= BIT(index); /* enable page mode */ + else + reg_val &= ~BIT(index); /* disable page mode */ + + elm_write_reg(info, ELM_PAGE_CTRL, reg_val); +} + +/** + * elm_load_syndrome - Load ELM syndrome reg + * @info: elm info + * @err_vec: elm error vectors + * @ecc: buffer with calculated ecc + * + * Load syndrome fragment registers with calculated ecc in reverse order. + */ +static void elm_load_syndrome(struct elm_info *info, + struct elm_errorvec *err_vec, u8 *ecc) +{ + int i, offset; + u32 val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + elm_configure_page_mode(info, i, true); + offset = ELM_SYNDROME_FRAGMENT_0 + + SYNDROME_FRAGMENT_REG_SIZE * i; + switch (info->bch_type) { + case BCH8_ECC: + /* syndrome fragment 0 = ecc[9-12B] */ + val = cpu_to_be32(*(u32 *) &ecc[9]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[5-8B] */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[5]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 2 = ecc[1-4B] */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[1]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 3 = ecc[0B] */ + offset += 4; + val = ecc[0]; + elm_write_reg(info, offset, val); + break; + case BCH4_ECC: + /* syndrome fragment 0 = ecc[20-52b] bits */ + val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) | + ((ecc[2] & 0xf) << 28); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[0-20b] bits */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12; + elm_write_reg(info, offset, val); + break; + case BCH16_ECC: + val = cpu_to_be32(*(u32 *) &ecc[22]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[18]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[14]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[10]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[6]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[2]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[0]) >> 16; + elm_write_reg(info, offset, val); + break; + default: + pr_err("invalid config bch_type\n"); + } + } + + /* Update ecc pointer with ecc byte size */ + ecc += info->ecc_syndrome_size; + } +} + +/** + * elm_start_processing - start elm syndrome processing + * @info: elm info + * @err_vec: elm error vectors + * + * Set syndrome valid bit for syndrome fragment registers for which + * elm syndrome fragment registers are loaded. This enables elm module + * to start processing syndrome vectors. + */ +static void elm_start_processing(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, offset; + u32 reg_val; + + /* + * Set syndrome vector valid, so that ELM module + * will process it for vectors error is reported + */ + for (i = 0; i < info->ecc_steps; i++) { + if (err_vec[i].error_reported) { + offset = ELM_SYNDROME_FRAGMENT_6 + + SYNDROME_FRAGMENT_REG_SIZE * i; + reg_val = elm_read_reg(info, offset); + reg_val |= ELM_SYNDROME_VALID; + elm_write_reg(info, offset, reg_val); + } + } +} + +/** + * elm_error_correction - locate correctable error position + * @info: elm info + * @err_vec: elm error vectors + * + * On completion of processing by elm module, error location status + * register updated with correctable/uncorrectable error information. + * In case of correctable errors, number of errors located from + * elm location status register & read the positions from + * elm error location register. + */ +static void elm_error_correction(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, j, errors = 0; + int offset; + u32 reg_val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i; + reg_val = elm_read_reg(info, offset); + + /* Check correctable error or not */ + if (reg_val & ECC_CORRECTABLE_MASK) { + offset = ELM_ERROR_LOCATION_0 + + ERROR_LOCATION_SIZE * i; + + /* Read count of correctable errors */ + err_vec[i].error_count = reg_val & + ECC_NB_ERRORS_MASK; + + /* Update the error locations in error vector */ + for (j = 0; j < err_vec[i].error_count; j++) { + + reg_val = elm_read_reg(info, offset); + err_vec[i].error_loc[j] = reg_val & + ECC_ERROR_LOCATION_MASK; + + /* Update error location register */ + offset += 4; + } + + errors += err_vec[i].error_count; + } else { + err_vec[i].error_uncorrectable = true; + } + + /* Clearing interrupts for processed error vectors */ + elm_write_reg(info, ELM_IRQSTATUS, BIT(i)); + + /* Disable page mode */ + elm_configure_page_mode(info, i, false); + } + } +} + +static int elm_poll(struct elm_info *info) +{ + u32 reg_val; + + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + + /* All error vectors processed */ + if (reg_val & INTR_STATUS_PAGE_VALID) { + elm_write_reg(info, ELM_IRQSTATUS, + reg_val & INTR_STATUS_PAGE_VALID); + return 0; + } + + return -EINPROGRESS; +} + +/** + * elm_decode_bch_error_page - Locate error position + * @dev: device pointer + * @ecc_calc: calculated ECC bytes from GPMC + * @err_vec: elm error vectors + * + * Called with one or more error reported vectors & vectors with + * error reported is updated in err_vec[].error_reported + */ +int elm_decode_bch_error_page(u8 *ecc_calc, struct elm_errorvec *err_vec) +{ + struct elm_info *info = ginfo; + u32 reg_val; + uint64_t start; + + if (!info) + return -ENODEV; + + /* Enable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID); + elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK); + + /* Load valid ecc byte to syndrome fragment register */ + elm_load_syndrome(info, err_vec, ecc_calc); + + /* Enable syndrome processing for which syndrome fragment is updated */ + elm_start_processing(info, err_vec); + + /* Wait for ELM module to finish locating error correction */ + start = get_time_ns(); + while (elm_poll(info) < 0) { + if (is_timeout(start, SECOND)) + return -ETIMEDOUT; + } + + /* Disable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQENABLE); + elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK); + elm_error_correction(info, err_vec); + + return 0; +} + +static int elm_probe(struct device_d *dev) +{ + struct resource *res; + struct elm_info *info; + + info = xzalloc(sizeof(*info)); + + info->dev = dev; + + res = dev_request_mem_resource(dev, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + info->elm_base = IOMEM(res->start); + + dev->priv = info; + + ginfo = info; + + return 0; +} + +static struct of_device_id elm_compatible[] = { + { + .compatible = "ti,am3352-elm", + }, { + /* sentinel */ + } +}; + +static struct driver_d omap_elm_driver = { + .name = "omap-elm", + .probe = elm_probe, + .of_compatible = DRV_OF_COMPAT(elm_compatible) +}; +coredevice_platform_driver(omap_elm_driver); |