All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-28  8:21 ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-mtd, linux-arm-kernel, dedekind1
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, Josh Wu, ivan.djelic,
	computersforpeace, plagnioj

Those patches is based on the l2-mtd.git three.

Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
which includes the modification according to Richard Genoud's review suggestion.

Changes since v11,
	add documentation for atmel nand reg parameter and one example for PMECC support.
	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
	fix the bug: no correction when ecc data has bit flipped.
	add clearer debug information for data correction.

Josh Wu (2):
  MTD: at91: add dt parameters for Atmel PMECC
  MTD: at91: atmel_nand: Update driver to support Programmable Multibit
    ECC controller

 .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
 drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
 drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
 3 files changed, 951 insertions(+), 4 deletions(-)

-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-28  8:21 ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-arm-kernel

Those patches is based on the l2-mtd.git three.

Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
which includes the modification according to Richard Genoud's review suggestion.

Changes since v11,
	add documentation for atmel nand reg parameter and one example for PMECC support.
	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
	fix the bug: no correction when ecc data has bit flipped.
	add clearer debug information for data correction.

Josh Wu (2):
  MTD: at91: add dt parameters for Atmel PMECC
  MTD: at91: atmel_nand: Update driver to support Programmable Multibit
    ECC controller

 .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
 drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
 drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
 3 files changed, 951 insertions(+), 4 deletions(-)

-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
  2012-06-28  8:21 ` Josh Wu
@ 2012-06-28  8:21   ` Josh Wu
  -1 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-mtd, linux-arm-kernel, dedekind1
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, Josh Wu, ivan.djelic,
	computersforpeace, plagnioj

Add DT support for PMECC parameters.

Signed-off-by: Josh Wu <josh.wu@atmel.com>
---
changes:
since v11
   1. update the documentation for reg parameter
   2. add one example for PMECC support.

 .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
 drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
index a200695..d555421 100644
--- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
+++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
@@ -3,7 +3,9 @@ Atmel NAND flash
 Required properties:
 - compatible : "atmel,at91rm9200-nand".
 - reg : should specify localbus address and size used for the chip,
-	and if availlable the ECC.
+	and hardware ECC controller if available.
+	If the hardware ECC is PMECC, it should contain address and size for
+	PMECC, PMECC Error Location controller and ROM which has lookup tables.
 - atmel,nand-addr-offset : offset for the address latch.
 - atmel,nand-cmd-offset : offset for the command latch.
 - #address-cells, #size-cells : Must be present if the device has sub-nodes
@@ -16,6 +18,15 @@ Optional properties:
 - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
   Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
   "soft_bch".
+- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
+  Only supported by at91sam9x5 or later sam9 product.
+- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
+  Controller. Supported values are: 2, 4, 8, 12, 24.
+- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
+  are: 512, 1024.
+- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
+  for different sector size. First one is for sector size 512, the next is for
+  sector size 1024.
 - nand-bus-width : 8 or 16 bus width if not present 8
 - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
 
@@ -39,3 +50,30 @@ nand0: nand@40000000,0 {
 		...
 	};
 };
+
+/* for PMECC supported chips */
+nand0: nand@40000000 {
+	compatible = "atmel,at91rm9200-nand";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	reg = < 0x40000000 0x10000000	/* bus addr & size */
+		0xffffe000 0x00000600	/* PMECC addr & size */
+		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
+		0x00100000 0x00100000	/* ROM addr & size */
+		>;
+	atmel,nand-addr-offset = <21>;	/* ale */
+	atmel,nand-cmd-offset = <22>;	/* cle */
+	nand-on-flash-bbt;
+	nand-ecc-mode = "hw";
+	atmel,has-pmecc;	/* enable PMECC */
+	atmel,pmecc-cap = <2>;
+	atmel,pmecc-sector-size = <512>;
+	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
+	gpios = <&pioD 5 0	/* rdy */
+		 &pioD 4 0	/* nce */
+		 0		/* cd */
+		>;
+	partition@0 {
+		...
+	};
+};
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index 7a41a04..b97ad9f 100644
--- a/drivers/mtd/nand/atmel_nand.c
+++ b/drivers/mtd/nand/atmel_nand.c
@@ -93,6 +93,11 @@ struct atmel_nand_host {
 
 	struct completion	comp;
 	struct dma_chan		*dma_chan;
+
+	bool			has_pmecc;
+	u8			pmecc_corr_cap;
+	u16			pmecc_sector_size;
+	u32			pmecc_lookup_table_offset;
 };
 
 static int cpu_has_dma(void)
@@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
 static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
 					 struct device_node *np)
 {
-	u32 val;
+	u32 val, table_offset;
+	u32 offset[2];
 	int ecc_mode;
 	struct atmel_nand_data *board = &host->board;
 	enum of_gpio_flags flags;
@@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
 	board->enable_pin = of_get_gpio(np, 1);
 	board->det_pin = of_get_gpio(np, 2);
 
+	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
+
+	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
+		return 0;	/* Not using PMECC */
+
+	/* use PMECC, get correction capability, sector size and lookup
+	 * table offset.
+	 */
+	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
+		dev_err(host->dev, "Cannot decide PMECC Capability\n");
+		return -EINVAL;
+	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
+	    (val != 24)) {
+		dev_err(host->dev,
+			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
+			val);
+		return -EINVAL;
+	}
+	host->pmecc_corr_cap = (u8)val;
+
+	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
+		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
+		return -EINVAL;
+	} else if ((val != 512) && (val != 1024)) {
+		dev_err(host->dev,
+			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
+			val);
+		return -EINVAL;
+	}
+	host->pmecc_sector_size = (u16)val;
+
+	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
+			offset, 2) != 0) {
+		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
+		return -EINVAL;
+	}
+	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
+
+	if (!table_offset) {
+		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
+		return -EINVAL;
+	}
+	host->pmecc_lookup_table_offset = table_offset;
+
 	return 0;
 }
 #else
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
@ 2012-06-28  8:21   ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-arm-kernel

Add DT support for PMECC parameters.

Signed-off-by: Josh Wu <josh.wu@atmel.com>
---
changes:
since v11
   1. update the documentation for reg parameter
   2. add one example for PMECC support.

 .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
 drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
index a200695..d555421 100644
--- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
+++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
@@ -3,7 +3,9 @@ Atmel NAND flash
 Required properties:
 - compatible : "atmel,at91rm9200-nand".
 - reg : should specify localbus address and size used for the chip,
-	and if availlable the ECC.
+	and hardware ECC controller if available.
+	If the hardware ECC is PMECC, it should contain address and size for
+	PMECC, PMECC Error Location controller and ROM which has lookup tables.
 - atmel,nand-addr-offset : offset for the address latch.
 - atmel,nand-cmd-offset : offset for the command latch.
 - #address-cells, #size-cells : Must be present if the device has sub-nodes
@@ -16,6 +18,15 @@ Optional properties:
 - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
   Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
   "soft_bch".
+- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
+  Only supported by at91sam9x5 or later sam9 product.
+- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
+  Controller. Supported values are: 2, 4, 8, 12, 24.
+- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
+  are: 512, 1024.
+- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
+  for different sector size. First one is for sector size 512, the next is for
+  sector size 1024.
 - nand-bus-width : 8 or 16 bus width if not present 8
 - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
 
@@ -39,3 +50,30 @@ nand0: nand at 40000000,0 {
 		...
 	};
 };
+
+/* for PMECC supported chips */
+nand0: nand at 40000000 {
+	compatible = "atmel,at91rm9200-nand";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	reg = < 0x40000000 0x10000000	/* bus addr & size */
+		0xffffe000 0x00000600	/* PMECC addr & size */
+		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
+		0x00100000 0x00100000	/* ROM addr & size */
+		>;
+	atmel,nand-addr-offset = <21>;	/* ale */
+	atmel,nand-cmd-offset = <22>;	/* cle */
+	nand-on-flash-bbt;
+	nand-ecc-mode = "hw";
+	atmel,has-pmecc;	/* enable PMECC */
+	atmel,pmecc-cap = <2>;
+	atmel,pmecc-sector-size = <512>;
+	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
+	gpios = <&pioD 5 0	/* rdy */
+		 &pioD 4 0	/* nce */
+		 0		/* cd */
+		>;
+	partition at 0 {
+		...
+	};
+};
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index 7a41a04..b97ad9f 100644
--- a/drivers/mtd/nand/atmel_nand.c
+++ b/drivers/mtd/nand/atmel_nand.c
@@ -93,6 +93,11 @@ struct atmel_nand_host {
 
 	struct completion	comp;
 	struct dma_chan		*dma_chan;
+
+	bool			has_pmecc;
+	u8			pmecc_corr_cap;
+	u16			pmecc_sector_size;
+	u32			pmecc_lookup_table_offset;
 };
 
 static int cpu_has_dma(void)
@@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
 static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
 					 struct device_node *np)
 {
-	u32 val;
+	u32 val, table_offset;
+	u32 offset[2];
 	int ecc_mode;
 	struct atmel_nand_data *board = &host->board;
 	enum of_gpio_flags flags;
@@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
 	board->enable_pin = of_get_gpio(np, 1);
 	board->det_pin = of_get_gpio(np, 2);
 
+	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
+
+	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
+		return 0;	/* Not using PMECC */
+
+	/* use PMECC, get correction capability, sector size and lookup
+	 * table offset.
+	 */
+	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
+		dev_err(host->dev, "Cannot decide PMECC Capability\n");
+		return -EINVAL;
+	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
+	    (val != 24)) {
+		dev_err(host->dev,
+			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
+			val);
+		return -EINVAL;
+	}
+	host->pmecc_corr_cap = (u8)val;
+
+	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
+		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
+		return -EINVAL;
+	} else if ((val != 512) && (val != 1024)) {
+		dev_err(host->dev,
+			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
+			val);
+		return -EINVAL;
+	}
+	host->pmecc_sector_size = (u16)val;
+
+	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
+			offset, 2) != 0) {
+		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
+		return -EINVAL;
+	}
+	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
+
+	if (!table_offset) {
+		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
+		return -EINVAL;
+	}
+	host->pmecc_lookup_table_offset = table_offset;
+
 	return 0;
 }
 #else
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v12 2/2] MTD: at91: atmel_nand: Update driver to support Programmable Multibit ECC controller
  2012-06-28  8:21 ` Josh Wu
@ 2012-06-28  8:21   ` Josh Wu
  -1 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-mtd, linux-arm-kernel, dedekind1
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, Josh Wu, ivan.djelic,
	computersforpeace, plagnioj

The Programmable Multibit 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 PMECC in this driver, the user needs to set the address and size of
PMECC, PMECC error location controllers and ROM. And also needs to pass the
correction capability, the sector size and ROM lookup table offsets via dt.

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@atmel.com>
Signed-off-by: Josh Wu <josh.wu@atmel.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
---
since v11
   1. fix bug: not report error if fail to get resources for PMECC ERRLOC or ROM.
   2. fix bug: no correction when ecc data has bit flipped.
   3. add clearer debug information for data correction.

since v10
   1. atmel_nand_pmecc_write_page() will return -EIO when time out to read the pmecc status register.
   2. increase the time-out duration to 100ms, which has more toleration.
   3. add oob_required argument for pmecc read/write functions to align with v3.5-rc4.

 drivers/mtd/nand/atmel_nand.c     |  749 ++++++++++++++++++++++++++++++++++++-
 drivers/mtd/nand/atmel_nand_ecc.h |  114 +++++-
 2 files changed, 861 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index b97ad9f..328b4d9 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 Multibit ECC support for various AT91 SoC
+ *     (C) Copyright 2012 ATMEL, Hong Xu
  *
  * 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
@@ -98,8 +100,31 @@ struct atmel_nand_host {
 	u8			pmecc_corr_cap;
 	u16			pmecc_sector_size;
 	u32			pmecc_lookup_table_offset;
+
+	int			pmecc_bytes_per_sector;
+	int			pmecc_sector_number;
+	int			pmecc_degree;	/* Degree of remainders */
+	int			pmecc_cw_len;	/* Length of codeword */
+
+	void __iomem		*pmerrloc_base;
+	void __iomem		*pmecc_rom_base;
+
+	/* lookup table for alpha_to and index_of */
+	void __iomem		*pmecc_alpha_to;
+	void __iomem		*pmecc_index_of;
+
+	/* data for pmecc computation */
+	int16_t			*pmecc_partial_syn;
+	int16_t			*pmecc_si;
+	int16_t			*pmecc_smu;	/* Sigma table */
+	int16_t			*pmecc_lmu;	/* polynomal order */
+	int			*pmecc_mu;
+	int			*pmecc_dmu;
+	int			*pmecc_delta;
 };
 
