[PATCH v4 1/2] MTD: atmel_nand: Update driver to support Programmable HW ECC controller
Jean-Christophe PLAGNIOL-VILLARD
plagnioj at jcrosoft.com
Fri Apr 20 04:25:33 EDT 2012
On 15:26 Fri 20 Apr , Hong Xu wrote:
> The Programmable Hardware ECC (PMECC) controller is a programmable binary
> BCH(Bose, Chaudhuri and Hocquenghem) encoder and decoder. This controller
> can be used to support both SLC and MLC NAND Flash devices. It supports to
> generate ECC to correct 2, 4, 8, 12 or 24 bits of error per sector of data.
>
> To use this driver, the user needs to pass in the correction capability and
> the sector size.
>
> This driver has been tested on AT91SAM9X5-EK and AT91SAM9N12-EK with JFFS2,
> YAFFS2, UBIFS and mtd-utils.
>
> Signed-off-by: Hong Xu <hong.xu at atmel.com>
what is the status on the other soc?
> ---
> Changes since v3,
> Rebased on AT91 Nand fixes and DT
>
> drivers/mtd/nand/atmel_nand.c | 955 ++++++++++++++++++++++++++++++++++---
> drivers/mtd/nand/atmel_nand_ecc.h | 123 ++++-
> 2 files changed, 998 insertions(+), 80 deletions(-)
>
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index 2165576..7d494c8 100644
> --- a/drivers/mtd/nand/atmel_nand.c
> +++ b/drivers/mtd/nand/atmel_nand.c
> @@ -15,6 +15,8 @@
> * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
> * (C) Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
> *
> + * Add Programmable Hardware ECC support for various AT91 SoC
> + * (C) Copyright 2012 ATMEL, Hong Xu
the comment is wrong the hw ecc is already implemented
> *
> * This program is free software; you can redistribute it and/or modify
> * it under the terms of the GNU General Public License version 2 as
> @@ -42,19 +44,50 @@
>
> #include <mach/cpu.h>
>
> +/* Hardware ECC registers */
> +#include "atmel_nand_ecc.h"
> +
> static int use_dma = 1;
> module_param(use_dma, int, 0);
>
> static int on_flash_bbt = 0;
> module_param(on_flash_bbt, int, 0);
>
> -/* Register access macros */
> -#define ecc_readl(add, reg) \
> - __raw_readl(add + ATMEL_ECC_##reg)
> -#define ecc_writel(add, reg, value) \
> - __raw_writel((value), add + ATMEL_ECC_##reg)
> +struct atmel_nand_host {
> + struct nand_chip nand_chip;
> + struct mtd_info mtd;
> + void __iomem *io_base;
> + dma_addr_t io_phys;
> + struct atmel_nand_data board;
> + struct device *dev;
> + void __iomem *ecc;
>
> -#include "atmel_nand_ecc.h" /* Hardware ECC registers */
> + struct completion comp;
> + struct dma_chan *dma_chan;
> +
> + void __iomem *pmerrloc_base;
> + void __iomem *rom_base;
> +
> + int cap; /* Correcting capability */
> + int ecc_bytes_per_sector;
> + int degree; /*Degree of remainders,GF(2**degree)*/
> + int cw_len; /*Length of codeword*/
> + int sector_number;
> +
> + /* lookup table for alpha_to and index_of */
> + void __iomem *alpha_to;
> + void __iomem *index_of;
> +
> + int16_t partial_syn[2 * AT_NB_ERROR_MAX + 1];
> + int16_t si[2 * AT_NB_ERROR_MAX + 1];
> +
> + /* Sigma table */
> + int16_t smu[AT_NB_ERROR_MAX + 2][2 * AT_NB_ERROR_MAX + 1];
> + /* polynomal order */
> + int16_t lmu[AT_NB_ERROR_MAX + 1];
> +
> + uint8_t ecc_table[AT_MAX_ECC_BYTES * AT_MAX_NB_SECTOR];
> +};
>
> /* oob layout for large page size
> * bad block info is on bytes 0 and 1
> @@ -82,17 +115,23 @@ static struct nand_ecclayout atmel_oobinfo_small = {
> },
> };
>
> -struct atmel_nand_host {
> - struct nand_chip nand_chip;
> - struct mtd_info mtd;
> - void __iomem *io_base;
> - dma_addr_t io_phys;
> - struct atmel_nand_data board;
> - struct device *dev;
> - void __iomem *ecc;
> +static struct nand_ecclayout atmel_pmecc_oobinfo;
>
> - struct completion comp;
> - struct dma_chan *dma_chan;
> +/*
> + * Number of ECC bytes per sector is determined by both sector size
> + * and correction capability
> + *
> + * Correction Capability Sector_512_bytes Sector_1024_bytes
> + * ===================== ================ =================
> + * 2-bits 4-bytes 4-bytes
> + * 4-bits 7-bytes 7-bytes
> + * 8-bits 13-bytes 14-bytes
> + * 12-bits 20-bytes 21-bytes
> + * 24-bits 39-bytes 42-bytes
> + *
> + */
> +static const int ecc_bytes_table[5][2] = {
> + {4, 4}, {7, 7}, {13, 14}, {20, 21}, {39, 42}
> };
>
> static int cpu_has_dma(void)
> @@ -288,6 +327,718 @@ static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
> }
>
> /*
> + * Return number of ecc bytes per sector according to sector size and
> + * correction capability
> + */
> +static int pmecc_get_ecc_bytes(int cap, int sector_size)
> +{
> + int i, j;
> +
> + switch (cap) {
> + case 2:
> + i = 0;
> + break;
> + case 4:
> + i = 1;
> + break;
> + case 8:
> + i = 2;
> + break;
> + case 12:
> + i = 3;
> + break;
> + case 24:
> + i = 4;
> + break;
> + default:
> + BUG();
> + }
> +
> + switch (sector_size) {
> + case 512:
> + j = 0;
> + break;
> + case 1024:
> + j = 1;
> + break;
> + default:
> + BUG();
> + }
> +
> + return ecc_bytes_table[i][j];
> +}
> +
> +static int cpu_has_pmecc(void)
> +{
> + if (cpu_is_at91sam9x5())
> + return 1;
this must be pass via DT do not use cpu_is
> +
> + return 0;
> +}
> +
> +static int pmecc_get_clkctrl(void)
> +{
> + /* See datasheet about PMECC Clock Control Register */
> + if (cpu_is_at91sam9x5())
> + return 2;
> +
> + return 0;
> +}
> +
> +static void pmecc_config_ecc_layout(struct nand_ecclayout *layout, int oobsize,
> + int ecc_len)
> +{
> + int i;
> +
> + layout->eccbytes = ecc_len;
> +
> + /* ECC will occupy the last ecc_len bytes continuously */
> + for (i = 0; i < ecc_len; i++)
> + layout->eccpos[i] = oobsize - ecc_len + i;
> +
> + layout->oobfree[0].offset = 0;
> + layout->oobfree[0].length = oobsize - ecc_len;
> +}
> +
> +static void __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
> +{
> + void __iomem *p;
> +
> + switch (host->board.sector_size) {
> + case 512:
> + p = host->rom_base + AT_PMECC_LOOKUP_TABLE_OFFSET_512 +
> + AT_PMECC_LOOKUP_TABLE_SIZE_512;
> + break;
> + case 1024:
> + p = host->rom_base + AT_PMECC_LOOKUP_TABLE_OFFSET_1024 +
> + AT_PMECC_LOOKUP_TABLE_SIZE_1024;
> + break;
> + default:
> + BUG();
> + }
> +
> + return p;
> +}
> +
> +static void __iomem *pmecc_get_index_of(struct atmel_nand_host *host)
> +{
> + void __iomem *p;
> +
> + switch (host->board.sector_size) {
> + case 512:
> + p = host->rom_base + AT_PMECC_LOOKUP_TABLE_OFFSET_512;
> + break;
> + case 1024:
> + p = host->rom_base + AT_PMECC_LOOKUP_TABLE_OFFSET_1024;
> + break;
> + default:
> + BUG();
> + }
> +
> + return p;
> +}
> +
> +static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector)
> +{
> + int i;
> + uint32_t value;
> + struct nand_chip *nand_chip = mtd->priv;
> + struct atmel_nand_host *host = nand_chip->priv;
> +
> + /* Fill odd syndromes */
> + for (i = 0; i < host->cap; i++) {
> + value = pmecc_readl_rem(host->ecc, sector, i / 2);
> + value = (i & 1) ? (value & 0xffff0000) >> 16 : value & 0xffff;
> + host->partial_syn[(2 * i) + 1] = (int16_t)value;
> + }
> +}
> +
> +static void pmecc_substitute(struct mtd_info *mtd)
> +{
> + int16_t *si;
> + int i, j;
> + struct nand_chip *nand_chip = mtd->priv;
> + struct atmel_nand_host *host = nand_chip->priv;
> + int16_t __iomem *alpha_to = host->alpha_to;
> + int16_t __iomem *index_of = host->index_of;
> + int16_t *partial_syn = host->partial_syn;
> +
> + /* si[] is a table that holds the current syndrome value,
> + * an element of that table belongs to the field
> + */
> + si = host->si;
> +
> + for (i = 1; i < 2 * AT_NB_ERROR_MAX; i++)
> + si[i] = 0;
> +
> + /* Computation 2t syndromes based on S(x) */
> + /* Odd syndromes */
> + for (i = 1; i <= 2 * host->cap - 1; i = i + 2) {
i += 2
< without the -1
> + si[i] = 0;
> + for (j = 0; j < host->degree; j++) {
> + if (partial_syn[i] & ((unsigned short)0x1 << j))
> + si[i] = __raw_readw(alpha_to + i * j) ^ si[i];
use relaxed read/write
> + }
> + }
> + /* Even syndrome = (Odd syndrome) ** 2 */
> + for (i = 2; i <= 2 * host->cap; i = i + 2) {
> + j = i / 2;
> + if (si[j] == 0)
> + si[i] = 0;
> + else {
> + int16_t tmp;
please check your patch with checkpath
> + tmp = __raw_readw(index_of + si[j]);
> + tmp = (tmp * 2) % host->cw_len;
> + si[i] = __raw_readw(alpha_to + tmp);
> + }
> + }
> +
> + return;
> +}
> +
> +static void pmecc_get_sigma(struct mtd_info *mtd)
> +{
> + int i, j, k;
> + struct nand_chip *nand_chip = mtd->priv;
> + struct atmel_nand_host *host = nand_chip->priv;
> +
> + uint32_t dmu_0_count, tmp;
> + int16_t *lmu = host->lmu;
> + int16_t *si = host->si;
> + int16_t cap = host->cap;
> + int16_t __iomem *index_of = host->index_of;
> + int16_t __iomem *alpha_to = host->alpha_to;
> +
> + /* mu */
> + int mu[AT_NB_ERROR_MAX + 1];
> +
> + /* discrepancy */
> + int dmu[AT_NB_ERROR_MAX + 1];
> +
> + /* delta order */
> + int delta[AT_NB_ERROR_MAX + 1];
allocate these
> +
> + /* index of largest delta */
> + int ro;
> + int largest;
> + int diff;
> +
> + dmu_0_count = 0;
> +
> + /* First Row */
> +
> + /* Mu */
> + mu[0] = -1;
> +
> + for (i = 0; i < 2 * AT_NB_ERROR_MAX + 1; i++)
> + host->smu[0][i] = 0;
use memeset
Best Regards,
J.
More information about the linux-mtd
mailing list