+static struct nand_ecclayout atmel_pmecc_oobinfo;
+
 static int cpu_has_dma(void)
 {
 	return cpu_is_at91sam9rl() || cpu_is_at91sam9g45();
@@ -293,6 +318,705 @@ 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
+ *
+ * Following table shows what at91 PMECC supported:
+ * 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 int __devinit pmecc_get_ecc_bytes(int cap, int sector_size)
+{
+	int m = 12 + sector_size / 512;
+	return (m * cap + 7) / 8;
+}
+
+static void __devinit 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 = 2;
+	layout->oobfree[0].length =
+		oobsize - ecc_len - layout->oobfree[0].offset;
+}
+
+static void __devinit __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
+{
+	int table_size;
+
+	table_size = host->pmecc_sector_size == 512 ?
+		PMECC_LOOKUP_TABLE_SIZE_512 : PMECC_LOOKUP_TABLE_SIZE_1024;
+
+	return host->pmecc_rom_base + host->pmecc_lookup_table_offset +
+			table_size * sizeof(int16_t);
+}
+
+static void pmecc_data_free(struct atmel_nand_host *host)
+{
+	kfree(host->pmecc_partial_syn);
+	kfree(host->pmecc_si);
+	kfree(host->pmecc_lmu);
+	kfree(host->pmecc_smu);
+	kfree(host->pmecc_mu);
+	kfree(host->pmecc_dmu);
+	kfree(host->pmecc_delta);
+}
+
+static int __devinit pmecc_data_alloc(struct atmel_nand_host *host)
+{
+	const int cap = host->pmecc_corr_cap;
+
+	host->pmecc_partial_syn = kzalloc((2 * cap + 1) * sizeof(int16_t),
+					GFP_KERNEL);
+	host->pmecc_si = kzalloc((2 * cap + 1) * sizeof(int16_t), GFP_KERNEL);
+	host->pmecc_lmu = kzalloc((cap + 1) * sizeof(int16_t), GFP_KERNEL);
+	host->pmecc_smu = kzalloc((cap + 2) * (2 * cap + 1) * sizeof(int16_t),
+					GFP_KERNEL);
+	host->pmecc_mu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+	host->pmecc_dmu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+	host->pmecc_delta = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+
+	if (host->pmecc_partial_syn &&
+			host->pmecc_si &&
+			host->pmecc_lmu &&
+			host->pmecc_smu &&
+			host->pmecc_mu &&
+			host->pmecc_dmu &&
+			host->pmecc_delta)
+		return 0;
+
+	/* error happened */
+	pmecc_data_free(host);
+	return -ENOMEM;
+}
+
+static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i;
+	uint32_t value;
+
+	/* Fill odd syndromes */
+	for (i = 0; i < host->pmecc_corr_cap; i++) {
+		value = pmecc_readl_rem_relaxed(host->ecc, sector, i / 2);
+		if (i & 1)
+			value >>= 16;
+		value &= 0xffff;
+		host->pmecc_partial_syn[(2 * i) + 1] = (int16_t)value;
+	}
+}
+
+static void pmecc_substitute(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int16_t __iomem *alpha_to = host->pmecc_alpha_to;
+	int16_t __iomem *index_of = host->pmecc_index_of;
+	int16_t *partial_syn = host->pmecc_partial_syn;
+	const int cap = host->pmecc_corr_cap;
+	int16_t *si;
+	int i, j;
+
+	/* si[] is a table that holds the current syndrome value,
+	 * an element of that table belongs to the field
+	 */
+	si = host->pmecc_si;
+
+	memset(&si[1], 0, sizeof(int16_t) * (2 * cap - 1));
+
+	/* Computation 2t syndromes based on S(x) */
+	/* Odd syndromes */
+	for (i = 1; i < 2 * cap; i += 2) {
+		for (j = 0; j < host->pmecc_degree; j++) {
+			if (partial_syn[i] & ((unsigned short)0x1 << j))
+				si[i] = readw_relaxed(alpha_to + i * j) ^ si[i];
+		}
+	}
+	/* Even syndrome = (Odd syndrome) ** 2 */
+	for (i = 2, j = 1; j <= cap; i = ++j << 1) {
+		if (si[j] == 0) {
+			si[i] = 0;
+		} else {
+			int16_t tmp;
+
+			tmp = readw_relaxed(index_of + si[j]);
+			tmp = (tmp * 2) % host->pmecc_cw_len;
+			si[i] = readw_relaxed(alpha_to + tmp);
+		}
+	}
+
+	return;
+}
+
+static void pmecc_get_sigma(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+
+	int16_t *lmu = host->pmecc_lmu;
+	int16_t *si = host->pmecc_si;
+	int *mu = host->pmecc_mu;
+	int *dmu = host->pmecc_dmu;	/* Discrepancy */
+	int *delta = host->pmecc_delta; /* Delta order */
+	int cw_len = host->pmecc_cw_len;
+	const int16_t cap = host->pmecc_corr_cap;
+	int16_t __iomem	*index_of = host->pmecc_index_of;
+	int16_t __iomem	*alpha_to = host->pmecc_alpha_to;
+	int i, j, k;
+	uint32_t dmu_0_count, tmp;
+	int16_t (*smu)[2 * cap + 1];
+
+	/* index of largest delta */
+	int ro;
+	int largest;
+	int diff;
+
+	dmu_0_count = 0;
+	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
+
+	/* First Row */
+
+	/* Mu */
+	mu[0] = -1;
+
+	memset(&smu[0][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+	smu[0][0] = 1;
+
+	/* discrepancy set to 1 */
+	dmu[0] = 1;
+	/* polynom order set to 0 */
+	lmu[0] = 0;
+	delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
+
+	/* Second Row */
+
+	/* Mu */
+	mu[1] = 0;
+	/* Sigma(x) set to 1 */
+	memset(&smu[1][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+	smu[1][0] = 1;
+
+	/* discrepancy set to S1 */
+	dmu[1] = si[1];
+
+	/* polynom order set to 0 */
+	lmu[1] = 0;
+
+	delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
+
+	/* Init the Sigma(x) last row */
+	memset(&smu[cap + 1][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+
+	for (i = 1; i <= cap; i++) {
+		mu[i + 1] = i << 1;
+		/* Begin Computing Sigma (Mu+1) and L(mu) */
+		/* check if discrepancy is set to 0 */
+		if (dmu[i] == 0) {
+			dmu_0_count++;
+
+			tmp = ((cap - (lmu[i] >> 1) - 1) / 2);
+			if ((cap - (lmu[i] >> 1) - 1) & 0x1)
+				tmp += 2;
+			else
+				tmp += 1;
+
+			if (dmu_0_count == tmp) {
+				for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
+					smu[cap + 1][j] = smu[i][j];
+				lmu[cap + 1] = lmu[i];
+				return;
+			}
+
+			/* copy polynom */
+			for (j = 0; j <= lmu[i] >> 1; j++)
+				smu[i + 1][j] = smu[i][j];
+
+			/* copy previous polynom order to the next */
+			lmu[i + 1] = lmu[i];
+		} else {
+			ro = 0;
+			largest = -1;
+			/* find largest delta with dmu != 0 */
+			for (j = 0; j < i; j++) {
+				if ((dmu[j]) && (delta[j] > largest)) {
+					largest = delta[j];
+					ro = j;
+				}
+			}
+
+			/* compute difference */
+			diff = (mu[i] - mu[ro]);
+
+			/* Compute degree of the new smu polynomial */
+			if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
+				lmu[i + 1] = lmu[i];
+			else
+				lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
+
+			/* Init smu[i+1] with 0 */
+			for (k = 0; k < (2 * cap + 1); k++)
+				smu[i + 1][k] = 0;
+
+			/* Compute smu[i+1] */
+			for (k = 0; k <= lmu[ro] >> 1; k++) {
+				int16_t a, b, c;
+
+				if (!(smu[ro][k] && dmu[i]))
+					continue;
+				a = readw_relaxed(index_of + dmu[i]);
+				b = readw_relaxed(index_of + dmu[ro]);
+				c = readw_relaxed(index_of + smu[ro][k]);
+				tmp = a + (cw_len - b) + c;
+				a = readw_relaxed(alpha_to + tmp % cw_len);
+				smu[i + 1][k + diff] = a;
+			}
+
+			for (k = 0; k <= lmu[i] >> 1; k++)
+				smu[i + 1][k] ^= smu[i][k];
+		}
+
+		/* End Computing Sigma (Mu+1) and L(mu) */
+		/* In either case compute delta */
+		delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
+
+		/* Do not compute discrepancy for the last iteration */
+		if (i >= cap)
+			continue;
+
+		for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
+			tmp = 2 * (i - 1);
+			if (k == 0) {
+				dmu[i + 1] = si[tmp + 3];
+			} else if (smu[i + 1][k] && si[tmp + 3 - k]) {
+				int16_t a, b, c;
+				a = readw_relaxed(index_of + smu[i + 1][k]);
+				b = si[2 * (i - 1) + 3 - k];
+				c = readw_relaxed(index_of + b);
+				tmp = a + c;
+				tmp %= cw_len;
+				dmu[i + 1] = readw_relaxed(alpha_to + tmp) ^
+					dmu[i + 1];
+			}
+		}
+	}
+
+	return;
+}
+
+static int pmecc_err_location(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	unsigned long end_time;
+	const int cap = host->pmecc_corr_cap;
+	int sector_size = host->pmecc_sector_size;
+	int err_nbr = 0;	/* number of error */
+	int roots_nbr;		/* number of roots */
+	int i;
+	uint32_t val;
+	int16_t (*smu)[2 * cap + 1];
+
+	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
+
+	pmerrloc_writel(host->pmerrloc_base, ELDIS, PMERRLOC_DISABLE);
+
+	for (i = 0; i <= host->pmecc_lmu[cap + 1] >> 1; i++) {
+		pmerrloc_writel_sigma_relaxed(host->pmerrloc_base, i,
+				      smu[cap + 1][i]);
+		err_nbr++;
+	}
+
+	val = (err_nbr - 1) << 16;
+	if (sector_size == 1024)
+		val |= 1;
+
+	pmerrloc_writel(host->pmerrloc_base, ELCFG, val);
+	pmerrloc_writel(host->pmerrloc_base, ELEN,
+			sector_size * 8 + host->pmecc_degree * cap);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while (!(pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
+		 & PMERRLOC_CALC_DONE)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to calculate error location.\n");
+			return -1;
+		}
+		cpu_relax();
+	}
+
+	roots_nbr = (pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
+		& PMERRLOC_ERR_NUM_MASK) >> 8;
+	/* Number of roots == degree of smu hence <= cap */
+	if (roots_nbr == host->pmecc_lmu[cap + 1] >> 1)
+		return err_nbr - 1;
+
+	/* Number of roots does not match the degree of smu
+	 * unable to correct error */
+	return -1;
+}
+
+static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
+		int sector_num, int extra_bytes, int err_nbr)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i = 0;
+	int byte_pos, bit_pos, sector_size, ecc_size, pos;
+	uint32_t tmp;
+	uint8_t err_byte;
+
+	sector_size = host->pmecc_sector_size;
+	ecc_size = nand_chip->ecc.bytes;
+
+	while (err_nbr) {
+		tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1;
+		byte_pos = tmp / 8;
+		bit_pos  = tmp % 8;
+
+		if (byte_pos >= (sector_size + extra_bytes))
+			BUG();	/* should never happen */
+
+		if (byte_pos < sector_size) {
+			err_byte = *(buf + byte_pos);
+			*(buf + byte_pos) ^= (1 << bit_pos);
+
+			pos = sector_num * host->pmecc_sector_size + byte_pos;
+			dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+				pos, bit_pos, err_byte, *(buf + byte_pos));
+		} else {
+			/* Bit flip in OOB area */
+			tmp = sector_num * host->pmecc_bytes_per_sector
+					+ (byte_pos - sector_size);
+			err_byte = ecc[tmp];
+			ecc[tmp] ^= (1 << bit_pos);
+
+			pos = tmp + nand_chip->ecc.layout->eccpos[0];
+			dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+				pos, bit_pos, err_byte, ecc[tmp]);
+		}
+
+		i++;
+		err_nbr--;
+	}
+
+	return;
+}
+
+static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf,
+	u8 *ecc)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i, err_nbr, eccbytes;
+	uint8_t *buf_pos;
+
+	eccbytes = nand_chip->ecc.bytes;
+	for (i = 0; i < eccbytes; i++)
+		if (ecc[i] != 0xff)
+			goto normal_check;
+	/* Erased page, return OK */
+	return 0;
+
+normal_check:
+	for (i = 0; i < host->pmecc_sector_number; i++) {
+		err_nbr = 0;
+		if (pmecc_stat & 0x1) {
+			buf_pos = buf + i * host->pmecc_sector_size;
+
+			pmecc_gen_syndrome(mtd, i);
+			pmecc_substitute(mtd);
+			pmecc_get_sigma(mtd);
+
+			err_nbr = pmecc_err_location(mtd);
+			if (err_nbr == -1) {
+				dev_err(host->dev, "PMECC: Too many errors\n");
+				mtd->ecc_stats.failed++;
+				return -EIO;
+			} else {
+				pmecc_correct_data(mtd, buf_pos, ecc, i,
+					host->pmecc_bytes_per_sector, err_nbr);
+				mtd->ecc_stats.corrected += err_nbr;
+			}
+		}
+		pmecc_stat >>= 1;
+	}
+
+	return 0;
+}
+
+static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
+	struct nand_chip *chip, uint8_t *buf, int oob_required, int page)
+{
+	struct atmel_nand_host *host = chip->priv;
+	int eccsize = chip->ecc.size;
+	uint8_t *oob = chip->oob_poi;
+	uint32_t *eccpos = chip->ecc.layout->eccpos;
+	uint32_t stat;
+	unsigned long end_time;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG)
+		& ~PMECC_CFG_WRITE_OP) | PMECC_CFG_AUTO_ENABLE);
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
+
+	chip->read_buf(mtd, buf, eccsize);
+	chip->read_buf(mtd, oob, mtd->oobsize);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to get error status.\n");
+			return -EIO;
+		}
+		cpu_relax();
+	}
+
+	stat = pmecc_readl_relaxed(host->ecc, ISR);
+	if (stat != 0)
+		if (pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]) != 0)
+			return -EIO;
+
+	return 0;
+}
+
+static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
+		struct nand_chip *chip, const uint8_t *buf, int oob_required)
+{
+	struct atmel_nand_host *host = chip->priv;
+	uint32_t *eccpos = chip->ecc.layout->eccpos;
+	int i, j;
+	unsigned long end_time;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+
+	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG) |
+		PMECC_CFG_WRITE_OP) & ~PMECC_CFG_AUTO_ENABLE);
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
+
+	chip->write_buf(mtd, (u8 *)buf, mtd->writesize);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to get ECC value.\n");
+			return -EIO;
+		}
+		cpu_relax();
+	}
+
+	for (i = 0; i < host->pmecc_sector_number; i++) {
+		for (j = 0; j < host->pmecc_bytes_per_sector; j++) {
+			int pos;
+
+			pos = i * host->pmecc_bytes_per_sector + j;
+			chip->oob_poi[eccpos[pos]] =
+				pmecc_readb_ecc_relaxed(host->ecc, i, j);
+		}
+	}
+	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	return 0;
+}
+
+static void atmel_pmecc_core_init(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	uint32_t val = 0;
+	struct nand_ecclayout *ecc_layout;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+
+	switch (host->pmecc_corr_cap) {
+	case 2:
+		val = PMECC_CFG_BCH_ERR2;
+		break;
+	case 4:
+		val = PMECC_CFG_BCH_ERR4;
+		break;
+	case 8:
+		val = PMECC_CFG_BCH_ERR8;
+		break;
+	case 12:
+		val = PMECC_CFG_BCH_ERR12;
+		break;
+	case 24:
+		val = PMECC_CFG_BCH_ERR24;
+		break;
+	}
+
+	if (host->pmecc_sector_size == 512)
+		val |= PMECC_CFG_SECTOR512;
+	else if (host->pmecc_sector_size == 1024)
+		val |= PMECC_CFG_SECTOR1024;
+
+	switch (host->pmecc_sector_number) {
+	case 1:
+		val |= PMECC_CFG_PAGE_1SECTOR;
+		break;
+	case 2:
+		val |= PMECC_CFG_PAGE_2SECTORS;
+		break;
+	case 4:
+		val |= PMECC_CFG_PAGE_4SECTORS;
+		break;
+	case 8:
+		val |= PMECC_CFG_PAGE_8SECTORS;
+		break;
+	}
+
+	val |= (PMECC_CFG_READ_OP | PMECC_CFG_SPARE_DISABLE
+		| PMECC_CFG_AUTO_DISABLE);
+	pmecc_writel(host->ecc, CFG, val);
+
+	ecc_layout = nand_chip->ecc.layout;
+	pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1);
+	pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]);
+	pmecc_writel(host->ecc, EADDR,
+			ecc_layout->eccpos[ecc_layout->eccbytes - 1]);
+	/* See datasheet about PMECC Clock Control Register */
+	pmecc_writel(host->ecc, CLK, 2);
+	pmecc_writel(host->ecc, IDR, 0xff);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+}
+
+static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
+					 struct atmel_nand_host *host)
+{
+	struct mtd_info *mtd = &host->mtd;
+	struct nand_chip *nand_chip = &host->nand_chip;
+	struct resource *regs, *regs_pmerr, *regs_rom;
+	int cap, sector_size, err_no;
+
+	cap = host->pmecc_corr_cap;
+	sector_size = host->pmecc_sector_size;
+	dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
+		 cap, sector_size);
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!regs) {
+		dev_warn(host->dev,
+			"Can't get I/O resource regs for PMECC controller, rolling back on software ECC\n");
+		nand_chip->ecc.mode = NAND_ECC_SOFT;
+		return 0;
+	}
+
+	host->ecc = ioremap(regs->start, resource_size(regs));
+	if (host->ecc == NULL) {
+		dev_err(host->dev, "ioremap failed\n");
+		err_no = -EIO;
+		goto err_pmecc_ioremap;
+	}
+
+	regs_pmerr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+	if (regs_pmerr && regs_rom) {
+		host->pmerrloc_base = ioremap(regs_pmerr->start,
+			resource_size(regs_pmerr));
+		host->pmecc_rom_base = ioremap(regs_rom->start,
+			resource_size(regs_rom));
+	}
+
+	if (!host->pmerrloc_base || !host->pmecc_rom_base) {
+		dev_err(host->dev,
+			"Can not get I/O resource for PMECC ERRLOC controller or ROM!\n");
+		err_no = -EIO;
+		goto err_pmloc_ioremap;
+	}
+
+	/* ECC is calculated for the whole page (1 step) */
+	nand_chip->ecc.size = mtd->writesize;
+
+	/* set ECC page size and oob layout */
+	switch (mtd->writesize) {
+	case 2048:
+		host->pmecc_degree = PMECC_GF_DIMENSION_13;
+		host->pmecc_cw_len = (1 << host->pmecc_degree) - 1;
+		host->pmecc_sector_number = mtd->writesize / sector_size;
+		host->pmecc_bytes_per_sector = pmecc_get_ecc_bytes(
+			cap, sector_size);
+		host->pmecc_alpha_to = pmecc_get_alpha_to(host);
+		host->pmecc_index_of = host->pmecc_rom_base +
+			host->pmecc_lookup_table_offset;
+
+		nand_chip->ecc.steps = 1;
+		nand_chip->ecc.strength = cap;
+		nand_chip->ecc.bytes = host->pmecc_bytes_per_sector *
+				       host->pmecc_sector_number;
+		if (nand_chip->ecc.bytes > mtd->oobsize - 2) {
+			dev_err(host->dev, "No room for ECC bytes\n");
+			err_no = -EINVAL;
+			goto err_no_ecc_room;
+		}
+		pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
+					mtd->oobsize,
+					nand_chip->ecc.bytes);
+		nand_chip->ecc.layout = &atmel_pmecc_oobinfo;
+		break;
+	case 512:
+	case 1024:
+	case 4096:
+		/* TODO */
+		dev_warn(host->dev,
+			"Unsupported page size for PMECC, use Software ECC\n");
+	default:
+		/* page size not handled by HW ECC */
+		/* switching back to soft ECC */
+		nand_chip->ecc.mode = NAND_ECC_SOFT;
+		return 0;
+	}
+
+	/* Allocate data for PMECC computation */
+	err_no = pmecc_data_alloc(host);
+	if (err_no) {
+		dev_err(host->dev,
+				"Cannot allocate memory for PMECC computation!\n");
+		goto err_pmecc_data_alloc;
+	}
+
+	nand_chip->ecc.read_page = atmel_nand_pmecc_read_page;
+	nand_chip->ecc.write_page = atmel_nand_pmecc_write_page;
+
+	atmel_pmecc_core_init(mtd);
+
+	return 0;
+
+err_pmecc_data_alloc:
+err_no_ecc_room:
+err_pmloc_ioremap:
+	iounmap(host->ecc);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
+err_pmecc_ioremap:
+	return err_no;
+}
+
+/*
  * Calculate HW ECC
  *
  * function called after a write
@@ -747,7 +1471,11 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
 	}
 
 	if (nand_chip->ecc.mode == NAND_ECC_HW) {
-		res = atmel_hw_nand_init_params(pdev, host);
+		if (host->has_pmecc)
+			res = atmel_pmecc_nand_init_params(pdev, host);
+		else
+			res = atmel_hw_nand_init_params(pdev, host);
+
 		if (res != 0)
 			goto err_hw_ecc;
 	}
@@ -766,8 +1494,16 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
 		return res;
 
 err_scan_tail:
+	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
+		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+		pmecc_data_free(host);
+	}
 	if (host->ecc)
 		iounmap(host->ecc);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
 err_hw_ecc:
 err_scan_ident:
 err_no_card:
@@ -793,8 +1529,19 @@ static int __exit atmel_nand_remove(struct platform_device *pdev)
 
 	atmel_nand_disable(host);
 
+	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
+		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+		pmerrloc_writel(host->pmerrloc_base, ELDIS,
+				PMERRLOC_DISABLE);
+		pmecc_data_free(host);
+	}
+
 	if (host->ecc)
 		iounmap(host->ecc);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
 
 	if (host->dma_chan)
 		dma_release_channel(host->dma_chan);
diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h
index 578c776..8a1e9a6 100644
--- a/drivers/mtd/nand/atmel_nand_ecc.h
+++ b/drivers/mtd/nand/atmel_nand_ecc.h
@@ -3,7 +3,7 @@
  * Based on AT91SAM9260 datasheet revision B.
  *
  * Copyright (C) 2007 Andrew Victor
- * Copyright (C) 2007 Atmel Corporation.
+ * Copyright (C) 2007 - 2012 Atmel Corporation.
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -36,4 +36,116 @@
 #define ATMEL_ECC_NPR		0x10			/* NParity register */
 #define		ATMEL_ECC_NPARITY	(0xffff << 0)		/* NParity */
 
+/* PMECC Register Definitions */
+#define ATMEL_PMECC_CFG			0x000	/* Configuration Register */
+#define		PMECC_CFG_BCH_ERR2		(0 << 0)
+#define		PMECC_CFG_BCH_ERR4		(1 << 0)
+#define		PMECC_CFG_BCH_ERR8		(2 << 0)
+#define		PMECC_CFG_BCH_ERR12		(3 << 0)
+#define		PMECC_CFG_BCH_ERR24		(4 << 0)
+
+#define		PMECC_CFG_SECTOR512		(0 << 4)
+#define		PMECC_CFG_SECTOR1024		(1 << 4)
+
+#define		PMECC_CFG_PAGE_1SECTOR		(0 << 8)
+#define		PMECC_CFG_PAGE_2SECTORS		(1 << 8)
+#define		PMECC_CFG_PAGE_4SECTORS		(2 << 8)
+#define		PMECC_CFG_PAGE_8SECTORS		(3 << 8)
+
+#define		PMECC_CFG_READ_OP		(0 << 12)
+#define		PMECC_CFG_WRITE_OP		(1 << 12)
+
+#define		PMECC_CFG_SPARE_ENABLE		(1 << 16)
+#define		PMECC_CFG_SPARE_DISABLE		(0 << 16)
+
+#define		PMECC_CFG_AUTO_ENABLE		(1 << 20)
+#define		PMECC_CFG_AUTO_DISABLE		(0 << 20)
+
+#define ATMEL_PMECC_SAREA		0x004	/* Spare area size */
+#define ATMEL_PMECC_SADDR		0x008	/* PMECC starting address */
+#define ATMEL_PMECC_EADDR		0x00c	/* PMECC ending address */
+#define ATMEL_PMECC_CLK			0x010	/* PMECC clock control */
+#define		PMECC_CLK_133MHZ		(2 << 0)
+
+#define ATMEL_PMECC_CTRL		0x014	/* PMECC control register */
+#define		PMECC_CTRL_RST			(1 << 0)
+#define		PMECC_CTRL_DATA			(1 << 1)
+#define		PMECC_CTRL_USER			(1 << 2)
+#define		PMECC_CTRL_ENABLE		(1 << 4)
+#define		PMECC_CTRL_DISABLE		(1 << 5)
+
+#define ATMEL_PMECC_SR			0x018	/* PMECC status register */
+#define		PMECC_SR_BUSY			(1 << 0)
+#define		PMECC_SR_ENABLE			(1 << 4)
+
+#define ATMEL_PMECC_IER			0x01c	/* PMECC interrupt enable */
+#define		PMECC_IER_ENABLE		(1 << 0)
+#define ATMEL_PMECC_IDR			0x020	/* PMECC interrupt disable */
+#define		PMECC_IER_DISABLE		(1 << 0)
+#define ATMEL_PMECC_IMR			0x024	/* PMECC interrupt mask */
+#define		PMECC_IER_MASK			(1 << 0)
+#define ATMEL_PMECC_ISR			0x028	/* PMECC interrupt status */
+#define ATMEL_PMECC_ECCx		0x040	/* PMECC ECC x */
+#define ATMEL_PMECC_REMx		0x240	/* PMECC REM x */
+
+/* PMERRLOC Register Definitions */
+#define ATMEL_PMERRLOC_ELCFG		0x000	/* Error location config */
+#define		PMERRLOC_ELCFG_SECTOR_512	(0 << 0)
+#define		PMERRLOC_ELCFG_SECTOR_1024	(1 << 0)
+#define		PMERRLOC_ELCFG_NUM_ERRORS(n)	((n) << 16)
+
+#define ATMEL_PMERRLOC_ELPRIM		0x004	/* Error location primitive */
+#define ATMEL_PMERRLOC_ELEN		0x008	/* Error location enable */
+#define ATMEL_PMERRLOC_ELDIS		0x00c	/* Error location disable */
+#define		PMERRLOC_DISABLE		(1 << 0)
+
+#define ATMEL_PMERRLOC_ELSR		0x010	/* Error location status */
+#define		PMERRLOC_ELSR_BUSY		(1 << 0)
+#define ATMEL_PMERRLOC_ELIER		0x014	/* Error location int enable */
+#define ATMEL_PMERRLOC_ELIDR		0x018	/* Error location int disable */
+#define ATMEL_PMERRLOC_ELIMR		0x01c	/* Error location int mask */
+#define ATMEL_PMERRLOC_ELISR		0x020	/* Error location int status */
+#define		PMERRLOC_ERR_NUM_MASK		(0x1f << 8)
+#define		PMERRLOC_CALC_DONE		(1 << 0)
+#define ATMEL_PMERRLOC_SIGMAx		0x028	/* Error location SIGMA x */
+#define ATMEL_PMERRLOC_ELx		0x08c	/* Error location x */
+
+/* Register access macros for PMECC */
+#define pmecc_readl_relaxed(addr, reg) \
+	readl_relaxed((addr) + ATMEL_PMECC_##reg)
+
+#define pmecc_writel(addr, reg, value) \
+	writel((value), (addr) + ATMEL_PMECC_##reg)
+
+#define pmecc_readb_ecc_relaxed(addr, sector, n) \
+	readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
+
+#define pmecc_readl_rem_relaxed(addr, sector, n) \
+	readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
+
+#define pmerrloc_readl_relaxed(addr, reg) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
+
+#define pmerrloc_writel(addr, reg, value) \
+	writel((value), (addr) + ATMEL_PMERRLOC_##reg)
+
+#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
+	writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
+
+#define pmerrloc_readl_sigma_relaxed(addr, n) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
+
+#define pmerrloc_readl_el_relaxed(addr, n) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
+
+/* Galois field dimension */
+#define PMECC_GF_DIMENSION_13			13
+#define PMECC_GF_DIMENSION_14			14
+
+#define PMECC_LOOKUP_TABLE_SIZE_512		0x2000
+#define PMECC_LOOKUP_TABLE_SIZE_1024		0x4000
+
+/* Time out value for reading PMECC status register */
+#define PMECC_MAX_TIMEOUT_MS			100
+
 #endif
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v12 2/2] MTD: at91: atmel_nand: Update driver to support Programmable Multibit ECC controller
@ 2012-06-28  8:21   ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:21 UTC (permalink / raw)
  To: linux-arm-kernel

The Programmable Multibit 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 PMECC in this driver, the user needs to set the address and size of
PMECC, PMECC error location controllers and ROM. And also needs to pass the
correction capability, the sector size and ROM lookup table offsets via dt.

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@atmel.com>
Signed-off-by: Josh Wu <josh.wu@atmel.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
---
since v11
   1. fix bug: not report error if fail to get resources for PMECC ERRLOC or ROM.
   2. fix bug: no correction when ecc data has bit flipped.
   3. add clearer debug information for data correction.

since v10
   1. atmel_nand_pmecc_write_page() will return -EIO when time out to read the pmecc status register.
   2. increase the time-out duration to 100ms, which has more toleration.
   3. add oob_required argument for pmecc read/write functions to align with v3.5-rc4.

 drivers/mtd/nand/atmel_nand.c     |  749 ++++++++++++++++++++++++++++++++++++-
 drivers/mtd/nand/atmel_nand_ecc.h |  114 +++++-
 2 files changed, 861 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index b97ad9f..328b4d9 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 Multibit ECC support for various AT91 SoC
+ *     (C) Copyright 2012 ATMEL, Hong Xu
  *
  * 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
@@ -98,8 +100,31 @@ struct atmel_nand_host {
 	u8			pmecc_corr_cap;
 	u16			pmecc_sector_size;
 	u32			pmecc_lookup_table_offset;
+
+	int			pmecc_bytes_per_sector;
+	int			pmecc_sector_number;
+	int			pmecc_degree;	/* Degree of remainders */
+	int			pmecc_cw_len;	/* Length of codeword */
+
+	void __iomem		*pmerrloc_base;
+	void __iomem		*pmecc_rom_base;
+
+	/* lookup table for alpha_to and index_of */
+	void __iomem		*pmecc_alpha_to;
+	void __iomem		*pmecc_index_of;
+
+	/* data for pmecc computation */
+	int16_t			*pmecc_partial_syn;
+	int16_t			*pmecc_si;
+	int16_t			*pmecc_smu;	/* Sigma table */
+	int16_t			*pmecc_lmu;	/* polynomal order */
+	int			*pmecc_mu;
+	int			*pmecc_dmu;
+	int			*pmecc_delta;
 };
 
+static struct nand_ecclayout atmel_pmecc_oobinfo;
+
 static int cpu_has_dma(void)
 {
 	return cpu_is_at91sam9rl() || cpu_is_at91sam9g45();
@@ -293,6 +318,705 @@ 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
+ *
+ * Following table shows what at91 PMECC supported:
+ * 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 int __devinit pmecc_get_ecc_bytes(int cap, int sector_size)
+{
+	int m = 12 + sector_size / 512;
+	return (m * cap + 7) / 8;
+}
+
+static void __devinit 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 = 2;
+	layout->oobfree[0].length =
+		oobsize - ecc_len - layout->oobfree[0].offset;
+}
+
+static void __devinit __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
+{
+	int table_size;
+
+	table_size = host->pmecc_sector_size == 512 ?
+		PMECC_LOOKUP_TABLE_SIZE_512 : PMECC_LOOKUP_TABLE_SIZE_1024;
+
+	return host->pmecc_rom_base + host->pmecc_lookup_table_offset +
+			table_size * sizeof(int16_t);
+}
+
+static void pmecc_data_free(struct atmel_nand_host *host)
+{
+	kfree(host->pmecc_partial_syn);
+	kfree(host->pmecc_si);
+	kfree(host->pmecc_lmu);
+	kfree(host->pmecc_smu);
+	kfree(host->pmecc_mu);
+	kfree(host->pmecc_dmu);
+	kfree(host->pmecc_delta);
+}
+
+static int __devinit pmecc_data_alloc(struct atmel_nand_host *host)
+{
+	const int cap = host->pmecc_corr_cap;
+
+	host->pmecc_partial_syn = kzalloc((2 * cap + 1) * sizeof(int16_t),
+					GFP_KERNEL);
+	host->pmecc_si = kzalloc((2 * cap + 1) * sizeof(int16_t), GFP_KERNEL);
+	host->pmecc_lmu = kzalloc((cap + 1) * sizeof(int16_t), GFP_KERNEL);
+	host->pmecc_smu = kzalloc((cap + 2) * (2 * cap + 1) * sizeof(int16_t),
+					GFP_KERNEL);
+	host->pmecc_mu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+	host->pmecc_dmu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+	host->pmecc_delta = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
+
+	if (host->pmecc_partial_syn &&
+			host->pmecc_si &&
+			host->pmecc_lmu &&
+			host->pmecc_smu &&
+			host->pmecc_mu &&
+			host->pmecc_dmu &&
+			host->pmecc_delta)
+		return 0;
+
+	/* error happened */
+	pmecc_data_free(host);
+	return -ENOMEM;
+}
+
+static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i;
+	uint32_t value;
+
+	/* Fill odd syndromes */
+	for (i = 0; i < host->pmecc_corr_cap; i++) {
+		value = pmecc_readl_rem_relaxed(host->ecc, sector, i / 2);
+		if (i & 1)
+			value >>= 16;
+		value &= 0xffff;
+		host->pmecc_partial_syn[(2 * i) + 1] = (int16_t)value;
+	}
+}
+
+static void pmecc_substitute(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int16_t __iomem *alpha_to = host->pmecc_alpha_to;
+	int16_t __iomem *index_of = host->pmecc_index_of;
+	int16_t *partial_syn = host->pmecc_partial_syn;
+	const int cap = host->pmecc_corr_cap;
+	int16_t *si;
+	int i, j;
+
+	/* si[] is a table that holds the current syndrome value,
+	 * an element of that table belongs to the field
+	 */
+	si = host->pmecc_si;
+
+	memset(&si[1], 0, sizeof(int16_t) * (2 * cap - 1));
+
+	/* Computation 2t syndromes based on S(x) */
+	/* Odd syndromes */
+	for (i = 1; i < 2 * cap; i += 2) {
+		for (j = 0; j < host->pmecc_degree; j++) {
+			if (partial_syn[i] & ((unsigned short)0x1 << j))
+				si[i] = readw_relaxed(alpha_to + i * j) ^ si[i];
+		}
+	}
+	/* Even syndrome = (Odd syndrome) ** 2 */
+	for (i = 2, j = 1; j <= cap; i = ++j << 1) {
+		if (si[j] == 0) {
+			si[i] = 0;
+		} else {
+			int16_t tmp;
+
+			tmp = readw_relaxed(index_of + si[j]);
+			tmp = (tmp * 2) % host->pmecc_cw_len;
+			si[i] = readw_relaxed(alpha_to + tmp);
+		}
+	}
+
+	return;
+}
+
+static void pmecc_get_sigma(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+
+	int16_t *lmu = host->pmecc_lmu;
+	int16_t *si = host->pmecc_si;
+	int *mu = host->pmecc_mu;
+	int *dmu = host->pmecc_dmu;	/* Discrepancy */
+	int *delta = host->pmecc_delta; /* Delta order */
+	int cw_len = host->pmecc_cw_len;
+	const int16_t cap = host->pmecc_corr_cap;
+	int16_t __iomem	*index_of = host->pmecc_index_of;
+	int16_t __iomem	*alpha_to = host->pmecc_alpha_to;
+	int i, j, k;
+	uint32_t dmu_0_count, tmp;
+	int16_t (*smu)[2 * cap + 1];
+
+	/* index of largest delta */
+	int ro;
+	int largest;
+	int diff;
+
+	dmu_0_count = 0;
+	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
+
+	/* First Row */
+
+	/* Mu */
+	mu[0] = -1;
+
+	memset(&smu[0][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+	smu[0][0] = 1;
+
+	/* discrepancy set to 1 */
+	dmu[0] = 1;
+	/* polynom order set to 0 */
+	lmu[0] = 0;
+	delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
+
+	/* Second Row */
+
+	/* Mu */
+	mu[1] = 0;
+	/* Sigma(x) set to 1 */
+	memset(&smu[1][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+	smu[1][0] = 1;
+
+	/* discrepancy set to S1 */
+	dmu[1] = si[1];
+
+	/* polynom order set to 0 */
+	lmu[1] = 0;
+
+	delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
+
+	/* Init the Sigma(x) last row */
+	memset(&smu[cap + 1][0], 0,
+		sizeof(int16_t) * (2 * cap + 1));
+
+	for (i = 1; i <= cap; i++) {
+		mu[i + 1] = i << 1;
+		/* Begin Computing Sigma (Mu+1) and L(mu) */
+		/* check if discrepancy is set to 0 */
+		if (dmu[i] == 0) {
+			dmu_0_count++;
+
+			tmp = ((cap - (lmu[i] >> 1) - 1) / 2);
+			if ((cap - (lmu[i] >> 1) - 1) & 0x1)
+				tmp += 2;
+			else
+				tmp += 1;
+
+			if (dmu_0_count == tmp) {
+				for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
+					smu[cap + 1][j] = smu[i][j];
+				lmu[cap + 1] = lmu[i];
+				return;
+			}
+
+			/* copy polynom */
+			for (j = 0; j <= lmu[i] >> 1; j++)
+				smu[i + 1][j] = smu[i][j];
+
+			/* copy previous polynom order to the next */
+			lmu[i + 1] = lmu[i];
+		} else {
+			ro = 0;
+			largest = -1;
+			/* find largest delta with dmu != 0 */
+			for (j = 0; j < i; j++) {
+				if ((dmu[j]) && (delta[j] > largest)) {
+					largest = delta[j];
+					ro = j;
+				}
+			}
+
+			/* compute difference */
+			diff = (mu[i] - mu[ro]);
+
+			/* Compute degree of the new smu polynomial */
+			if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
+				lmu[i + 1] = lmu[i];
+			else
+				lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
+
+			/* Init smu[i+1] with 0 */
+			for (k = 0; k < (2 * cap + 1); k++)
+				smu[i + 1][k] = 0;
+
+			/* Compute smu[i+1] */
+			for (k = 0; k <= lmu[ro] >> 1; k++) {
+				int16_t a, b, c;
+
+				if (!(smu[ro][k] && dmu[i]))
+					continue;
+				a = readw_relaxed(index_of + dmu[i]);
+				b = readw_relaxed(index_of + dmu[ro]);
+				c = readw_relaxed(index_of + smu[ro][k]);
+				tmp = a + (cw_len - b) + c;
+				a = readw_relaxed(alpha_to + tmp % cw_len);
+				smu[i + 1][k + diff] = a;
+			}
+
+			for (k = 0; k <= lmu[i] >> 1; k++)
+				smu[i + 1][k] ^= smu[i][k];
+		}
+
+		/* End Computing Sigma (Mu+1) and L(mu) */
+		/* In either case compute delta */
+		delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
+
+		/* Do not compute discrepancy for the last iteration */
+		if (i >= cap)
+			continue;
+
+		for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
+			tmp = 2 * (i - 1);
+			if (k == 0) {
+				dmu[i + 1] = si[tmp + 3];
+			} else if (smu[i + 1][k] && si[tmp + 3 - k]) {
+				int16_t a, b, c;
+				a = readw_relaxed(index_of + smu[i + 1][k]);
+				b = si[2 * (i - 1) + 3 - k];
+				c = readw_relaxed(index_of + b);
+				tmp = a + c;
+				tmp %= cw_len;
+				dmu[i + 1] = readw_relaxed(alpha_to + tmp) ^
+					dmu[i + 1];
+			}
+		}
+	}
+
+	return;
+}
+
+static int pmecc_err_location(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	unsigned long end_time;
+	const int cap = host->pmecc_corr_cap;
+	int sector_size = host->pmecc_sector_size;
+	int err_nbr = 0;	/* number of error */
+	int roots_nbr;		/* number of roots */
+	int i;
+	uint32_t val;
+	int16_t (*smu)[2 * cap + 1];
+
+	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
+
+	pmerrloc_writel(host->pmerrloc_base, ELDIS, PMERRLOC_DISABLE);
+
+	for (i = 0; i <= host->pmecc_lmu[cap + 1] >> 1; i++) {
+		pmerrloc_writel_sigma_relaxed(host->pmerrloc_base, i,
+				      smu[cap + 1][i]);
+		err_nbr++;
+	}
+
+	val = (err_nbr - 1) << 16;
+	if (sector_size == 1024)
+		val |= 1;
+
+	pmerrloc_writel(host->pmerrloc_base, ELCFG, val);
+	pmerrloc_writel(host->pmerrloc_base, ELEN,
+			sector_size * 8 + host->pmecc_degree * cap);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while (!(pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
+		 & PMERRLOC_CALC_DONE)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to calculate error location.\n");
+			return -1;
+		}
+		cpu_relax();
+	}
+
+	roots_nbr = (pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
+		& PMERRLOC_ERR_NUM_MASK) >> 8;
+	/* Number of roots == degree of smu hence <= cap */
+	if (roots_nbr == host->pmecc_lmu[cap + 1] >> 1)
+		return err_nbr - 1;
+
+	/* Number of roots does not match the degree of smu
+	 * unable to correct error */
+	return -1;
+}
+
+static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
+		int sector_num, int extra_bytes, int err_nbr)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i = 0;
+	int byte_pos, bit_pos, sector_size, ecc_size, pos;
+	uint32_t tmp;
+	uint8_t err_byte;
+
+	sector_size = host->pmecc_sector_size;
+	ecc_size = nand_chip->ecc.bytes;
+
+	while (err_nbr) {
+		tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1;
+		byte_pos = tmp / 8;
+		bit_pos  = tmp % 8;
+
+		if (byte_pos >= (sector_size + extra_bytes))
+			BUG();	/* should never happen */
+
+		if (byte_pos < sector_size) {
+			err_byte = *(buf + byte_pos);
+			*(buf + byte_pos) ^= (1 << bit_pos);
+
+			pos = sector_num * host->pmecc_sector_size + byte_pos;
+			dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+				pos, bit_pos, err_byte, *(buf + byte_pos));
+		} else {
+			/* Bit flip in OOB area */
+			tmp = sector_num * host->pmecc_bytes_per_sector
+					+ (byte_pos - sector_size);
+			err_byte = ecc[tmp];
+			ecc[tmp] ^= (1 << bit_pos);
+
+			pos = tmp + nand_chip->ecc.layout->eccpos[0];
+			dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+				pos, bit_pos, err_byte, ecc[tmp]);
+		}
+
+		i++;
+		err_nbr--;
+	}
+
+	return;
+}
+
+static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf,
+	u8 *ecc)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	int i, err_nbr, eccbytes;
+	uint8_t *buf_pos;
+
+	eccbytes = nand_chip->ecc.bytes;
+	for (i = 0; i < eccbytes; i++)
+		if (ecc[i] != 0xff)
+			goto normal_check;
+	/* Erased page, return OK */
+	return 0;
+
+normal_check:
+	for (i = 0; i < host->pmecc_sector_number; i++) {
+		err_nbr = 0;
+		if (pmecc_stat & 0x1) {
+			buf_pos = buf + i * host->pmecc_sector_size;
+
+			pmecc_gen_syndrome(mtd, i);
+			pmecc_substitute(mtd);
+			pmecc_get_sigma(mtd);
+
+			err_nbr = pmecc_err_location(mtd);
+			if (err_nbr == -1) {
+				dev_err(host->dev, "PMECC: Too many errors\n");
+				mtd->ecc_stats.failed++;
+				return -EIO;
+			} else {
+				pmecc_correct_data(mtd, buf_pos, ecc, i,
+					host->pmecc_bytes_per_sector, err_nbr);
+				mtd->ecc_stats.corrected += err_nbr;
+			}
+		}
+		pmecc_stat >>= 1;
+	}
+
+	return 0;
+}
+
+static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
+	struct nand_chip *chip, uint8_t *buf, int oob_required, int page)
+{
+	struct atmel_nand_host *host = chip->priv;
+	int eccsize = chip->ecc.size;
+	uint8_t *oob = chip->oob_poi;
+	uint32_t *eccpos = chip->ecc.layout->eccpos;
+	uint32_t stat;
+	unsigned long end_time;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG)
+		& ~PMECC_CFG_WRITE_OP) | PMECC_CFG_AUTO_ENABLE);
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
+
+	chip->read_buf(mtd, buf, eccsize);
+	chip->read_buf(mtd, oob, mtd->oobsize);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to get error status.\n");
+			return -EIO;
+		}
+		cpu_relax();
+	}
+
+	stat = pmecc_readl_relaxed(host->ecc, ISR);
+	if (stat != 0)
+		if (pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]) != 0)
+			return -EIO;
+
+	return 0;
+}
+
+static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
+		struct nand_chip *chip, const uint8_t *buf, int oob_required)
+{
+	struct atmel_nand_host *host = chip->priv;
+	uint32_t *eccpos = chip->ecc.layout->eccpos;
+	int i, j;
+	unsigned long end_time;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+
+	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG) |
+		PMECC_CFG_WRITE_OP) & ~PMECC_CFG_AUTO_ENABLE);
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
+
+	chip->write_buf(mtd, (u8 *)buf, mtd->writesize);
+
+	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
+	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
+		if (unlikely(time_after(jiffies, end_time))) {
+			dev_err(host->dev, "PMECC: Timeout to get ECC value.\n");
+			return -EIO;
+		}
+		cpu_relax();
+	}
+
+	for (i = 0; i < host->pmecc_sector_number; i++) {
+		for (j = 0; j < host->pmecc_bytes_per_sector; j++) {
+			int pos;
+
+			pos = i * host->pmecc_bytes_per_sector + j;
+			chip->oob_poi[eccpos[pos]] =
+				pmecc_readb_ecc_relaxed(host->ecc, i, j);
+		}
+	}
+	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+	return 0;
+}
+
+static void atmel_pmecc_core_init(struct mtd_info *mtd)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct atmel_nand_host *host = nand_chip->priv;
+	uint32_t val = 0;
+	struct nand_ecclayout *ecc_layout;
+
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+
+	switch (host->pmecc_corr_cap) {
+	case 2:
+		val = PMECC_CFG_BCH_ERR2;
+		break;
+	case 4:
+		val = PMECC_CFG_BCH_ERR4;
+		break;
+	case 8:
+		val = PMECC_CFG_BCH_ERR8;
+		break;
+	case 12:
+		val = PMECC_CFG_BCH_ERR12;
+		break;
+	case 24:
+		val = PMECC_CFG_BCH_ERR24;
+		break;
+	}
+
+	if (host->pmecc_sector_size == 512)
+		val |= PMECC_CFG_SECTOR512;
+	else if (host->pmecc_sector_size == 1024)
+		val |= PMECC_CFG_SECTOR1024;
+
+	switch (host->pmecc_sector_number) {
+	case 1:
+		val |= PMECC_CFG_PAGE_1SECTOR;
+		break;
+	case 2:
+		val |= PMECC_CFG_PAGE_2SECTORS;
+		break;
+	case 4:
+		val |= PMECC_CFG_PAGE_4SECTORS;
+		break;
+	case 8:
+		val |= PMECC_CFG_PAGE_8SECTORS;
+		break;
+	}
+
+	val |= (PMECC_CFG_READ_OP | PMECC_CFG_SPARE_DISABLE
+		| PMECC_CFG_AUTO_DISABLE);
+	pmecc_writel(host->ecc, CFG, val);
+
+	ecc_layout = nand_chip->ecc.layout;
+	pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1);
+	pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]);
+	pmecc_writel(host->ecc, EADDR,
+			ecc_layout->eccpos[ecc_layout->eccbytes - 1]);
+	/* See datasheet about PMECC Clock Control Register */
+	pmecc_writel(host->ecc, CLK, 2);
+	pmecc_writel(host->ecc, IDR, 0xff);
+	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
+}
+
+static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
+					 struct atmel_nand_host *host)
+{
+	struct mtd_info *mtd = &host->mtd;
+	struct nand_chip *nand_chip = &host->nand_chip;
+	struct resource *regs, *regs_pmerr, *regs_rom;
+	int cap, sector_size, err_no;
+
+	cap = host->pmecc_corr_cap;
+	sector_size = host->pmecc_sector_size;
+	dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
+		 cap, sector_size);
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!regs) {
+		dev_warn(host->dev,
+			"Can't get I/O resource regs for PMECC controller, rolling back on software ECC\n");
+		nand_chip->ecc.mode = NAND_ECC_SOFT;
+		return 0;
+	}
+
+	host->ecc = ioremap(regs->start, resource_size(regs));
+	if (host->ecc == NULL) {
+		dev_err(host->dev, "ioremap failed\n");
+		err_no = -EIO;
+		goto err_pmecc_ioremap;
+	}
+
+	regs_pmerr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+	if (regs_pmerr && regs_rom) {
+		host->pmerrloc_base = ioremap(regs_pmerr->start,
+			resource_size(regs_pmerr));
+		host->pmecc_rom_base = ioremap(regs_rom->start,
+			resource_size(regs_rom));
+	}
+
+	if (!host->pmerrloc_base || !host->pmecc_rom_base) {
+		dev_err(host->dev,
+			"Can not get I/O resource for PMECC ERRLOC controller or ROM!\n");
+		err_no = -EIO;
+		goto err_pmloc_ioremap;
+	}
+
+	/* ECC is calculated for the whole page (1 step) */
+	nand_chip->ecc.size = mtd->writesize;
+
+	/* set ECC page size and oob layout */
+	switch (mtd->writesize) {
+	case 2048:
+		host->pmecc_degree = PMECC_GF_DIMENSION_13;
+		host->pmecc_cw_len = (1 << host->pmecc_degree) - 1;
+		host->pmecc_sector_number = mtd->writesize / sector_size;
+		host->pmecc_bytes_per_sector = pmecc_get_ecc_bytes(
+			cap, sector_size);
+		host->pmecc_alpha_to = pmecc_get_alpha_to(host);
+		host->pmecc_index_of = host->pmecc_rom_base +
+			host->pmecc_lookup_table_offset;
+
+		nand_chip->ecc.steps = 1;
+		nand_chip->ecc.strength = cap;
+		nand_chip->ecc.bytes = host->pmecc_bytes_per_sector *
+				       host->pmecc_sector_number;
+		if (nand_chip->ecc.bytes > mtd->oobsize - 2) {
+			dev_err(host->dev, "No room for ECC bytes\n");
+			err_no = -EINVAL;
+			goto err_no_ecc_room;
+		}
+		pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
+					mtd->oobsize,
+					nand_chip->ecc.bytes);
+		nand_chip->ecc.layout = &atmel_pmecc_oobinfo;
+		break;
+	case 512:
+	case 1024:
+	case 4096:
+		/* TODO */
+		dev_warn(host->dev,
+			"Unsupported page size for PMECC, use Software ECC\n");
+	default:
+		/* page size not handled by HW ECC */
+		/* switching back to soft ECC */
+		nand_chip->ecc.mode = NAND_ECC_SOFT;
+		return 0;
+	}
+
+	/* Allocate data for PMECC computation */
+	err_no = pmecc_data_alloc(host);
+	if (err_no) {
+		dev_err(host->dev,
+				"Cannot allocate memory for PMECC computation!\n");
+		goto err_pmecc_data_alloc;
+	}
+
+	nand_chip->ecc.read_page = atmel_nand_pmecc_read_page;
+	nand_chip->ecc.write_page = atmel_nand_pmecc_write_page;
+
+	atmel_pmecc_core_init(mtd);
+
+	return 0;
+
+err_pmecc_data_alloc:
+err_no_ecc_room:
+err_pmloc_ioremap:
+	iounmap(host->ecc);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
+err_pmecc_ioremap:
+	return err_no;
+}
+
+/*
  * Calculate HW ECC
  *
  * function called after a write
@@ -747,7 +1471,11 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
 	}
 
 	if (nand_chip->ecc.mode == NAND_ECC_HW) {
-		res = atmel_hw_nand_init_params(pdev, host);
+		if (host->has_pmecc)
+			res = atmel_pmecc_nand_init_params(pdev, host);
+		else
+			res = atmel_hw_nand_init_params(pdev, host);
+
 		if (res != 0)
 			goto err_hw_ecc;
 	}
@@ -766,8 +1494,16 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
 		return res;
 
 err_scan_tail:
+	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
+		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+		pmecc_data_free(host);
+	}
 	if (host->ecc)
 		iounmap(host->ecc);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
 err_hw_ecc:
 err_scan_ident:
 err_no_card:
@@ -793,8 +1529,19 @@ static int __exit atmel_nand_remove(struct platform_device *pdev)
 
 	atmel_nand_disable(host);
 
+	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
+		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
+		pmerrloc_writel(host->pmerrloc_base, ELDIS,
+				PMERRLOC_DISABLE);
+		pmecc_data_free(host);
+	}
+
 	if (host->ecc)
 		iounmap(host->ecc);
+	if (host->pmecc_rom_base)
+		iounmap(host->pmecc_rom_base);
+	if (host->pmerrloc_base)
+		iounmap(host->pmerrloc_base);
 
 	if (host->dma_chan)
 		dma_release_channel(host->dma_chan);
diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h
index 578c776..8a1e9a6 100644
--- a/drivers/mtd/nand/atmel_nand_ecc.h
+++ b/drivers/mtd/nand/atmel_nand_ecc.h
@@ -3,7 +3,7 @@
  * Based on AT91SAM9260 datasheet revision B.
  *
  * Copyright (C) 2007 Andrew Victor
- * Copyright (C) 2007 Atmel Corporation.
+ * Copyright (C) 2007 - 2012 Atmel Corporation.
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -36,4 +36,116 @@
 #define ATMEL_ECC_NPR		0x10			/* NParity register */
 #define		ATMEL_ECC_NPARITY	(0xffff << 0)		/* NParity */
 
+/* PMECC Register Definitions */
+#define ATMEL_PMECC_CFG			0x000	/* Configuration Register */
+#define		PMECC_CFG_BCH_ERR2		(0 << 0)
+#define		PMECC_CFG_BCH_ERR4		(1 << 0)
+#define		PMECC_CFG_BCH_ERR8		(2 << 0)
+#define		PMECC_CFG_BCH_ERR12		(3 << 0)
+#define		PMECC_CFG_BCH_ERR24		(4 << 0)
+
+#define		PMECC_CFG_SECTOR512		(0 << 4)
+#define		PMECC_CFG_SECTOR1024		(1 << 4)
+
+#define		PMECC_CFG_PAGE_1SECTOR		(0 << 8)
+#define		PMECC_CFG_PAGE_2SECTORS		(1 << 8)
+#define		PMECC_CFG_PAGE_4SECTORS		(2 << 8)
+#define		PMECC_CFG_PAGE_8SECTORS		(3 << 8)
+
+#define		PMECC_CFG_READ_OP		(0 << 12)
+#define		PMECC_CFG_WRITE_OP		(1 << 12)
+
+#define		PMECC_CFG_SPARE_ENABLE		(1 << 16)
+#define		PMECC_CFG_SPARE_DISABLE		(0 << 16)
+
+#define		PMECC_CFG_AUTO_ENABLE		(1 << 20)
+#define		PMECC_CFG_AUTO_DISABLE		(0 << 20)
+
+#define ATMEL_PMECC_SAREA		0x004	/* Spare area size */
+#define ATMEL_PMECC_SADDR		0x008	/* PMECC starting address */
+#define ATMEL_PMECC_EADDR		0x00c	/* PMECC ending address */
+#define ATMEL_PMECC_CLK			0x010	/* PMECC clock control */
+#define		PMECC_CLK_133MHZ		(2 << 0)
+
+#define ATMEL_PMECC_CTRL		0x014	/* PMECC control register */
+#define		PMECC_CTRL_RST			(1 << 0)
+#define		PMECC_CTRL_DATA			(1 << 1)
+#define		PMECC_CTRL_USER			(1 << 2)
+#define		PMECC_CTRL_ENABLE		(1 << 4)
+#define		PMECC_CTRL_DISABLE		(1 << 5)
+
+#define ATMEL_PMECC_SR			0x018	/* PMECC status register */
+#define		PMECC_SR_BUSY			(1 << 0)
+#define		PMECC_SR_ENABLE			(1 << 4)
+
+#define ATMEL_PMECC_IER			0x01c	/* PMECC interrupt enable */
+#define		PMECC_IER_ENABLE		(1 << 0)
+#define ATMEL_PMECC_IDR			0x020	/* PMECC interrupt disable */
+#define		PMECC_IER_DISABLE		(1 << 0)
+#define ATMEL_PMECC_IMR			0x024	/* PMECC interrupt mask */
+#define		PMECC_IER_MASK			(1 << 0)
+#define ATMEL_PMECC_ISR			0x028	/* PMECC interrupt status */
+#define ATMEL_PMECC_ECCx		0x040	/* PMECC ECC x */
+#define ATMEL_PMECC_REMx		0x240	/* PMECC REM x */
+
+/* PMERRLOC Register Definitions */
+#define ATMEL_PMERRLOC_ELCFG		0x000	/* Error location config */
+#define		PMERRLOC_ELCFG_SECTOR_512	(0 << 0)
+#define		PMERRLOC_ELCFG_SECTOR_1024	(1 << 0)
+#define		PMERRLOC_ELCFG_NUM_ERRORS(n)	((n) << 16)
+
+#define ATMEL_PMERRLOC_ELPRIM		0x004	/* Error location primitive */
+#define ATMEL_PMERRLOC_ELEN		0x008	/* Error location enable */
+#define ATMEL_PMERRLOC_ELDIS		0x00c	/* Error location disable */
+#define		PMERRLOC_DISABLE		(1 << 0)
+
+#define ATMEL_PMERRLOC_ELSR		0x010	/* Error location status */
+#define		PMERRLOC_ELSR_BUSY		(1 << 0)
+#define ATMEL_PMERRLOC_ELIER		0x014	/* Error location int enable */
+#define ATMEL_PMERRLOC_ELIDR		0x018	/* Error location int disable */
+#define ATMEL_PMERRLOC_ELIMR		0x01c	/* Error location int mask */
+#define ATMEL_PMERRLOC_ELISR		0x020	/* Error location int status */
+#define		PMERRLOC_ERR_NUM_MASK		(0x1f << 8)
+#define		PMERRLOC_CALC_DONE		(1 << 0)
+#define ATMEL_PMERRLOC_SIGMAx		0x028	/* Error location SIGMA x */
+#define ATMEL_PMERRLOC_ELx		0x08c	/* Error location x */
+
+/* Register access macros for PMECC */
+#define pmecc_readl_relaxed(addr, reg) \
+	readl_relaxed((addr) + ATMEL_PMECC_##reg)
+
+#define pmecc_writel(addr, reg, value) \
+	writel((value), (addr) + ATMEL_PMECC_##reg)
+
+#define pmecc_readb_ecc_relaxed(addr, sector, n) \
+	readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
+
+#define pmecc_readl_rem_relaxed(addr, sector, n) \
+	readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
+
+#define pmerrloc_readl_relaxed(addr, reg) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
+
+#define pmerrloc_writel(addr, reg, value) \
+	writel((value), (addr) + ATMEL_PMERRLOC_##reg)
+
+#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
+	writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
+
+#define pmerrloc_readl_sigma_relaxed(addr, n) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
+
+#define pmerrloc_readl_el_relaxed(addr, n) \
+	readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
+
+/* Galois field dimension */
+#define PMECC_GF_DIMENSION_13			13
+#define PMECC_GF_DIMENSION_14			14
+
+#define PMECC_LOOKUP_TABLE_SIZE_512		0x2000
+#define PMECC_LOOKUP_TABLE_SIZE_1024		0x4000
+
+/* Time out value for reading PMECC status register */
+#define PMECC_MAX_TIMEOUT_MS			100
+
 #endif
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
  2012-06-28  8:21 ` Josh Wu
@ 2012-06-28  8:33   ` Nicolas Ferre
  -1 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28  8:33 UTC (permalink / raw)
  To: Josh Wu, linux-mtd, dedekind1
  Cc: hongxu.cn, richard.genoud, ivan.djelic, computersforpeace,
	plagnioj, linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> Those patches is based on the l2-mtd.git three.
> 
> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
> which includes the modification according to Richard Genoud's review suggestion.
> 
> Changes since v11,
> 	add documentation for atmel nand reg parameter and one example for PMECC support.
> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
> 	fix the bug: no correction when ecc data has bit flipped.
> 	add clearer debug information for data correction.
> 
> Josh Wu (2):
>   MTD: at91: add dt parameters for Atmel PMECC
>   MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>     ECC controller
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>  drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>  3 files changed, 951 insertions(+), 4 deletions(-)

Nice work for making this append Josh!

Thanks, I will add my "Acked-by" line but I guess that it is not so
important as many people kindly already reviewed and tested the patch
series: a big thanks to all of you!

Best regards,
-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-28  8:33   ` Nicolas Ferre
  0 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28  8:33 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> Those patches is based on the l2-mtd.git three.
> 
> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
> which includes the modification according to Richard Genoud's review suggestion.
> 
> Changes since v11,
> 	add documentation for atmel nand reg parameter and one example for PMECC support.
> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
> 	fix the bug: no correction when ecc data has bit flipped.
> 	add clearer debug information for data correction.
> 
> Josh Wu (2):
>   MTD: at91: add dt parameters for Atmel PMECC
>   MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>     ECC controller
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>  drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>  3 files changed, 951 insertions(+), 4 deletions(-)

Nice work for making this append Josh!

Thanks, I will add my "Acked-by" line but I guess that it is not so
important as many people kindly already reviewed and tested the patch
series: a big thanks to all of you!

Best regards,
-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
  2012-06-28  8:21   ` Josh Wu
@ 2012-06-28  8:33     ` Josh Wu
  -1 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:33 UTC (permalink / raw)
  To: Josh Wu
  Cc: hongxu.cn, dedekind1, richard.genoud, nicolas.ferre, linux-mtd,
	ivan.djelic, computersforpeace, plagnioj, linux-arm-kernel

Hi,  Artem

On 6/28/2012 4:21 PM, Josh Wu wrote:
> Add DT support for PMECC parameters.
>
> Signed-off-by: Josh Wu <josh.wu@atmel.com>

Sorry, I forgot following signed-off in this patch:
   Signed-off-by: Hong Xu <hong.xu@atmel.com>

Best Regards,
Josh Wu

> ---
> changes:
> since v11
>     1. update the documentation for reg parameter
>     2. add one example for PMECC support.
>
>   .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
>   drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
>   2 files changed, 90 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> index a200695..d555421 100644
> --- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> +++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> @@ -3,7 +3,9 @@ Atmel NAND flash
>   Required properties:
>   - compatible : "atmel,at91rm9200-nand".
>   - reg : should specify localbus address and size used for the chip,
> -	and if availlable the ECC.
> +	and hardware ECC controller if available.
> +	If the hardware ECC is PMECC, it should contain address and size for
> +	PMECC, PMECC Error Location controller and ROM which has lookup tables.
>   - atmel,nand-addr-offset : offset for the address latch.
>   - atmel,nand-cmd-offset : offset for the command latch.
>   - #address-cells, #size-cells : Must be present if the device has sub-nodes
> @@ -16,6 +18,15 @@ Optional properties:
>   - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
>     Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>     "soft_bch".
> +- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
> +  Only supported by at91sam9x5 or later sam9 product.
> +- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
> +  Controller. Supported values are: 2, 4, 8, 12, 24.
> +- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
> +  are: 512, 1024.
> +- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
> +  for different sector size. First one is for sector size 512, the next is for
> +  sector size 1024.
>   - nand-bus-width : 8 or 16 bus width if not present 8
>   - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>   
> @@ -39,3 +50,30 @@ nand0: nand@40000000,0 {
>   		...
>   	};
>   };
> +
> +/* for PMECC supported chips */
> +nand0: nand@40000000 {
> +	compatible = "atmel,at91rm9200-nand";
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	reg = < 0x40000000 0x10000000	/* bus addr & size */
> +		0xffffe000 0x00000600	/* PMECC addr & size */
> +		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
> +		0x00100000 0x00100000	/* ROM addr & size */
> +		>;
> +	atmel,nand-addr-offset = <21>;	/* ale */
> +	atmel,nand-cmd-offset = <22>;	/* cle */
> +	nand-on-flash-bbt;
> +	nand-ecc-mode = "hw";
> +	atmel,has-pmecc;	/* enable PMECC */
> +	atmel,pmecc-cap = <2>;
> +	atmel,pmecc-sector-size = <512>;
> +	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
> +	gpios = <&pioD 5 0	/* rdy */
> +		 &pioD 4 0	/* nce */
> +		 0		/* cd */
> +		>;
> +	partition@0 {
> +		...
> +	};
> +};
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index 7a41a04..b97ad9f 100644
> --- a/drivers/mtd/nand/atmel_nand.c
> +++ b/drivers/mtd/nand/atmel_nand.c
> @@ -93,6 +93,11 @@ struct atmel_nand_host {
>   
>   	struct completion	comp;
>   	struct dma_chan		*dma_chan;
> +
> +	bool			has_pmecc;
> +	u8			pmecc_corr_cap;
> +	u16			pmecc_sector_size;
> +	u32			pmecc_lookup_table_offset;
>   };
>   
>   static int cpu_has_dma(void)
> @@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
>   static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>   					 struct device_node *np)
>   {
> -	u32 val;
> +	u32 val, table_offset;
> +	u32 offset[2];
>   	int ecc_mode;
>   	struct atmel_nand_data *board = &host->board;
>   	enum of_gpio_flags flags;
> @@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>   	board->enable_pin = of_get_gpio(np, 1);
>   	board->det_pin = of_get_gpio(np, 2);
>   
> +	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
> +
> +	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
> +		return 0;	/* Not using PMECC */
> +
> +	/* use PMECC, get correction capability, sector size and lookup
> +	 * table offset.
> +	 */
> +	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Capability\n");
> +		return -EINVAL;
> +	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
> +	    (val != 24)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_corr_cap = (u8)val;
> +
> +	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
> +		return -EINVAL;
> +	} else if ((val != 512) && (val != 1024)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_sector_size = (u16)val;
> +
> +	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
> +			offset, 2) != 0) {
> +		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
> +
> +	if (!table_offset) {
> +		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	host->pmecc_lookup_table_offset = table_offset;
> +
>   	return 0;
>   }
>   #else

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
@ 2012-06-28  8:33     ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-28  8:33 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,  Artem

On 6/28/2012 4:21 PM, Josh Wu wrote:
> Add DT support for PMECC parameters.
>
> Signed-off-by: Josh Wu <josh.wu@atmel.com>

Sorry, I forgot following signed-off in this patch:
   Signed-off-by: Hong Xu <hong.xu@atmel.com>

Best Regards,
Josh Wu

> ---
> changes:
> since v11
>     1. update the documentation for reg parameter
>     2. add one example for PMECC support.
>
>   .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
>   drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
>   2 files changed, 90 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> index a200695..d555421 100644
> --- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> +++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> @@ -3,7 +3,9 @@ Atmel NAND flash
>   Required properties:
>   - compatible : "atmel,at91rm9200-nand".
>   - reg : should specify localbus address and size used for the chip,
> -	and if availlable the ECC.
> +	and hardware ECC controller if available.
> +	If the hardware ECC is PMECC, it should contain address and size for
> +	PMECC, PMECC Error Location controller and ROM which has lookup tables.
>   - atmel,nand-addr-offset : offset for the address latch.
>   - atmel,nand-cmd-offset : offset for the command latch.
>   - #address-cells, #size-cells : Must be present if the device has sub-nodes
> @@ -16,6 +18,15 @@ Optional properties:
>   - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
>     Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>     "soft_bch".
> +- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
> +  Only supported by at91sam9x5 or later sam9 product.
> +- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
> +  Controller. Supported values are: 2, 4, 8, 12, 24.
> +- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
> +  are: 512, 1024.
> +- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
> +  for different sector size. First one is for sector size 512, the next is for
> +  sector size 1024.
>   - nand-bus-width : 8 or 16 bus width if not present 8
>   - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>   
> @@ -39,3 +50,30 @@ nand0: nand at 40000000,0 {
>   		...
>   	};
>   };
> +
> +/* for PMECC supported chips */
> +nand0: nand at 40000000 {
> +	compatible = "atmel,at91rm9200-nand";
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	reg = < 0x40000000 0x10000000	/* bus addr & size */
> +		0xffffe000 0x00000600	/* PMECC addr & size */
> +		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
> +		0x00100000 0x00100000	/* ROM addr & size */
> +		>;
> +	atmel,nand-addr-offset = <21>;	/* ale */
> +	atmel,nand-cmd-offset = <22>;	/* cle */
> +	nand-on-flash-bbt;
> +	nand-ecc-mode = "hw";
> +	atmel,has-pmecc;	/* enable PMECC */
> +	atmel,pmecc-cap = <2>;
> +	atmel,pmecc-sector-size = <512>;
> +	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
> +	gpios = <&pioD 5 0	/* rdy */
> +		 &pioD 4 0	/* nce */
> +		 0		/* cd */
> +		>;
> +	partition at 0 {
> +		...
> +	};
> +};
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index 7a41a04..b97ad9f 100644
> --- a/drivers/mtd/nand/atmel_nand.c
> +++ b/drivers/mtd/nand/atmel_nand.c
> @@ -93,6 +93,11 @@ struct atmel_nand_host {
>   
>   	struct completion	comp;
>   	struct dma_chan		*dma_chan;
> +
> +	bool			has_pmecc;
> +	u8			pmecc_corr_cap;
> +	u16			pmecc_sector_size;
> +	u32			pmecc_lookup_table_offset;
>   };
>   
>   static int cpu_has_dma(void)
> @@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
>   static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>   					 struct device_node *np)
>   {
> -	u32 val;
> +	u32 val, table_offset;
> +	u32 offset[2];
>   	int ecc_mode;
>   	struct atmel_nand_data *board = &host->board;
>   	enum of_gpio_flags flags;
> @@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>   	board->enable_pin = of_get_gpio(np, 1);
>   	board->det_pin = of_get_gpio(np, 2);
>   
> +	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
> +
> +	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
> +		return 0;	/* Not using PMECC */
> +
> +	/* use PMECC, get correction capability, sector size and lookup
> +	 * table offset.
> +	 */
> +	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Capability\n");
> +		return -EINVAL;
> +	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
> +	    (val != 24)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_corr_cap = (u8)val;
> +
> +	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
> +		return -EINVAL;
> +	} else if ((val != 512) && (val != 1024)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_sector_size = (u16)val;
> +
> +	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
> +			offset, 2) != 0) {
> +		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
> +
> +	if (!table_offset) {
> +		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	host->pmecc_lookup_table_offset = table_offset;
> +
>   	return 0;
>   }
>   #else

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 2/2] MTD: at91: atmel_nand: Update driver to support Programmable Multibit ECC controller
  2012-06-28  8:21   ` Josh Wu
@ 2012-06-28  8:36     ` Nicolas Ferre
  -1 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28  8:36 UTC (permalink / raw)
  To: Josh Wu, linux-mtd
  Cc: hongxu.cn, dedekind1, richard.genoud, ivan.djelic,
	computersforpeace, plagnioj, linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> The Programmable Multibit 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 PMECC in this driver, the user needs to set the address and size of
> PMECC, PMECC error location controllers and ROM. And also needs to pass the
> correction capability, the sector size and ROM lookup table offsets via dt.
> 
> 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@atmel.com>
> Signed-off-by: Josh Wu <josh.wu@atmel.com>
> Tested-by: Richard Genoud <richard.genoud@gmail.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> ---
> since v11
>    1. fix bug: not report error if fail to get resources for PMECC ERRLOC or ROM.
>    2. fix bug: no correction when ecc data has bit flipped.
>    3. add clearer debug information for data correction.
> 
> since v10
>    1. atmel_nand_pmecc_write_page() will return -EIO when time out to read the pmecc status register.
>    2. increase the time-out duration to 100ms, which has more toleration.
>    3. add oob_required argument for pmecc read/write functions to align with v3.5-rc4.
> 
>  drivers/mtd/nand/atmel_nand.c     |  749 ++++++++++++++++++++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h |  114 +++++-
>  2 files changed, 861 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index b97ad9f..328b4d9 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 Multibit ECC support for various AT91 SoC
> + *     (C) Copyright 2012 ATMEL, Hong Xu
>   *
>   * 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
> @@ -98,8 +100,31 @@ struct atmel_nand_host {
>  	u8			pmecc_corr_cap;
>  	u16			pmecc_sector_size;
>  	u32			pmecc_lookup_table_offset;
> +
> +	int			pmecc_bytes_per_sector;
> +	int			pmecc_sector_number;
> +	int			pmecc_degree;	/* Degree of remainders */
> +	int			pmecc_cw_len;	/* Length of codeword */
> +
> +	void __iomem		*pmerrloc_base;
> +	void __iomem		*pmecc_rom_base;
> +
> +	/* lookup table for alpha_to and index_of */
> +	void __iomem		*pmecc_alpha_to;
> +	void __iomem		*pmecc_index_of;
> +
> +	/* data for pmecc computation */
> +	int16_t			*pmecc_partial_syn;
> +	int16_t			*pmecc_si;
> +	int16_t			*pmecc_smu;	/* Sigma table */
> +	int16_t			*pmecc_lmu;	/* polynomal order */
> +	int			*pmecc_mu;
> +	int			*pmecc_dmu;
> +	int			*pmecc_delta;
>  };
>  
> +static struct nand_ecclayout atmel_pmecc_oobinfo;
> +
>  static int cpu_has_dma(void)
>  {
>  	return cpu_is_at91sam9rl() || cpu_is_at91sam9g45();
> @@ -293,6 +318,705 @@ 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
> + *
> + * Following table shows what at91 PMECC supported:
> + * 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 int __devinit pmecc_get_ecc_bytes(int cap, int sector_size)
> +{
> +	int m = 12 + sector_size / 512;
> +	return (m * cap + 7) / 8;
> +}
> +
> +static void __devinit 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 = 2;
> +	layout->oobfree[0].length =
> +		oobsize - ecc_len - layout->oobfree[0].offset;
> +}
> +
> +static void __devinit __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
> +{
> +	int table_size;
> +
> +	table_size = host->pmecc_sector_size == 512 ?
> +		PMECC_LOOKUP_TABLE_SIZE_512 : PMECC_LOOKUP_TABLE_SIZE_1024;
> +
> +	return host->pmecc_rom_base + host->pmecc_lookup_table_offset +
> +			table_size * sizeof(int16_t);
> +}
> +
> +static void pmecc_data_free(struct atmel_nand_host *host)
> +{
> +	kfree(host->pmecc_partial_syn);
> +	kfree(host->pmecc_si);
> +	kfree(host->pmecc_lmu);
> +	kfree(host->pmecc_smu);
> +	kfree(host->pmecc_mu);
> +	kfree(host->pmecc_dmu);
> +	kfree(host->pmecc_delta);
> +}
> +
> +static int __devinit pmecc_data_alloc(struct atmel_nand_host *host)
> +{
> +	const int cap = host->pmecc_corr_cap;
> +
> +	host->pmecc_partial_syn = kzalloc((2 * cap + 1) * sizeof(int16_t),
> +					GFP_KERNEL);
> +	host->pmecc_si = kzalloc((2 * cap + 1) * sizeof(int16_t), GFP_KERNEL);
> +	host->pmecc_lmu = kzalloc((cap + 1) * sizeof(int16_t), GFP_KERNEL);
> +	host->pmecc_smu = kzalloc((cap + 2) * (2 * cap + 1) * sizeof(int16_t),
> +					GFP_KERNEL);
> +	host->pmecc_mu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +	host->pmecc_dmu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +	host->pmecc_delta = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +
> +	if (host->pmecc_partial_syn &&
> +			host->pmecc_si &&
> +			host->pmecc_lmu &&
> +			host->pmecc_smu &&
> +			host->pmecc_mu &&
> +			host->pmecc_dmu &&
> +			host->pmecc_delta)
> +		return 0;
> +
> +	/* error happened */
> +	pmecc_data_free(host);
> +	return -ENOMEM;
> +}
> +
> +static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i;
> +	uint32_t value;
> +
> +	/* Fill odd syndromes */
> +	for (i = 0; i < host->pmecc_corr_cap; i++) {
> +		value = pmecc_readl_rem_relaxed(host->ecc, sector, i / 2);
> +		if (i & 1)
> +			value >>= 16;
> +		value &= 0xffff;
> +		host->pmecc_partial_syn[(2 * i) + 1] = (int16_t)value;
> +	}
> +}
> +
> +static void pmecc_substitute(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int16_t __iomem *alpha_to = host->pmecc_alpha_to;
> +	int16_t __iomem *index_of = host->pmecc_index_of;
> +	int16_t *partial_syn = host->pmecc_partial_syn;
> +	const int cap = host->pmecc_corr_cap;
> +	int16_t *si;
> +	int i, j;
> +
> +	/* si[] is a table that holds the current syndrome value,
> +	 * an element of that table belongs to the field
> +	 */
> +	si = host->pmecc_si;
> +
> +	memset(&si[1], 0, sizeof(int16_t) * (2 * cap - 1));
> +
> +	/* Computation 2t syndromes based on S(x) */
> +	/* Odd syndromes */
> +	for (i = 1; i < 2 * cap; i += 2) {
> +		for (j = 0; j < host->pmecc_degree; j++) {
> +			if (partial_syn[i] & ((unsigned short)0x1 << j))
> +				si[i] = readw_relaxed(alpha_to + i * j) ^ si[i];
> +		}
> +	}
> +	/* Even syndrome = (Odd syndrome) ** 2 */
> +	for (i = 2, j = 1; j <= cap; i = ++j << 1) {
> +		if (si[j] == 0) {
> +			si[i] = 0;
> +		} else {
> +			int16_t tmp;
> +
> +			tmp = readw_relaxed(index_of + si[j]);
> +			tmp = (tmp * 2) % host->pmecc_cw_len;
> +			si[i] = readw_relaxed(alpha_to + tmp);
> +		}
> +	}
> +
> +	return;
> +}
> +
> +static void pmecc_get_sigma(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +
> +	int16_t *lmu = host->pmecc_lmu;
> +	int16_t *si = host->pmecc_si;
> +	int *mu = host->pmecc_mu;
> +	int *dmu = host->pmecc_dmu;	/* Discrepancy */
> +	int *delta = host->pmecc_delta; /* Delta order */
> +	int cw_len = host->pmecc_cw_len;
> +	const int16_t cap = host->pmecc_corr_cap;
> +	int16_t __iomem	*index_of = host->pmecc_index_of;
> +	int16_t __iomem	*alpha_to = host->pmecc_alpha_to;
> +	int i, j, k;
> +	uint32_t dmu_0_count, tmp;
> +	int16_t (*smu)[2 * cap + 1];
> +
> +	/* index of largest delta */
> +	int ro;
> +	int largest;
> +	int diff;
> +
> +	dmu_0_count = 0;
> +	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
> +
> +	/* First Row */
> +
> +	/* Mu */
> +	mu[0] = -1;
> +
> +	memset(&smu[0][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +	smu[0][0] = 1;
> +
> +	/* discrepancy set to 1 */
> +	dmu[0] = 1;
> +	/* polynom order set to 0 */
> +	lmu[0] = 0;
> +	delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
> +
> +	/* Second Row */
> +
> +	/* Mu */
> +	mu[1] = 0;
> +	/* Sigma(x) set to 1 */
> +	memset(&smu[1][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +	smu[1][0] = 1;
> +
> +	/* discrepancy set to S1 */
> +	dmu[1] = si[1];
> +
> +	/* polynom order set to 0 */
> +	lmu[1] = 0;
> +
> +	delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
> +
> +	/* Init the Sigma(x) last row */
> +	memset(&smu[cap + 1][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +
> +	for (i = 1; i <= cap; i++) {
> +		mu[i + 1] = i << 1;
> +		/* Begin Computing Sigma (Mu+1) and L(mu) */
> +		/* check if discrepancy is set to 0 */
> +		if (dmu[i] == 0) {
> +			dmu_0_count++;
> +
> +			tmp = ((cap - (lmu[i] >> 1) - 1) / 2);
> +			if ((cap - (lmu[i] >> 1) - 1) & 0x1)
> +				tmp += 2;
> +			else
> +				tmp += 1;
> +
> +			if (dmu_0_count == tmp) {
> +				for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
> +					smu[cap + 1][j] = smu[i][j];
> +				lmu[cap + 1] = lmu[i];
> +				return;
> +			}
> +
> +			/* copy polynom */
> +			for (j = 0; j <= lmu[i] >> 1; j++)
> +				smu[i + 1][j] = smu[i][j];
> +
> +			/* copy previous polynom order to the next */
> +			lmu[i + 1] = lmu[i];
> +		} else {
> +			ro = 0;
> +			largest = -1;
> +			/* find largest delta with dmu != 0 */
> +			for (j = 0; j < i; j++) {
> +				if ((dmu[j]) && (delta[j] > largest)) {
> +					largest = delta[j];
> +					ro = j;
> +				}
> +			}
> +
> +			/* compute difference */
> +			diff = (mu[i] - mu[ro]);
> +
> +			/* Compute degree of the new smu polynomial */
> +			if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
> +				lmu[i + 1] = lmu[i];
> +			else
> +				lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
> +
> +			/* Init smu[i+1] with 0 */
> +			for (k = 0; k < (2 * cap + 1); k++)
> +				smu[i + 1][k] = 0;
> +
> +			/* Compute smu[i+1] */
> +			for (k = 0; k <= lmu[ro] >> 1; k++) {
> +				int16_t a, b, c;
> +
> +				if (!(smu[ro][k] && dmu[i]))
> +					continue;
> +				a = readw_relaxed(index_of + dmu[i]);
> +				b = readw_relaxed(index_of + dmu[ro]);
> +				c = readw_relaxed(index_of + smu[ro][k]);
> +				tmp = a + (cw_len - b) + c;
> +				a = readw_relaxed(alpha_to + tmp % cw_len);
> +				smu[i + 1][k + diff] = a;
> +			}
> +
> +			for (k = 0; k <= lmu[i] >> 1; k++)
> +				smu[i + 1][k] ^= smu[i][k];
> +		}
> +
> +		/* End Computing Sigma (Mu+1) and L(mu) */
> +		/* In either case compute delta */
> +		delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
> +
> +		/* Do not compute discrepancy for the last iteration */
> +		if (i >= cap)
> +			continue;
> +
> +		for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
> +			tmp = 2 * (i - 1);
> +			if (k == 0) {
> +				dmu[i + 1] = si[tmp + 3];
> +			} else if (smu[i + 1][k] && si[tmp + 3 - k]) {
> +				int16_t a, b, c;
> +				a = readw_relaxed(index_of + smu[i + 1][k]);
> +				b = si[2 * (i - 1) + 3 - k];
> +				c = readw_relaxed(index_of + b);
> +				tmp = a + c;
> +				tmp %= cw_len;
> +				dmu[i + 1] = readw_relaxed(alpha_to + tmp) ^
> +					dmu[i + 1];
> +			}
> +		}
> +	}
> +
> +	return;
> +}
> +
> +static int pmecc_err_location(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	unsigned long end_time;
> +	const int cap = host->pmecc_corr_cap;
> +	int sector_size = host->pmecc_sector_size;
> +	int err_nbr = 0;	/* number of error */
> +	int roots_nbr;		/* number of roots */
> +	int i;
> +	uint32_t val;
> +	int16_t (*smu)[2 * cap + 1];
> +
> +	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
> +
> +	pmerrloc_writel(host->pmerrloc_base, ELDIS, PMERRLOC_DISABLE);
> +
> +	for (i = 0; i <= host->pmecc_lmu[cap + 1] >> 1; i++) {
> +		pmerrloc_writel_sigma_relaxed(host->pmerrloc_base, i,
> +				      smu[cap + 1][i]);
> +		err_nbr++;
> +	}
> +
> +	val = (err_nbr - 1) << 16;
> +	if (sector_size == 1024)
> +		val |= 1;
> +
> +	pmerrloc_writel(host->pmerrloc_base, ELCFG, val);
> +	pmerrloc_writel(host->pmerrloc_base, ELEN,
> +			sector_size * 8 + host->pmecc_degree * cap);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while (!(pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
> +		 & PMERRLOC_CALC_DONE)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to calculate error location.\n");
> +			return -1;
> +		}
> +		cpu_relax();
> +	}
> +
> +	roots_nbr = (pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
> +		& PMERRLOC_ERR_NUM_MASK) >> 8;
> +	/* Number of roots == degree of smu hence <= cap */
> +	if (roots_nbr == host->pmecc_lmu[cap + 1] >> 1)
> +		return err_nbr - 1;
> +
> +	/* Number of roots does not match the degree of smu
> +	 * unable to correct error */
> +	return -1;
> +}
> +
> +static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
> +		int sector_num, int extra_bytes, int err_nbr)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i = 0;
> +	int byte_pos, bit_pos, sector_size, ecc_size, pos;
> +	uint32_t tmp;
> +	uint8_t err_byte;
> +
> +	sector_size = host->pmecc_sector_size;
> +	ecc_size = nand_chip->ecc.bytes;
> +
> +	while (err_nbr) {
> +		tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1;
> +		byte_pos = tmp / 8;
> +		bit_pos  = tmp % 8;
> +
> +		if (byte_pos >= (sector_size + extra_bytes))
> +			BUG();	/* should never happen */
> +
> +		if (byte_pos < sector_size) {
> +			err_byte = *(buf + byte_pos);
> +			*(buf + byte_pos) ^= (1 << bit_pos);
> +
> +			pos = sector_num * host->pmecc_sector_size + byte_pos;
> +			dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
> +				pos, bit_pos, err_byte, *(buf + byte_pos));
> +		} else {
> +			/* Bit flip in OOB area */
> +			tmp = sector_num * host->pmecc_bytes_per_sector
> +					+ (byte_pos - sector_size);
> +			err_byte = ecc[tmp];
> +			ecc[tmp] ^= (1 << bit_pos);
> +
> +			pos = tmp + nand_chip->ecc.layout->eccpos[0];
> +			dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
> +				pos, bit_pos, err_byte, ecc[tmp]);
> +		}
> +
> +		i++;
> +		err_nbr--;
> +	}
> +
> +	return;
> +}
> +
> +static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf,
> +	u8 *ecc)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i, err_nbr, eccbytes;
> +	uint8_t *buf_pos;
> +
> +	eccbytes = nand_chip->ecc.bytes;
> +	for (i = 0; i < eccbytes; i++)
> +		if (ecc[i] != 0xff)
> +			goto normal_check;
> +	/* Erased page, return OK */
> +	return 0;
> +
> +normal_check:
> +	for (i = 0; i < host->pmecc_sector_number; i++) {
> +		err_nbr = 0;
> +		if (pmecc_stat & 0x1) {
> +			buf_pos = buf + i * host->pmecc_sector_size;
> +
> +			pmecc_gen_syndrome(mtd, i);
> +			pmecc_substitute(mtd);
> +			pmecc_get_sigma(mtd);
> +
> +			err_nbr = pmecc_err_location(mtd);
> +			if (err_nbr == -1) {
> +				dev_err(host->dev, "PMECC: Too many errors\n");
> +				mtd->ecc_stats.failed++;
> +				return -EIO;
> +			} else {
> +				pmecc_correct_data(mtd, buf_pos, ecc, i,
> +					host->pmecc_bytes_per_sector, err_nbr);
> +				mtd->ecc_stats.corrected += err_nbr;
> +			}
> +		}
> +		pmecc_stat >>= 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
> +	struct nand_chip *chip, uint8_t *buf, int oob_required, int page)
> +{
> +	struct atmel_nand_host *host = chip->priv;
> +	int eccsize = chip->ecc.size;
> +	uint8_t *oob = chip->oob_poi;
> +	uint32_t *eccpos = chip->ecc.layout->eccpos;
> +	uint32_t stat;
> +	unsigned long end_time;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG)
> +		& ~PMECC_CFG_WRITE_OP) | PMECC_CFG_AUTO_ENABLE);
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
> +
> +	chip->read_buf(mtd, buf, eccsize);
> +	chip->read_buf(mtd, oob, mtd->oobsize);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to get error status.\n");
> +			return -EIO;
> +		}
> +		cpu_relax();
> +	}
> +
> +	stat = pmecc_readl_relaxed(host->ecc, ISR);
> +	if (stat != 0)
> +		if (pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]) != 0)
> +			return -EIO;
> +
> +	return 0;
> +}
> +
> +static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
> +		struct nand_chip *chip, const uint8_t *buf, int oob_required)
> +{
> +	struct atmel_nand_host *host = chip->priv;
> +	uint32_t *eccpos = chip->ecc.layout->eccpos;
> +	int i, j;
> +	unsigned long end_time;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +
> +	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG) |
> +		PMECC_CFG_WRITE_OP) & ~PMECC_CFG_AUTO_ENABLE);
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
> +
> +	chip->write_buf(mtd, (u8 *)buf, mtd->writesize);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to get ECC value.\n");
> +			return -EIO;
> +		}
> +		cpu_relax();
> +	}
> +
> +	for (i = 0; i < host->pmecc_sector_number; i++) {
> +		for (j = 0; j < host->pmecc_bytes_per_sector; j++) {
> +			int pos;
> +
> +			pos = i * host->pmecc_bytes_per_sector + j;
> +			chip->oob_poi[eccpos[pos]] =
> +				pmecc_readb_ecc_relaxed(host->ecc, i, j);
> +		}
> +	}
> +	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
> +
> +	return 0;
> +}
> +
> +static void atmel_pmecc_core_init(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	uint32_t val = 0;
> +	struct nand_ecclayout *ecc_layout;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +
> +	switch (host->pmecc_corr_cap) {
> +	case 2:
> +		val = PMECC_CFG_BCH_ERR2;
> +		break;
> +	case 4:
> +		val = PMECC_CFG_BCH_ERR4;
> +		break;
> +	case 8:
> +		val = PMECC_CFG_BCH_ERR8;
> +		break;
> +	case 12:
> +		val = PMECC_CFG_BCH_ERR12;
> +		break;
> +	case 24:
> +		val = PMECC_CFG_BCH_ERR24;
> +		break;
> +	}
> +
> +	if (host->pmecc_sector_size == 512)
> +		val |= PMECC_CFG_SECTOR512;
> +	else if (host->pmecc_sector_size == 1024)
> +		val |= PMECC_CFG_SECTOR1024;
> +
> +	switch (host->pmecc_sector_number) {
> +	case 1:
> +		val |= PMECC_CFG_PAGE_1SECTOR;
> +		break;
> +	case 2:
> +		val |= PMECC_CFG_PAGE_2SECTORS;
> +		break;
> +	case 4:
> +		val |= PMECC_CFG_PAGE_4SECTORS;
> +		break;
> +	case 8:
> +		val |= PMECC_CFG_PAGE_8SECTORS;
> +		break;
> +	}
> +
> +	val |= (PMECC_CFG_READ_OP | PMECC_CFG_SPARE_DISABLE
> +		| PMECC_CFG_AUTO_DISABLE);
> +	pmecc_writel(host->ecc, CFG, val);
> +
> +	ecc_layout = nand_chip->ecc.layout;
> +	pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1);
> +	pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]);
> +	pmecc_writel(host->ecc, EADDR,
> +			ecc_layout->eccpos[ecc_layout->eccbytes - 1]);
> +	/* See datasheet about PMECC Clock Control Register */
> +	pmecc_writel(host->ecc, CLK, 2);
> +	pmecc_writel(host->ecc, IDR, 0xff);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +}
> +
> +static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
> +					 struct atmel_nand_host *host)
> +{
> +	struct mtd_info *mtd = &host->mtd;
> +	struct nand_chip *nand_chip = &host->nand_chip;
> +	struct resource *regs, *regs_pmerr, *regs_rom;
> +	int cap, sector_size, err_no;
> +
> +	cap = host->pmecc_corr_cap;
> +	sector_size = host->pmecc_sector_size;
> +	dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
> +		 cap, sector_size);
> +
> +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	if (!regs) {
> +		dev_warn(host->dev,
> +			"Can't get I/O resource regs for PMECC controller, rolling back on software ECC\n");
> +		nand_chip->ecc.mode = NAND_ECC_SOFT;
> +		return 0;
> +	}
> +
> +	host->ecc = ioremap(regs->start, resource_size(regs));
> +	if (host->ecc == NULL) {
> +		dev_err(host->dev, "ioremap failed\n");
> +		err_no = -EIO;
> +		goto err_pmecc_ioremap;
> +	}
> +
> +	regs_pmerr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +	regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3);
> +	if (regs_pmerr && regs_rom) {
> +		host->pmerrloc_base = ioremap(regs_pmerr->start,
> +			resource_size(regs_pmerr));
> +		host->pmecc_rom_base = ioremap(regs_rom->start,
> +			resource_size(regs_rom));
> +	}
> +
> +	if (!host->pmerrloc_base || !host->pmecc_rom_base) {
> +		dev_err(host->dev,
> +			"Can not get I/O resource for PMECC ERRLOC controller or ROM!\n");
> +		err_no = -EIO;
> +		goto err_pmloc_ioremap;
> +	}
> +
> +	/* ECC is calculated for the whole page (1 step) */
> +	nand_chip->ecc.size = mtd->writesize;
> +
> +	/* set ECC page size and oob layout */
> +	switch (mtd->writesize) {
> +	case 2048:
> +		host->pmecc_degree = PMECC_GF_DIMENSION_13;
> +		host->pmecc_cw_len = (1 << host->pmecc_degree) - 1;
> +		host->pmecc_sector_number = mtd->writesize / sector_size;
> +		host->pmecc_bytes_per_sector = pmecc_get_ecc_bytes(
> +			cap, sector_size);
> +		host->pmecc_alpha_to = pmecc_get_alpha_to(host);
> +		host->pmecc_index_of = host->pmecc_rom_base +
> +			host->pmecc_lookup_table_offset;
> +
> +		nand_chip->ecc.steps = 1;
> +		nand_chip->ecc.strength = cap;
> +		nand_chip->ecc.bytes = host->pmecc_bytes_per_sector *
> +				       host->pmecc_sector_number;
> +		if (nand_chip->ecc.bytes > mtd->oobsize - 2) {
> +			dev_err(host->dev, "No room for ECC bytes\n");
> +			err_no = -EINVAL;
> +			goto err_no_ecc_room;
> +		}
> +		pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
> +					mtd->oobsize,
> +					nand_chip->ecc.bytes);
> +		nand_chip->ecc.layout = &atmel_pmecc_oobinfo;
> +		break;
> +	case 512:
> +	case 1024:
> +	case 4096:
> +		/* TODO */
> +		dev_warn(host->dev,
> +			"Unsupported page size for PMECC, use Software ECC\n");
> +	default:
> +		/* page size not handled by HW ECC */
> +		/* switching back to soft ECC */
> +		nand_chip->ecc.mode = NAND_ECC_SOFT;
> +		return 0;
> +	}
> +
> +	/* Allocate data for PMECC computation */
> +	err_no = pmecc_data_alloc(host);
> +	if (err_no) {
> +		dev_err(host->dev,
> +				"Cannot allocate memory for PMECC computation!\n");
> +		goto err_pmecc_data_alloc;
> +	}
> +
> +	nand_chip->ecc.read_page = atmel_nand_pmecc_read_page;
> +	nand_chip->ecc.write_page = atmel_nand_pmecc_write_page;
> +
> +	atmel_pmecc_core_init(mtd);
> +
> +	return 0;
> +
> +err_pmecc_data_alloc:
> +err_no_ecc_room:
> +err_pmloc_ioremap:
> +	iounmap(host->ecc);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
> +err_pmecc_ioremap:
> +	return err_no;
> +}
> +
> +/*
>   * Calculate HW ECC
>   *
>   * function called after a write
> @@ -747,7 +1471,11 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
>  	}
>  
>  	if (nand_chip->ecc.mode == NAND_ECC_HW) {
> -		res = atmel_hw_nand_init_params(pdev, host);
> +		if (host->has_pmecc)
> +			res = atmel_pmecc_nand_init_params(pdev, host);
> +		else
> +			res = atmel_hw_nand_init_params(pdev, host);
> +
>  		if (res != 0)
>  			goto err_hw_ecc;
>  	}
> @@ -766,8 +1494,16 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
>  		return res;
>  
>  err_scan_tail:
> +	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
> +		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +		pmecc_data_free(host);
> +	}
>  	if (host->ecc)
>  		iounmap(host->ecc);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
>  err_hw_ecc:
>  err_scan_ident:
>  err_no_card:
> @@ -793,8 +1529,19 @@ static int __exit atmel_nand_remove(struct platform_device *pdev)
>  
>  	atmel_nand_disable(host);
>  
> +	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
> +		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +		pmerrloc_writel(host->pmerrloc_base, ELDIS,
> +				PMERRLOC_DISABLE);
> +		pmecc_data_free(host);
> +	}
> +
>  	if (host->ecc)
>  		iounmap(host->ecc);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
>  
>  	if (host->dma_chan)
>  		dma_release_channel(host->dma_chan);
> diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h
> index 578c776..8a1e9a6 100644
> --- a/drivers/mtd/nand/atmel_nand_ecc.h
> +++ b/drivers/mtd/nand/atmel_nand_ecc.h
> @@ -3,7 +3,7 @@
>   * Based on AT91SAM9260 datasheet revision B.
>   *
>   * Copyright (C) 2007 Andrew Victor
> - * Copyright (C) 2007 Atmel Corporation.
> + * Copyright (C) 2007 - 2012 Atmel Corporation.
>   *
>   * This program is free software; you can redistribute it and/or modify it
>   * under the terms of the GNU General Public License as published by the
> @@ -36,4 +36,116 @@
>  #define ATMEL_ECC_NPR		0x10			/* NParity register */
>  #define		ATMEL_ECC_NPARITY	(0xffff << 0)		/* NParity */
>  
> +/* PMECC Register Definitions */
> +#define ATMEL_PMECC_CFG			0x000	/* Configuration Register */
> +#define		PMECC_CFG_BCH_ERR2		(0 << 0)
> +#define		PMECC_CFG_BCH_ERR4		(1 << 0)
> +#define		PMECC_CFG_BCH_ERR8		(2 << 0)
> +#define		PMECC_CFG_BCH_ERR12		(3 << 0)
> +#define		PMECC_CFG_BCH_ERR24		(4 << 0)
> +
> +#define		PMECC_CFG_SECTOR512		(0 << 4)
> +#define		PMECC_CFG_SECTOR1024		(1 << 4)
> +
> +#define		PMECC_CFG_PAGE_1SECTOR		(0 << 8)
> +#define		PMECC_CFG_PAGE_2SECTORS		(1 << 8)
> +#define		PMECC_CFG_PAGE_4SECTORS		(2 << 8)
> +#define		PMECC_CFG_PAGE_8SECTORS		(3 << 8)
> +
> +#define		PMECC_CFG_READ_OP		(0 << 12)
> +#define		PMECC_CFG_WRITE_OP		(1 << 12)
> +
> +#define		PMECC_CFG_SPARE_ENABLE		(1 << 16)
> +#define		PMECC_CFG_SPARE_DISABLE		(0 << 16)
> +
> +#define		PMECC_CFG_AUTO_ENABLE		(1 << 20)
> +#define		PMECC_CFG_AUTO_DISABLE		(0 << 20)
> +
> +#define ATMEL_PMECC_SAREA		0x004	/* Spare area size */
> +#define ATMEL_PMECC_SADDR		0x008	/* PMECC starting address */
> +#define ATMEL_PMECC_EADDR		0x00c	/* PMECC ending address */
> +#define ATMEL_PMECC_CLK			0x010	/* PMECC clock control */
> +#define		PMECC_CLK_133MHZ		(2 << 0)
> +
> +#define ATMEL_PMECC_CTRL		0x014	/* PMECC control register */
> +#define		PMECC_CTRL_RST			(1 << 0)
> +#define		PMECC_CTRL_DATA			(1 << 1)
> +#define		PMECC_CTRL_USER			(1 << 2)
> +#define		PMECC_CTRL_ENABLE		(1 << 4)
> +#define		PMECC_CTRL_DISABLE		(1 << 5)
> +
> +#define ATMEL_PMECC_SR			0x018	/* PMECC status register */
> +#define		PMECC_SR_BUSY			(1 << 0)
> +#define		PMECC_SR_ENABLE			(1 << 4)
> +
> +#define ATMEL_PMECC_IER			0x01c	/* PMECC interrupt enable */
> +#define		PMECC_IER_ENABLE		(1 << 0)
> +#define ATMEL_PMECC_IDR			0x020	/* PMECC interrupt disable */
> +#define		PMECC_IER_DISABLE		(1 << 0)
> +#define ATMEL_PMECC_IMR			0x024	/* PMECC interrupt mask */
> +#define		PMECC_IER_MASK			(1 << 0)
> +#define ATMEL_PMECC_ISR			0x028	/* PMECC interrupt status */
> +#define ATMEL_PMECC_ECCx		0x040	/* PMECC ECC x */
> +#define ATMEL_PMECC_REMx		0x240	/* PMECC REM x */
> +
> +/* PMERRLOC Register Definitions */
> +#define ATMEL_PMERRLOC_ELCFG		0x000	/* Error location config */
> +#define		PMERRLOC_ELCFG_SECTOR_512	(0 << 0)
> +#define		PMERRLOC_ELCFG_SECTOR_1024	(1 << 0)
> +#define		PMERRLOC_ELCFG_NUM_ERRORS(n)	((n) << 16)
> +
> +#define ATMEL_PMERRLOC_ELPRIM		0x004	/* Error location primitive */
> +#define ATMEL_PMERRLOC_ELEN		0x008	/* Error location enable */
> +#define ATMEL_PMERRLOC_ELDIS		0x00c	/* Error location disable */
> +#define		PMERRLOC_DISABLE		(1 << 0)
> +
> +#define ATMEL_PMERRLOC_ELSR		0x010	/* Error location status */
> +#define		PMERRLOC_ELSR_BUSY		(1 << 0)
> +#define ATMEL_PMERRLOC_ELIER		0x014	/* Error location int enable */
> +#define ATMEL_PMERRLOC_ELIDR		0x018	/* Error location int disable */
> +#define ATMEL_PMERRLOC_ELIMR		0x01c	/* Error location int mask */
> +#define ATMEL_PMERRLOC_ELISR		0x020	/* Error location int status */
> +#define		PMERRLOC_ERR_NUM_MASK		(0x1f << 8)
> +#define		PMERRLOC_CALC_DONE		(1 << 0)
> +#define ATMEL_PMERRLOC_SIGMAx		0x028	/* Error location SIGMA x */
> +#define ATMEL_PMERRLOC_ELx		0x08c	/* Error location x */
> +
> +/* Register access macros for PMECC */
> +#define pmecc_readl_relaxed(addr, reg) \
> +	readl_relaxed((addr) + ATMEL_PMECC_##reg)
> +
> +#define pmecc_writel(addr, reg, value) \
> +	writel((value), (addr) + ATMEL_PMECC_##reg)
> +
> +#define pmecc_readb_ecc_relaxed(addr, sector, n) \
> +	readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
> +
> +#define pmecc_readl_rem_relaxed(addr, sector, n) \
> +	readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
> +
> +#define pmerrloc_readl_relaxed(addr, reg) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
> +
> +#define pmerrloc_writel(addr, reg, value) \
> +	writel((value), (addr) + ATMEL_PMERRLOC_##reg)
> +
> +#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
> +	writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
> +
> +#define pmerrloc_readl_sigma_relaxed(addr, n) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
> +
> +#define pmerrloc_readl_el_relaxed(addr, n) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
> +
> +/* Galois field dimension */
> +#define PMECC_GF_DIMENSION_13			13
> +#define PMECC_GF_DIMENSION_14			14
> +
> +#define PMECC_LOOKUP_TABLE_SIZE_512		0x2000
> +#define PMECC_LOOKUP_TABLE_SIZE_1024		0x4000
> +
> +/* Time out value for reading PMECC status register */
> +#define PMECC_MAX_TIMEOUT_MS			100
> +
>  #endif
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 2/2] MTD: at91: atmel_nand: Update driver to support Programmable Multibit ECC controller
@ 2012-06-28  8:36     ` Nicolas Ferre
  0 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28  8:36 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> The Programmable Multibit 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 PMECC in this driver, the user needs to set the address and size of
> PMECC, PMECC error location controllers and ROM. And also needs to pass the
> correction capability, the sector size and ROM lookup table offsets via dt.
> 
> 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@atmel.com>
> Signed-off-by: Josh Wu <josh.wu@atmel.com>
> Tested-by: Richard Genoud <richard.genoud@gmail.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> ---
> since v11
>    1. fix bug: not report error if fail to get resources for PMECC ERRLOC or ROM.
>    2. fix bug: no correction when ecc data has bit flipped.
>    3. add clearer debug information for data correction.
> 
> since v10
>    1. atmel_nand_pmecc_write_page() will return -EIO when time out to read the pmecc status register.
>    2. increase the time-out duration to 100ms, which has more toleration.
>    3. add oob_required argument for pmecc read/write functions to align with v3.5-rc4.
> 
>  drivers/mtd/nand/atmel_nand.c     |  749 ++++++++++++++++++++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h |  114 +++++-
>  2 files changed, 861 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index b97ad9f..328b4d9 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 Multibit ECC support for various AT91 SoC
> + *     (C) Copyright 2012 ATMEL, Hong Xu
>   *
>   * 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
> @@ -98,8 +100,31 @@ struct atmel_nand_host {
>  	u8			pmecc_corr_cap;
>  	u16			pmecc_sector_size;
>  	u32			pmecc_lookup_table_offset;
> +
> +	int			pmecc_bytes_per_sector;
> +	int			pmecc_sector_number;
> +	int			pmecc_degree;	/* Degree of remainders */
> +	int			pmecc_cw_len;	/* Length of codeword */
> +
> +	void __iomem		*pmerrloc_base;
> +	void __iomem		*pmecc_rom_base;
> +
> +	/* lookup table for alpha_to and index_of */
> +	void __iomem		*pmecc_alpha_to;
> +	void __iomem		*pmecc_index_of;
> +
> +	/* data for pmecc computation */
> +	int16_t			*pmecc_partial_syn;
> +	int16_t			*pmecc_si;
> +	int16_t			*pmecc_smu;	/* Sigma table */
> +	int16_t			*pmecc_lmu;	/* polynomal order */
> +	int			*pmecc_mu;
> +	int			*pmecc_dmu;
> +	int			*pmecc_delta;
>  };
>  
> +static struct nand_ecclayout atmel_pmecc_oobinfo;
> +
>  static int cpu_has_dma(void)
>  {
>  	return cpu_is_at91sam9rl() || cpu_is_at91sam9g45();
> @@ -293,6 +318,705 @@ 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
> + *
> + * Following table shows what at91 PMECC supported:
> + * 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 int __devinit pmecc_get_ecc_bytes(int cap, int sector_size)
> +{
> +	int m = 12 + sector_size / 512;
> +	return (m * cap + 7) / 8;
> +}
> +
> +static void __devinit 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 = 2;
> +	layout->oobfree[0].length =
> +		oobsize - ecc_len - layout->oobfree[0].offset;
> +}
> +
> +static void __devinit __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
> +{
> +	int table_size;
> +
> +	table_size = host->pmecc_sector_size == 512 ?
> +		PMECC_LOOKUP_TABLE_SIZE_512 : PMECC_LOOKUP_TABLE_SIZE_1024;
> +
> +	return host->pmecc_rom_base + host->pmecc_lookup_table_offset +
> +			table_size * sizeof(int16_t);
> +}
> +
> +static void pmecc_data_free(struct atmel_nand_host *host)
> +{
> +	kfree(host->pmecc_partial_syn);
> +	kfree(host->pmecc_si);
> +	kfree(host->pmecc_lmu);
> +	kfree(host->pmecc_smu);
> +	kfree(host->pmecc_mu);
> +	kfree(host->pmecc_dmu);
> +	kfree(host->pmecc_delta);
> +}
> +
> +static int __devinit pmecc_data_alloc(struct atmel_nand_host *host)
> +{
> +	const int cap = host->pmecc_corr_cap;
> +
> +	host->pmecc_partial_syn = kzalloc((2 * cap + 1) * sizeof(int16_t),
> +					GFP_KERNEL);
> +	host->pmecc_si = kzalloc((2 * cap + 1) * sizeof(int16_t), GFP_KERNEL);
> +	host->pmecc_lmu = kzalloc((cap + 1) * sizeof(int16_t), GFP_KERNEL);
> +	host->pmecc_smu = kzalloc((cap + 2) * (2 * cap + 1) * sizeof(int16_t),
> +					GFP_KERNEL);
> +	host->pmecc_mu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +	host->pmecc_dmu = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +	host->pmecc_delta = kzalloc((cap + 1) * sizeof(int), GFP_KERNEL);
> +
> +	if (host->pmecc_partial_syn &&
> +			host->pmecc_si &&
> +			host->pmecc_lmu &&
> +			host->pmecc_smu &&
> +			host->pmecc_mu &&
> +			host->pmecc_dmu &&
> +			host->pmecc_delta)
> +		return 0;
> +
> +	/* error happened */
> +	pmecc_data_free(host);
> +	return -ENOMEM;
> +}
> +
> +static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i;
> +	uint32_t value;
> +
> +	/* Fill odd syndromes */
> +	for (i = 0; i < host->pmecc_corr_cap; i++) {
> +		value = pmecc_readl_rem_relaxed(host->ecc, sector, i / 2);
> +		if (i & 1)
> +			value >>= 16;
> +		value &= 0xffff;
> +		host->pmecc_partial_syn[(2 * i) + 1] = (int16_t)value;
> +	}
> +}
> +
> +static void pmecc_substitute(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int16_t __iomem *alpha_to = host->pmecc_alpha_to;
> +	int16_t __iomem *index_of = host->pmecc_index_of;
> +	int16_t *partial_syn = host->pmecc_partial_syn;
> +	const int cap = host->pmecc_corr_cap;
> +	int16_t *si;
> +	int i, j;
> +
> +	/* si[] is a table that holds the current syndrome value,
> +	 * an element of that table belongs to the field
> +	 */
> +	si = host->pmecc_si;
> +
> +	memset(&si[1], 0, sizeof(int16_t) * (2 * cap - 1));
> +
> +	/* Computation 2t syndromes based on S(x) */
> +	/* Odd syndromes */
> +	for (i = 1; i < 2 * cap; i += 2) {
> +		for (j = 0; j < host->pmecc_degree; j++) {
> +			if (partial_syn[i] & ((unsigned short)0x1 << j))
> +				si[i] = readw_relaxed(alpha_to + i * j) ^ si[i];
> +		}
> +	}
> +	/* Even syndrome = (Odd syndrome) ** 2 */
> +	for (i = 2, j = 1; j <= cap; i = ++j << 1) {
> +		if (si[j] == 0) {
> +			si[i] = 0;
> +		} else {
> +			int16_t tmp;
> +
> +			tmp = readw_relaxed(index_of + si[j]);
> +			tmp = (tmp * 2) % host->pmecc_cw_len;
> +			si[i] = readw_relaxed(alpha_to + tmp);
> +		}
> +	}
> +
> +	return;
> +}
> +
> +static void pmecc_get_sigma(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +
> +	int16_t *lmu = host->pmecc_lmu;
> +	int16_t *si = host->pmecc_si;
> +	int *mu = host->pmecc_mu;
> +	int *dmu = host->pmecc_dmu;	/* Discrepancy */
> +	int *delta = host->pmecc_delta; /* Delta order */
> +	int cw_len = host->pmecc_cw_len;
> +	const int16_t cap = host->pmecc_corr_cap;
> +	int16_t __iomem	*index_of = host->pmecc_index_of;
> +	int16_t __iomem	*alpha_to = host->pmecc_alpha_to;
> +	int i, j, k;
> +	uint32_t dmu_0_count, tmp;
> +	int16_t (*smu)[2 * cap + 1];
> +
> +	/* index of largest delta */
> +	int ro;
> +	int largest;
> +	int diff;
> +
> +	dmu_0_count = 0;
> +	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
> +
> +	/* First Row */
> +
> +	/* Mu */
> +	mu[0] = -1;
> +
> +	memset(&smu[0][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +	smu[0][0] = 1;
> +
> +	/* discrepancy set to 1 */
> +	dmu[0] = 1;
> +	/* polynom order set to 0 */
> +	lmu[0] = 0;
> +	delta[0] = (mu[0] * 2 - lmu[0]) >> 1;
> +
> +	/* Second Row */
> +
> +	/* Mu */
> +	mu[1] = 0;
> +	/* Sigma(x) set to 1 */
> +	memset(&smu[1][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +	smu[1][0] = 1;
> +
> +	/* discrepancy set to S1 */
> +	dmu[1] = si[1];
> +
> +	/* polynom order set to 0 */
> +	lmu[1] = 0;
> +
> +	delta[1] = (mu[1] * 2 - lmu[1]) >> 1;
> +
> +	/* Init the Sigma(x) last row */
> +	memset(&smu[cap + 1][0], 0,
> +		sizeof(int16_t) * (2 * cap + 1));
> +
> +	for (i = 1; i <= cap; i++) {
> +		mu[i + 1] = i << 1;
> +		/* Begin Computing Sigma (Mu+1) and L(mu) */
> +		/* check if discrepancy is set to 0 */
> +		if (dmu[i] == 0) {
> +			dmu_0_count++;
> +
> +			tmp = ((cap - (lmu[i] >> 1) - 1) / 2);
> +			if ((cap - (lmu[i] >> 1) - 1) & 0x1)
> +				tmp += 2;
> +			else
> +				tmp += 1;
> +
> +			if (dmu_0_count == tmp) {
> +				for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
> +					smu[cap + 1][j] = smu[i][j];
> +				lmu[cap + 1] = lmu[i];
> +				return;
> +			}
> +
> +			/* copy polynom */
> +			for (j = 0; j <= lmu[i] >> 1; j++)
> +				smu[i + 1][j] = smu[i][j];
> +
> +			/* copy previous polynom order to the next */
> +			lmu[i + 1] = lmu[i];
> +		} else {
> +			ro = 0;
> +			largest = -1;
> +			/* find largest delta with dmu != 0 */
> +			for (j = 0; j < i; j++) {
> +				if ((dmu[j]) && (delta[j] > largest)) {
> +					largest = delta[j];
> +					ro = j;
> +				}
> +			}
> +
> +			/* compute difference */
> +			diff = (mu[i] - mu[ro]);
> +
> +			/* Compute degree of the new smu polynomial */
> +			if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
> +				lmu[i + 1] = lmu[i];
> +			else
> +				lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;
> +
> +			/* Init smu[i+1] with 0 */
> +			for (k = 0; k < (2 * cap + 1); k++)
> +				smu[i + 1][k] = 0;
> +
> +			/* Compute smu[i+1] */
> +			for (k = 0; k <= lmu[ro] >> 1; k++) {
> +				int16_t a, b, c;
> +
> +				if (!(smu[ro][k] && dmu[i]))
> +					continue;
> +				a = readw_relaxed(index_of + dmu[i]);
> +				b = readw_relaxed(index_of + dmu[ro]);
> +				c = readw_relaxed(index_of + smu[ro][k]);
> +				tmp = a + (cw_len - b) + c;
> +				a = readw_relaxed(alpha_to + tmp % cw_len);
> +				smu[i + 1][k + diff] = a;
> +			}
> +
> +			for (k = 0; k <= lmu[i] >> 1; k++)
> +				smu[i + 1][k] ^= smu[i][k];
> +		}
> +
> +		/* End Computing Sigma (Mu+1) and L(mu) */
> +		/* In either case compute delta */
> +		delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;
> +
> +		/* Do not compute discrepancy for the last iteration */
> +		if (i >= cap)
> +			continue;
> +
> +		for (k = 0; k <= (lmu[i + 1] >> 1); k++) {
> +			tmp = 2 * (i - 1);
> +			if (k == 0) {
> +				dmu[i + 1] = si[tmp + 3];
> +			} else if (smu[i + 1][k] && si[tmp + 3 - k]) {
> +				int16_t a, b, c;
> +				a = readw_relaxed(index_of + smu[i + 1][k]);
> +				b = si[2 * (i - 1) + 3 - k];
> +				c = readw_relaxed(index_of + b);
> +				tmp = a + c;
> +				tmp %= cw_len;
> +				dmu[i + 1] = readw_relaxed(alpha_to + tmp) ^
> +					dmu[i + 1];
> +			}
> +		}
> +	}
> +
> +	return;
> +}
> +
> +static int pmecc_err_location(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	unsigned long end_time;
> +	const int cap = host->pmecc_corr_cap;
> +	int sector_size = host->pmecc_sector_size;
> +	int err_nbr = 0;	/* number of error */
> +	int roots_nbr;		/* number of roots */
> +	int i;
> +	uint32_t val;
> +	int16_t (*smu)[2 * cap + 1];
> +
> +	smu = (int16_t(*)[2 * cap + 1])host->pmecc_smu;
> +
> +	pmerrloc_writel(host->pmerrloc_base, ELDIS, PMERRLOC_DISABLE);
> +
> +	for (i = 0; i <= host->pmecc_lmu[cap + 1] >> 1; i++) {
> +		pmerrloc_writel_sigma_relaxed(host->pmerrloc_base, i,
> +				      smu[cap + 1][i]);
> +		err_nbr++;
> +	}
> +
> +	val = (err_nbr - 1) << 16;
> +	if (sector_size == 1024)
> +		val |= 1;
> +
> +	pmerrloc_writel(host->pmerrloc_base, ELCFG, val);
> +	pmerrloc_writel(host->pmerrloc_base, ELEN,
> +			sector_size * 8 + host->pmecc_degree * cap);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while (!(pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
> +		 & PMERRLOC_CALC_DONE)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to calculate error location.\n");
> +			return -1;
> +		}
> +		cpu_relax();
> +	}
> +
> +	roots_nbr = (pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR)
> +		& PMERRLOC_ERR_NUM_MASK) >> 8;
> +	/* Number of roots == degree of smu hence <= cap */
> +	if (roots_nbr == host->pmecc_lmu[cap + 1] >> 1)
> +		return err_nbr - 1;
> +
> +	/* Number of roots does not match the degree of smu
> +	 * unable to correct error */
> +	return -1;
> +}
> +
> +static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
> +		int sector_num, int extra_bytes, int err_nbr)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i = 0;
> +	int byte_pos, bit_pos, sector_size, ecc_size, pos;
> +	uint32_t tmp;
> +	uint8_t err_byte;
> +
> +	sector_size = host->pmecc_sector_size;
> +	ecc_size = nand_chip->ecc.bytes;
> +
> +	while (err_nbr) {
> +		tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1;
> +		byte_pos = tmp / 8;
> +		bit_pos  = tmp % 8;
> +
> +		if (byte_pos >= (sector_size + extra_bytes))
> +			BUG();	/* should never happen */
> +
> +		if (byte_pos < sector_size) {
> +			err_byte = *(buf + byte_pos);
> +			*(buf + byte_pos) ^= (1 << bit_pos);
> +
> +			pos = sector_num * host->pmecc_sector_size + byte_pos;
> +			dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
> +				pos, bit_pos, err_byte, *(buf + byte_pos));
> +		} else {
> +			/* Bit flip in OOB area */
> +			tmp = sector_num * host->pmecc_bytes_per_sector
> +					+ (byte_pos - sector_size);
> +			err_byte = ecc[tmp];
> +			ecc[tmp] ^= (1 << bit_pos);
> +
> +			pos = tmp + nand_chip->ecc.layout->eccpos[0];
> +			dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
> +				pos, bit_pos, err_byte, ecc[tmp]);
> +		}
> +
> +		i++;
> +		err_nbr--;
> +	}
> +
> +	return;
> +}
> +
> +static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf,
> +	u8 *ecc)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	int i, err_nbr, eccbytes;
> +	uint8_t *buf_pos;
> +
> +	eccbytes = nand_chip->ecc.bytes;
> +	for (i = 0; i < eccbytes; i++)
> +		if (ecc[i] != 0xff)
> +			goto normal_check;
> +	/* Erased page, return OK */
> +	return 0;
> +
> +normal_check:
> +	for (i = 0; i < host->pmecc_sector_number; i++) {
> +		err_nbr = 0;
> +		if (pmecc_stat & 0x1) {
> +			buf_pos = buf + i * host->pmecc_sector_size;
> +
> +			pmecc_gen_syndrome(mtd, i);
> +			pmecc_substitute(mtd);
> +			pmecc_get_sigma(mtd);
> +
> +			err_nbr = pmecc_err_location(mtd);
> +			if (err_nbr == -1) {
> +				dev_err(host->dev, "PMECC: Too many errors\n");
> +				mtd->ecc_stats.failed++;
> +				return -EIO;
> +			} else {
> +				pmecc_correct_data(mtd, buf_pos, ecc, i,
> +					host->pmecc_bytes_per_sector, err_nbr);
> +				mtd->ecc_stats.corrected += err_nbr;
> +			}
> +		}
> +		pmecc_stat >>= 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
> +	struct nand_chip *chip, uint8_t *buf, int oob_required, int page)
> +{
> +	struct atmel_nand_host *host = chip->priv;
> +	int eccsize = chip->ecc.size;
> +	uint8_t *oob = chip->oob_poi;
> +	uint32_t *eccpos = chip->ecc.layout->eccpos;
> +	uint32_t stat;
> +	unsigned long end_time;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG)
> +		& ~PMECC_CFG_WRITE_OP) | PMECC_CFG_AUTO_ENABLE);
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
> +
> +	chip->read_buf(mtd, buf, eccsize);
> +	chip->read_buf(mtd, oob, mtd->oobsize);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to get error status.\n");
> +			return -EIO;
> +		}
> +		cpu_relax();
> +	}
> +
> +	stat = pmecc_readl_relaxed(host->ecc, ISR);
> +	if (stat != 0)
> +		if (pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]) != 0)
> +			return -EIO;
> +
> +	return 0;
> +}
> +
> +static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
> +		struct nand_chip *chip, const uint8_t *buf, int oob_required)
> +{
> +	struct atmel_nand_host *host = chip->priv;
> +	uint32_t *eccpos = chip->ecc.layout->eccpos;
> +	int i, j;
> +	unsigned long end_time;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +
> +	pmecc_writel(host->ecc, CFG, (pmecc_readl_relaxed(host->ecc, CFG) |
> +		PMECC_CFG_WRITE_OP) & ~PMECC_CFG_AUTO_ENABLE);
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA);
> +
> +	chip->write_buf(mtd, (u8 *)buf, mtd->writesize);
> +
> +	end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS);
> +	while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) {
> +		if (unlikely(time_after(jiffies, end_time))) {
> +			dev_err(host->dev, "PMECC: Timeout to get ECC value.\n");
> +			return -EIO;
> +		}
> +		cpu_relax();
> +	}
> +
> +	for (i = 0; i < host->pmecc_sector_number; i++) {
> +		for (j = 0; j < host->pmecc_bytes_per_sector; j++) {
> +			int pos;
> +
> +			pos = i * host->pmecc_bytes_per_sector + j;
> +			chip->oob_poi[eccpos[pos]] =
> +				pmecc_readb_ecc_relaxed(host->ecc, i, j);
> +		}
> +	}
> +	chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
> +
> +	return 0;
> +}
> +
> +static void atmel_pmecc_core_init(struct mtd_info *mtd)
> +{
> +	struct nand_chip *nand_chip = mtd->priv;
> +	struct atmel_nand_host *host = nand_chip->priv;
> +	uint32_t val = 0;
> +	struct nand_ecclayout *ecc_layout;
> +
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +
> +	switch (host->pmecc_corr_cap) {
> +	case 2:
> +		val = PMECC_CFG_BCH_ERR2;
> +		break;
> +	case 4:
> +		val = PMECC_CFG_BCH_ERR4;
> +		break;
> +	case 8:
> +		val = PMECC_CFG_BCH_ERR8;
> +		break;
> +	case 12:
> +		val = PMECC_CFG_BCH_ERR12;
> +		break;
> +	case 24:
> +		val = PMECC_CFG_BCH_ERR24;
> +		break;
> +	}
> +
> +	if (host->pmecc_sector_size == 512)
> +		val |= PMECC_CFG_SECTOR512;
> +	else if (host->pmecc_sector_size == 1024)
> +		val |= PMECC_CFG_SECTOR1024;
> +
> +	switch (host->pmecc_sector_number) {
> +	case 1:
> +		val |= PMECC_CFG_PAGE_1SECTOR;
> +		break;
> +	case 2:
> +		val |= PMECC_CFG_PAGE_2SECTORS;
> +		break;
> +	case 4:
> +		val |= PMECC_CFG_PAGE_4SECTORS;
> +		break;
> +	case 8:
> +		val |= PMECC_CFG_PAGE_8SECTORS;
> +		break;
> +	}
> +
> +	val |= (PMECC_CFG_READ_OP | PMECC_CFG_SPARE_DISABLE
> +		| PMECC_CFG_AUTO_DISABLE);
> +	pmecc_writel(host->ecc, CFG, val);
> +
> +	ecc_layout = nand_chip->ecc.layout;
> +	pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1);
> +	pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]);
> +	pmecc_writel(host->ecc, EADDR,
> +			ecc_layout->eccpos[ecc_layout->eccbytes - 1]);
> +	/* See datasheet about PMECC Clock Control Register */
> +	pmecc_writel(host->ecc, CLK, 2);
> +	pmecc_writel(host->ecc, IDR, 0xff);
> +	pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
> +}
> +
> +static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
> +					 struct atmel_nand_host *host)
> +{
> +	struct mtd_info *mtd = &host->mtd;
> +	struct nand_chip *nand_chip = &host->nand_chip;
> +	struct resource *regs, *regs_pmerr, *regs_rom;
> +	int cap, sector_size, err_no;
> +
> +	cap = host->pmecc_corr_cap;
> +	sector_size = host->pmecc_sector_size;
> +	dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
> +		 cap, sector_size);
> +
> +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	if (!regs) {
> +		dev_warn(host->dev,
> +			"Can't get I/O resource regs for PMECC controller, rolling back on software ECC\n");
> +		nand_chip->ecc.mode = NAND_ECC_SOFT;
> +		return 0;
> +	}
> +
> +	host->ecc = ioremap(regs->start, resource_size(regs));
> +	if (host->ecc == NULL) {
> +		dev_err(host->dev, "ioremap failed\n");
> +		err_no = -EIO;
> +		goto err_pmecc_ioremap;
> +	}
> +
> +	regs_pmerr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +	regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3);
> +	if (regs_pmerr && regs_rom) {
> +		host->pmerrloc_base = ioremap(regs_pmerr->start,
> +			resource_size(regs_pmerr));
> +		host->pmecc_rom_base = ioremap(regs_rom->start,
> +			resource_size(regs_rom));
> +	}
> +
> +	if (!host->pmerrloc_base || !host->pmecc_rom_base) {
> +		dev_err(host->dev,
> +			"Can not get I/O resource for PMECC ERRLOC controller or ROM!\n");
> +		err_no = -EIO;
> +		goto err_pmloc_ioremap;
> +	}
> +
> +	/* ECC is calculated for the whole page (1 step) */
> +	nand_chip->ecc.size = mtd->writesize;
> +
> +	/* set ECC page size and oob layout */
> +	switch (mtd->writesize) {
> +	case 2048:
> +		host->pmecc_degree = PMECC_GF_DIMENSION_13;
> +		host->pmecc_cw_len = (1 << host->pmecc_degree) - 1;
> +		host->pmecc_sector_number = mtd->writesize / sector_size;
> +		host->pmecc_bytes_per_sector = pmecc_get_ecc_bytes(
> +			cap, sector_size);
> +		host->pmecc_alpha_to = pmecc_get_alpha_to(host);
> +		host->pmecc_index_of = host->pmecc_rom_base +
> +			host->pmecc_lookup_table_offset;
> +
> +		nand_chip->ecc.steps = 1;
> +		nand_chip->ecc.strength = cap;
> +		nand_chip->ecc.bytes = host->pmecc_bytes_per_sector *
> +				       host->pmecc_sector_number;
> +		if (nand_chip->ecc.bytes > mtd->oobsize - 2) {
> +			dev_err(host->dev, "No room for ECC bytes\n");
> +			err_no = -EINVAL;
> +			goto err_no_ecc_room;
> +		}
> +		pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
> +					mtd->oobsize,
> +					nand_chip->ecc.bytes);
> +		nand_chip->ecc.layout = &atmel_pmecc_oobinfo;
> +		break;
> +	case 512:
> +	case 1024:
> +	case 4096:
> +		/* TODO */
> +		dev_warn(host->dev,
> +			"Unsupported page size for PMECC, use Software ECC\n");
> +	default:
> +		/* page size not handled by HW ECC */
> +		/* switching back to soft ECC */
> +		nand_chip->ecc.mode = NAND_ECC_SOFT;
> +		return 0;
> +	}
> +
> +	/* Allocate data for PMECC computation */
> +	err_no = pmecc_data_alloc(host);
> +	if (err_no) {
> +		dev_err(host->dev,
> +				"Cannot allocate memory for PMECC computation!\n");
> +		goto err_pmecc_data_alloc;
> +	}
> +
> +	nand_chip->ecc.read_page = atmel_nand_pmecc_read_page;
> +	nand_chip->ecc.write_page = atmel_nand_pmecc_write_page;
> +
> +	atmel_pmecc_core_init(mtd);
> +
> +	return 0;
> +
> +err_pmecc_data_alloc:
> +err_no_ecc_room:
> +err_pmloc_ioremap:
> +	iounmap(host->ecc);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
> +err_pmecc_ioremap:
> +	return err_no;
> +}
> +
> +/*
>   * Calculate HW ECC
>   *
>   * function called after a write
> @@ -747,7 +1471,11 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
>  	}
>  
>  	if (nand_chip->ecc.mode == NAND_ECC_HW) {
> -		res = atmel_hw_nand_init_params(pdev, host);
> +		if (host->has_pmecc)
> +			res = atmel_pmecc_nand_init_params(pdev, host);
> +		else
> +			res = atmel_hw_nand_init_params(pdev, host);
> +
>  		if (res != 0)
>  			goto err_hw_ecc;
>  	}
> @@ -766,8 +1494,16 @@ static int __init atmel_nand_probe(struct platform_device *pdev)
>  		return res;
>  
>  err_scan_tail:
> +	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
> +		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +		pmecc_data_free(host);
> +	}
>  	if (host->ecc)
>  		iounmap(host->ecc);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
>  err_hw_ecc:
>  err_scan_ident:
>  err_no_card:
> @@ -793,8 +1529,19 @@ static int __exit atmel_nand_remove(struct platform_device *pdev)
>  
>  	atmel_nand_disable(host);
>  
> +	if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) {
> +		pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
> +		pmerrloc_writel(host->pmerrloc_base, ELDIS,
> +				PMERRLOC_DISABLE);
> +		pmecc_data_free(host);
> +	}
> +
>  	if (host->ecc)
>  		iounmap(host->ecc);
> +	if (host->pmecc_rom_base)
> +		iounmap(host->pmecc_rom_base);
> +	if (host->pmerrloc_base)
> +		iounmap(host->pmerrloc_base);
>  
>  	if (host->dma_chan)
>  		dma_release_channel(host->dma_chan);
> diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h
> index 578c776..8a1e9a6 100644
> --- a/drivers/mtd/nand/atmel_nand_ecc.h
> +++ b/drivers/mtd/nand/atmel_nand_ecc.h
> @@ -3,7 +3,7 @@
>   * Based on AT91SAM9260 datasheet revision B.
>   *
>   * Copyright (C) 2007 Andrew Victor
> - * Copyright (C) 2007 Atmel Corporation.
> + * Copyright (C) 2007 - 2012 Atmel Corporation.
>   *
>   * This program is free software; you can redistribute it and/or modify it
>   * under the terms of the GNU General Public License as published by the
> @@ -36,4 +36,116 @@
>  #define ATMEL_ECC_NPR		0x10			/* NParity register */
>  #define		ATMEL_ECC_NPARITY	(0xffff << 0)		/* NParity */
>  
> +/* PMECC Register Definitions */
> +#define ATMEL_PMECC_CFG			0x000	/* Configuration Register */
> +#define		PMECC_CFG_BCH_ERR2		(0 << 0)
> +#define		PMECC_CFG_BCH_ERR4		(1 << 0)
> +#define		PMECC_CFG_BCH_ERR8		(2 << 0)
> +#define		PMECC_CFG_BCH_ERR12		(3 << 0)
> +#define		PMECC_CFG_BCH_ERR24		(4 << 0)
> +
> +#define		PMECC_CFG_SECTOR512		(0 << 4)
> +#define		PMECC_CFG_SECTOR1024		(1 << 4)
> +
> +#define		PMECC_CFG_PAGE_1SECTOR		(0 << 8)
> +#define		PMECC_CFG_PAGE_2SECTORS		(1 << 8)
> +#define		PMECC_CFG_PAGE_4SECTORS		(2 << 8)
> +#define		PMECC_CFG_PAGE_8SECTORS		(3 << 8)
> +
> +#define		PMECC_CFG_READ_OP		(0 << 12)
> +#define		PMECC_CFG_WRITE_OP		(1 << 12)
> +
> +#define		PMECC_CFG_SPARE_ENABLE		(1 << 16)
> +#define		PMECC_CFG_SPARE_DISABLE		(0 << 16)
> +
> +#define		PMECC_CFG_AUTO_ENABLE		(1 << 20)
> +#define		PMECC_CFG_AUTO_DISABLE		(0 << 20)
> +
> +#define ATMEL_PMECC_SAREA		0x004	/* Spare area size */
> +#define ATMEL_PMECC_SADDR		0x008	/* PMECC starting address */
> +#define ATMEL_PMECC_EADDR		0x00c	/* PMECC ending address */
> +#define ATMEL_PMECC_CLK			0x010	/* PMECC clock control */
> +#define		PMECC_CLK_133MHZ		(2 << 0)
> +
> +#define ATMEL_PMECC_CTRL		0x014	/* PMECC control register */
> +#define		PMECC_CTRL_RST			(1 << 0)
> +#define		PMECC_CTRL_DATA			(1 << 1)
> +#define		PMECC_CTRL_USER			(1 << 2)
> +#define		PMECC_CTRL_ENABLE		(1 << 4)
> +#define		PMECC_CTRL_DISABLE		(1 << 5)
> +
> +#define ATMEL_PMECC_SR			0x018	/* PMECC status register */
> +#define		PMECC_SR_BUSY			(1 << 0)
> +#define		PMECC_SR_ENABLE			(1 << 4)
> +
> +#define ATMEL_PMECC_IER			0x01c	/* PMECC interrupt enable */
> +#define		PMECC_IER_ENABLE		(1 << 0)
> +#define ATMEL_PMECC_IDR			0x020	/* PMECC interrupt disable */
> +#define		PMECC_IER_DISABLE		(1 << 0)
> +#define ATMEL_PMECC_IMR			0x024	/* PMECC interrupt mask */
> +#define		PMECC_IER_MASK			(1 << 0)
> +#define ATMEL_PMECC_ISR			0x028	/* PMECC interrupt status */
> +#define ATMEL_PMECC_ECCx		0x040	/* PMECC ECC x */
> +#define ATMEL_PMECC_REMx		0x240	/* PMECC REM x */
> +
> +/* PMERRLOC Register Definitions */
> +#define ATMEL_PMERRLOC_ELCFG		0x000	/* Error location config */
> +#define		PMERRLOC_ELCFG_SECTOR_512	(0 << 0)
> +#define		PMERRLOC_ELCFG_SECTOR_1024	(1 << 0)
> +#define		PMERRLOC_ELCFG_NUM_ERRORS(n)	((n) << 16)
> +
> +#define ATMEL_PMERRLOC_ELPRIM		0x004	/* Error location primitive */
> +#define ATMEL_PMERRLOC_ELEN		0x008	/* Error location enable */
> +#define ATMEL_PMERRLOC_ELDIS		0x00c	/* Error location disable */
> +#define		PMERRLOC_DISABLE		(1 << 0)
> +
> +#define ATMEL_PMERRLOC_ELSR		0x010	/* Error location status */
> +#define		PMERRLOC_ELSR_BUSY		(1 << 0)
> +#define ATMEL_PMERRLOC_ELIER		0x014	/* Error location int enable */
> +#define ATMEL_PMERRLOC_ELIDR		0x018	/* Error location int disable */
> +#define ATMEL_PMERRLOC_ELIMR		0x01c	/* Error location int mask */
> +#define ATMEL_PMERRLOC_ELISR		0x020	/* Error location int status */
> +#define		PMERRLOC_ERR_NUM_MASK		(0x1f << 8)
> +#define		PMERRLOC_CALC_DONE		(1 << 0)
> +#define ATMEL_PMERRLOC_SIGMAx		0x028	/* Error location SIGMA x */
> +#define ATMEL_PMERRLOC_ELx		0x08c	/* Error location x */
> +
> +/* Register access macros for PMECC */
> +#define pmecc_readl_relaxed(addr, reg) \
> +	readl_relaxed((addr) + ATMEL_PMECC_##reg)
> +
> +#define pmecc_writel(addr, reg, value) \
> +	writel((value), (addr) + ATMEL_PMECC_##reg)
> +
> +#define pmecc_readb_ecc_relaxed(addr, sector, n) \
> +	readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
> +
> +#define pmecc_readl_rem_relaxed(addr, sector, n) \
> +	readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
> +
> +#define pmerrloc_readl_relaxed(addr, reg) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
> +
> +#define pmerrloc_writel(addr, reg, value) \
> +	writel((value), (addr) + ATMEL_PMERRLOC_##reg)
> +
> +#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
> +	writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
> +
> +#define pmerrloc_readl_sigma_relaxed(addr, n) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
> +
> +#define pmerrloc_readl_el_relaxed(addr, n) \
> +	readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
> +
> +/* Galois field dimension */
> +#define PMECC_GF_DIMENSION_13			13
> +#define PMECC_GF_DIMENSION_14			14
> +
> +#define PMECC_LOOKUP_TABLE_SIZE_512		0x2000
> +#define PMECC_LOOKUP_TABLE_SIZE_1024		0x4000
> +
> +/* Time out value for reading PMECC status register */
> +#define PMECC_MAX_TIMEOUT_MS			100
> +
>  #endif
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
  2012-06-28  8:21   ` Josh Wu
@ 2012-06-28 12:56     ` Nicolas Ferre
  -1 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28 12:56 UTC (permalink / raw)
  To: Josh Wu, linux-mtd, dedekind1
  Cc: hongxu.cn, richard.genoud, ivan.djelic, computersforpeace,
	plagnioj, linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> Add DT support for PMECC parameters.
> 
> Signed-off-by: Josh Wu <josh.wu@atmel.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> ---
> changes:
> since v11
>    1. update the documentation for reg parameter
>    2. add one example for PMECC support.
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
>  drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
>  2 files changed, 90 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> index a200695..d555421 100644
> --- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> +++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> @@ -3,7 +3,9 @@ Atmel NAND flash
>  Required properties:
>  - compatible : "atmel,at91rm9200-nand".
>  - reg : should specify localbus address and size used for the chip,
> -	and if availlable the ECC.
> +	and hardware ECC controller if available.
> +	If the hardware ECC is PMECC, it should contain address and size for
> +	PMECC, PMECC Error Location controller and ROM which has lookup tables.
>  - atmel,nand-addr-offset : offset for the address latch.
>  - atmel,nand-cmd-offset : offset for the command latch.
>  - #address-cells, #size-cells : Must be present if the device has sub-nodes
> @@ -16,6 +18,15 @@ Optional properties:
>  - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
>    Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>    "soft_bch".
> +- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
> +  Only supported by at91sam9x5 or later sam9 product.
> +- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
> +  Controller. Supported values are: 2, 4, 8, 12, 24.
> +- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
> +  are: 512, 1024.
> +- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
> +  for different sector size. First one is for sector size 512, the next is for
> +  sector size 1024.
>  - nand-bus-width : 8 or 16 bus width if not present 8
>  - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>  
> @@ -39,3 +50,30 @@ nand0: nand@40000000,0 {
>  		...
>  	};
>  };
> +
> +/* for PMECC supported chips */
> +nand0: nand@40000000 {
> +	compatible = "atmel,at91rm9200-nand";
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	reg = < 0x40000000 0x10000000	/* bus addr & size */
> +		0xffffe000 0x00000600	/* PMECC addr & size */
> +		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
> +		0x00100000 0x00100000	/* ROM addr & size */
> +		>;
> +	atmel,nand-addr-offset = <21>;	/* ale */
> +	atmel,nand-cmd-offset = <22>;	/* cle */
> +	nand-on-flash-bbt;
> +	nand-ecc-mode = "hw";
> +	atmel,has-pmecc;	/* enable PMECC */
> +	atmel,pmecc-cap = <2>;
> +	atmel,pmecc-sector-size = <512>;
> +	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
> +	gpios = <&pioD 5 0	/* rdy */
> +		 &pioD 4 0	/* nce */
> +		 0		/* cd */
> +		>;
> +	partition@0 {
> +		...
> +	};
> +};
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index 7a41a04..b97ad9f 100644
> --- a/drivers/mtd/nand/atmel_nand.c
> +++ b/drivers/mtd/nand/atmel_nand.c
> @@ -93,6 +93,11 @@ struct atmel_nand_host {
>  
>  	struct completion	comp;
>  	struct dma_chan		*dma_chan;
> +
> +	bool			has_pmecc;
> +	u8			pmecc_corr_cap;
> +	u16			pmecc_sector_size;
> +	u32			pmecc_lookup_table_offset;
>  };
>  
>  static int cpu_has_dma(void)
> @@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
>  static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>  					 struct device_node *np)
>  {
> -	u32 val;
> +	u32 val, table_offset;
> +	u32 offset[2];
>  	int ecc_mode;
>  	struct atmel_nand_data *board = &host->board;
>  	enum of_gpio_flags flags;
> @@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>  	board->enable_pin = of_get_gpio(np, 1);
>  	board->det_pin = of_get_gpio(np, 2);
>  
> +	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
> +
> +	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
> +		return 0;	/* Not using PMECC */
> +
> +	/* use PMECC, get correction capability, sector size and lookup
> +	 * table offset.
> +	 */
> +	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Capability\n");
> +		return -EINVAL;
> +	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
> +	    (val != 24)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_corr_cap = (u8)val;
> +
> +	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
> +		return -EINVAL;
> +	} else if ((val != 512) && (val != 1024)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_sector_size = (u16)val;
> +
> +	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
> +			offset, 2) != 0) {
> +		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
> +
> +	if (!table_offset) {
> +		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	host->pmecc_lookup_table_offset = table_offset;
> +
>  	return 0;
>  }
>  #else
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC
@ 2012-06-28 12:56     ` Nicolas Ferre
  0 siblings, 0 replies; 20+ messages in thread
From: Nicolas Ferre @ 2012-06-28 12:56 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/28/2012 10:21 AM, Josh Wu :
> Add DT support for PMECC parameters.
> 
> Signed-off-by: Josh Wu <josh.wu@atmel.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> ---
> changes:
> since v11
>    1. update the documentation for reg parameter
>    2. add one example for PMECC support.
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 ++++++++++++++-
>  drivers/mtd/nand/atmel_nand.c                      |   52 +++++++++++++++++++-
>  2 files changed, 90 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/mtd/atmel-nand.txt b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> index a200695..d555421 100644
> --- a/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> +++ b/Documentation/devicetree/bindings/mtd/atmel-nand.txt
> @@ -3,7 +3,9 @@ Atmel NAND flash
>  Required properties:
>  - compatible : "atmel,at91rm9200-nand".
>  - reg : should specify localbus address and size used for the chip,
> -	and if availlable the ECC.
> +	and hardware ECC controller if available.
> +	If the hardware ECC is PMECC, it should contain address and size for
> +	PMECC, PMECC Error Location controller and ROM which has lookup tables.
>  - atmel,nand-addr-offset : offset for the address latch.
>  - atmel,nand-cmd-offset : offset for the command latch.
>  - #address-cells, #size-cells : Must be present if the device has sub-nodes
> @@ -16,6 +18,15 @@ Optional properties:
>  - nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
>    Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
>    "soft_bch".
> +- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
> +  Only supported by at91sam9x5 or later sam9 product.
> +- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
> +  Controller. Supported values are: 2, 4, 8, 12, 24.
> +- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
> +  are: 512, 1024.
> +- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
> +  for different sector size. First one is for sector size 512, the next is for
> +  sector size 1024.
>  - nand-bus-width : 8 or 16 bus width if not present 8
>  - nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
>  
> @@ -39,3 +50,30 @@ nand0: nand at 40000000,0 {
>  		...
>  	};
>  };
> +
> +/* for PMECC supported chips */
> +nand0: nand at 40000000 {
> +	compatible = "atmel,at91rm9200-nand";
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	reg = < 0x40000000 0x10000000	/* bus addr & size */
> +		0xffffe000 0x00000600	/* PMECC addr & size */
> +		0xffffe600 0x00000200	/* PMECC ERRLOC addr & size */
> +		0x00100000 0x00100000	/* ROM addr & size */
> +		>;
> +	atmel,nand-addr-offset = <21>;	/* ale */
> +	atmel,nand-cmd-offset = <22>;	/* cle */
> +	nand-on-flash-bbt;
> +	nand-ecc-mode = "hw";
> +	atmel,has-pmecc;	/* enable PMECC */
> +	atmel,pmecc-cap = <2>;
> +	atmel,pmecc-sector-size = <512>;
> +	atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
> +	gpios = <&pioD 5 0	/* rdy */
> +		 &pioD 4 0	/* nce */
> +		 0		/* cd */
> +		>;
> +	partition at 0 {
> +		...
> +	};
> +};
> diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
> index 7a41a04..b97ad9f 100644
> --- a/drivers/mtd/nand/atmel_nand.c
> +++ b/drivers/mtd/nand/atmel_nand.c
> @@ -93,6 +93,11 @@ struct atmel_nand_host {
>  
>  	struct completion	comp;
>  	struct dma_chan		*dma_chan;
> +
> +	bool			has_pmecc;
> +	u8			pmecc_corr_cap;
> +	u16			pmecc_sector_size;
> +	u32			pmecc_lookup_table_offset;
>  };
>  
>  static int cpu_has_dma(void)
> @@ -481,7 +486,8 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
>  static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>  					 struct device_node *np)
>  {
> -	u32 val;
> +	u32 val, table_offset;
> +	u32 offset[2];
>  	int ecc_mode;
>  	struct atmel_nand_data *board = &host->board;
>  	enum of_gpio_flags flags;
> @@ -517,6 +523,50 @@ static int __devinit atmel_of_init_port(struct atmel_nand_host *host,
>  	board->enable_pin = of_get_gpio(np, 1);
>  	board->det_pin = of_get_gpio(np, 2);
>  
> +	host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
> +
> +	if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
> +		return 0;	/* Not using PMECC */
> +
> +	/* use PMECC, get correction capability, sector size and lookup
> +	 * table offset.
> +	 */
> +	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Capability\n");
> +		return -EINVAL;
> +	} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
> +	    (val != 24)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_corr_cap = (u8)val;
> +
> +	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
> +		dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
> +		return -EINVAL;
> +	} else if ((val != 512) && (val != 1024)) {
> +		dev_err(host->dev,
> +			"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
> +			val);
> +		return -EINVAL;
> +	}
> +	host->pmecc_sector_size = (u16)val;
> +
> +	if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
> +			offset, 2) != 0) {
> +		dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
> +
> +	if (!table_offset) {
> +		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
> +		return -EINVAL;
> +	}
> +	host->pmecc_lookup_table_offset = table_offset;
> +
>  	return 0;
>  }
>  #else
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
  2012-06-28  8:21 ` Josh Wu
@ 2012-06-28 15:40   ` Artem Bityutskiy
  -1 siblings, 0 replies; 20+ messages in thread
From: Artem Bityutskiy @ 2012-06-28 15:40 UTC (permalink / raw)
  To: Josh Wu
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, linux-mtd, ivan.djelic,
	computersforpeace, plagnioj, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 2332 bytes --]

On Thu, 2012-06-28 at 16:21 +0800, Josh Wu wrote:
> Those patches is based on the l2-mtd.git three.
> 
> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
> which includes the modification according to Richard Genoud's review suggestion.
> 
> Changes since v11,
> 	add documentation for atmel nand reg parameter and one example for PMECC support.
> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
> 	fix the bug: no correction when ecc data has bit flipped.
> 	add clearer debug information for data correction.
> 
> Josh Wu (2):
>   MTD: at91: add dt parameters for Atmel PMECC
>   MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>     ECC controller
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>  drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>  3 files changed, 951 insertions(+), 4 deletions(-)

Aiaiai is not 100% happy, could you take a look?

dedekind@blue:~/git/maintaining$ ../aiaiai/aiaiai-concat-mboxes ~/tmp/wu* | ./verify ../l2-mtd/ atmel_nand
Tested the patch(es) on top of the following commits:
9328bb5 Quick fixes - applied by aiaiai
ea3b6ef mtd: fix bogus inequation
0960e57 mtd: move mtd_read_oob() definition out of mtd.h
0560176 mtd: nand_bbt: refactor check_pattern_no_oob()

--------------------------------------------------------------------------------

Successfully built configuration "arm-at91cap9_defconfig,arm,arm-unknown-linux-gnueabi-", results:

--- before_patching.log
+++ after_patching.log
@@ @@
+drivers/mtd/nand/atmel_nand.c: In function 'pmecc_correct_data':
+drivers/mtd/nand/atmel_nand.c:680:38: warning: variable 'ecc_size' set but not used [-Wunused-but-set-variable]
+drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]

--------------------------------------------------------------------------------


-- 
Best Regards,
Artem Bityutskiy

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-28 15:40   ` Artem Bityutskiy
  0 siblings, 0 replies; 20+ messages in thread
From: Artem Bityutskiy @ 2012-06-28 15:40 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, 2012-06-28 at 16:21 +0800, Josh Wu wrote:
> Those patches is based on the l2-mtd.git three.
> 
> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
> which includes the modification according to Richard Genoud's review suggestion.
> 
> Changes since v11,
> 	add documentation for atmel nand reg parameter and one example for PMECC support.
> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
> 	fix the bug: no correction when ecc data has bit flipped.
> 	add clearer debug information for data correction.
> 
> Josh Wu (2):
>   MTD: at91: add dt parameters for Atmel PMECC
>   MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>     ECC controller
> 
>  .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>  drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>  drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>  3 files changed, 951 insertions(+), 4 deletions(-)

Aiaiai is not 100% happy, could you take a look?

dedekind at blue:~/git/maintaining$ ../aiaiai/aiaiai-concat-mboxes ~/tmp/wu* | ./verify ../l2-mtd/ atmel_nand
Tested the patch(es) on top of the following commits:
9328bb5 Quick fixes - applied by aiaiai
ea3b6ef mtd: fix bogus inequation
0960e57 mtd: move mtd_read_oob() definition out of mtd.h
0560176 mtd: nand_bbt: refactor check_pattern_no_oob()

--------------------------------------------------------------------------------

Successfully built configuration "arm-at91cap9_defconfig,arm,arm-unknown-linux-gnueabi-", results:

--- before_patching.log
+++ after_patching.log
@@ @@
+drivers/mtd/nand/atmel_nand.c: In function 'pmecc_correct_data':
+drivers/mtd/nand/atmel_nand.c:680:38: warning: variable 'ecc_size' set but not used [-Wunused-but-set-variable]
+drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
+drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]

--------------------------------------------------------------------------------


-- 
Best Regards,
Artem Bityutskiy
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: This is a digitally signed message part
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120628/7dc4e586/attachment.sig>

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
  2012-06-28 15:40   ` Artem Bityutskiy
@ 2012-06-29  9:00     ` Josh Wu
  -1 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-29  9:00 UTC (permalink / raw)
  To: dedekind1
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, linux-mtd, ivan.djelic,
	computersforpeace, plagnioj, linux-arm-kernel

Hi, Artem

On 6/28/2012 11:40 PM, Artem Bityutskiy wrote:
> On Thu, 2012-06-28 at 16:21 +0800, Josh Wu wrote:
>> Those patches is based on the l2-mtd.git three.
>>
>> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
>> which includes the modification according to Richard Genoud's review suggestion.
>>
>> Changes since v11,
>> 	add documentation for atmel nand reg parameter and one example for PMECC support.
>> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
>> 	fix the bug: no correction when ecc data has bit flipped.
>> 	add clearer debug information for data correction.
>>
>> Josh Wu (2):
>>    MTD: at91: add dt parameters for Atmel PMECC
>>    MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>>      ECC controller
>>
>>   .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>>   drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>>   drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>>   3 files changed, 951 insertions(+), 4 deletions(-)
> Aiaiai is not 100% happy, could you take a look?
>
> dedekind@blue:~/git/maintaining$ ../aiaiai/aiaiai-concat-mboxes ~/tmp/wu* | ./verify ../l2-mtd/ atmel_nand
> Tested the patch(es) on top of the following commits:
> 9328bb5 Quick fixes - applied by aiaiai
> ea3b6ef mtd: fix bogus inequation
> 0960e57 mtd: move mtd_read_oob() definition out of mtd.h
> 0560176 mtd: nand_bbt: refactor check_pattern_no_oob()
>
> --------------------------------------------------------------------------------
>
> Successfully built configuration "arm-at91cap9_defconfig,arm,arm-unknown-linux-gnueabi-", results:
>
> --- before_patching.log
> +++ after_patching.log
> @@ @@
> +drivers/mtd/nand/atmel_nand.c: In function 'pmecc_correct_data':
> +drivers/mtd/nand/atmel_nand.c:680:38: warning: variable 'ecc_size' set but not used [-Wunused-but-set-variable]

oops, I'll fix this warning in next version.

> +drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]

I use CodeSourcery 2010q1, and no any error reported. So it seems 
related with the compiler.

 From the log, I think the error is caused by the dynamic 2d array: 
smu[cap + 2][2 * cap + 1]. which is used to calculate the sigma table.

Since the smu is allocated when driver is probed, and the length of the 
smu depend on PMECC correction capablity. So when I declare the type of 
smu by following line: (cap is declared as const type)
          int16_t (*smu)[2 * cap + 1];
That make Sparse compiler not happy, since the cap is a variable. so it 
reports an error. But for this case, the CodeSourcery works fine.

For fixing this compile error, in next version,  I will use 1D array 
instead of 2D array, for instance: all smu[i][j] changes to smu[i * num 
+ j].  // in this line, num equal to 2 * cap + 1.
I think in such way, all compiler should be happy. :)

When I send out the next version of the patch, Could you please test it 
about above fix? since I don't have Sparse compiler in my hand. Thanks.

>
> --------------------------------------------------------------------------------
>
>

Best Regards,
Josh Wu

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-29  9:00     ` Josh Wu
  0 siblings, 0 replies; 20+ messages in thread
From: Josh Wu @ 2012-06-29  9:00 UTC (permalink / raw)
  To: linux-arm-kernel

Hi, Artem

On 6/28/2012 11:40 PM, Artem Bityutskiy wrote:
> On Thu, 2012-06-28 at 16:21 +0800, Josh Wu wrote:
>> Those patches is based on the l2-mtd.git three.
>>
>> Since there are two patches of v11 already pushed, so this version v12 only includes the left two patches.
>> which includes the modification according to Richard Genoud's review suggestion.
>>
>> Changes since v11,
>> 	add documentation for atmel nand reg parameter and one example for PMECC support.
>> 	fix the bug: no error reported when fail to get resoures of PMECC ERRLOC or ROM.
>> 	fix the bug: no correction when ecc data has bit flipped.
>> 	add clearer debug information for data correction.
>>
>> Josh Wu (2):
>>    MTD: at91: add dt parameters for Atmel PMECC
>>    MTD: at91: atmel_nand: Update driver to support Programmable Multibit
>>      ECC controller
>>
>>   .../devicetree/bindings/mtd/atmel-nand.txt         |   40 +-
>>   drivers/mtd/nand/atmel_nand.c                      |  801 +++++++++++++++++++-
>>   drivers/mtd/nand/atmel_nand_ecc.h                  |  114 ++-
>>   3 files changed, 951 insertions(+), 4 deletions(-)
> Aiaiai is not 100% happy, could you take a look?
>
> dedekind at blue:~/git/maintaining$ ../aiaiai/aiaiai-concat-mboxes ~/tmp/wu* | ./verify ../l2-mtd/ atmel_nand
> Tested the patch(es) on top of the following commits:
> 9328bb5 Quick fixes - applied by aiaiai
> ea3b6ef mtd: fix bogus inequation
> 0960e57 mtd: move mtd_read_oob() definition out of mtd.h
> 0560176 mtd: nand_bbt: refactor check_pattern_no_oob()
>
> --------------------------------------------------------------------------------
>
> Successfully built configuration "arm-at91cap9_defconfig,arm,arm-unknown-linux-gnueabi-", results:
>
> --- before_patching.log
> +++ after_patching.log
> @@ @@
> +drivers/mtd/nand/atmel_nand.c: In function 'pmecc_correct_data':
> +drivers/mtd/nand/atmel_nand.c:680:38: warning: variable 'ecc_size' set but not used [-Wunused-but-set-variable]

oops, I'll fix this warning in next version.

> +drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
> +drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]

I use CodeSourcery 2010q1, and no any error reported. So it seems 
related with the compiler.

 From the log, I think the error is caused by the dynamic 2d array: 
smu[cap + 2][2 * cap + 1]. which is used to calculate the sigma table.

Since the smu is allocated when driver is probed, and the length of the 
smu depend on PMECC correction capablity. So when I declare the type of 
smu by following line: (cap is declared as const type)
          int16_t (*smu)[2 * cap + 1];
That make Sparse compiler not happy, since the cap is a variable. so it 
reports an error. But for this case, the CodeSourcery works fine.

For fixing this compile error, in next version,  I will use 1D array 
instead of 2D array, for instance: all smu[i][j] changes to smu[i * num 
+ j].  // in this line, num equal to 2 * cap + 1.
I think in such way, all compiler should be happy. :)

When I send out the next version of the patch, Could you please test it 
about above fix? since I don't have Sparse compiler in my hand. Thanks.

>
> --------------------------------------------------------------------------------
>
>

Best Regards,
Josh Wu

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
  2012-06-29  9:00     ` Josh Wu
@ 2012-06-29  9:09       ` Artem Bityutskiy
  -1 siblings, 0 replies; 20+ messages in thread
From: Artem Bityutskiy @ 2012-06-29  9:09 UTC (permalink / raw)
  To: Josh Wu
  Cc: hongxu.cn, richard.genoud, nicolas.ferre, linux-mtd, ivan.djelic,
	computersforpeace, plagnioj, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 1061 bytes --]

On Fri, 2012-06-29 at 17:00 +0800, Josh Wu wrote:
> > +drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]
> 
> I use CodeSourcery 2010q1, and no any error reported. So it seems 
> related with the compiler.

They are from sparse, not gcc.

> When I send out the next version of the patch, Could you please test it 
> about above fix? since I don't have Sparse compiler in my hand. Thanks.

I will feed it to aiaiai, but you can also do it easily if you want. You
can download my maintaining scripts from here:
http://git.infradead.org/users/dedekind/maintaining.git
and there is the documentation. You can use aiaiai in general for
checking your patching before submitting out. Catches some embarrassing
errors sometimes :-)

-- 
Best Regards,
Artem Bityutskiy

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver
@ 2012-06-29  9:09       ` Artem Bityutskiy
  0 siblings, 0 replies; 20+ messages in thread
From: Artem Bityutskiy @ 2012-06-29  9:09 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, 2012-06-29 at 17:00 +0800, Josh Wu wrote:
> > +drivers/mtd/nand/atmel_nand.c:480:32: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:488:35: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:633:32: error: bad constant expression [sparse]
> > +drivers/mtd/nand/atmel_nand.c:635:35: error: bad constant expression [sparse]
> 
> I use CodeSourcery 2010q1, and no any error reported. So it seems 
> related with the compiler.

They are from sparse, not gcc.

> When I send out the next version of the patch, Could you please test it 
> about above fix? since I don't have Sparse compiler in my hand. Thanks.

I will feed it to aiaiai, but you can also do it easily if you want. You
can download my maintaining scripts from here:
http://git.infradead.org/users/dedekind/maintaining.git
and there is the documentation. You can use aiaiai in general for
checking your patching before submitting out. Catches some embarrassing
errors sometimes :-)

-- 
Best Regards,
Artem Bityutskiy
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: This is a digitally signed message part
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120629/30dc8c8e/attachment.sig>

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2012-06-29  9:09 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-06-28  8:21 [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver Josh Wu
2012-06-28  8:21 ` Josh Wu
2012-06-28  8:21 ` [PATCH v12 1/2] MTD: at91: add dt parameters for Atmel PMECC Josh Wu
2012-06-28  8:21   ` Josh Wu
2012-06-28  8:33   ` Josh Wu
2012-06-28  8:33     ` Josh Wu
2012-06-28 12:56   ` Nicolas Ferre
2012-06-28 12:56     ` Nicolas Ferre
2012-06-28  8:21 ` [PATCH v12 2/2] MTD: at91: atmel_nand: Update driver to support Programmable Multibit ECC controller Josh Wu
2012-06-28  8:21   ` Josh Wu
2012-06-28  8:36   ` Nicolas Ferre
2012-06-28  8:36     ` Nicolas Ferre
2012-06-28  8:33 ` [PATCH v12 0/2] MTD: at91: Add PMECC support for at91 nand flash driver Nicolas Ferre
2012-06-28  8:33   ` Nicolas Ferre
2012-06-28 15:40 ` Artem Bityutskiy
2012-06-28 15:40   ` Artem Bityutskiy
2012-06-29  9:00   ` Josh Wu
2012-06-29  9:00     ` Josh Wu
2012-06-29  9:09     ` Artem Bityutskiy
2012-06-29  9:09       ` Artem Bityutskiy

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.