mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver
@ 2023-01-11 17:40 Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 01/15] asm-generic: io.h: sync with Linux Ahmad Fatoum
                   ` (15 more replies)
  0 siblings, 16 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox

For a few years, Linux has been using the new EBI bindings for NAND
controllers on all AT91 SoCs newer than the AT91RM2000. We have so far
only supported the old bindings by hacking the DT, but this doesn't
suffice for the SAMA5D4. Therefore import a new state of the Linux NAND
controller driver. We still keep around the old barebox driver to
support the non-DT enabled AT91 platforms.

Ahmad Fatoum (15):
  asm-generic: io.h: sync with Linux
  mtd: nand: base: implement nand_gpio_waitrdy
  mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_
  mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements
  mtd: nand: define nand_get_(small|large)_page_ooblayout
  mtd: nand: define nand_interface_is_sdr
  mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type
  driver: implement dev_request_resource
  lib: provide stub Linux "generic" allocator API
  memory: add Atmel EBI driver
  mfd: add atmel-smc driver
  mtd: nand: atmel: import Linux NAND controller driver
  ARM: AT91: sama5d3_xplained: switch to upstream binding
  mtd: nand: drop DT support in legacy driver
  ARM: AT91: sama5d3: always read memory size from controller

 arch/arm/dts/at91-microchip-ksz9477-evb.dts   |    4 -
 arch/arm/dts/at91-sama5d3_xplained.dts        |   29 -
 arch/arm/dts/sama5d3.dtsi                     |   17 +-
 drivers/base/driver.c                         |   19 +-
 drivers/memory/Kconfig                        |   14 +
 drivers/memory/Makefile                       |    1 +
 drivers/memory/atmel-ebi.c                    |  614 +++++
 drivers/mfd/Kconfig                           |    4 +
 drivers/mfd/Makefile                          |    1 +
 drivers/mfd/atmel-smc.c                       |  352 +++
 drivers/mtd/nand/Kconfig                      |   11 +-
 drivers/mtd/nand/Makefile                     |    2 +-
 drivers/mtd/nand/atmel/Makefile               |    3 +
 drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h |    0
 .../mtd/nand/{atmel_nand.c => atmel/legacy.c} |  106 +-
 drivers/mtd/nand/atmel/nand-controller.c      | 2049 +++++++++++++++++
 drivers/mtd/nand/atmel/pmecc.c                |  992 ++++++++
 drivers/mtd/nand/atmel/pmecc.h                |   70 +
 drivers/mtd/nand/nand_base.c                  |   86 +-
 drivers/mtd/nand/nand_esmt.c                  |   10 +-
 drivers/mtd/nand/nand_fsl_ifc.c               |    2 +-
 drivers/mtd/nand/nand_hynix.c                 |   40 +-
 drivers/mtd/nand/nand_jedec.c                 |    4 +-
 drivers/mtd/nand/nand_micron.c                |   16 +-
 drivers/mtd/nand/nand_onfi.c                  |    8 +-
 drivers/mtd/nand/nand_samsung.c               |   18 +-
 drivers/mtd/nand/nand_toshiba.c               |   12 +-
 include/asm-generic/io.h                      |  401 +++-
 include/driver.h                              |    5 +
 include/linux/genalloc.h                      |   36 +
 include/linux/mfd/syscon/atmel-matrix.h       |  112 +
 include/linux/mfd/syscon/atmel-smc.h          |  119 +
 include/linux/mtd/nand.h                      |   27 +-
 include/linux/mtd/rawnand.h                   |   43 +-
 include/linux/mutex.h                         |    2 +
 include/soc/at91/atmel-sfr.h                  |    2 +
 lib/Kconfig                                   |    5 +
 lib/Makefile                                  |    1 +
 lib/genalloc.c                                |  118 +
 39 files changed, 5074 insertions(+), 281 deletions(-)
 create mode 100644 drivers/memory/atmel-ebi.c
 create mode 100644 drivers/mfd/atmel-smc.c
 create mode 100644 drivers/mtd/nand/atmel/Makefile
 rename drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h (100%)
 rename drivers/mtd/nand/{atmel_nand.c => atmel/legacy.c} (92%)
 create mode 100644 drivers/mtd/nand/atmel/nand-controller.c
 create mode 100644 drivers/mtd/nand/atmel/pmecc.c
 create mode 100644 drivers/mtd/nand/atmel/pmecc.h
 create mode 100644 include/linux/genalloc.h
 create mode 100644 include/linux/mfd/syscon/atmel-matrix.h
 create mode 100644 include/linux/mfd/syscon/atmel-smc.h
 create mode 100644 lib/genalloc.c

-- 
2.30.2




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

* [PATCH 01/15] asm-generic: io.h: sync with Linux
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 02/15] mtd: nand: base: implement nand_gpio_waitrdy Ahmad Fatoum
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

We lack definitions for iowrite32_rep and friends, so sync partially
with Linux to get it.  While at, sync with Linux in the implementation of
insb: This is implemented in terms of readsb, while we had it the other
way round.

No functional change intended.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/asm-generic/io.h | 401 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 371 insertions(+), 30 deletions(-)

diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h
index 51f65ceb0a2e..acb70509d168 100644
--- a/include/asm-generic/io.h
+++ b/include/asm-generic/io.h
@@ -195,111 +195,370 @@ static inline void outl_p(u32 value, unsigned long addr)
 }
 #endif
 
-#ifndef insb
-static inline void insb(unsigned long addr, void *buffer, int count)
+/*
+ * {read,write}s{b,w,l,q}() repeatedly access the same memory address in
+ * native endianness in 8-, 16-, 32- or 64-bit chunks (@count times).
+ */
+#ifndef readsb
+#define readsb readsb
+static inline void readsb(const volatile void __iomem *addr, void *buffer,
+			  unsigned int count)
 {
 	if (count) {
 		u8 *buf = buffer;
+
 		do {
-			u8 x = inb(addr);
+			u8 x = __raw_readb(addr);
 			*buf++ = x;
 		} while (--count);
 	}
 }
 #endif
 
-#ifndef insw
-static inline void insw(unsigned long addr, void *buffer, int count)
+#ifndef readsw
+#define readsw readsw
+static inline void readsw(const volatile void __iomem *addr, void *buffer,
+			  unsigned int count)
 {
 	if (count) {
 		u16 *buf = buffer;
+
 		do {
-			u16 x = inw(addr);
+			u16 x = __raw_readw(addr);
 			*buf++ = x;
 		} while (--count);
 	}
 }
 #endif
 
-#ifndef insl
-static inline void insl(unsigned long addr, void *buffer, int count)
+#ifndef readsl
+#define readsl readsl
+static inline void readsl(const volatile void __iomem *addr, void *buffer,
+			  unsigned int count)
 {
 	if (count) {
 		u32 *buf = buffer;
+
 		do {
-			u32 x = inl(addr);
+			u32 x = __raw_readl(addr);
 			*buf++ = x;
 		} while (--count);
 	}
 }
 #endif
 
-#ifndef outsb
-static inline void outsb(unsigned long addr, const void *buffer, int count)
+#ifdef CONFIG_64BIT
+#ifndef readsq
+#define readsq readsq
+static inline void readsq(const volatile void __iomem *addr, void *buffer,
+			  unsigned int count)
+{
+	if (count) {
+		u64 *buf = buffer;
+
+		do {
+			u64 x = __raw_readq(addr);
+			*buf++ = x;
+		} while (--count);
+	}
+}
+#endif
+#endif /* CONFIG_64BIT */
+
+#ifndef writesb
+#define writesb writesb
+static inline void writesb(volatile void __iomem *addr, const void *buffer,
+			   unsigned int count)
 {
 	if (count) {
 		const u8 *buf = buffer;
+
 		do {
-			outb(*buf++, addr);
+			__raw_writeb(*buf++, addr);
 		} while (--count);
 	}
 }
 #endif
 
-#ifndef outsw
-static inline void outsw(unsigned long addr, const void *buffer, int count)
+#ifndef writesw
+#define writesw writesw
+static inline void writesw(volatile void __iomem *addr, const void *buffer,
+			   unsigned int count)
 {
 	if (count) {
 		const u16 *buf = buffer;
+
 		do {
-			outw(*buf++, addr);
+			__raw_writew(*buf++, addr);
 		} while (--count);
 	}
 }
 #endif
 
-#ifndef outsl
-static inline void outsl(unsigned long addr, const void *buffer, int count)
+#ifndef writesl
+#define writesl writesl
+static inline void writesl(volatile void __iomem *addr, const void *buffer,
+			   unsigned int count)
 {
 	if (count) {
 		const u32 *buf = buffer;
+
 		do {
-			outl(*buf++, addr);
+			__raw_writel(*buf++, addr);
 		} while (--count);
 	}
 }
 #endif
 
-static inline void readsl(const void __iomem *addr, void *buf, int len)
+#ifdef CONFIG_64BIT
+#ifndef writesq
+#define writesq writesq
+static inline void writesq(volatile void __iomem *addr, const void *buffer,
+			   unsigned int count)
 {
-	insl(addr - PCI_IOBASE, buf, len);
+	if (count) {
+		const u64 *buf = buffer;
+
+		do {
+			__raw_writeq(*buf++, addr);
+		} while (--count);
+	}
+}
+#endif
+#endif /* CONFIG_64BIT */
+
+/*
+ * {in,out}{b,w,l}() access little endian I/O. {in,out}{b,w,l}_p() can be
+ * implemented on hardware that needs an additional delay for I/O accesses to
+ * take effect.
+ */
+
+#if !defined(inb) && !defined(_inb)
+#define _inb _inb
+static inline u8 _inb(unsigned long addr)
+{
+	return __raw_readb(PCI_IOBASE + addr);
 }
+#endif
 
-static inline void readsw(const void __iomem *addr, void *buf, int len)
+#if !defined(inw) && !defined(_inw)
+#define _inw _inw
+static inline u16 _inw(unsigned long addr)
 {
-	insw(addr - PCI_IOBASE, buf, len);
+	return __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
 }
+#endif
 
-static inline void readsb(const void __iomem *addr, void *buf, int len)
+#if !defined(inl) && !defined(_inl)
+#define _inl _inl
+static inline u32 _inl(unsigned long addr)
 {
-	insb(addr - PCI_IOBASE, buf, len);
+	return __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
 }
+#endif
 
-static inline void writesl(const void __iomem *addr, const void *buf, int len)
+#if !defined(outb) && !defined(_outb)
+#define _outb _outb
+static inline void _outb(u8 value, unsigned long addr)
 {
-	outsl(addr - PCI_IOBASE, buf, len);
+	return __raw_writeb(value, PCI_IOBASE + addr);
 }
+#endif
 
-static inline void writesw(const void __iomem *addr, const void *buf, int len)
+#if !defined(outw) && !defined(_outw)
+#define _outw _outw
+static inline void _outw(u16 value, unsigned long addr)
 {
-	outsw(addr - PCI_IOBASE, buf, len);
+	return __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
 }
+#endif
 
-static inline void writesb(const void __iomem *addr, const void *buf, int len)
+#if !defined(outl) && !defined(_outl)
+#define _outl _outl
+static inline void _outl(u32 value, unsigned long addr)
 {
-	outsb(addr - PCI_IOBASE, buf, len);
+	return __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
 }
+#endif
 
+#ifndef inb
+#define inb _inb
+#endif
+
+#ifndef inw
+#define inw _inw
+#endif
+
+#ifndef inl
+#define inl _inl
+#endif
+
+#ifndef outb
+#define outb _outb
+#endif
+
+#ifndef outw
+#define outw _outw
+#endif
+
+#ifndef outl
+#define outl _outl
+#endif
+
+#ifndef inb_p
+#define inb_p inb_p
+static inline u8 inb_p(unsigned long addr)
+{
+	return inb(addr);
+}
+#endif
+
+#ifndef inw_p
+#define inw_p inw_p
+static inline u16 inw_p(unsigned long addr)
+{
+	return inw(addr);
+}
+#endif
+
+#ifndef inl_p
+#define inl_p inl_p
+static inline u32 inl_p(unsigned long addr)
+{
+	return inl(addr);
+}
+#endif
+
+#ifndef outb_p
+#define outb_p outb_p
+static inline void outb_p(u8 value, unsigned long addr)
+{
+	outb(value, addr);
+}
+#endif
+
+#ifndef outw_p
+#define outw_p outw_p
+static inline void outw_p(u16 value, unsigned long addr)
+{
+	outw(value, addr);
+}
+#endif
+
+#ifndef outl_p
+#define outl_p outl_p
+static inline void outl_p(u32 value, unsigned long addr)
+{
+	outl(value, addr);
+}
+#endif
+
+/*
+ * {in,out}s{b,w,l}{,_p}() are variants of the above that repeatedly access a
+ * single I/O port multiple times.
+ */
+
+#ifndef insb
+#define insb insb
+static inline void insb(unsigned long addr, void *buffer, unsigned int count)
+{
+	readsb(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef insw
+#define insw insw
+static inline void insw(unsigned long addr, void *buffer, unsigned int count)
+{
+	readsw(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef insl
+#define insl insl
+static inline void insl(unsigned long addr, void *buffer, unsigned int count)
+{
+	readsl(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef outsb
+#define outsb outsb
+static inline void outsb(unsigned long addr, const void *buffer,
+			 unsigned int count)
+{
+	writesb(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef outsw
+#define outsw outsw
+static inline void outsw(unsigned long addr, const void *buffer,
+			 unsigned int count)
+{
+	writesw(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef outsl
+#define outsl outsl
+static inline void outsl(unsigned long addr, const void *buffer,
+			 unsigned int count)
+{
+	writesl(PCI_IOBASE + addr, buffer, count);
+}
+#endif
+
+#ifndef insb_p
+#define insb_p insb_p
+static inline void insb_p(unsigned long addr, void *buffer, unsigned int count)
+{
+	insb(addr, buffer, count);
+}
+#endif
+
+#ifndef insw_p
+#define insw_p insw_p
+static inline void insw_p(unsigned long addr, void *buffer, unsigned int count)
+{
+	insw(addr, buffer, count);
+}
+#endif
+
+#ifndef insl_p
+#define insl_p insl_p
+static inline void insl_p(unsigned long addr, void *buffer, unsigned int count)
+{
+	insl(addr, buffer, count);
+}
+#endif
+
+#ifndef outsb_p
+#define outsb_p outsb_p
+static inline void outsb_p(unsigned long addr, const void *buffer,
+			   unsigned int count)
+{
+	outsb(addr, buffer, count);
+}
+#endif
+
+#ifndef outsw_p
+#define outsw_p outsw_p
+static inline void outsw_p(unsigned long addr, const void *buffer,
+			   unsigned int count)
+{
+	outsw(addr, buffer, count);
+}
+#endif
+
+#ifndef outsl_p
+#define outsl_p outsl_p
+static inline void outsl_p(unsigned long addr, const void *buffer,
+			   unsigned int count)
+{
+	outsl(addr, buffer, count);
+}
+#endif
 
 #ifndef ioread8
 #define ioread8 ioread8
@@ -421,6 +680,88 @@ static inline void iowrite64be(u64 value, volatile void __iomem *addr)
 #endif
 #endif /* CONFIG_64BIT */
 
+#ifndef ioread8_rep
+#define ioread8_rep ioread8_rep
+static inline void ioread8_rep(const volatile void __iomem *addr, void *buffer,
+			       unsigned int count)
+{
+	readsb(addr, buffer, count);
+}
+#endif
+
+#ifndef ioread16_rep
+#define ioread16_rep ioread16_rep
+static inline void ioread16_rep(const volatile void __iomem *addr,
+				void *buffer, unsigned int count)
+{
+	readsw(addr, buffer, count);
+}
+#endif
+
+#ifndef ioread32_rep
+#define ioread32_rep ioread32_rep
+static inline void ioread32_rep(const volatile void __iomem *addr,
+				void *buffer, unsigned int count)
+{
+	readsl(addr, buffer, count);
+}
+#endif
+
+#ifdef CONFIG_64BIT
+#ifndef ioread64_rep
+#define ioread64_rep ioread64_rep
+static inline void ioread64_rep(const volatile void __iomem *addr,
+				void *buffer, unsigned int count)
+{
+	readsq(addr, buffer, count);
+}
+#endif
+#endif /* CONFIG_64BIT */
+
+#ifndef iowrite8_rep
+#define iowrite8_rep iowrite8_rep
+static inline void iowrite8_rep(volatile void __iomem *addr,
+				const void *buffer,
+				unsigned int count)
+{
+	writesb(addr, buffer, count);
+}
+#endif
+
+#ifndef iowrite16_rep
+#define iowrite16_rep iowrite16_rep
+static inline void iowrite16_rep(volatile void __iomem *addr,
+				 const void *buffer,
+				 unsigned int count)
+{
+	writesw(addr, buffer, count);
+}
+#endif
+
+#ifndef iowrite32_rep
+#define iowrite32_rep iowrite32_rep
+static inline void iowrite32_rep(volatile void __iomem *addr,
+				 const void *buffer,
+				 unsigned int count)
+{
+	writesl(addr, buffer, count);
+}
+#endif
+
+#ifdef CONFIG_64BIT
+#ifndef iowrite64_rep
+#define iowrite64_rep iowrite64_rep
+static inline void iowrite64_rep(volatile void __iomem *addr,
+				 const void *buffer,
+				 unsigned int count)
+{
+	writesq(addr, buffer, count);
+}
+#endif
+#endif /* CONFIG_64BIT */
+
+
+
 /*
  * Change virtual addresses to physical addresses and vv.
  * These are pretty trivial
-- 
2.30.2




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

* [PATCH 02/15] mtd: nand: base: implement nand_gpio_waitrdy
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 01/15] asm-generic: io.h: sync with Linux Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 03/15] mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_ Ahmad Fatoum
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

To simplify porting kernel NAND drivers a tiny bit, provide the Linux
nand_gpio_waitrdy. This is readily implementable with barebox' own
gpio_poll_timeout_us.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/nand_base.c | 22 ++++++++++++++++++++++
 include/linux/mtd/rawnand.h  |  2 ++
 2 files changed, 24 insertions(+)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 4c90ad975771..c3cd22935bb2 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -36,6 +36,7 @@
 #include <asm/byteorder.h>
 #include <io.h>
 #include <malloc.h>
+#include <gpio.h>
 #include <module.h>
 #include <of_mtd.h>
 
@@ -785,6 +786,27 @@ int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms)
 };
 EXPORT_SYMBOL_GPL(nand_soft_waitrdy);
 
+/**
+ * nand_gpio_waitrdy - Poll R/B GPIO pin until ready
+ * @chip: NAND chip structure
+ * @gpiod: GPIO descriptor of R/B pin
+ * @timeout_ms: Timeout in ms
+ *
+ * Poll the R/B GPIO pin until it becomes ready. If that does not happen
+ * whitin the specified timeout, -ETIMEDOUT is returned.
+ *
+ * This helper is intended to be used when the controller has access to the
+ * NAND R/B pin over GPIO.
+ *
+ * Return 0 if the R/B pin indicates chip is ready, a negative error otherwise.
+ */
+int nand_gpio_waitrdy(struct nand_chip *chip, int gpio,
+		      unsigned long timeout_ms)
+{
+	return gpio_poll_timeout_us(gpio, true, timeout_ms * USEC_PER_MSEC);
+};
+EXPORT_SYMBOL_GPL(nand_gpio_waitrdy);
+
 static bool nand_supports_get_features(struct nand_chip *chip, int addr)
 {
 	return (chip->parameters.supports_set_get_features &&
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 1147f235a680..a05c67e1404b 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1414,6 +1414,8 @@ void nand_cleanup(struct nand_chip *chip);
  * instruction and have no physical pin to check it.
  */
 int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms);
+int nand_gpio_waitrdy(struct nand_chip *chip, int gpio,
+		      unsigned long timeout_ms);
 
 /* Select/deselect a NAND target. */
 void nand_select_target(struct nand_chip *chip, unsigned int cs);
-- 
2.30.2




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

* [PATCH 03/15] mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 01/15] asm-generic: io.h: sync with Linux Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 02/15] mtd: nand: base: implement nand_gpio_waitrdy Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 04/15] mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements Ahmad Fatoum
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

This aligns us with how Linux now names these constants.
No functional change.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/nand_base.c    | 26 +++++++++++++-------------
 drivers/mtd/nand/nand_fsl_ifc.c |  2 +-
 drivers/mtd/nand/nand_micron.c  |  2 +-
 include/linux/mtd/rawnand.h     |  8 ++++----
 4 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c3cd22935bb2..b686cef79bc6 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -4936,9 +4936,9 @@ free_detect_allocation:
 }
 
 static const char * const nand_ecc_algos[] = {
-	[NAND_ECC_HAMMING]	= "hamming",
-	[NAND_ECC_BCH]		= "bch",
-	[NAND_ECC_RS]		= "rs",
+	[NAND_ECC_ALGO_HAMMING]		= "hamming",
+	[NAND_ECC_ALGO_BCH]		= "bch",
+	[NAND_ECC_ALGO_RS]		= "rs",
 };
 
 static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np)
@@ -4949,7 +4949,7 @@ static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np)
 
 	err = of_property_read_string(np, "nand-ecc-algo", &pm);
 	if (!err) {
-		for (ecc_algo = NAND_ECC_HAMMING;
+		for (ecc_algo = NAND_ECC_ALGO_HAMMING;
 		     ecc_algo < ARRAY_SIZE(nand_ecc_algos);
 		     ecc_algo++) {
 			if (!strcasecmp(pm, nand_ecc_algos[ecc_algo]))
@@ -4964,12 +4964,12 @@ static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np)
 	err = of_property_read_string(np, "nand-ecc-mode", &pm);
 	if (!err) {
 		if (!strcasecmp(pm, "soft"))
-			return NAND_ECC_HAMMING;
+			return NAND_ECC_ALGO_HAMMING;
 		else if (!strcasecmp(pm, "soft_bch"))
-			return NAND_ECC_BCH;
+			return NAND_ECC_ALGO_BCH;
 	}
 
-	return NAND_ECC_UNKNOWN;
+	return NAND_ECC_ALGO_UNKNOWN;
 }
 
 static int nand_dt_init(struct nand_chip *chip)
@@ -4998,7 +4998,7 @@ static int nand_dt_init(struct nand_chip *chip)
 	if (ecc_mode >= 0)
 		chip->ecc.mode = ecc_mode;
 
-	if (ecc_algo != NAND_ECC_UNKNOWN)
+	if (ecc_algo != NAND_ECC_ALGO_UNKNOWN)
 		chip->ecc.algo = ecc_algo;
 
 	if (ecc_strength >= 0)
@@ -5127,7 +5127,7 @@ static int nand_set_ecc_soft_ops(struct nand_chip *chip)
 		return -EINVAL;
 
 	switch (ecc->algo) {
-	case NAND_ECC_HAMMING:
+	case NAND_ECC_ALGO_HAMMING:
 		ecc->calculate = nand_calculate_ecc;
 		ecc->correct = nand_correct_data;
 		ecc->read_page = nand_read_page_swecc;
@@ -5148,7 +5148,7 @@ static int nand_set_ecc_soft_ops(struct nand_chip *chip)
 			ecc->options |= NAND_ECC_SOFT_HAMMING_SM_ORDER;
 
 		return 0;
-	case NAND_ECC_BCH:
+	case NAND_ECC_ALGO_BCH:
 		if (!mtd_nand_has_bch()) {
 			WARN(1, "CONFIG_MTD_NAND_ECC_SW_BCH not enabled\n");
 			return -EINVAL;
@@ -5588,7 +5588,7 @@ int nand_scan_tail(struct nand_chip *chip)
 	 * If no default placement scheme is given, select an appropriate one.
 	 */
 	if (!mtd->ooblayout &&
-	    !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_BCH)) {
+	    !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_ALGO_BCH)) {
 		switch (mtd->oobsize) {
 		case 8:
 		case 16:
@@ -5684,7 +5684,7 @@ int nand_scan_tail(struct nand_chip *chip)
 		pr_warn("%d byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",
 			ecc->size, mtd->writesize);
 		ecc->mode = NAND_ECC_SOFT;
-		ecc->algo = NAND_ECC_HAMMING;
+		ecc->algo = NAND_ECC_ALGO_HAMMING;
 	case NAND_ECC_SOFT:
 		ret = nand_set_ecc_soft_ops(chip);
 		if (ret) {
@@ -5935,7 +5935,7 @@ EXPORT_SYMBOL(nand_scan_with_ids);
 void nand_cleanup(struct nand_chip *chip)
 {
 	if (chip->ecc.mode == NAND_ECC_SOFT &&
-	    chip->ecc.algo == NAND_ECC_BCH)
+	    chip->ecc.algo == NAND_ECC_ALGO_BCH)
 		nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
 
 	nanddev_cleanup(&chip->base);
diff --git a/drivers/mtd/nand/nand_fsl_ifc.c b/drivers/mtd/nand/nand_fsl_ifc.c
index 74bc9fe1edfd..8eef77e93c2f 100644
--- a/drivers/mtd/nand/nand_fsl_ifc.c
+++ b/drivers/mtd/nand/nand_fsl_ifc.c
@@ -964,7 +964,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
 		mtd_set_ooblayout(mtd, &fsl_ifc_ooblayout_ops);
 	} else {
 		nand->ecc.mode = NAND_ECC_SOFT;
-		nand->ecc.algo = NAND_ECC_HAMMING;
+		nand->ecc.algo = NAND_ECC_ALGO_HAMMING;
 	}
 
 	if (ctrl->version >= FSL_IFC_V1_1_0) {
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index d59be7ca7b02..4401a55886b2 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -544,7 +544,7 @@ static int micron_nand_init(struct nand_chip *chip)
 		chip->ecc.bytes = chip->base.eccreq.strength * 2;
 		chip->ecc.size = 512;
 		chip->ecc.strength = chip->base.eccreq.strength;
-		chip->ecc.algo = NAND_ECC_BCH;
+		chip->ecc.algo = NAND_ECC_ALGO_BCH;
 		chip->ecc.read_page = micron_nand_read_page_on_die_ecc;
 		chip->ecc.write_page = micron_nand_write_page_on_die_ecc;
 
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index a05c67e1404b..87e37fa9a349 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -96,10 +96,10 @@ enum nand_ecc_mode {
 };
 
 enum nand_ecc_algo {
-	NAND_ECC_UNKNOWN,
-	NAND_ECC_HAMMING,
-	NAND_ECC_BCH,
-	NAND_ECC_RS,
+	NAND_ECC_ALGO_UNKNOWN,
+	NAND_ECC_ALGO_HAMMING,
+	NAND_ECC_ALGO_BCH,
+	NAND_ECC_ALGO_RS,
 };
 
 /*
-- 
2.30.2




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

* [PATCH 04/15] mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (2 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 03/15] mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_ Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 05/15] mtd: nand: define nand_get_(small|large)_page_ooblayout Ahmad Fatoum
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Adopt the Linux nomenclature to make future ports easier. No functional
change.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/nand_base.c    | 20 ++++++++---------
 drivers/mtd/nand/nand_esmt.c    | 10 ++++-----
 drivers/mtd/nand/nand_hynix.c   | 40 ++++++++++++++++-----------------
 drivers/mtd/nand/nand_jedec.c   |  4 ++--
 drivers/mtd/nand/nand_micron.c  | 14 +++++++-----
 drivers/mtd/nand/nand_onfi.c    |  8 +++----
 drivers/mtd/nand/nand_samsung.c | 18 +++++++--------
 drivers/mtd/nand/nand_toshiba.c | 10 ++++-----
 include/linux/mtd/nand.h        | 27 ++++++++++++++++++----
 9 files changed, 86 insertions(+), 65 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index b686cef79bc6..beb17f38a0e4 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -4673,8 +4673,8 @@ static bool find_full_id_nand(struct nand_chip *chip,
 					   memorg->pagesize *
 					   memorg->pages_per_eraseblock);
 		chip->options |= type->options;
-		chip->base.eccreq.strength = NAND_ECC_STRENGTH(type);
-		chip->base.eccreq.step_size = NAND_ECC_STEP(type);
+		chip->base.ecc.requirements.strength = NAND_ECC_STRENGTH(type);
+		chip->base.ecc.requirements.step_size = NAND_ECC_STEP(type);
 
 		chip->parameters.model = strdup(type->name);
 		if (!chip->parameters.model)
@@ -5292,8 +5292,8 @@ nand_match_ecc_req(struct nand_chip *chip,
 {
 	struct mtd_info *mtd = nand_to_mtd(chip);
 	const struct nand_ecc_step_info *stepinfo;
-	int req_step = chip->base.eccreq.step_size;
-	int req_strength = chip->base.eccreq.strength;
+	int req_step = chip->base.ecc.requirements.step_size;
+	int req_strength = chip->base.ecc.requirements.strength;
 	int req_corr, step_size, strength, nsteps, ecc_bytes, ecc_bytes_total;
 	int best_step, best_strength, best_ecc_bytes;
 	int best_ecc_bytes_total = INT_MAX;
@@ -5486,7 +5486,7 @@ static bool nand_ecc_strength_good(struct nand_chip *chip)
 	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int corr, ds_corr;
 
-	if (ecc->size == 0 || chip->base.eccreq.step_size == 0)
+	if (ecc->size == 0 || chip->base.ecc.requirements.step_size == 0)
 		/* Not enough information */
 		return true;
 
@@ -5495,10 +5495,10 @@ static bool nand_ecc_strength_good(struct nand_chip *chip)
 	 * the correction density.
 	 */
 	corr = (mtd->writesize * ecc->strength) / ecc->size;
-	ds_corr = (mtd->writesize * chip->base.eccreq.strength) /
-		  chip->base.eccreq.step_size;
+	ds_corr = (mtd->writesize * chip->base.ecc.requirements.strength) /
+		  chip->base.ecc.requirements.step_size;
 
-	return corr >= ds_corr && ecc->strength >= chip->base.eccreq.strength;
+	return corr >= ds_corr && ecc->strength >= chip->base.ecc.requirements.strength;
 }
 
 static int rawnand_erase(struct nand_device *nand, const struct nand_pos *pos)
@@ -5774,8 +5774,8 @@ int nand_scan_tail(struct nand_chip *chip)
 	if (!nand_ecc_strength_good(chip))
 		pr_warn("WARNING: %s: the ECC used on your system (%db/%dB) is too weak compared to the one required by the NAND chip (%db/%dB)\n",
 			mtd->name, chip->ecc.strength, chip->ecc.size,
-			chip->base.eccreq.strength,
-			chip->base.eccreq.step_size);
+			chip->base.ecc.requirements.strength,
+			chip->base.ecc.requirements.step_size);
 
 	/* Allow subpage writes up to ecc.steps. Not possible for MLC flash */
 	if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && nand_is_slc(chip)) {
diff --git a/drivers/mtd/nand/nand_esmt.c b/drivers/mtd/nand/nand_esmt.c
index 2331eb174ebd..cd635c27efcc 100644
--- a/drivers/mtd/nand/nand_esmt.c
+++ b/drivers/mtd/nand/nand_esmt.c
@@ -14,20 +14,20 @@ static void esmt_nand_decode_id(struct nand_chip *chip)
 
 	/* Extract ECC requirements from 5th id byte. */
 	if (chip->id.len >= 5 && nand_is_slc(chip)) {
-		chip->base.eccreq.step_size = 512;
+		chip->base.ecc.requirements.step_size = 512;
 		switch (chip->id.data[4] & 0x3) {
 		case 0x0:
-			chip->base.eccreq.strength = 4;
+			chip->base.ecc.requirements.strength = 4;
 			break;
 		case 0x1:
-			chip->base.eccreq.strength = 2;
+			chip->base.ecc.requirements.strength = 2;
 			break;
 		case 0x2:
-			chip->base.eccreq.strength = 1;
+			chip->base.ecc.requirements.strength = 1;
 			break;
 		default:
 			WARN(1, "Could not get ECC info");
-			chip->base.eccreq.step_size = 0;
+			chip->base.ecc.requirements.step_size = 0;
 			break;
 		}
 	}
diff --git a/drivers/mtd/nand/nand_hynix.c b/drivers/mtd/nand/nand_hynix.c
index 0422ed53aa3a..fef92074953b 100644
--- a/drivers/mtd/nand/nand_hynix.c
+++ b/drivers/mtd/nand/nand_hynix.c
@@ -498,30 +498,30 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip,
 
 	if (valid_jedecid) {
 		/* Reference: H27UCG8T2E datasheet */
-		chip->base.eccreq.step_size = 1024;
+		chip->base.ecc.requirements.step_size = 1024;
 
 		switch (ecc_level) {
 		case 0:
-			chip->base.eccreq.step_size = 0;
-			chip->base.eccreq.strength = 0;
+			chip->base.ecc.requirements.step_size = 0;
+			chip->base.ecc.requirements.strength = 0;
 			break;
 		case 1:
-			chip->base.eccreq.strength = 4;
+			chip->base.ecc.requirements.strength = 4;
 			break;
 		case 2:
-			chip->base.eccreq.strength = 24;
+			chip->base.ecc.requirements.strength = 24;
 			break;
 		case 3:
-			chip->base.eccreq.strength = 32;
+			chip->base.ecc.requirements.strength = 32;
 			break;
 		case 4:
-			chip->base.eccreq.strength = 40;
+			chip->base.ecc.requirements.strength = 40;
 			break;
 		case 5:
-			chip->base.eccreq.strength = 50;
+			chip->base.ecc.requirements.strength = 50;
 			break;
 		case 6:
-			chip->base.eccreq.strength = 60;
+			chip->base.ecc.requirements.strength = 60;
 			break;
 		default:
 			/*
@@ -542,14 +542,14 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip,
 		if (nand_tech < 3) {
 			/* > 26nm, reference: H27UBG8T2A datasheet */
 			if (ecc_level < 5) {
-				chip->base.eccreq.step_size = 512;
-				chip->base.eccreq.strength = 1 << ecc_level;
+				chip->base.ecc.requirements.step_size = 512;
+				chip->base.ecc.requirements.strength = 1 << ecc_level;
 			} else if (ecc_level < 7) {
 				if (ecc_level == 5)
-					chip->base.eccreq.step_size = 2048;
+					chip->base.ecc.requirements.step_size = 2048;
 				else
-					chip->base.eccreq.step_size = 1024;
-				chip->base.eccreq.strength = 24;
+					chip->base.ecc.requirements.step_size = 1024;
+				chip->base.ecc.requirements.strength = 24;
 			} else {
 				/*
 				 * We should never reach this case, but if that
@@ -562,14 +562,14 @@ static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip,
 		} else {
 			/* <= 26nm, reference: H27UBG8T2B datasheet */
 			if (!ecc_level) {
-				chip->base.eccreq.step_size = 0;
-				chip->base.eccreq.strength = 0;
+				chip->base.ecc.requirements.step_size = 0;
+				chip->base.ecc.requirements.strength = 0;
 			} else if (ecc_level < 5) {
-				chip->base.eccreq.step_size = 512;
-				chip->base.eccreq.strength = 1 << (ecc_level - 1);
+				chip->base.ecc.requirements.step_size = 512;
+				chip->base.ecc.requirements.strength = 1 << (ecc_level - 1);
 			} else {
-				chip->base.eccreq.step_size = 1024;
-				chip->base.eccreq.strength = 24 +
+				chip->base.ecc.requirements.step_size = 1024;
+				chip->base.ecc.requirements.strength = 24 +
 							(8 * (ecc_level - 5));
 			}
 		}
diff --git a/drivers/mtd/nand/nand_jedec.c b/drivers/mtd/nand/nand_jedec.c
index 48997bd5f53d..2b21e2d5b548 100644
--- a/drivers/mtd/nand/nand_jedec.c
+++ b/drivers/mtd/nand/nand_jedec.c
@@ -121,8 +121,8 @@ int nand_jedec_detect(struct nand_chip *chip)
 	ecc = &p->ecc_info[0];
 
 	if (ecc->codeword_size >= 9) {
-		chip->base.eccreq.strength = ecc->ecc_bits;
-		chip->base.eccreq.step_size = 1 << ecc->codeword_size;
+		chip->base.ecc.requirements.strength = ecc->ecc_bits;
+		chip->base.ecc.requirements.step_size = 1 << ecc->codeword_size;
 	} else {
 		pr_warn("Invalid codeword size\n");
 	}
diff --git a/drivers/mtd/nand/nand_micron.c b/drivers/mtd/nand/nand_micron.c
index 4401a55886b2..758316e68139 100644
--- a/drivers/mtd/nand/nand_micron.c
+++ b/drivers/mtd/nand/nand_micron.c
@@ -426,7 +426,8 @@ static int micron_supports_on_die_ecc(struct nand_chip *chip)
 	/*
 	 * We only support on-die ECC of 4/512 or 8/512
 	 */
-	if  (chip->base.eccreq.strength != 4 && chip->base.eccreq.strength != 8)
+	if  (chip->base.ecc.requirements.strength != 4 &&
+	     chip->base.ecc.requirements.strength != 8)
 		return MICRON_ON_DIE_UNSUPPORTED;
 
 	/* 0x2 means on-die ECC is available. */
@@ -467,7 +468,8 @@ static int micron_supports_on_die_ecc(struct nand_chip *chip)
 	/*
 	 * We only support on-die ECC of 4/512 or 8/512
 	 */
-	if  (chip->base.eccreq.strength != 4 && chip->base.eccreq.strength != 8)
+	if  (chip->base.ecc.requirements.strength != 4 &&
+	     chip->base.ecc.requirements.strength != 8)
 		return MICRON_ON_DIE_UNSUPPORTED;
 
 	return MICRON_ON_DIE_SUPPORTED;
@@ -524,7 +526,7 @@ static int micron_nand_init(struct nand_chip *chip)
 		 * That's not needed for 8-bit ECC, because the status expose
 		 * a better approximation of the number of bitflips in a page.
 		 */
-		if (chip->base.eccreq.strength == 4) {
+		if (chip->base.ecc.requirements.strength == 4) {
 			micron->ecc.rawbuf = kmalloc(mtd->writesize +
 						     mtd->oobsize,
 						     GFP_KERNEL);
@@ -534,16 +536,16 @@ static int micron_nand_init(struct nand_chip *chip)
 			}
 		}
 
-		if (chip->base.eccreq.strength == 4)
+		if (chip->base.ecc.requirements.strength == 4)
 			mtd_set_ooblayout(mtd,
 					  &micron_nand_on_die_4_ooblayout_ops);
 		else
 			mtd_set_ooblayout(mtd,
 					  &micron_nand_on_die_8_ooblayout_ops);
 
-		chip->ecc.bytes = chip->base.eccreq.strength * 2;
+		chip->ecc.bytes = chip->base.ecc.requirements.strength * 2;
 		chip->ecc.size = 512;
-		chip->ecc.strength = chip->base.eccreq.strength;
+		chip->ecc.strength = chip->base.ecc.requirements.strength;
 		chip->ecc.algo = NAND_ECC_ALGO_BCH;
 		chip->ecc.read_page = micron_nand_read_page_on_die_ecc;
 		chip->ecc.write_page = micron_nand_write_page_on_die_ecc;
diff --git a/drivers/mtd/nand/nand_onfi.c b/drivers/mtd/nand/nand_onfi.c
index c6b187be0285..5dd29ba6baf4 100644
--- a/drivers/mtd/nand/nand_onfi.c
+++ b/drivers/mtd/nand/nand_onfi.c
@@ -95,8 +95,8 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
 		goto ext_out;
 	}
 
-	chip->base.eccreq.strength = ecc->ecc_bits;
-	chip->base.eccreq.step_size = 1 << ecc->codeword_size;
+	chip->base.ecc.requirements.strength = ecc->ecc_bits;
+	chip->base.ecc.requirements.step_size = 1 << ecc->codeword_size;
 	ret = 0;
 
 ext_out:
@@ -266,8 +266,8 @@ int nand_onfi_detect(struct nand_chip *chip)
 		chip->options |= NAND_BUSWIDTH_16;
 
 	if (p->ecc_bits != 0xff) {
-		chip->base.eccreq.strength = p->ecc_bits;
-		chip->base.eccreq.step_size = 512;
+		chip->base.ecc.requirements.strength = p->ecc_bits;
+		chip->base.ecc.requirements.step_size = 512;
 	} else if (onfi_version >= 21 &&
 		(le16_to_cpu(p->features) & ONFI_FEATURE_EXT_PARAM_PAGE)) {
 
diff --git a/drivers/mtd/nand/nand_samsung.c b/drivers/mtd/nand/nand_samsung.c
index 3a4a19e808f6..ee993af1e52f 100644
--- a/drivers/mtd/nand/nand_samsung.c
+++ b/drivers/mtd/nand/nand_samsung.c
@@ -71,23 +71,23 @@ static void samsung_nand_decode_id(struct nand_chip *chip)
 		/* Extract ECC requirements from 5th id byte*/
 		extid = (chip->id.data[4] >> 4) & 0x07;
 		if (extid < 5) {
-			chip->base.eccreq.step_size = 512;
-			chip->base.eccreq.strength = 1 << extid;
+			chip->base.ecc.requirements.step_size = 512;
+			chip->base.ecc.requirements.strength = 1 << extid;
 		} else {
-			chip->base.eccreq.step_size = 1024;
+			chip->base.ecc.requirements.step_size = 1024;
 			switch (extid) {
 			case 5:
-				chip->base.eccreq.strength = 24;
+				chip->base.ecc.requirements.strength = 24;
 				break;
 			case 6:
-				chip->base.eccreq.strength = 40;
+				chip->base.ecc.requirements.strength = 40;
 				break;
 			case 7:
-				chip->base.eccreq.strength = 60;
+				chip->base.ecc.requirements.strength = 60;
 				break;
 			default:
 				WARN(1, "Could not decode ECC info");
-				chip->base.eccreq.step_size = 0;
+				chip->base.ecc.requirements.step_size = 0;
 			}
 		}
 	} else {
@@ -97,8 +97,8 @@ static void samsung_nand_decode_id(struct nand_chip *chip)
 			switch (chip->id.data[1]) {
 			/* K9F4G08U0D-S[I|C]B0(T00) */
 			case 0xDC:
-				chip->base.eccreq.step_size = 512;
-				chip->base.eccreq.strength = 1;
+				chip->base.ecc.requirements.step_size = 512;
+				chip->base.ecc.requirements.strength = 1;
 				break;
 
 			/* K9F1G08U0E 21nm chips do not support subpage write */
diff --git a/drivers/mtd/nand/nand_toshiba.c b/drivers/mtd/nand/nand_toshiba.c
index 21a5dbc7e01b..cd29cc587293 100644
--- a/drivers/mtd/nand/nand_toshiba.c
+++ b/drivers/mtd/nand/nand_toshiba.c
@@ -175,20 +175,20 @@ static void toshiba_nand_decode_id(struct nand_chip *chip)
 	 *  - 24nm: 8 bit ECC for each 512Byte is required.
 	 */
 	if (chip->id.len >= 6 && nand_is_slc(chip)) {
-		chip->base.eccreq.step_size = 512;
+		chip->base.ecc.requirements.step_size = 512;
 		switch (chip->id.data[5] & 0x7) {
 		case 0x4:
-			chip->base.eccreq.strength = 1;
+			chip->base.ecc.requirements.strength = 1;
 			break;
 		case 0x5:
-			chip->base.eccreq.strength = 4;
+			chip->base.ecc.requirements.strength = 4;
 			break;
 		case 0x6:
-			chip->base.eccreq.strength = 8;
+			chip->base.ecc.requirements.strength = 8;
 			break;
 		default:
 			WARN(1, "Could not get ECC info");
-			chip->base.eccreq.step_size = 0;
+			chip->base.ecc.requirements.step_size = 0;
 			break;
 		}
 	}
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 876849e7e806..6ce5c1d041b8 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -165,11 +165,19 @@ struct nand_ops {
 	bool (*isbad)(struct nand_device *nand, const struct nand_pos *pos);
 };
 
+/**
+ * struct nand_ecc - Information relative to the ECC
+ * @requirements: ECC requirements from the NAND chip perspective
+ */
+struct nand_ecc {
+	struct nand_ecc_props requirements;
+};
+
 /**
  * struct nand_device - NAND device
  * @mtd: MTD instance attached to the NAND device
  * @memorg: memory layout
- * @eccreq: ECC requirements
+ * @ecc: ECC information
  * @rowconv: position to row address converter
  * @bbt: bad block table info
  * @ops: NAND operations attached to the NAND device
@@ -177,8 +185,8 @@ struct nand_ops {
  * Generic NAND object. Specialized NAND layers (raw NAND, SPI NAND, OneNAND)
  * should declare their own NAND object embedding a nand_device struct (that's
  * how inheritance is done).
- * struct_nand_device->memorg and struct_nand_device->eccreq should be filled
- * at device detection time to reflect the NAND device
+ * struct_nand_device->memorg and struct_nand_device->ecc.requirement should
+ * be filled at device detection time to reflect the NAND device
  * capabilities/requirements. Once this is done nanddev_init() can be called.
  * It will take care of converting NAND information into MTD ones, which means
  * the specialized NAND layers should never manually tweak
@@ -187,7 +195,7 @@ struct nand_ops {
 struct nand_device {
 	struct mtd_info mtd;
 	struct nand_memory_organization memorg;
-	struct nand_ecc_props eccreq;
+	struct nand_ecc ecc;
 	struct nand_row_converter rowconv;
 	struct nand_bbt bbt;
 	const struct nand_ops *ops;
@@ -395,6 +403,17 @@ int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
 		 struct module *owner);
 void nanddev_cleanup(struct nand_device *nand);
 
+/**
+ * nanddev_get_ecc_requirements() - Extract the ECC requirements from a NAND
+ *                                  device
+ * @nand: NAND device
+ */
+static inline const struct nand_ecc_props *
+nanddev_get_ecc_requirements(struct nand_device *nand)
+{
+	return &nand->ecc.requirements;
+}
+
 /**
  * nanddev_offs_to_pos() - Convert an absolute NAND offset into a NAND position
  * @nand: NAND device
-- 
2.30.2




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

* [PATCH 05/15] mtd: nand: define nand_get_(small|large)_page_ooblayout
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (3 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 04/15] mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 06/15] mtd: nand: define nand_interface_is_sdr Ahmad Fatoum
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Adopt the Linux nomenclature to make future ports easier. No functional
change.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/atmel_nand.c   |  2 +-
 drivers/mtd/nand/nand_base.c    | 18 ++++++++++++++----
 drivers/mtd/nand/nand_toshiba.c |  2 +-
 include/linux/mtd/rawnand.h     |  4 ++--
 4 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index 2884a9d1fe4b..bd8923f93dd5 100644
--- a/drivers/mtd/nand/atmel_nand.c
+++ b/drivers/mtd/nand/atmel_nand.c
@@ -909,7 +909,7 @@ static int __init atmel_pmecc_nand_init_params(struct device *dev,
 			dev_err(host->dev, "No room for ECC bytes\n");
 			return -EINVAL;
 		}
-		mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
+		mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
 		break;
 	case 512:
 	case 1024:
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index beb17f38a0e4..55218b120210 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -92,11 +92,16 @@ static int nand_ooblayout_free_sp(struct mtd_info *mtd, int section,
 	return 0;
 }
 
-const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = {
+static const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = {
 	.ecc = nand_ooblayout_ecc_sp,
 	.free = nand_ooblayout_free_sp,
 };
-EXPORT_SYMBOL_GPL(nand_ooblayout_sp_ops);
+
+const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void)
+{
+	return &nand_ooblayout_sp_ops;
+}
+EXPORT_SYMBOL_GPL(nand_get_small_page_ooblayout);
 
 static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section,
 				 struct mtd_oob_region *oobregion)
@@ -128,11 +133,16 @@ static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section,
 	return 0;
 }
 
-const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = {
+static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = {
 	.ecc = nand_ooblayout_ecc_lp,
 	.free = nand_ooblayout_free_lp,
 };
-EXPORT_SYMBOL_GPL(nand_ooblayout_lp_ops);
+
+const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void)
+{
+	return &nand_ooblayout_lp_ops;
+}
+EXPORT_SYMBOL_GPL(nand_get_large_page_ooblayout);
 
 /*
  * Support the old "large page" layout used for 1-bit Hamming ECC where ECC
diff --git a/drivers/mtd/nand/nand_toshiba.c b/drivers/mtd/nand/nand_toshiba.c
index cd29cc587293..3fe0347bfdfb 100644
--- a/drivers/mtd/nand/nand_toshiba.c
+++ b/drivers/mtd/nand/nand_toshiba.c
@@ -140,7 +140,7 @@ static void toshiba_nand_benand_init(struct nand_chip *chip)
 
 	chip->options |= NAND_SUBPAGE_READ;
 
-	mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
+	mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
 }
 
 static void toshiba_nand_decode_id(struct nand_chip *chip)
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 87e37fa9a349..0df142098d43 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1168,8 +1168,8 @@ struct nand_chip {
 	unsigned int bbt_type;
 };
 
-extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
-extern const struct mtd_ooblayout_ops nand_ooblayout_lp_ops;
+const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void);
+const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void);
 
 static inline struct nand_chip *mtd_to_nand(struct mtd_info *mtd)
 {
-- 
2.30.2




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

* [PATCH 06/15] mtd: nand: define nand_interface_is_sdr
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (4 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 05/15] mtd: nand: define nand_get_(small|large)_page_ooblayout Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 07/15] mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type Ahmad Fatoum
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

To simplify porting kernel NAND drivers a tiny bit, define the
nand_interface_is_sdr() helper.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/linux/mtd/rawnand.h | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 0df142098d43..9c8b0a1fe18e 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -519,6 +519,15 @@ struct nand_interface_config {
 	} timings;
 };
 
+/**
+ * nand_interface_is_sdr - get the interface type
+ * @conf:	The data interface
+ */
+static bool nand_interface_is_sdr(const struct nand_interface_config *conf)
+{
+	return conf->type == NAND_SDR_IFACE;
+}
+
 /**
  * nand_get_sdr_timings - get SDR timing from data interface
  * @conf:	The data interface
@@ -526,7 +535,7 @@ struct nand_interface_config {
 static inline const struct nand_sdr_timings *
 nand_get_sdr_timings(const struct nand_interface_config *conf)
 {
-	if (conf->type != NAND_SDR_IFACE)
+	if (!nand_interface_is_sdr(conf))
 		return ERR_PTR(-EINVAL);
 
 	return &conf->timings.sdr;
-- 
2.30.2




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

* [PATCH 07/15] mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (5 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 06/15] mtd: nand: define nand_interface_is_sdr Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 08/15] driver: implement dev_request_resource Ahmad Fatoum
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The Linux MTD-NAND core doesn't seem to care much for the engine_type,
but different drivers use it to record during probe, which kind of
ECC engine should be used during future operation. Provide the member
to make porting drivers a tiny bit easier.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/linux/mtd/rawnand.h | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 9c8b0a1fe18e..ce8944c481df 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -95,6 +95,22 @@ enum nand_ecc_mode {
 	NAND_ECC_SOFT_BCH
 };
 
+/**
+ * enum nand_ecc_engine_type - NAND ECC engine type
+ * @NAND_ECC_ENGINE_TYPE_INVALID: Invalid value
+ * @NAND_ECC_ENGINE_TYPE_NONE: No ECC correction
+ * @NAND_ECC_ENGINE_TYPE_SOFT: Software ECC correction
+ * @NAND_ECC_ENGINE_TYPE_ON_HOST: On host hardware ECC correction
+ * @NAND_ECC_ENGINE_TYPE_ON_DIE: On chip hardware ECC correction
+ */
+enum nand_ecc_engine_type {
+	NAND_ECC_ENGINE_TYPE_INVALID,
+	NAND_ECC_ENGINE_TYPE_NONE,
+	NAND_ECC_ENGINE_TYPE_SOFT,
+	NAND_ECC_ENGINE_TYPE_ON_HOST,
+	NAND_ECC_ENGINE_TYPE_ON_DIE,
+};
+
 enum nand_ecc_algo {
 	NAND_ECC_ALGO_UNKNOWN,
 	NAND_ECC_ALGO_HAMMING,
@@ -313,6 +329,7 @@ static const struct nand_ecc_caps __name = {			\
 
 /**
  * struct nand_ecc_ctrl - Control structure for ECC
+ * @engine_type: ECC engine type
  * @mode:	ECC mode
  * @algo:	ECC algorithm
  * @steps:	number of ECC steps per page
@@ -365,6 +382,7 @@ static const struct nand_ecc_caps __name = {			\
  * @write_oob:	function to write chip OOB data
  */
 struct nand_ecc_ctrl {
+	enum nand_ecc_engine_type engine_type;
 	enum nand_ecc_mode mode;
 	enum nand_ecc_algo algo;
 	int steps;
-- 
2.30.2




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

* [PATCH 08/15] driver: implement dev_request_resource
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (6 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 07/15] mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 09/15] lib: provide stub Linux "generic" allocator API Ahmad Fatoum
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

dev_request_resource is useful for porting kernel drivers that use
devm_request_source on a resource retrieved via <linux/of_address.h>.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/base/driver.c | 19 +++++++++++++++----
 include/driver.h      |  3 +++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index 839a8513fc25..efbffcdddbcb 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -453,8 +453,19 @@ struct resource *dev_get_resource_by_name(struct device *dev,
 	return ERR_PTR(-ENOENT);
 }
 
-struct resource *dev_request_mem_resource_by_name(struct device *dev,
-						  const char *name)
+static struct resource *dev_request_iomem_resource(struct device *dev,
+						   const struct resource *res)
+{
+	return request_iomem_region(dev_name(dev), res->start, res->end);
+}
+
+int dev_request_resource(struct device *dev, const struct resource *res)
+{
+	return PTR_ERR_OR_ZERO(dev_request_iomem_resource(dev, res));
+}
+EXPORT_SYMBOL(dev_request_resource);
+
+struct resource *dev_request_mem_resource_by_name(struct device *dev, const char *name)
 {
 	struct resource *res;
 
@@ -462,7 +473,7 @@ struct resource *dev_request_mem_resource_by_name(struct device *dev,
 	if (IS_ERR(res))
 		return ERR_CAST(res);
 
-	return request_iomem_region(dev_name(dev), res->start, res->end);
+	return dev_request_iomem_resource(dev, res);
 }
 EXPORT_SYMBOL(dev_request_mem_resource_by_name);
 
@@ -487,7 +498,7 @@ struct resource *dev_request_mem_resource(struct device *dev, int num)
 	if (IS_ERR(res))
 		return ERR_CAST(res);
 
-	return request_iomem_region(dev_name(dev), res->start, res->end);
+	return dev_request_iomem_resource(dev, res);
 }
 
 void __iomem *dev_request_mem_region_err_null(struct device *dev, int num)
diff --git a/include/driver.h b/include/driver.h
index e1b1ebde5b5f..b7d6ea1e52c1 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -220,6 +220,9 @@ struct resource *dev_get_resource_by_name(struct device *dev,
 					  unsigned long type,
 					  const char *name);
 
+int dev_request_resource(struct device *dev,
+			 const struct resource *res);
+
 /*
  * exlusively request register base 'name' for a device
  */
-- 
2.30.2




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

* [PATCH 09/15] lib: provide stub Linux "generic" allocator API
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (7 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 08/15] driver: implement dev_request_resource Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-12 13:26   ` Sascha Hauer
  2023-01-11 17:40 ` [PATCH 10/15] memory: add Atmel EBI driver Ahmad Fatoum
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

We may want to port the whole of the Linux generic allocator
implementation in future as it would come in handy in drivers that need
special memory for DMA. For now, support just the use case of the
incoming Atmel NAND driver, which is mapping the whole of a mmio-sram.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/linux/genalloc.h |  36 ++++++++++++
 lib/Kconfig              |   5 ++
 lib/Makefile             |   1 +
 lib/genalloc.c           | 118 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 160 insertions(+)
 create mode 100644 include/linux/genalloc.h
 create mode 100644 lib/genalloc.c

diff --git a/include/linux/genalloc.h b/include/linux/genalloc.h
new file mode 100644
index 000000000000..566e62d1965c
--- /dev/null
+++ b/include/linux/genalloc.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Basic general purpose allocator for managing special purpose
+ * memory, for example, memory that is not managed by the regular
+ * kmalloc/kfree interface.  Uses for this includes on-device special
+ * memory, uncached memory etc.
+ */
+
+
+#ifndef __GENALLOC_H__
+#define __GENALLOC_H__
+
+#include <linux/types.h>
+
+struct device_node;
+
+struct gen_pool;
+
+extern phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long);
+
+extern void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size,
+		dma_addr_t *dma);
+
+extern void *gen_pool_dma_zalloc(struct gen_pool *pool, size_t size, dma_addr_t *dma);
+
+#ifdef CONFIG_OFDEVICE
+extern struct gen_pool *of_gen_pool_get(struct device_node *np,
+	const char *propname, int index);
+#else
+static inline struct gen_pool *of_gen_pool_get(struct device_node *np,
+	const char *propname, int index)
+{
+	return NULL;
+}
+#endif
+#endif /* __GENALLOC_H__ */
diff --git a/lib/Kconfig b/lib/Kconfig
index 5af7ea33f27b..b8bc9d63d4f0 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -210,4 +210,9 @@ config ARCH_HAS_ZERO_PAGE
 config HAVE_EFFICIENT_UNALIGNED_ACCESS
 	bool
 
+config GENERIC_ALLOCATOR
+	bool
+	help
+	  Support is curently limited to allocaing a complete mmio-sram at once.
+
 endmenu
diff --git a/lib/Makefile b/lib/Makefile
index 4717b8aec364..38478625423b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_CRC8)	+= crc8.o
 obj-$(CONFIG_NLS)	+= nls_base.o
 obj-$(CONFIG_FSL_QE_FIRMWARE) += fsl-qe-firmware.o
 obj-$(CONFIG_UBSAN)	+= ubsan.o
+obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
 
 # GCC library routines
 obj-$(CONFIG_GENERIC_LIB_ASHLDI3) += ashldi3.o
diff --git a/lib/genalloc.c b/lib/genalloc.c
new file mode 100644
index 000000000000..906e2dd5f14b
--- /dev/null
+++ b/lib/genalloc.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: 2005 Jes Sorensen <jes@trained-monkey.org>
+/*
+ * Basic general purpose allocator for managing special purpose
+ * memory, for example, memory that is not managed by the regular
+ * kmalloc/kfree interface.  Uses for this includes on-device special
+ * memory, uncached memory etc.
+ */
+
+#include <io.h>
+#include <linux/ioport.h>
+#include <linux/genalloc.h>
+#include <linux/export.h>
+#include <of.h>
+#include <driver.h>
+#include <linux/string.h>
+
+struct gen_pool {
+	struct resource res;
+};
+
+#define res_to_gen_pool(res) \
+	container_of(res, struct gen_pool, res)
+
+/**
+ * gen_pool_virt_to_phys - return the physical address of memory
+ * @pool: pool to allocate from
+ * @addr: starting address of memory
+ *
+ * Returns the physical address on success, or -1 on error.
+ */
+phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long addr)
+{
+	return virt_to_phys((void *)addr);
+}
+EXPORT_SYMBOL(gen_pool_virt_to_phys);
+
+/**
+ * gen_pool_dma_alloc - allocate special memory from the pool for DMA usage
+ * @pool: pool to allocate from
+ * @size: number of bytes to allocate from the pool
+ * @dma: dma-view physical address return value.  Use %NULL if unneeded.
+ *
+ * Allocate the requested number of bytes from the specified pool.
+ * Uses the pool allocation function (with first-fit algorithm by default).
+ * Can not be used in NMI handler on architectures without
+ * NMI-safe cmpxchg implementation.
+ *
+ * Return: virtual address of the allocated memory, or %NULL on failure
+ */
+void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
+{
+	unsigned long vaddr;
+
+	if (!pool || resource_size(&pool->res) != size)
+		return NULL;
+
+	vaddr = pool->res.start;
+
+	if (dma)
+		*dma = gen_pool_virt_to_phys(pool, vaddr);
+
+	return (void *)vaddr;
+}
+EXPORT_SYMBOL(gen_pool_dma_alloc);
+
+/**
+ * gen_pool_dma_zalloc - allocate special zeroed memory from the pool for
+ * DMA usage
+ * @pool: pool to allocate from
+ * @size: number of bytes to allocate from the pool
+ * @dma: dma-view physical address return value.  Use %NULL if unneeded.
+ *
+ * Return: virtual address of the allocated zeroed memory, or %NULL on failure
+ */
+void *gen_pool_dma_zalloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
+{
+	void *vaddr = gen_pool_dma_alloc(pool, size, dma);
+
+	if (vaddr)
+		memset(vaddr, 0, size);
+
+	return vaddr;
+}
+EXPORT_SYMBOL(gen_pool_dma_zalloc);
+
+#ifdef CONFIG_OFDEVICE
+/**
+ * of_gen_pool_get - find a pool by phandle property
+ * @np: device node
+ * @propname: property name containing phandle(s)
+ * @index: index into the phandle array
+ *
+ * Returns the pool that contains the chunk starting at the physical
+ * address of the device tree node pointed at by the phandle property,
+ * or NULL if not found.
+ */
+struct gen_pool *of_gen_pool_get(struct device_node *np,
+	const char *propname, int index)
+{
+	struct device *dev;
+	struct device_node *np_pool;
+
+	np_pool = of_parse_phandle(np, propname, index);
+	if (!np_pool)
+		return NULL;
+
+	if (!of_device_is_compatible(np_pool, "mmio-sram"))
+		return NULL;
+
+	dev = of_find_device_by_node(np_pool);
+	if (!dev)
+		return NULL;
+
+	return container_of(&dev->resource[0], struct gen_pool, res);
+}
+EXPORT_SYMBOL_GPL(of_gen_pool_get);
+#endif /* CONFIG_OF */
-- 
2.30.2




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

* [PATCH 10/15] memory: add Atmel EBI driver
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (8 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 09/15] lib: provide stub Linux "generic" allocator API Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 11/15] mfd: add atmel-smc driver Ahmad Fatoum
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

This driver is used to configure the EBI (external bus interface) when
the device-tree is used. This bus supports NANDs, external Ethernet
controller, SRAMs, ATA devices, etc.

We import it from Linux in barebox in preparation for importing a newer
state of the Atmel NAND controller driver.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/memory/Kconfig                  |  14 +
 drivers/memory/Makefile                 |   1 +
 drivers/memory/atmel-ebi.c              | 614 ++++++++++++++++++++++++
 include/linux/mfd/syscon/atmel-matrix.h | 112 +++++
 include/soc/at91/atmel-sfr.h            |   1 +
 5 files changed, 742 insertions(+)
 create mode 100644 drivers/memory/atmel-ebi.c
 create mode 100644 include/linux/mfd/syscon/atmel-matrix.h

diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index e27695e98143..e18b452009d0 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -1,6 +1,20 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menu "Memory controller drivers"
 
+config ATMEL_EBI
+	bool "Atmel EBI driver"
+	default y if ARCH_AT91
+	depends on ARCH_AT91 || COMPILE_TEST
+	depends on OFDEVICE
+	select MFD_SYSCON
+	select MFD_ATMEL_SMC
+	help
+	  Driver for Atmel EBI controller.
+	  Used to configure the EBI (external bus interface) when the device-
+	  tree is used. This bus supports NANDs, external ethernet controller,
+	  SRAMs, ATA devices, etc.
+
+
 config MC_TEGRA124
 	bool "Support for Tegra124 memory controller"
 	depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index 39d1ba08ea1a..bdf8db66e88d 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ATMEL_EBI)		+= atmel-ebi.o
 obj-$(CONFIG_MC_TEGRA124)	+= mc-tegra124.o
diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c
new file mode 100644
index 000000000000..cbb4f4cfe77d
--- /dev/null
+++ b/drivers/memory/atmel-ebi.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * EBI driver for Atmel chips
+ * inspired by the fsl weim bus driver
+ *
+ * Copyright (C) 2013 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
+ */
+
+#include <linux/clk.h>
+#include <io.h>
+#include <mfd/syscon.h>
+#include <linux/mfd/syscon/atmel-matrix.h>
+#include <linux/mfd/syscon/atmel-smc.h>
+#include <init.h>
+#include <driver.h>
+#include <of_device.h>
+#include <regmap.h>
+#include <soc/at91/atmel-sfr.h>
+#include <linux/time.h>
+#include <linux/printk.h>
+
+#define AT91_EBI_NUM_CS		8
+
+struct atmel_ebi_dev_config {
+	int cs;
+	struct atmel_smc_cs_conf smcconf;
+};
+
+struct atmel_ebi;
+
+struct atmel_ebi_dev {
+	struct list_head node;
+	struct atmel_ebi *ebi;
+	u32 mode;
+	int numcs;
+	struct atmel_ebi_dev_config configs[];
+};
+
+struct atmel_ebi_caps {
+	unsigned int available_cs;
+	unsigned int ebi_csa_offs;
+	const char *regmap_name;
+	void (*get_config)(struct atmel_ebi_dev *ebid,
+			   struct atmel_ebi_dev_config *conf);
+	int (*xlate_config)(struct atmel_ebi_dev *ebid,
+			    struct device_node *configs_np,
+			    struct atmel_ebi_dev_config *conf);
+	void (*apply_config)(struct atmel_ebi_dev *ebid,
+			     struct atmel_ebi_dev_config *conf);
+};
+
+struct atmel_ebi {
+	struct clk *clk;
+	struct regmap *regmap;
+	struct  {
+		struct regmap *regmap;
+		struct clk *clk;
+		const struct atmel_hsmc_reg_layout *layout;
+	} smc;
+
+	struct device *dev;
+	const struct atmel_ebi_caps *caps;
+	struct list_head devs;
+};
+
+struct atmel_smc_timing_xlate {
+	const char *name;
+	int (*converter)(struct atmel_smc_cs_conf *conf,
+			 unsigned int shift, unsigned int nycles);
+	unsigned int shift;
+};
+
+#define ATMEL_SMC_SETUP_XLATE(nm, pos)	\
+	{ .name = nm, .converter = atmel_smc_cs_conf_set_setup, .shift = pos}
+
+#define ATMEL_SMC_PULSE_XLATE(nm, pos)	\
+	{ .name = nm, .converter = atmel_smc_cs_conf_set_pulse, .shift = pos}
+
+#define ATMEL_SMC_CYCLE_XLATE(nm, pos)	\
+	{ .name = nm, .converter = atmel_smc_cs_conf_set_cycle, .shift = pos}
+
+static inline void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags)
+{
+	return kzalloc(size, flags);
+}
+
+static void at91sam9_ebi_get_config(struct atmel_ebi_dev *ebid,
+				    struct atmel_ebi_dev_config *conf)
+{
+	atmel_smc_cs_conf_get(ebid->ebi->smc.regmap, conf->cs,
+			      &conf->smcconf);
+}
+
+static void sama5_ebi_get_config(struct atmel_ebi_dev *ebid,
+				 struct atmel_ebi_dev_config *conf)
+{
+	atmel_hsmc_cs_conf_get(ebid->ebi->smc.regmap, ebid->ebi->smc.layout,
+			       conf->cs, &conf->smcconf);
+}
+
+static const struct atmel_smc_timing_xlate timings_xlate_table[] = {
+	ATMEL_SMC_SETUP_XLATE("atmel,smc-ncs-rd-setup-ns",
+			      ATMEL_SMC_NCS_RD_SHIFT),
+	ATMEL_SMC_SETUP_XLATE("atmel,smc-ncs-wr-setup-ns",
+			      ATMEL_SMC_NCS_WR_SHIFT),
+	ATMEL_SMC_SETUP_XLATE("atmel,smc-nrd-setup-ns", ATMEL_SMC_NRD_SHIFT),
+	ATMEL_SMC_SETUP_XLATE("atmel,smc-nwe-setup-ns", ATMEL_SMC_NWE_SHIFT),
+	ATMEL_SMC_PULSE_XLATE("atmel,smc-ncs-rd-pulse-ns",
+			      ATMEL_SMC_NCS_RD_SHIFT),
+	ATMEL_SMC_PULSE_XLATE("atmel,smc-ncs-wr-pulse-ns",
+			      ATMEL_SMC_NCS_WR_SHIFT),
+	ATMEL_SMC_PULSE_XLATE("atmel,smc-nrd-pulse-ns", ATMEL_SMC_NRD_SHIFT),
+	ATMEL_SMC_PULSE_XLATE("atmel,smc-nwe-pulse-ns", ATMEL_SMC_NWE_SHIFT),
+	ATMEL_SMC_CYCLE_XLATE("atmel,smc-nrd-cycle-ns", ATMEL_SMC_NRD_SHIFT),
+	ATMEL_SMC_CYCLE_XLATE("atmel,smc-nwe-cycle-ns", ATMEL_SMC_NWE_SHIFT),
+};
+
+static int atmel_ebi_xslate_smc_timings(struct atmel_ebi_dev *ebid,
+					struct device_node *np,
+					struct atmel_smc_cs_conf *smcconf)
+{
+	unsigned int clk_rate = clk_get_rate(ebid->ebi->clk);
+	unsigned int clk_period_ns = NSEC_PER_SEC / clk_rate;
+	bool required = false;
+	unsigned int ncycles;
+	int ret, i;
+	u32 val;
+
+	ret = of_property_read_u32(np, "atmel,smc-tdf-ns", &val);
+	if (!ret) {
+		required = true;
+		ncycles = DIV_ROUND_UP(val, clk_period_ns);
+		if (ncycles > ATMEL_SMC_MODE_TDF_MAX) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (ncycles < ATMEL_SMC_MODE_TDF_MIN)
+			ncycles = ATMEL_SMC_MODE_TDF_MIN;
+
+		smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(timings_xlate_table); i++) {
+		const struct atmel_smc_timing_xlate *xlate;
+
+		xlate = &timings_xlate_table[i];
+
+		ret = of_property_read_u32(np, xlate->name, &val);
+		if (ret) {
+			if (!required)
+				continue;
+			else
+				break;
+		}
+
+		if (!required) {
+			ret = -EINVAL;
+			break;
+		}
+
+		ncycles = DIV_ROUND_UP(val, clk_period_ns);
+		ret = xlate->converter(smcconf, xlate->shift, ncycles);
+		if (ret)
+			goto out;
+	}
+
+out:
+	if (ret) {
+		dev_err(ebid->ebi->dev,
+			"missing or invalid timings definition in %pOF",
+			np);
+		return ret;
+	}
+
+	return required;
+}
+
+static int atmel_ebi_xslate_smc_config(struct atmel_ebi_dev *ebid,
+				       struct device_node *np,
+				       struct atmel_ebi_dev_config *conf)
+{
+	struct atmel_smc_cs_conf *smcconf = &conf->smcconf;
+	bool required = false;
+	const char *tmp_str;
+	u32 tmp;
+	int ret;
+
+	ret = of_property_read_u32(np, "atmel,smc-bus-width", &tmp);
+	if (!ret) {
+		switch (tmp) {
+		case 8:
+			smcconf->mode |= ATMEL_SMC_MODE_DBW_8;
+			break;
+
+		case 16:
+			smcconf->mode |= ATMEL_SMC_MODE_DBW_16;
+			break;
+
+		case 32:
+			smcconf->mode |= ATMEL_SMC_MODE_DBW_32;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		required = true;
+	}
+
+	if (of_property_read_bool(np, "atmel,smc-tdf-optimized")) {
+		smcconf->mode |= ATMEL_SMC_MODE_TDFMODE_OPTIMIZED;
+		required = true;
+	}
+
+	tmp_str = NULL;
+	of_property_read_string(np, "atmel,smc-byte-access-type", &tmp_str);
+	if (tmp_str && !strcmp(tmp_str, "write")) {
+		smcconf->mode |= ATMEL_SMC_MODE_BAT_WRITE;
+		required = true;
+	}
+
+	tmp_str = NULL;
+	of_property_read_string(np, "atmel,smc-read-mode", &tmp_str);
+	if (tmp_str && !strcmp(tmp_str, "nrd")) {
+		smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD;
+		required = true;
+	}
+
+	tmp_str = NULL;
+	of_property_read_string(np, "atmel,smc-write-mode", &tmp_str);
+	if (tmp_str && !strcmp(tmp_str, "nwe")) {
+		smcconf->mode |= ATMEL_SMC_MODE_WRITEMODE_NWE;
+		required = true;
+	}
+
+	tmp_str = NULL;
+	of_property_read_string(np, "atmel,smc-exnw-mode", &tmp_str);
+	if (tmp_str) {
+		if (!strcmp(tmp_str, "frozen"))
+			smcconf->mode |= ATMEL_SMC_MODE_EXNWMODE_FROZEN;
+		else if (!strcmp(tmp_str, "ready"))
+			smcconf->mode |= ATMEL_SMC_MODE_EXNWMODE_READY;
+		else if (strcmp(tmp_str, "disabled"))
+			return -EINVAL;
+
+		required = true;
+	}
+
+	ret = of_property_read_u32(np, "atmel,smc-page-mode", &tmp);
+	if (!ret) {
+		switch (tmp) {
+		case 4:
+			smcconf->mode |= ATMEL_SMC_MODE_PS_4;
+			break;
+
+		case 8:
+			smcconf->mode |= ATMEL_SMC_MODE_PS_8;
+			break;
+
+		case 16:
+			smcconf->mode |= ATMEL_SMC_MODE_PS_16;
+			break;
+
+		case 32:
+			smcconf->mode |= ATMEL_SMC_MODE_PS_32;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		smcconf->mode |= ATMEL_SMC_MODE_PMEN;
+		required = true;
+	}
+
+	ret = atmel_ebi_xslate_smc_timings(ebid, np, &conf->smcconf);
+	if (ret < 0)
+		return -EINVAL;
+
+	if ((ret > 0 && !required) || (!ret && required)) {
+		dev_err(ebid->ebi->dev, "missing atmel,smc- properties in %pOF",
+			np);
+		return -EINVAL;
+	}
+
+	return required;
+}
+
+static void at91sam9_ebi_apply_config(struct atmel_ebi_dev *ebid,
+				      struct atmel_ebi_dev_config *conf)
+{
+	atmel_smc_cs_conf_apply(ebid->ebi->smc.regmap, conf->cs,
+				&conf->smcconf);
+}
+
+static void sama5_ebi_apply_config(struct atmel_ebi_dev *ebid,
+				   struct atmel_ebi_dev_config *conf)
+{
+	atmel_hsmc_cs_conf_apply(ebid->ebi->smc.regmap, ebid->ebi->smc.layout,
+				 conf->cs, &conf->smcconf);
+}
+
+static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np,
+			       int reg_cells)
+{
+	const struct atmel_ebi_caps *caps = ebi->caps;
+	struct atmel_ebi_dev_config conf = { };
+	struct device *dev = ebi->dev;
+	struct atmel_ebi_dev *ebid;
+	unsigned long cslines = 0;
+	int ret, numcs = 0, nentries, i;
+	bool apply = false;
+	u32 cs;
+
+	nentries = of_property_count_elems_of_size(np, "reg",
+						   reg_cells * sizeof(u32));
+	for (i = 0; i < nentries; i++) {
+		ret = of_property_read_u32_index(np, "reg", i * reg_cells,
+						 &cs);
+		if (ret)
+			return ret;
+
+		if (cs >= AT91_EBI_NUM_CS ||
+		    !(ebi->caps->available_cs & BIT(cs))) {
+			dev_err(dev, "invalid reg property in %pOF\n", np);
+			return -EINVAL;
+		}
+
+		if (!test_and_set_bit(cs, &cslines))
+			numcs++;
+	}
+
+	if (!numcs) {
+		dev_err(dev, "invalid reg property in %pOF\n", np);
+		return -EINVAL;
+	}
+
+	ebid = devm_kzalloc(ebi->dev, struct_size(ebid, configs, numcs),
+			    GFP_KERNEL);
+	if (!ebid)
+		return -ENOMEM;
+
+	ebid->ebi = ebi;
+	ebid->numcs = numcs;
+
+	ret = caps->xlate_config(ebid, np, &conf);
+	if (ret < 0)
+		return ret;
+	else if (ret)
+		apply = true;
+
+	i = 0;
+	for_each_set_bit(cs, &cslines, AT91_EBI_NUM_CS) {
+		ebid->configs[i].cs = cs;
+
+		if (apply) {
+			conf.cs = cs;
+			caps->apply_config(ebid, &conf);
+		}
+
+		caps->get_config(ebid, &ebid->configs[i]);
+
+		/*
+		 * Attach the EBI device to the generic SMC logic if at least
+		 * one "atmel,smc-" property is present.
+		 */
+		if (ebi->caps->ebi_csa_offs && apply)
+			regmap_update_bits(ebi->regmap,
+					   ebi->caps->ebi_csa_offs,
+					   BIT(cs), 0);
+
+		i++;
+	}
+
+	list_add_tail(&ebid->node, &ebi->devs);
+
+	return 0;
+}
+
+static const struct atmel_ebi_caps at91sam9260_ebi_caps = {
+	.available_cs = 0xff,
+	.ebi_csa_offs = AT91SAM9260_MATRIX_EBICSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9261_ebi_caps = {
+	.available_cs = 0xff,
+	.ebi_csa_offs = AT91SAM9261_MATRIX_EBICSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9263_ebi0_caps = {
+	.available_cs = 0x3f,
+	.ebi_csa_offs = AT91SAM9263_MATRIX_EBI0CSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9263_ebi1_caps = {
+	.available_cs = 0x7,
+	.ebi_csa_offs = AT91SAM9263_MATRIX_EBI1CSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9rl_ebi_caps = {
+	.available_cs = 0x3f,
+	.ebi_csa_offs = AT91SAM9RL_MATRIX_EBICSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9g45_ebi_caps = {
+	.available_cs = 0x3f,
+	.ebi_csa_offs = AT91SAM9G45_MATRIX_EBICSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps at91sam9x5_ebi_caps = {
+	.available_cs = 0x3f,
+	.ebi_csa_offs = AT91SAM9X5_MATRIX_EBICSA,
+	.regmap_name = "atmel,matrix",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps sama5d3_ebi_caps = {
+	.available_cs = 0xf,
+	.get_config = sama5_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = sama5_ebi_apply_config,
+};
+
+static const struct atmel_ebi_caps sam9x60_ebi_caps = {
+	.available_cs = 0x3f,
+	.ebi_csa_offs = AT91_SFR_CCFG_EBICSA,
+	.regmap_name = "microchip,sfr",
+	.get_config = at91sam9_ebi_get_config,
+	.xlate_config = atmel_ebi_xslate_smc_config,
+	.apply_config = at91sam9_ebi_apply_config,
+};
+
+static const struct of_device_id atmel_ebi_id_table[] = {
+	{
+		.compatible = "atmel,at91sam9260-ebi",
+		.data = &at91sam9260_ebi_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9261-ebi",
+		.data = &at91sam9261_ebi_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9263-ebi0",
+		.data = &at91sam9263_ebi0_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9263-ebi1",
+		.data = &at91sam9263_ebi1_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9rl-ebi",
+		.data = &at91sam9rl_ebi_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9g45-ebi",
+		.data = &at91sam9g45_ebi_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9x5-ebi",
+		.data = &at91sam9x5_ebi_caps,
+	},
+	{
+		.compatible = "atmel,sama5d3-ebi",
+		.data = &sama5d3_ebi_caps,
+	},
+	{
+		.compatible = "microchip,sam9x60-ebi",
+		.data = &sam9x60_ebi_caps,
+	},
+	{ /* sentinel */ }
+};
+
+static int atmel_ebi_probe(struct device *dev)
+{
+	struct device_node *child, *np = dev->of_node, *smc_np;
+	const struct of_device_id *match;
+	struct atmel_ebi *ebi;
+	int ret, reg_cells;
+	struct clk *clk;
+	u32 val;
+
+	match = of_match_device(atmel_ebi_id_table, dev);
+	if (!match || !match->data)
+		return -EINVAL;
+
+	ebi = devm_kzalloc(dev, sizeof(*ebi), GFP_KERNEL);
+	if (!ebi)
+		return -ENOMEM;
+
+	dev->priv = ebi;
+
+	INIT_LIST_HEAD(&ebi->devs);
+	ebi->caps = match->data;
+	ebi->dev = dev;
+
+	clk = clk_get(dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	ebi->clk = clk;
+
+	smc_np = of_parse_phandle(dev->of_node, "atmel,smc", 0);
+
+	ebi->smc.regmap = syscon_node_to_regmap(smc_np);
+	if (IS_ERR(ebi->smc.regmap)) {
+		ret = PTR_ERR(ebi->smc.regmap);
+		goto put_node;
+	}
+
+	ebi->smc.layout = atmel_hsmc_get_reg_layout(smc_np);
+	if (IS_ERR(ebi->smc.layout)) {
+		ret = PTR_ERR(ebi->smc.layout);
+		goto put_node;
+	}
+
+	ebi->smc.clk = of_clk_get(smc_np, 0);
+	if (IS_ERR(ebi->smc.clk)) {
+		if (PTR_ERR(ebi->smc.clk) != -ENOENT) {
+			ret = PTR_ERR(ebi->smc.clk);
+			goto put_node;
+		}
+
+		ebi->smc.clk = NULL;
+	}
+	of_node_put(smc_np);
+	ret = clk_prepare_enable(ebi->smc.clk);
+	if (ret)
+		return ret;
+
+	/*
+	 * The sama5d3 does not provide an EBICSA register and thus does need
+	 * to access it.
+	 */
+	if (ebi->caps->ebi_csa_offs) {
+		ebi->regmap =
+			syscon_regmap_lookup_by_phandle(np,
+							ebi->caps->regmap_name);
+		if (IS_ERR(ebi->regmap))
+			return PTR_ERR(ebi->regmap);
+	}
+
+	ret = of_property_read_u32(np, "#address-cells", &val);
+	if (ret) {
+		dev_err(dev, "missing #address-cells property\n");
+		return ret;
+	}
+
+	reg_cells = val;
+
+	ret = of_property_read_u32(np, "#size-cells", &val);
+	if (ret) {
+		dev_err(dev, "missing #address-cells property\n");
+		return ret;
+	}
+
+	reg_cells += val;
+
+	for_each_available_child_of_node(np, child) {
+		if (!of_find_property(child, "reg", NULL))
+			continue;
+
+		ret = atmel_ebi_dev_setup(ebi, child, reg_cells);
+		if (ret) {
+			dev_err(dev, "failed to configure EBI bus for %pOF, disabling the device",
+				child);
+
+			ret = of_device_disable(child);
+			if (ret) {
+				of_node_put(child);
+				return ret;
+			}
+		}
+	}
+
+	return of_platform_populate(np, NULL, dev);
+
+put_node:
+	of_node_put(smc_np);
+	return ret;
+}
+
+static struct driver atmel_ebi_driver = {
+	.name = "atmel-ebi",
+	.of_match_table	= atmel_ebi_id_table,
+	.probe = atmel_ebi_probe,
+};
+coredevice_platform_driver(atmel_ebi_driver);
diff --git a/include/linux/mfd/syscon/atmel-matrix.h b/include/linux/mfd/syscon/atmel-matrix.h
new file mode 100644
index 000000000000..20c25665216a
--- /dev/null
+++ b/include/linux/mfd/syscon/atmel-matrix.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ *  Copyright (C) 2014 Atmel Corporation.
+ *
+ * Memory Controllers (MATRIX, EBI) - System peripherals registers.
+ */
+
+#ifndef _LINUX_MFD_SYSCON_ATMEL_MATRIX_H
+#define _LINUX_MFD_SYSCON_ATMEL_MATRIX_H
+
+#define AT91SAM9260_MATRIX_MCFG			0x00
+#define AT91SAM9260_MATRIX_SCFG			0x40
+#define AT91SAM9260_MATRIX_PRS			0x80
+#define AT91SAM9260_MATRIX_MRCR			0x100
+#define AT91SAM9260_MATRIX_EBICSA		0x11c
+
+#define AT91SAM9261_MATRIX_MRCR			0x0
+#define AT91SAM9261_MATRIX_SCFG			0x4
+#define AT91SAM9261_MATRIX_TCR			0x24
+#define AT91SAM9261_MATRIX_EBICSA		0x30
+#define AT91SAM9261_MATRIX_USBPUCR		0x34
+
+#define AT91SAM9263_MATRIX_MCFG			0x00
+#define AT91SAM9263_MATRIX_SCFG			0x40
+#define AT91SAM9263_MATRIX_PRS			0x80
+#define AT91SAM9263_MATRIX_MRCR			0x100
+#define AT91SAM9263_MATRIX_TCR			0x114
+#define AT91SAM9263_MATRIX_EBI0CSA		0x120
+#define AT91SAM9263_MATRIX_EBI1CSA		0x124
+
+#define AT91SAM9RL_MATRIX_MCFG			0x00
+#define AT91SAM9RL_MATRIX_SCFG			0x40
+#define AT91SAM9RL_MATRIX_PRS			0x80
+#define AT91SAM9RL_MATRIX_MRCR			0x100
+#define AT91SAM9RL_MATRIX_TCR			0x114
+#define AT91SAM9RL_MATRIX_EBICSA		0x120
+
+#define AT91SAM9G45_MATRIX_MCFG			0x00
+#define AT91SAM9G45_MATRIX_SCFG			0x40
+#define AT91SAM9G45_MATRIX_PRS			0x80
+#define AT91SAM9G45_MATRIX_MRCR			0x100
+#define AT91SAM9G45_MATRIX_TCR			0x110
+#define AT91SAM9G45_MATRIX_DDRMPR		0x118
+#define AT91SAM9G45_MATRIX_EBICSA		0x128
+
+#define AT91SAM9N12_MATRIX_MCFG			0x00
+#define AT91SAM9N12_MATRIX_SCFG			0x40
+#define AT91SAM9N12_MATRIX_PRS			0x80
+#define AT91SAM9N12_MATRIX_MRCR			0x100
+#define AT91SAM9N12_MATRIX_EBICSA		0x118
+
+#define AT91SAM9X5_MATRIX_MCFG			0x00
+#define AT91SAM9X5_MATRIX_SCFG			0x40
+#define AT91SAM9X5_MATRIX_PRS			0x80
+#define AT91SAM9X5_MATRIX_MRCR			0x100
+#define AT91SAM9X5_MATRIX_EBICSA		0x120
+
+#define SAMA5D3_MATRIX_MCFG			0x00
+#define SAMA5D3_MATRIX_SCFG			0x40
+#define SAMA5D3_MATRIX_PRS			0x80
+#define SAMA5D3_MATRIX_MRCR			0x100
+
+#define AT91_MATRIX_MCFG(o, x)			((o) + ((x) * 0x4))
+#define AT91_MATRIX_ULBT			GENMASK(2, 0)
+#define AT91_MATRIX_ULBT_INFINITE		(0 << 0)
+#define AT91_MATRIX_ULBT_SINGLE			(1 << 0)
+#define AT91_MATRIX_ULBT_FOUR			(2 << 0)
+#define AT91_MATRIX_ULBT_EIGHT			(3 << 0)
+#define AT91_MATRIX_ULBT_SIXTEEN		(4 << 0)
+
+#define AT91_MATRIX_SCFG(o, x)			((o) + ((x) * 0x4))
+#define AT91_MATRIX_SLOT_CYCLE			GENMASK(7,  0)
+#define AT91_MATRIX_DEFMSTR_TYPE		GENMASK(17, 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_NONE		(0 << 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_LAST		(1 << 16)
+#define AT91_MATRIX_DEFMSTR_TYPE_FIXED		(2 << 16)
+#define AT91_MATRIX_FIXED_DEFMSTR		GENMASK(20, 18)
+#define AT91_MATRIX_ARBT			GENMASK(25, 24)
+#define AT91_MATRIX_ARBT_ROUND_ROBIN		(0 << 24)
+#define AT91_MATRIX_ARBT_FIXED_PRIORITY		(1 << 24)
+
+#define AT91_MATRIX_ITCM_SIZE			GENMASK(3, 0)
+#define AT91_MATRIX_ITCM_0			(0 << 0)
+#define AT91_MATRIX_ITCM_16			(5 << 0)
+#define AT91_MATRIX_ITCM_32			(6 << 0)
+#define AT91_MATRIX_ITCM_64			(7 << 0)
+#define	AT91_MATRIX_DTCM_SIZE			GENMASK(7, 4)
+#define	AT91_MATRIX_DTCM_0			(0 << 4)
+#define	AT91_MATRIX_DTCM_16			(5 << 4)
+#define AT91_MATRIX_DTCM_32			(6 << 4)
+#define AT91_MATRIX_DTCM_64			(7 << 4)
+
+#define AT91_MATRIX_PRAS(o, x)			((o) + ((x) * 0x8))
+#define AT91_MATRIX_PRBS(o, x)			((o) + ((x) * 0x8) + 0x4)
+#define AT91_MATRIX_MPR(x)			GENMASK(((x) * 0x4) + 1, ((x) * 0x4))
+
+#define AT91_MATRIX_RCB(x)			BIT(x)
+
+#define AT91_MATRIX_CSA(cs, val)		(val << (cs))
+#define AT91_MATRIX_DBPUC			BIT(8)
+#define AT91_MATRIX_DBPDC			BIT(9)
+#define AT91_MATRIX_VDDIOMSEL			BIT(16)
+#define AT91_MATRIX_VDDIOMSEL_1_8V		(0 << 16)
+#define AT91_MATRIX_VDDIOMSEL_3_3V		(1 << 16)
+#define AT91_MATRIX_EBI_IOSR			BIT(17)
+#define AT91_MATRIX_DDR_IOSR			BIT(18)
+#define AT91_MATRIX_NFD0_SELECT			BIT(24)
+#define AT91_MATRIX_DDR_MP_EN			BIT(25)
+
+#define AT91_MATRIX_USBPUCR_PUON		BIT(30)
+
+#endif /* _LINUX_MFD_SYSCON_ATMEL_MATRIX_H */
diff --git a/include/soc/at91/atmel-sfr.h b/include/soc/at91/atmel-sfr.h
index 7418c5cab4a1..8e75508165b7 100644
--- a/include/soc/at91/atmel-sfr.h
+++ b/include/soc/at91/atmel-sfr.h
@@ -11,6 +11,7 @@
 #define _LINUX_MFD_SYSCON_ATMEL_SFR_H
 
 #define AT91_SFR_DDRCFG		0x04	/* DDR Configuration Register */
+#define AT91_SFR_CCFG_EBICSA	0x04	/* EBI Chip Select Register */
 /* 0x08 ~ 0x0c: Reserved */
 #define AT91_SFR_OHCIICR	0x10	/* OHCI INT Configuration Register */
 #define AT91_SFR_OHCIISR	0x14	/* OHCI INT Status Register */
-- 
2.30.2




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

* [PATCH 11/15] mfd: add atmel-smc driver
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (9 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 10/15] memory: add Atmel EBI driver Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 12/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Port over the Linux Atmel Static Memory Controller driver for use by the
incoming update to the Atmel NAND controller driver

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mfd/Kconfig                  |   4 +
 drivers/mfd/Makefile                 |   1 +
 drivers/mfd/atmel-smc.c              | 352 +++++++++++++++++++++++++++
 include/linux/mfd/syscon/atmel-smc.h | 119 +++++++++
 4 files changed, 476 insertions(+)
 create mode 100644 drivers/mfd/atmel-smc.c
 create mode 100644 include/linux/mfd/syscon/atmel-smc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c33fa262236c..f1f7e27fadfe 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -129,4 +129,8 @@ config MFD_AXP20X_I2C
 	  management ICs (PMICs) controlled with I2C.
 	  This driver currently only provide a character device in /dev.
 
+config MFD_ATMEL_SMC
+	bool
+	select MFD_SYSCON
+
 endmenu
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a3cb90e88346..b89ddab5947d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_MFD_STM32_TIMERS)	+= stm32-timers.o
 obj-$(CONFIG_MFD_ATMEL_FLEXCOM)	+= atmel-flexcom.o
 obj-$(CONFIG_MFD_RK808)		+= rk808.o
 obj-$(CONFIG_MFD_AXP20X_I2C)	+= axp20x-i2c.o axp20x.o
+obj-$(CONFIG_MFD_ATMEL_SMC)	+= atmel-smc.o
diff --git a/drivers/mfd/atmel-smc.c b/drivers/mfd/atmel-smc.c
new file mode 100644
index 000000000000..ed24ef650f5b
--- /dev/null
+++ b/drivers/mfd/atmel-smc.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Atmel SMC (Static Memory Controller) helper functions.
+ *
+ * Copyright (C) 2017 Atmel
+ * Copyright (C) 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ */
+
+#include <linux/mfd/syscon/atmel-smc.h>
+#include <linux/string.h>
+#include <linux/export.h>
+
+/**
+ * atmel_smc_cs_conf_init - initialize a SMC CS conf
+ * @conf: the SMC CS conf to initialize
+ *
+ * Set all fields to 0 so that one can start defining a new config.
+ */
+void atmel_smc_cs_conf_init(struct atmel_smc_cs_conf *conf)
+{
+	memset(conf, 0, sizeof(*conf));
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_init);
+
+/**
+ * atmel_smc_cs_encode_ncycles - encode a number of MCK clk cycles in the
+ *				 format expected by the SMC engine
+ * @ncycles: number of MCK clk cycles
+ * @msbpos: position of the MSB part of the timing field
+ * @msbwidth: width of the MSB part of the timing field
+ * @msbfactor: factor applied to the MSB
+ * @encodedval: param used to store the encoding result
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Setup/Pulse/Cycle/Timings Register"). This is a generic
+ * helper which called with different parameter depending on the encoding
+ * scheme.
+ *
+ * If the @ncycles value is too big to be encoded, -ERANGE is returned and
+ * the encodedval is contains the maximum val. Otherwise, 0 is returned.
+ */
+static int atmel_smc_cs_encode_ncycles(unsigned int ncycles,
+				       unsigned int msbpos,
+				       unsigned int msbwidth,
+				       unsigned int msbfactor,
+				       unsigned int *encodedval)
+{
+	unsigned int lsbmask = GENMASK(msbpos - 1, 0);
+	unsigned int msbmask = GENMASK(msbwidth - 1, 0);
+	unsigned int msb, lsb;
+	int ret = 0;
+
+	msb = ncycles / msbfactor;
+	lsb = ncycles % msbfactor;
+
+	if (lsb > lsbmask) {
+		lsb = 0;
+		msb++;
+	}
+
+	/*
+	 * Let's just put the maximum we can if the requested setting does
+	 * not fit in the register field.
+	 * We still return -ERANGE in case the caller cares.
+	 */
+	if (msb > msbmask) {
+		msb = msbmask;
+		lsb = lsbmask;
+		ret = -ERANGE;
+	}
+
+	*encodedval = (msb << msbpos) | lsb;
+
+	return ret;
+}
+
+/**
+ * atmel_smc_cs_conf_set_timing - set the SMC CS conf Txx parameter to a
+ *				  specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the Txx field in the TIMINGS register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this Txx
+ *	     parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Timings Register"), and then stores the result in the
+ * @conf->timings field at @shift position.
+ *
+ * Returns -EINVAL if shift is invalid, -ERANGE if ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_timing(struct atmel_smc_cs_conf *conf,
+				 unsigned int shift, unsigned int ncycles)
+{
+	unsigned int val;
+	int ret;
+
+	if (shift != ATMEL_HSMC_TIMINGS_TCLR_SHIFT &&
+	    shift != ATMEL_HSMC_TIMINGS_TADL_SHIFT &&
+	    shift != ATMEL_HSMC_TIMINGS_TAR_SHIFT &&
+	    shift != ATMEL_HSMC_TIMINGS_TRR_SHIFT &&
+	    shift != ATMEL_HSMC_TIMINGS_TWB_SHIFT)
+		return -EINVAL;
+
+	/*
+	 * The formula described in atmel datasheets (section "HSMC Timings
+	 * Register"):
+	 *
+	 * ncycles = (Txx[3] * 64) + Txx[2:0]
+	 */
+	ret = atmel_smc_cs_encode_ncycles(ncycles, 3, 1, 64, &val);
+	conf->timings &= ~GENMASK(shift + 3, shift);
+	conf->timings |= val << shift;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_timing);
+
+/**
+ * atmel_smc_cs_conf_set_setup - set the SMC CS conf xx_SETUP parameter to a
+ *				 specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_SETUP field in the SETUP register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_SETUP
+ *	     parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Setup Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_setup(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles)
+{
+	unsigned int val;
+	int ret;
+
+	if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NCS_WR_SHIFT &&
+	    shift != ATMEL_SMC_NRD_SHIFT && shift != ATMEL_SMC_NCS_RD_SHIFT)
+		return -EINVAL;
+
+	/*
+	 * The formula described in atmel datasheets (section "SMC Setup
+	 * Register"):
+	 *
+	 * ncycles = (128 * xx_SETUP[5]) + xx_SETUP[4:0]
+	 */
+	ret = atmel_smc_cs_encode_ncycles(ncycles, 5, 1, 128, &val);
+	conf->setup &= ~GENMASK(shift + 7, shift);
+	conf->setup |= val << shift;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_setup);
+
+/**
+ * atmel_smc_cs_conf_set_pulse - set the SMC CS conf xx_PULSE parameter to a
+ *				 specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_PULSE field in the PULSE register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_PULSE
+ *	     parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Pulse Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_pulse(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles)
+{
+	unsigned int val;
+	int ret;
+
+	if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NCS_WR_SHIFT &&
+	    shift != ATMEL_SMC_NRD_SHIFT && shift != ATMEL_SMC_NCS_RD_SHIFT)
+		return -EINVAL;
+
+	/*
+	 * The formula described in atmel datasheets (section "SMC Pulse
+	 * Register"):
+	 *
+	 * ncycles = (256 * xx_PULSE[6]) + xx_PULSE[5:0]
+	 */
+	ret = atmel_smc_cs_encode_ncycles(ncycles, 6, 1, 256, &val);
+	conf->pulse &= ~GENMASK(shift + 7, shift);
+	conf->pulse |= val << shift;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_pulse);
+
+/**
+ * atmel_smc_cs_conf_set_cycle - set the SMC CS conf xx_CYCLE parameter to a
+ *				 specific value
+ * @conf: SMC CS conf descriptor
+ * @shift: the position of the xx_CYCLE field in the CYCLE register
+ * @ncycles: value (expressed in MCK clk cycles) to assign to this xx_CYCLE
+ *	     parameter
+ *
+ * This function encodes the @ncycles value as described in the datasheet
+ * (section "SMC Cycle Register"), and then stores the result in the
+ * @conf->setup field at @shift position.
+ *
+ * Returns -EINVAL if @shift is invalid, -ERANGE if @ncycles does not fit in
+ * the field, and 0 otherwise.
+ */
+int atmel_smc_cs_conf_set_cycle(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles)
+{
+	unsigned int val;
+	int ret;
+
+	if (shift != ATMEL_SMC_NWE_SHIFT && shift != ATMEL_SMC_NRD_SHIFT)
+		return -EINVAL;
+
+	/*
+	 * The formula described in atmel datasheets (section "SMC Cycle
+	 * Register"):
+	 *
+	 * ncycles = (xx_CYCLE[8:7] * 256) + xx_CYCLE[6:0]
+	 */
+	ret = atmel_smc_cs_encode_ncycles(ncycles, 7, 2, 256, &val);
+	conf->cycle &= ~GENMASK(shift + 15, shift);
+	conf->cycle |= val << shift;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_set_cycle);
+
+/**
+ * atmel_smc_cs_conf_apply - apply an SMC CS conf
+ * @regmap: the SMC regmap
+ * @cs: the CS id
+ * @conf: the SMC CS conf to apply
+ *
+ * Applies an SMC CS configuration.
+ * Only valid on at91sam9/avr32 SoCs.
+ */
+void atmel_smc_cs_conf_apply(struct regmap *regmap, int cs,
+			     const struct atmel_smc_cs_conf *conf)
+{
+	regmap_write(regmap, ATMEL_SMC_SETUP(cs), conf->setup);
+	regmap_write(regmap, ATMEL_SMC_PULSE(cs), conf->pulse);
+	regmap_write(regmap, ATMEL_SMC_CYCLE(cs), conf->cycle);
+	regmap_write(regmap, ATMEL_SMC_MODE(cs), conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_apply);
+
+/**
+ * atmel_hsmc_cs_conf_apply - apply an SMC CS conf
+ * @regmap: the HSMC regmap
+ * @cs: the CS id
+ * @layout: the layout of registers
+ * @conf: the SMC CS conf to apply
+ *
+ * Applies an SMC CS configuration.
+ * Only valid on post-sama5 SoCs.
+ */
+void atmel_hsmc_cs_conf_apply(struct regmap *regmap,
+			      const struct atmel_hsmc_reg_layout *layout,
+			      int cs, const struct atmel_smc_cs_conf *conf)
+{
+	regmap_write(regmap, ATMEL_HSMC_SETUP(layout, cs), conf->setup);
+	regmap_write(regmap, ATMEL_HSMC_PULSE(layout, cs), conf->pulse);
+	regmap_write(regmap, ATMEL_HSMC_CYCLE(layout, cs), conf->cycle);
+	regmap_write(regmap, ATMEL_HSMC_TIMINGS(layout, cs), conf->timings);
+	regmap_write(regmap, ATMEL_HSMC_MODE(layout, cs), conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_hsmc_cs_conf_apply);
+
+/**
+ * atmel_smc_cs_conf_get - retrieve the current SMC CS conf
+ * @regmap: the SMC regmap
+ * @cs: the CS id
+ * @conf: the SMC CS conf object to store the current conf
+ *
+ * Retrieve the SMC CS configuration.
+ * Only valid on at91sam9/avr32 SoCs.
+ */
+void atmel_smc_cs_conf_get(struct regmap *regmap, int cs,
+			   struct atmel_smc_cs_conf *conf)
+{
+	regmap_read(regmap, ATMEL_SMC_SETUP(cs), &conf->setup);
+	regmap_read(regmap, ATMEL_SMC_PULSE(cs), &conf->pulse);
+	regmap_read(regmap, ATMEL_SMC_CYCLE(cs), &conf->cycle);
+	regmap_read(regmap, ATMEL_SMC_MODE(cs), &conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_smc_cs_conf_get);
+
+/**
+ * atmel_hsmc_cs_conf_get - retrieve the current SMC CS conf
+ * @regmap: the HSMC regmap
+ * @cs: the CS id
+ * @layout: the layout of registers
+ * @conf: the SMC CS conf object to store the current conf
+ *
+ * Retrieve the SMC CS configuration.
+ * Only valid on post-sama5 SoCs.
+ */
+void atmel_hsmc_cs_conf_get(struct regmap *regmap,
+			    const struct atmel_hsmc_reg_layout *layout,
+			    int cs, struct atmel_smc_cs_conf *conf)
+{
+	regmap_read(regmap, ATMEL_HSMC_SETUP(layout, cs), &conf->setup);
+	regmap_read(regmap, ATMEL_HSMC_PULSE(layout, cs), &conf->pulse);
+	regmap_read(regmap, ATMEL_HSMC_CYCLE(layout, cs), &conf->cycle);
+	regmap_read(regmap, ATMEL_HSMC_TIMINGS(layout, cs), &conf->timings);
+	regmap_read(regmap, ATMEL_HSMC_MODE(layout, cs), &conf->mode);
+}
+EXPORT_SYMBOL_GPL(atmel_hsmc_cs_conf_get);
+
+static const struct atmel_hsmc_reg_layout sama5d3_reg_layout = {
+	.timing_regs_offset = 0x600,
+};
+
+static const struct atmel_hsmc_reg_layout sama5d2_reg_layout = {
+	.timing_regs_offset = 0x700,
+};
+
+static const struct of_device_id atmel_smc_ids[] = {
+	{ .compatible = "atmel,at91sam9260-smc", .data = NULL },
+	{ .compatible = "atmel,sama5d3-smc", .data = &sama5d3_reg_layout },
+	{ .compatible = "atmel,sama5d2-smc", .data = &sama5d2_reg_layout },
+	{ /* sentinel */ },
+};
+
+/**
+ * atmel_hsmc_get_reg_layout - retrieve the layout of HSMC registers
+ * @np: the HSMC regmap
+ *
+ * Retrieve the layout of HSMC registers.
+ *
+ * Returns NULL in case of SMC, a struct atmel_hsmc_reg_layout pointer
+ * in HSMC case, otherwise ERR_PTR(-EINVAL).
+ */
+const struct atmel_hsmc_reg_layout *
+atmel_hsmc_get_reg_layout(struct device_node *np)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(atmel_smc_ids, np);
+
+	return match ? match->data : ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(atmel_hsmc_get_reg_layout);
diff --git a/include/linux/mfd/syscon/atmel-smc.h b/include/linux/mfd/syscon/atmel-smc.h
new file mode 100644
index 000000000000..881edefe4e9c
--- /dev/null
+++ b/include/linux/mfd/syscon/atmel-smc.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Atmel SMC (Static Memory Controller) register offsets and bit definitions.
+ *
+ * Copyright (C) 2014 Atmel
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ */
+
+#ifndef _LINUX_MFD_SYSCON_ATMEL_SMC_H_
+#define _LINUX_MFD_SYSCON_ATMEL_SMC_H_
+
+#include <linux/kernel.h>
+#include <of.h>
+#include <regmap.h>
+
+#define ATMEL_SMC_SETUP(cs)			(((cs) * 0x10))
+#define ATMEL_HSMC_SETUP(layout, cs)		\
+	((layout)->timing_regs_offset + ((cs) * 0x14))
+#define ATMEL_SMC_PULSE(cs)			(((cs) * 0x10) + 0x4)
+#define ATMEL_HSMC_PULSE(layout, cs)		\
+	((layout)->timing_regs_offset + ((cs) * 0x14) + 0x4)
+#define ATMEL_SMC_CYCLE(cs)			(((cs) * 0x10) + 0x8)
+#define ATMEL_HSMC_CYCLE(layout, cs)			\
+	((layout)->timing_regs_offset + ((cs) * 0x14) + 0x8)
+#define ATMEL_SMC_NWE_SHIFT			0
+#define ATMEL_SMC_NCS_WR_SHIFT			8
+#define ATMEL_SMC_NRD_SHIFT			16
+#define ATMEL_SMC_NCS_RD_SHIFT			24
+
+#define ATMEL_SMC_MODE(cs)			(((cs) * 0x10) + 0xc)
+#define ATMEL_HSMC_MODE(layout, cs)			\
+	((layout)->timing_regs_offset + ((cs) * 0x14) + 0x10)
+#define ATMEL_SMC_MODE_READMODE_MASK		BIT(0)
+#define ATMEL_SMC_MODE_READMODE_NCS		(0 << 0)
+#define ATMEL_SMC_MODE_READMODE_NRD		(1 << 0)
+#define ATMEL_SMC_MODE_WRITEMODE_MASK		BIT(1)
+#define ATMEL_SMC_MODE_WRITEMODE_NCS		(0 << 1)
+#define ATMEL_SMC_MODE_WRITEMODE_NWE		(1 << 1)
+#define ATMEL_SMC_MODE_EXNWMODE_MASK		GENMASK(5, 4)
+#define ATMEL_SMC_MODE_EXNWMODE_DISABLE		(0 << 4)
+#define ATMEL_SMC_MODE_EXNWMODE_FROZEN		(2 << 4)
+#define ATMEL_SMC_MODE_EXNWMODE_READY		(3 << 4)
+#define ATMEL_SMC_MODE_BAT_MASK			BIT(8)
+#define ATMEL_SMC_MODE_BAT_SELECT		(0 << 8)
+#define ATMEL_SMC_MODE_BAT_WRITE		(1 << 8)
+#define ATMEL_SMC_MODE_DBW_MASK			GENMASK(13, 12)
+#define ATMEL_SMC_MODE_DBW_8			(0 << 12)
+#define ATMEL_SMC_MODE_DBW_16			(1 << 12)
+#define ATMEL_SMC_MODE_DBW_32			(2 << 12)
+#define ATMEL_SMC_MODE_TDF_MASK			GENMASK(19, 16)
+#define ATMEL_SMC_MODE_TDF(x)			(((x) - 1) << 16)
+#define ATMEL_SMC_MODE_TDF_MAX			16
+#define ATMEL_SMC_MODE_TDF_MIN			1
+#define ATMEL_SMC_MODE_TDFMODE_OPTIMIZED	BIT(20)
+#define ATMEL_SMC_MODE_PMEN			BIT(24)
+#define ATMEL_SMC_MODE_PS_MASK			GENMASK(29, 28)
+#define ATMEL_SMC_MODE_PS_4			(0 << 28)
+#define ATMEL_SMC_MODE_PS_8			(1 << 28)
+#define ATMEL_SMC_MODE_PS_16			(2 << 28)
+#define ATMEL_SMC_MODE_PS_32			(3 << 28)
+
+#define ATMEL_HSMC_TIMINGS(layout, cs)			\
+	((layout)->timing_regs_offset + ((cs) * 0x14) + 0xc)
+#define ATMEL_HSMC_TIMINGS_OCMS			BIT(12)
+#define ATMEL_HSMC_TIMINGS_RBNSEL(x)		((x) << 28)
+#define ATMEL_HSMC_TIMINGS_NFSEL		BIT(31)
+#define ATMEL_HSMC_TIMINGS_TCLR_SHIFT		0
+#define ATMEL_HSMC_TIMINGS_TADL_SHIFT		4
+#define ATMEL_HSMC_TIMINGS_TAR_SHIFT		8
+#define ATMEL_HSMC_TIMINGS_TRR_SHIFT		16
+#define ATMEL_HSMC_TIMINGS_TWB_SHIFT		24
+
+struct atmel_hsmc_reg_layout {
+	unsigned int timing_regs_offset;
+};
+
+/**
+ * struct atmel_smc_cs_conf - SMC CS config as described in the datasheet.
+ * @setup: NCS/NWE/NRD setup timings (not applicable to at91rm9200)
+ * @pulse: NCS/NWE/NRD pulse timings (not applicable to at91rm9200)
+ * @cycle: NWE/NRD cycle timings (not applicable to at91rm9200)
+ * @timings: advanced NAND related timings (only applicable to HSMC)
+ * @mode: all kind of config parameters (see the fields definition above).
+ *	  The mode fields are different on at91rm9200
+ */
+struct atmel_smc_cs_conf {
+	u32 setup;
+	u32 pulse;
+	u32 cycle;
+	u32 timings;
+	u32 mode;
+};
+
+void atmel_smc_cs_conf_init(struct atmel_smc_cs_conf *conf);
+int atmel_smc_cs_conf_set_timing(struct atmel_smc_cs_conf *conf,
+				 unsigned int shift,
+				 unsigned int ncycles);
+int atmel_smc_cs_conf_set_setup(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles);
+int atmel_smc_cs_conf_set_pulse(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles);
+int atmel_smc_cs_conf_set_cycle(struct atmel_smc_cs_conf *conf,
+				unsigned int shift, unsigned int ncycles);
+void atmel_smc_cs_conf_apply(struct regmap *regmap, int cs,
+			     const struct atmel_smc_cs_conf *conf);
+void atmel_hsmc_cs_conf_apply(struct regmap *regmap,
+			      const struct atmel_hsmc_reg_layout *reglayout,
+			      int cs, const struct atmel_smc_cs_conf *conf);
+void atmel_smc_cs_conf_get(struct regmap *regmap, int cs,
+			   struct atmel_smc_cs_conf *conf);
+void atmel_hsmc_cs_conf_get(struct regmap *regmap,
+			    const struct atmel_hsmc_reg_layout *reglayout,
+			    int cs, struct atmel_smc_cs_conf *conf);
+const struct atmel_hsmc_reg_layout *
+atmel_hsmc_get_reg_layout(struct device_node *np);
+
+#endif /* _LINUX_MFD_SYSCON_ATMEL_SMC_H_ */
-- 
2.30.2




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

* [PATCH 12/15] mtd: nand: atmel: import Linux NAND controller driver
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (10 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 11/15] mfd: add atmel-smc driver Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 13/15] ARM: AT91: sama5d3_xplained: switch to upstream binding Ahmad Fatoum
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

For a few years, Linux has been using the new EBI bindings for NAND
controllers on all AT91 SoCs newer than the AT91RM2000. We have so far
only supported the old bindings by hacking the DT, but this doesn't
suffice for the SAMA5D4. Therefore import a new state of the Linux NAND
controller driver. We still keep around the old barebox driver to
support the non-DT enabled AT91 platforms.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/Kconfig                      |   12 +-
 drivers/mtd/nand/Makefile                     |    2 +-
 drivers/mtd/nand/atmel/Makefile               |    3 +
 drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h |    0
 .../mtd/nand/{atmel_nand.c => atmel/legacy.c} |    0
 drivers/mtd/nand/atmel/nand-controller.c      | 2049 +++++++++++++++++
 drivers/mtd/nand/atmel/pmecc.c                |  992 ++++++++
 drivers/mtd/nand/atmel/pmecc.h                |   70 +
 include/driver.h                              |    2 +
 include/linux/mutex.h                         |    2 +
 include/soc/at91/atmel-sfr.h                  |    1 +
 11 files changed, 3130 insertions(+), 3 deletions(-)
 create mode 100644 drivers/mtd/nand/atmel/Makefile
 rename drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h (100%)
 rename drivers/mtd/nand/{atmel_nand.c => atmel/legacy.c} (100%)
 create mode 100644 drivers/mtd/nand/atmel/nand-controller.c
 create mode 100644 drivers/mtd/nand/atmel/pmecc.c
 create mode 100644 drivers/mtd/nand/atmel/pmecc.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 2cd52f3820ba..4aeb2b603b95 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -97,12 +97,20 @@ config NAND_MRVL_NFC
 config NAND_ATMEL
 	bool
 	prompt "Atmel (AT91SAM9xxx) NAND driver"
-	depends on ARCH_AT91
+	select GENERIC_ALLOCATOR if OFDEVICE
+	depends on ARCH_AT91 || (OFDEVICE && COMPILE_TEST)
+
+config NAND_ATMEL_LEGACY
+	def_bool y
+	depends on NAND_ATMEL
+	help
+	  Select legacy driver for non-DT-enabled platforms
+	  and for the deprecated non-EBI binding.
 
 config NAND_ATMEL_PMECC
 	bool
 	prompt "PMECC support"
-	depends on NAND_ATMEL || COMPILE_TEST
+	depends on NAND_ATMEL_LEGACY
 	help
 	  Support for PMECC present on the SoC sam9x5 and sam9n12
 
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 1584e86694ef..1a17c49bff6c 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -17,7 +17,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC)		+= nand_omap_gpmc.o nand_omap_bch_decoder.o
 obj-$(CONFIG_MTD_NAND_OMAP_ELM)		+= omap_elm.o
 obj-$(CONFIG_NAND_ORION)		+= nand_orion.o
 obj-$(CONFIG_NAND_MRVL_NFC)		+= nand_mrvl_nfc.o
-obj-$(CONFIG_NAND_ATMEL)		+= atmel_nand.o
+obj-$(CONFIG_NAND_ATMEL)		+= atmel/
 obj-$(CONFIG_NAND_S3C24XX)		+= nand_s3c24xx.o
 pbl-$(CONFIG_NAND_S3C24XX)		+= nand_s3c24xx.o
 obj-$(CONFIG_NAND_MXS)			+= nand_mxs.o
diff --git a/drivers/mtd/nand/atmel/Makefile b/drivers/mtd/nand/atmel/Makefile
new file mode 100644
index 000000000000..0f739c3f3192
--- /dev/null
+++ b/drivers/mtd/nand/atmel/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_OFDEVICE)		+= nand-controller.o pmecc.o
+obj-$(CONFIG_NAND_ATMEL_LEGACY)	+= legacy.o
diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel/atmel_nand_ecc.h
similarity index 100%
rename from drivers/mtd/nand/atmel_nand_ecc.h
rename to drivers/mtd/nand/atmel/atmel_nand_ecc.h
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel/legacy.c
similarity index 100%
rename from drivers/mtd/nand/atmel_nand.c
rename to drivers/mtd/nand/atmel/legacy.c
diff --git a/drivers/mtd/nand/atmel/nand-controller.c b/drivers/mtd/nand/atmel/nand-controller.c
new file mode 100644
index 000000000000..6cd770074131
--- /dev/null
+++ b/drivers/mtd/nand/atmel/nand-controller.c
@@ -0,0 +1,2049 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2017 ATMEL
+ * Copyright 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ *   Copyright 2003 Rick Bronson
+ *
+ *   Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ *	Copyright 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ *   Derived from drivers/mtd/spia.c (removed in v3.8)
+ *	Copyright 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ *
+ *   Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ *	Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007
+ *
+ *   Derived from Das U-Boot source code
+ *	(u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ *	Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ *   Add Programmable Multibit ECC support for various AT91 SoC
+ *	Copyright 2012 ATMEL, Hong Xu
+ *
+ *   Add Nand Flash Controller support for SAMA5 SoC
+ *	Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ *
+ * A few words about the naming convention in this file. This convention
+ * applies to structure and function names.
+ *
+ * Prefixes:
+ *
+ * - atmel_nand_: all generic structures/functions
+ * - atmel_smc_nand_: all structures/functions specific to the SMC interface
+ *		      (at91sam9 and avr32 SoCs)
+ * - atmel_hsmc_nand_: all structures/functions specific to the HSMC interface
+ *		       (sama5 SoCs and later)
+ * - atmel_nfc_: all structures/functions used to manipulate the NFC sub-block
+ *		 that is available in the HSMC block
+ * - <soc>_nand_: all SoC specific structures/functions
+ */
+
+#include <linux/clk.h>
+#include <linux/genalloc.h>
+#include <gpiod.h>
+#include <mfd/syscon.h>
+#include <linux/mfd/syscon/atmel-matrix.h>
+#include <linux/mfd/syscon/atmel-smc.h>
+#include <module.h>
+#include <linux/mtd/rawnand.h>
+#include <of_address.h>
+#include <of.h>
+#include <of_device.h>
+#include <linux/iopoll.h>
+#include <regmap.h>
+#include <soc/at91/atmel-sfr.h>
+
+#include "pmecc.h"
+
+#define ATMEL_HSMC_NFC_CFG			0x0
+#define ATMEL_HSMC_NFC_CFG_SPARESIZE(x)		(((x) / 4) << 24)
+#define ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK	GENMASK(30, 24)
+#define ATMEL_HSMC_NFC_CFG_DTO(cyc, mul)	(((cyc) << 16) | ((mul) << 20))
+#define ATMEL_HSMC_NFC_CFG_DTO_MAX		GENMASK(22, 16)
+#define ATMEL_HSMC_NFC_CFG_RBEDGE		BIT(13)
+#define ATMEL_HSMC_NFC_CFG_FALLING_EDGE		BIT(12)
+#define ATMEL_HSMC_NFC_CFG_RSPARE		BIT(9)
+#define ATMEL_HSMC_NFC_CFG_WSPARE		BIT(8)
+#define ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK	GENMASK(2, 0)
+#define ATMEL_HSMC_NFC_CFG_PAGESIZE(x)		(fls((x) / 512) - 1)
+
+#define ATMEL_HSMC_NFC_CTRL			0x4
+#define ATMEL_HSMC_NFC_CTRL_EN			BIT(0)
+#define ATMEL_HSMC_NFC_CTRL_DIS			BIT(1)
+
+#define ATMEL_HSMC_NFC_SR			0x8
+#define ATMEL_HSMC_NFC_IER			0xc
+#define ATMEL_HSMC_NFC_IDR			0x10
+#define ATMEL_HSMC_NFC_IMR			0x14
+#define ATMEL_HSMC_NFC_SR_ENABLED		BIT(1)
+#define ATMEL_HSMC_NFC_SR_RB_RISE		BIT(4)
+#define ATMEL_HSMC_NFC_SR_RB_FALL		BIT(5)
+#define ATMEL_HSMC_NFC_SR_BUSY			BIT(8)
+#define ATMEL_HSMC_NFC_SR_WR			BIT(11)
+#define ATMEL_HSMC_NFC_SR_CSID			GENMASK(14, 12)
+#define ATMEL_HSMC_NFC_SR_XFRDONE		BIT(16)
+#define ATMEL_HSMC_NFC_SR_CMDDONE		BIT(17)
+#define ATMEL_HSMC_NFC_SR_DTOE			BIT(20)
+#define ATMEL_HSMC_NFC_SR_UNDEF			BIT(21)
+#define ATMEL_HSMC_NFC_SR_AWB			BIT(22)
+#define ATMEL_HSMC_NFC_SR_NFCASE		BIT(23)
+#define ATMEL_HSMC_NFC_SR_ERRORS		(ATMEL_HSMC_NFC_SR_DTOE | \
+						 ATMEL_HSMC_NFC_SR_UNDEF | \
+						 ATMEL_HSMC_NFC_SR_AWB | \
+						 ATMEL_HSMC_NFC_SR_NFCASE)
+#define ATMEL_HSMC_NFC_SR_RBEDGE(x)		BIT((x) + 24)
+
+#define ATMEL_HSMC_NFC_ADDR			0x18
+#define ATMEL_HSMC_NFC_BANK			0x1c
+
+#define ATMEL_NFC_MAX_RB_ID			7
+
+#define ATMEL_NFC_SRAM_SIZE			0x2400
+
+#define ATMEL_NFC_CMD(pos, cmd)			((cmd) << (((pos) * 8) + 2))
+#define ATMEL_NFC_VCMD2				BIT(18)
+#define ATMEL_NFC_ACYCLE(naddrs)		((naddrs) << 19)
+#define ATMEL_NFC_CSID(cs)			((cs) << 22)
+#define ATMEL_NFC_DATAEN			BIT(25)
+#define ATMEL_NFC_NFCWR				BIT(26)
+
+#define ATMEL_NFC_MAX_ADDR_CYCLES		5
+
+#define ATMEL_NAND_ALE_OFFSET			BIT(21)
+#define ATMEL_NAND_CLE_OFFSET			BIT(22)
+
+#define DEFAULT_TIMEOUT_MS			1000
+
+enum atmel_nand_rb_type {
+	ATMEL_NAND_NO_RB,
+	ATMEL_NAND_NATIVE_RB,
+	ATMEL_NAND_GPIO_RB,
+};
+
+struct atmel_nand_rb {
+	enum atmel_nand_rb_type type;
+	union {
+		int gpio;
+		int id;
+	};
+};
+
+struct atmel_nand_cs {
+	int id;
+	struct atmel_nand_rb rb;
+	int csgpio;
+	struct {
+		void __iomem *virt;
+	} io;
+
+	struct atmel_smc_cs_conf smcconf;
+};
+
+struct atmel_nand {
+	struct list_head node;
+	struct device *dev;
+	struct nand_chip base;
+	struct atmel_nand_cs *activecs;
+	struct atmel_pmecc_user *pmecc;
+	int cdgpio;
+	int numcs;
+	struct atmel_nand_cs cs[];
+};
+
+static inline struct atmel_nand *to_atmel_nand(struct nand_chip *chip)
+{
+	return container_of(chip, struct atmel_nand, base);
+}
+
+enum atmel_nfc_data_xfer {
+	ATMEL_NFC_NO_DATA,
+	ATMEL_NFC_READ_DATA,
+	ATMEL_NFC_WRITE_DATA,
+};
+
+struct atmel_nfc_op {
+	u8 cs;
+	u8 ncmds;
+	u8 cmds[2];
+	u8 naddrs;
+	u8 addrs[5];
+	enum atmel_nfc_data_xfer data;
+	u32 wait;
+	u32 errors;
+};
+
+struct atmel_nand_controller;
+struct atmel_nand_controller_caps;
+
+struct atmel_nand_controller_ops {
+	int (*probe)(struct device *dev,
+		     const struct atmel_nand_controller_caps *caps);
+	void (*nand_init)(struct atmel_nand_controller *nc,
+			  struct atmel_nand *nand);
+	int (*ecc_init)(struct nand_chip *chip);
+	int (*setup_interface)(struct atmel_nand *nand, int csline,
+			       const struct nand_interface_config *conf);
+	int (*exec_op)(struct atmel_nand *nand,
+		       const struct nand_operation *op, bool check_only);
+};
+
+struct atmel_nand_controller_caps {
+	u32 ale_offs;
+	u32 cle_offs;
+	const char *ebi_csa_regmap_name;
+	const struct atmel_nand_controller_ops *ops;
+};
+
+struct atmel_nand_controller {
+	struct nand_controller base;
+	const struct atmel_nand_controller_caps *caps;
+	struct device *dev;
+	struct regmap *smc;
+	struct atmel_pmecc *pmecc;
+	struct list_head chips;
+	struct clk *mck;
+};
+
+static inline struct atmel_nand_controller *
+to_nand_controller(struct nand_controller *ctl)
+{
+	return container_of(ctl, struct atmel_nand_controller, base);
+}
+
+struct atmel_smc_nand_ebi_csa_cfg {
+	u32 offs;
+	u32 nfd0_on_d16;
+};
+
+struct atmel_smc_nand_controller {
+	struct atmel_nand_controller base;
+	struct regmap *ebi_csa_regmap;
+	struct atmel_smc_nand_ebi_csa_cfg *ebi_csa;
+};
+
+static inline struct atmel_smc_nand_controller *
+to_smc_nand_controller(struct nand_controller *ctl)
+{
+	return container_of(to_nand_controller(ctl),
+			    struct atmel_smc_nand_controller, base);
+}
+
+struct atmel_hsmc_nand_controller {
+	struct atmel_nand_controller base;
+	struct {
+		struct gen_pool *pool;
+		void __iomem *virt;
+	} sram;
+	const struct atmel_hsmc_reg_layout *hsmc_layout;
+	struct regmap *io;
+	struct atmel_nfc_op op;
+	u32 cfg;
+};
+
+static inline struct atmel_hsmc_nand_controller *
+to_hsmc_nand_controller(struct nand_controller *ctl)
+{
+	return container_of(to_nand_controller(ctl),
+			    struct atmel_hsmc_nand_controller, base);
+}
+
+static bool atmel_nfc_op_done(struct atmel_nfc_op *op, u32 status)
+{
+	op->errors |= status & ATMEL_HSMC_NFC_SR_ERRORS;
+	op->wait ^= status & op->wait;
+
+	return !op->wait || op->errors;
+}
+
+static int atmel_nfc_wait(struct atmel_hsmc_nand_controller *nc,
+			  unsigned int timeout_ms)
+{
+	u32 status;
+	int ret;
+
+	if (!timeout_ms)
+		timeout_ms = DEFAULT_TIMEOUT_MS;
+
+
+	ret = regmap_read_poll_timeout(nc->base.smc,
+				       ATMEL_HSMC_NFC_SR, status,
+				       atmel_nfc_op_done(&nc->op,
+							 status),
+				       timeout_ms * 1000);
+
+	if (nc->op.errors & ATMEL_HSMC_NFC_SR_DTOE) {
+		dev_err(nc->base.dev, "Waiting NAND R/B Timeout\n");
+		ret = -ETIMEDOUT;
+	}
+
+	if (nc->op.errors & ATMEL_HSMC_NFC_SR_UNDEF) {
+		dev_err(nc->base.dev, "Access to an undefined area\n");
+		ret = -EIO;
+	}
+
+	if (nc->op.errors & ATMEL_HSMC_NFC_SR_AWB) {
+		dev_err(nc->base.dev, "Access while busy\n");
+		ret = -EIO;
+	}
+
+	if (nc->op.errors & ATMEL_HSMC_NFC_SR_NFCASE) {
+		dev_err(nc->base.dev, "Wrong access size\n");
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+static int atmel_nfc_exec_op(struct atmel_hsmc_nand_controller *nc)
+{
+	u8 *addrs = nc->op.addrs;
+	unsigned int op = 0;
+	u32 addr, val;
+	int i, ret;
+
+	nc->op.wait = ATMEL_HSMC_NFC_SR_CMDDONE;
+
+	for (i = 0; i < nc->op.ncmds; i++)
+		op |= ATMEL_NFC_CMD(i, nc->op.cmds[i]);
+
+	if (nc->op.naddrs == ATMEL_NFC_MAX_ADDR_CYCLES)
+		regmap_write(nc->base.smc, ATMEL_HSMC_NFC_ADDR, *addrs++);
+
+	op |= ATMEL_NFC_CSID(nc->op.cs) |
+	      ATMEL_NFC_ACYCLE(nc->op.naddrs);
+
+	if (nc->op.ncmds > 1)
+		op |= ATMEL_NFC_VCMD2;
+
+	addr = addrs[0] | (addrs[1] << 8) | (addrs[2] << 16) |
+	       (addrs[3] << 24);
+
+	if (nc->op.data != ATMEL_NFC_NO_DATA) {
+		op |= ATMEL_NFC_DATAEN;
+		nc->op.wait |= ATMEL_HSMC_NFC_SR_XFRDONE;
+
+		if (nc->op.data == ATMEL_NFC_WRITE_DATA)
+			op |= ATMEL_NFC_NFCWR;
+	}
+
+	/* Clear all flags. */
+	regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &val);
+
+	/* Send the command. */
+	regmap_write(nc->io, op, addr);
+
+	ret = atmel_nfc_wait(nc, 0);
+	if (ret)
+		dev_err(nc->base.dev,
+			"Failed to send NAND command (err = %d)!",
+			ret);
+
+	/* Reset the op state. */
+	memset(&nc->op, 0, sizeof(nc->op));
+
+	return ret;
+}
+
+static void atmel_nand_data_in(struct atmel_nand *nand, void *buf,
+			       unsigned int len, bool force_8bit)
+{
+	struct atmel_nand_controller *nc;
+
+	nc = to_nand_controller(nand->base.controller);
+
+	if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit)
+		ioread16_rep(nand->activecs->io.virt, buf, len / 2);
+	else
+		ioread8_rep(nand->activecs->io.virt, buf, len);
+}
+
+static void atmel_nand_data_out(struct atmel_nand *nand, const void *buf,
+				unsigned int len, bool force_8bit)
+{
+	struct atmel_nand_controller *nc;
+
+	nc = to_nand_controller(nand->base.controller);
+
+	if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit)
+		iowrite16_rep(nand->activecs->io.virt, buf, len / 2);
+	else
+		iowrite8_rep(nand->activecs->io.virt, buf, len);
+}
+
+static int atmel_nand_waitrdy(struct atmel_nand *nand, unsigned int timeout_ms)
+{
+	if (nand->activecs->rb.type == ATMEL_NAND_NO_RB)
+		return nand_soft_waitrdy(&nand->base, timeout_ms);
+
+	return nand_gpio_waitrdy(&nand->base, nand->activecs->rb.gpio,
+				 timeout_ms);
+}
+
+static int atmel_hsmc_nand_waitrdy(struct atmel_nand *nand,
+				   unsigned int timeout_ms)
+{
+	struct atmel_hsmc_nand_controller *nc;
+	u32 status, mask;
+
+	if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB)
+		return atmel_nand_waitrdy(nand, timeout_ms);
+
+	nc = to_hsmc_nand_controller(nand->base.controller);
+	mask = ATMEL_HSMC_NFC_SR_RBEDGE(nand->activecs->rb.id);
+	return regmap_read_poll_timeout(nc->base.smc, ATMEL_HSMC_NFC_SR,
+					status, status & mask,
+					timeout_ms * 1000);
+}
+
+static void atmel_nand_select_target(struct atmel_nand *nand,
+				     unsigned int cs)
+{
+	nand->activecs = &nand->cs[cs];
+}
+
+static void atmel_hsmc_nand_select_target(struct atmel_nand *nand,
+					  unsigned int cs)
+{
+	struct mtd_info *mtd = nand_to_mtd(&nand->base);
+	struct atmel_hsmc_nand_controller *nc;
+	u32 cfg = ATMEL_HSMC_NFC_CFG_PAGESIZE(mtd->writesize) |
+		  ATMEL_HSMC_NFC_CFG_SPARESIZE(mtd->oobsize) |
+		  ATMEL_HSMC_NFC_CFG_RSPARE;
+
+	nand->activecs = &nand->cs[cs];
+	nc = to_hsmc_nand_controller(nand->base.controller);
+	if (nc->cfg == cfg)
+		return;
+
+	regmap_update_bits(nc->base.smc, ATMEL_HSMC_NFC_CFG,
+			   ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK |
+			   ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK |
+			   ATMEL_HSMC_NFC_CFG_RSPARE |
+			   ATMEL_HSMC_NFC_CFG_WSPARE,
+			   cfg);
+	nc->cfg = cfg;
+}
+
+static int atmel_smc_nand_exec_instr(struct atmel_nand *nand,
+				     const struct nand_op_instr *instr)
+{
+	struct atmel_nand_controller *nc;
+	unsigned int i;
+
+	nc = to_nand_controller(nand->base.controller);
+	switch (instr->type) {
+	case NAND_OP_CMD_INSTR:
+		writeb(instr->ctx.cmd.opcode,
+		       nand->activecs->io.virt + nc->caps->cle_offs);
+		return 0;
+	case NAND_OP_ADDR_INSTR:
+		for (i = 0; i < instr->ctx.addr.naddrs; i++)
+			writeb(instr->ctx.addr.addrs[i],
+			       nand->activecs->io.virt + nc->caps->ale_offs);
+		return 0;
+	case NAND_OP_DATA_IN_INSTR:
+		atmel_nand_data_in(nand, instr->ctx.data.buf.in,
+				   instr->ctx.data.len,
+				   instr->ctx.data.force_8bit);
+		return 0;
+	case NAND_OP_DATA_OUT_INSTR:
+		atmel_nand_data_out(nand, instr->ctx.data.buf.out,
+				    instr->ctx.data.len,
+				    instr->ctx.data.force_8bit);
+		return 0;
+	case NAND_OP_WAITRDY_INSTR:
+		return atmel_nand_waitrdy(nand,
+					  instr->ctx.waitrdy.timeout_ms);
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int atmel_smc_nand_exec_op(struct atmel_nand *nand,
+				  const struct nand_operation *op,
+				  bool check_only)
+{
+	unsigned int i;
+	int ret = 0;
+
+	if (check_only)
+		return 0;
+
+	atmel_nand_select_target(nand, op->cs);
+	gpiod_set_value(nand->activecs->csgpio, 0);
+	for (i = 0; i < op->ninstrs; i++) {
+		ret = atmel_smc_nand_exec_instr(nand, &op->instrs[i]);
+		if (ret)
+			break;
+	}
+	gpiod_set_value(nand->activecs->csgpio, 1);
+
+	return ret;
+}
+
+static int atmel_hsmc_exec_cmd_addr(struct nand_chip *chip,
+				    const struct nand_subop *subop)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_hsmc_nand_controller *nc;
+	unsigned int i, j;
+
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	nc->op.cs = nand->activecs->id;
+	for (i = 0; i < subop->ninstrs; i++) {
+		const struct nand_op_instr *instr = &subop->instrs[i];
+
+		if (instr->type == NAND_OP_CMD_INSTR) {
+			nc->op.cmds[nc->op.ncmds++] = instr->ctx.cmd.opcode;
+			continue;
+		}
+
+		for (j = nand_subop_get_addr_start_off(subop, i);
+		     j < nand_subop_get_num_addr_cyc(subop, i); j++) {
+			nc->op.addrs[nc->op.naddrs] = instr->ctx.addr.addrs[j];
+			nc->op.naddrs++;
+		}
+	}
+
+	return atmel_nfc_exec_op(nc);
+}
+
+static int atmel_hsmc_exec_rw(struct nand_chip *chip,
+			      const struct nand_subop *subop)
+{
+	const struct nand_op_instr *instr = subop->instrs;
+	struct atmel_nand *nand = to_atmel_nand(chip);
+
+	if (instr->type == NAND_OP_DATA_IN_INSTR)
+		atmel_nand_data_in(nand, instr->ctx.data.buf.in,
+				   instr->ctx.data.len,
+				   instr->ctx.data.force_8bit);
+	else
+		atmel_nand_data_out(nand, instr->ctx.data.buf.out,
+				    instr->ctx.data.len,
+				    instr->ctx.data.force_8bit);
+
+	return 0;
+}
+
+static int atmel_hsmc_exec_waitrdy(struct nand_chip *chip,
+				   const struct nand_subop *subop)
+{
+	const struct nand_op_instr *instr = subop->instrs;
+	struct atmel_nand *nand = to_atmel_nand(chip);
+
+	return atmel_hsmc_nand_waitrdy(nand, instr->ctx.waitrdy.timeout_ms);
+}
+
+static const struct nand_op_parser atmel_hsmc_op_parser = NAND_OP_PARSER(
+	NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_cmd_addr,
+		NAND_OP_PARSER_PAT_CMD_ELEM(true),
+		NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5),
+		NAND_OP_PARSER_PAT_CMD_ELEM(true)),
+	NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw,
+		NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)),
+	NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw,
+		NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0)),
+	NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_waitrdy,
+		NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+);
+
+static int atmel_hsmc_nand_exec_op(struct atmel_nand *nand,
+				   const struct nand_operation *op,
+				   bool check_only)
+{
+	int ret;
+
+	if (check_only)
+		return nand_op_parser_exec_op(&nand->base,
+					      &atmel_hsmc_op_parser, op, true);
+
+	atmel_hsmc_nand_select_target(nand, op->cs);
+	ret = nand_op_parser_exec_op(&nand->base, &atmel_hsmc_op_parser, op,
+				     false);
+
+	return ret;
+}
+
+static void atmel_nfc_copy_to_sram(struct nand_chip *chip, const u8 *buf,
+				   bool oob_required)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_hsmc_nand_controller *nc;
+
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	/* Falling back to CPU copy. */
+	memcpy_toio(nc->sram.virt, buf, mtd->writesize);
+
+	if (oob_required)
+		memcpy_toio(nc->sram.virt + mtd->writesize, chip->oob_poi,
+			    mtd->oobsize);
+}
+
+static void atmel_nfc_copy_from_sram(struct nand_chip *chip, u8 *buf,
+				     bool oob_required)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_hsmc_nand_controller *nc;
+
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	memcpy_fromio(buf, nc->sram.virt, mtd->writesize);
+
+	if (oob_required)
+		memcpy_fromio(chip->oob_poi, nc->sram.virt + mtd->writesize,
+			      mtd->oobsize);
+}
+
+static void atmel_nfc_set_op_addr(struct nand_chip *chip, int page, int column)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_hsmc_nand_controller *nc;
+
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	if (column >= 0) {
+		nc->op.addrs[nc->op.naddrs++] = column;
+
+		/*
+		 * 2 address cycles for the column offset on large page NANDs.
+		 */
+		if (mtd->writesize > 512)
+			nc->op.addrs[nc->op.naddrs++] = column >> 8;
+	}
+
+	if (page >= 0) {
+		nc->op.addrs[nc->op.naddrs++] = page;
+		nc->op.addrs[nc->op.naddrs++] = page >> 8;
+
+		if (chip->options & NAND_ROW_ADDR_3)
+			nc->op.addrs[nc->op.naddrs++] = page >> 16;
+	}
+}
+
+static int atmel_nand_pmecc_enable(struct nand_chip *chip, int op, bool raw)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_nand_controller *nc;
+	int ret;
+
+	nc = to_nand_controller(chip->controller);
+
+	if (raw)
+		return 0;
+
+	ret = atmel_pmecc_enable(nand->pmecc, op);
+	if (ret)
+		dev_err(nc->dev,
+			"Failed to enable ECC engine (err = %d)\n", ret);
+
+	return ret;
+}
+
+static void atmel_nand_pmecc_disable(struct nand_chip *chip, bool raw)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+
+	if (!raw)
+		atmel_pmecc_disable(nand->pmecc);
+}
+
+static int atmel_nand_pmecc_generate_eccbytes(struct nand_chip *chip, bool raw)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand_controller *nc;
+	struct mtd_oob_region oobregion;
+	void *eccbuf;
+	int ret, i;
+
+	nc = to_nand_controller(chip->controller);
+
+	if (raw)
+		return 0;
+
+	ret = atmel_pmecc_wait_rdy(nand->pmecc);
+	if (ret) {
+		dev_err(nc->dev,
+			"Failed to transfer NAND page data (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	mtd_ooblayout_ecc(mtd, 0, &oobregion);
+	eccbuf = chip->oob_poi + oobregion.offset;
+
+	for (i = 0; i < chip->ecc.steps; i++) {
+		atmel_pmecc_get_generated_eccbytes(nand->pmecc, i,
+						   eccbuf);
+		eccbuf += chip->ecc.bytes;
+	}
+
+	return 0;
+}
+
+static int atmel_nand_pmecc_correct_data(struct nand_chip *chip, void *buf,
+					 bool raw)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand_controller *nc;
+	struct mtd_oob_region oobregion;
+	int ret, i, max_bitflips = 0;
+	void *databuf, *eccbuf;
+
+	nc = to_nand_controller(chip->controller);
+
+	if (raw)
+		return 0;
+
+	ret = atmel_pmecc_wait_rdy(nand->pmecc);
+	if (ret) {
+		dev_err(nc->dev,
+			"Failed to read NAND page data (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	mtd_ooblayout_ecc(mtd, 0, &oobregion);
+	eccbuf = chip->oob_poi + oobregion.offset;
+	databuf = buf;
+
+	for (i = 0; i < chip->ecc.steps; i++) {
+		ret = atmel_pmecc_correct_sector(nand->pmecc, i, databuf,
+						 eccbuf);
+		if (ret < 0 && !atmel_pmecc_correct_erased_chunks(nand->pmecc))
+			ret = nand_check_erased_ecc_chunk(databuf,
+							  chip->ecc.size,
+							  eccbuf,
+							  chip->ecc.bytes,
+							  NULL, 0,
+							  chip->ecc.strength);
+
+		if (ret >= 0) {
+			mtd->ecc_stats.corrected += ret;
+			max_bitflips = max(ret, max_bitflips);
+		} else {
+			mtd->ecc_stats.failed++;
+		}
+
+		databuf += chip->ecc.size;
+		eccbuf += chip->ecc.bytes;
+	}
+
+	return max_bitflips;
+}
+
+static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf,
+				     bool oob_required, int page, bool raw)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	int ret;
+
+	nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+
+	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
+	if (ret)
+		return ret;
+
+	nand_write_data_op(chip, buf, mtd->writesize, false);
+
+	ret = atmel_nand_pmecc_generate_eccbytes(chip, raw);
+	if (ret) {
+		atmel_pmecc_disable(nand->pmecc);
+		return ret;
+	}
+
+	atmel_nand_pmecc_disable(chip, raw);
+
+	nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+
+	return nand_prog_page_end_op(chip);
+}
+
+static int atmel_nand_pmecc_write_page(struct nand_chip *chip, const u8 *buf,
+				       int oob_required, int page)
+{
+	return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, false);
+}
+
+static int atmel_nand_pmecc_write_page_raw(struct nand_chip *chip,
+					   const u8 *buf, int oob_required,
+					   int page)
+{
+	return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, true);
+}
+
+static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
+				    bool oob_required, int page, bool raw)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int ret;
+
+	nand_read_page_op(chip, page, 0, NULL, 0);
+
+	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
+	if (ret)
+		return ret;
+
+	ret = nand_read_data_op(chip, buf, mtd->writesize, false, false);
+	if (ret)
+		goto out_disable;
+
+	ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, false);
+	if (ret)
+		goto out_disable;
+
+	ret = atmel_nand_pmecc_correct_data(chip, buf, raw);
+
+out_disable:
+	atmel_nand_pmecc_disable(chip, raw);
+
+	return ret;
+}
+
+static int atmel_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf,
+				      int oob_required, int page)
+{
+	return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, false);
+}
+
+static int atmel_nand_pmecc_read_page_raw(struct nand_chip *chip, u8 *buf,
+					  int oob_required, int page)
+{
+	return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, true);
+}
+
+static int atmel_hsmc_nand_pmecc_write_pg(struct nand_chip *chip,
+					  const u8 *buf, bool oob_required,
+					  int page, bool raw)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_hsmc_nand_controller *nc;
+	int ret;
+
+	atmel_hsmc_nand_select_target(nand, chip->cur_cs);
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	atmel_nfc_copy_to_sram(chip, buf, false);
+
+	nc->op.cmds[0] = NAND_CMD_SEQIN;
+	nc->op.ncmds = 1;
+	atmel_nfc_set_op_addr(chip, page, 0x0);
+	nc->op.cs = nand->activecs->id;
+	nc->op.data = ATMEL_NFC_WRITE_DATA;
+
+	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw);
+	if (ret)
+		return ret;
+
+	ret = atmel_nfc_exec_op(nc);
+	if (ret) {
+		atmel_nand_pmecc_disable(chip, raw);
+		dev_err(nc->base.dev,
+			"Failed to transfer NAND page data (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = atmel_nand_pmecc_generate_eccbytes(chip, raw);
+
+	atmel_nand_pmecc_disable(chip, raw);
+
+	if (ret)
+		return ret;
+
+	nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false);
+
+	return nand_prog_page_end_op(chip);
+}
+
+static int atmel_hsmc_nand_pmecc_write_page(struct nand_chip *chip,
+					    const u8 *buf, int oob_required,
+					    int page)
+{
+	return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page,
+					      false);
+}
+
+static int atmel_hsmc_nand_pmecc_write_page_raw(struct nand_chip *chip,
+						const u8 *buf,
+						int oob_required, int page)
+{
+	return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page,
+					      true);
+}
+
+static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf,
+					 bool oob_required, int page,
+					 bool raw)
+{
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_hsmc_nand_controller *nc;
+	int ret;
+
+	atmel_hsmc_nand_select_target(nand, chip->cur_cs);
+	nc = to_hsmc_nand_controller(chip->controller);
+
+	/*
+	 * Optimized read page accessors only work when the NAND R/B pin is
+	 * connected to a native SoC R/B pin. If that's not the case, fallback
+	 * to the non-optimized one.
+	 */
+	if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB)
+		return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page,
+						raw);
+
+	nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READ0;
+
+	if (mtd->writesize > 512)
+		nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READSTART;
+
+	atmel_nfc_set_op_addr(chip, page, 0x0);
+	nc->op.cs = nand->activecs->id;
+	nc->op.data = ATMEL_NFC_READ_DATA;
+
+	ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw);
+	if (ret)
+		return ret;
+
+	ret = atmel_nfc_exec_op(nc);
+	if (ret) {
+		atmel_nand_pmecc_disable(chip, raw);
+		dev_err(nc->base.dev,
+			"Failed to load NAND page data (err = %d)\n",
+			ret);
+		return ret;
+	}
+
+	atmel_nfc_copy_from_sram(chip, buf, true);
+
+	ret = atmel_nand_pmecc_correct_data(chip, buf, raw);
+
+	atmel_nand_pmecc_disable(chip, raw);
+
+	return ret;
+}
+
+static int atmel_hsmc_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf,
+					   int oob_required, int page)
+{
+	return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page,
+					     false);
+}
+
+static int atmel_hsmc_nand_pmecc_read_page_raw(struct nand_chip *chip,
+					       u8 *buf, int oob_required,
+					       int page)
+{
+	return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page,
+					     true);
+}
+
+static int atmel_nand_pmecc_init(struct nand_chip *chip)
+{
+	const struct nand_ecc_props *requirements =
+		nanddev_get_ecc_requirements(&chip->base);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_nand_controller *nc;
+	struct atmel_pmecc_user_req req;
+	struct device_node *dn;
+
+	nc = to_nand_controller(chip->controller);
+
+	if (!nc->pmecc) {
+		dev_err(nc->dev, "HW ECC not supported\n");
+		return -ENOTSUPP;
+	}
+
+	dn = nand_get_flash_node(chip);
+
+	if (of_property_read_bool(dn, "nand-ecc-maximize"))
+		req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH;
+	else if (chip->ecc.strength)
+		req.ecc.strength = chip->ecc.strength;
+	else if (requirements->strength)
+		req.ecc.strength = requirements->strength;
+	else
+		req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH;
+
+	if (chip->ecc.size)
+		req.ecc.sectorsize = chip->ecc.size;
+	else if (requirements->step_size)
+		req.ecc.sectorsize = requirements->step_size;
+	else
+		req.ecc.sectorsize = ATMEL_PMECC_SECTOR_SIZE_AUTO;
+
+	req.pagesize = mtd->writesize;
+	req.oobsize = mtd->oobsize;
+
+	if (mtd->writesize <= 512) {
+		req.ecc.bytes = 4;
+		req.ecc.ooboffset = 0;
+	} else {
+		req.ecc.bytes = mtd->oobsize - 2;
+		req.ecc.ooboffset = ATMEL_PMECC_OOBOFFSET_AUTO;
+	}
+
+	nand->pmecc = atmel_pmecc_create_user(nc->pmecc, &req);
+	if (IS_ERR(nand->pmecc))
+		return PTR_ERR(nand->pmecc);
+
+	chip->ecc.algo = NAND_ECC_ALGO_BCH;
+	chip->ecc.size = req.ecc.sectorsize;
+	chip->ecc.bytes = req.ecc.bytes / req.ecc.nsectors;
+	chip->ecc.strength = req.ecc.strength;
+
+	chip->options |= NAND_NO_SUBPAGE_WRITE;
+
+	mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
+
+	return 0;
+}
+
+static int atmel_nand_ecc_init(struct nand_chip *chip)
+{
+	struct atmel_nand_controller *nc;
+	int ret;
+
+	nc = to_nand_controller(chip->controller);
+
+	switch (chip->ecc.engine_type) {
+	case NAND_ECC_ENGINE_TYPE_NONE:
+	case NAND_ECC_ENGINE_TYPE_SOFT:
+		/*
+		 * Nothing to do, the core will initialize everything for us.
+		 */
+		break;
+
+	case NAND_ECC_ENGINE_TYPE_ON_HOST:
+		ret = atmel_nand_pmecc_init(chip);
+		if (ret)
+			return ret;
+
+		chip->ecc.read_page = atmel_nand_pmecc_read_page;
+		chip->ecc.write_page = atmel_nand_pmecc_write_page;
+		chip->ecc.read_page_raw = atmel_nand_pmecc_read_page_raw;
+		chip->ecc.write_page_raw = atmel_nand_pmecc_write_page_raw;
+		break;
+
+	default:
+		/* Other modes are not supported. */
+		dev_err(nc->dev, "Unsupported ECC mode: %d\n",
+			chip->ecc.engine_type);
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static int atmel_hsmc_nand_ecc_init(struct nand_chip *chip)
+{
+	int ret;
+
+	ret = atmel_nand_ecc_init(chip);
+	if (ret)
+		return ret;
+
+	if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST)
+		return 0;
+
+	/* Adjust the ECC operations for the HSMC IP. */
+	chip->ecc.read_page = atmel_hsmc_nand_pmecc_read_page;
+	chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page;
+	chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw;
+	chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw;
+
+	return 0;
+}
+
+static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
+					const struct nand_interface_config *conf,
+					struct atmel_smc_cs_conf *smcconf)
+{
+	u32 ncycles, totalcycles, timeps, mckperiodps;
+	struct atmel_nand_controller *nc;
+	int ret;
+
+	nc = to_nand_controller(nand->base.controller);
+
+	/* DDR interface not supported. */
+	if (!nand_interface_is_sdr(conf))
+		return -ENOTSUPP;
+
+	/*
+	 * tRC < 30ns implies EDO mode. This controller does not support this
+	 * mode.
+	 */
+	if (conf->timings.sdr.tRC_min < 30000)
+		return -ENOTSUPP;
+
+	atmel_smc_cs_conf_init(smcconf);
+
+	mckperiodps = NSEC_PER_SEC / clk_get_rate(nc->mck);
+	mckperiodps *= 1000;
+
+	/*
+	 * Set write pulse timing. This one is easy to extract:
+	 *
+	 * NWE_PULSE = tWP
+	 */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tWP_min, mckperiodps);
+	totalcycles = ncycles;
+	ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NWE_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * The write setup timing depends on the operation done on the NAND.
+	 * All operations goes through the same data bus, but the operation
+	 * type depends on the address we are writing to (ALE/CLE address
+	 * lines).
+	 * Since we have no way to differentiate the different operations at
+	 * the SMC level, we must consider the worst case (the biggest setup
+	 * time among all operation types):
+	 *
+	 * NWE_SETUP = max(tCLS, tCS, tALS, tDS) - NWE_PULSE
+	 */
+	timeps = max3(conf->timings.sdr.tCLS_min, conf->timings.sdr.tCS_min,
+		      conf->timings.sdr.tALS_min);
+	timeps = max(timeps, conf->timings.sdr.tDS_min);
+	ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+	ncycles = ncycles > totalcycles ? ncycles - totalcycles : 0;
+	totalcycles += ncycles;
+	ret = atmel_smc_cs_conf_set_setup(smcconf, ATMEL_SMC_NWE_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * As for the write setup timing, the write hold timing depends on the
+	 * operation done on the NAND:
+	 *
+	 * NWE_HOLD = max(tCLH, tCH, tALH, tDH, tWH)
+	 */
+	timeps = max3(conf->timings.sdr.tCLH_min, conf->timings.sdr.tCH_min,
+		      conf->timings.sdr.tALH_min);
+	timeps = max3(timeps, conf->timings.sdr.tDH_min,
+		      conf->timings.sdr.tWH_min);
+	ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+	totalcycles += ncycles;
+
+	/*
+	 * The write cycle timing is directly matching tWC, but is also
+	 * dependent on the other timings on the setup and hold timings we
+	 * calculated earlier, which gives:
+	 *
+	 * NWE_CYCLE = max(tWC, NWE_SETUP + NWE_PULSE + NWE_HOLD)
+	 */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tWC_min, mckperiodps);
+	ncycles = max(totalcycles, ncycles);
+	ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NWE_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * We don't want the CS line to be toggled between each byte/word
+	 * transfer to the NAND. The only way to guarantee that is to have the
+	 * NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means:
+	 *
+	 * NCS_WR_PULSE = NWE_CYCLE
+	 */
+	ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_WR_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * As for the write setup timing, the read hold timing depends on the
+	 * operation done on the NAND:
+	 *
+	 * NRD_HOLD = max(tREH, tRHOH)
+	 */
+	timeps = max(conf->timings.sdr.tREH_min, conf->timings.sdr.tRHOH_min);
+	ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+	totalcycles = ncycles;
+
+	/*
+	 * TDF = tRHZ - NRD_HOLD
+	 */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tRHZ_max, mckperiodps);
+	ncycles -= totalcycles;
+
+	/*
+	 * In ONFI 4.0 specs, tRHZ has been increased to support EDO NANDs and
+	 * we might end up with a config that does not fit in the TDF field.
+	 * Just take the max value in this case and hope that the NAND is more
+	 * tolerant than advertised.
+	 */
+	if (ncycles > ATMEL_SMC_MODE_TDF_MAX)
+		ncycles = ATMEL_SMC_MODE_TDF_MAX;
+	else if (ncycles < ATMEL_SMC_MODE_TDF_MIN)
+		ncycles = ATMEL_SMC_MODE_TDF_MIN;
+
+	smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles) |
+			 ATMEL_SMC_MODE_TDFMODE_OPTIMIZED;
+
+	/*
+	 * Read pulse timing directly matches tRP:
+	 *
+	 * NRD_PULSE = tRP
+	 */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps);
+	totalcycles += ncycles;
+	ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * The write cycle timing is directly matching tWC, but is also
+	 * dependent on the setup and hold timings we calculated earlier,
+	 * which gives:
+	 *
+	 * NRD_CYCLE = max(tRC, NRD_PULSE + NRD_HOLD)
+	 *
+	 * NRD_SETUP is always 0.
+	 */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tRC_min, mckperiodps);
+	ncycles = max(totalcycles, ncycles);
+	ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NRD_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/*
+	 * We don't want the CS line to be toggled between each byte/word
+	 * transfer from the NAND. The only way to guarantee that is to have
+	 * the NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means:
+	 *
+	 * NCS_RD_PULSE = NRD_CYCLE
+	 */
+	ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_RD_SHIFT,
+					  ncycles);
+	if (ret)
+		return ret;
+
+	/* Txxx timings are directly matching tXXX ones. */
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tCLR_min, mckperiodps);
+	ret = atmel_smc_cs_conf_set_timing(smcconf,
+					   ATMEL_HSMC_TIMINGS_TCLR_SHIFT,
+					   ncycles);
+	if (ret)
+		return ret;
+
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tADL_min, mckperiodps);
+	ret = atmel_smc_cs_conf_set_timing(smcconf,
+					   ATMEL_HSMC_TIMINGS_TADL_SHIFT,
+					   ncycles);
+	/*
+	 * Version 4 of the ONFI spec mandates that tADL be at least 400
+	 * nanoseconds, but, depending on the master clock rate, 400 ns may not
+	 * fit in the tADL field of the SMC reg. We need to relax the check and
+	 * accept the -ERANGE return code.
+	 *
+	 * Note that previous versions of the ONFI spec had a lower tADL_min
+	 * (100 or 200 ns). It's not clear why this timing constraint got
+	 * increased but it seems most NANDs are fine with values lower than
+	 * 400ns, so we should be safe.
+	 */
+	if (ret && ret != -ERANGE)
+		return ret;
+
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tAR_min, mckperiodps);
+	ret = atmel_smc_cs_conf_set_timing(smcconf,
+					   ATMEL_HSMC_TIMINGS_TAR_SHIFT,
+					   ncycles);
+	if (ret)
+		return ret;
+
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tRR_min, mckperiodps);
+	ret = atmel_smc_cs_conf_set_timing(smcconf,
+					   ATMEL_HSMC_TIMINGS_TRR_SHIFT,
+					   ncycles);
+	if (ret)
+		return ret;
+
+	ncycles = DIV_ROUND_UP(conf->timings.sdr.tWB_max, mckperiodps);
+	ret = atmel_smc_cs_conf_set_timing(smcconf,
+					   ATMEL_HSMC_TIMINGS_TWB_SHIFT,
+					   ncycles);
+	if (ret)
+		return ret;
+
+	/* Attach the CS line to the NFC logic. */
+	smcconf->timings |= ATMEL_HSMC_TIMINGS_NFSEL;
+
+	/* Set the appropriate data bus width. */
+	if (nand->base.options & NAND_BUSWIDTH_16)
+		smcconf->mode |= ATMEL_SMC_MODE_DBW_16;
+
+	/* Operate in NRD/NWE READ/WRITEMODE. */
+	smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD |
+			 ATMEL_SMC_MODE_WRITEMODE_NWE;
+
+	return 0;
+}
+
+static int atmel_smc_nand_setup_interface(struct atmel_nand *nand,
+					int csline,
+					const struct nand_interface_config *conf)
+{
+	struct atmel_nand_controller *nc;
+	struct atmel_smc_cs_conf smcconf;
+	struct atmel_nand_cs *cs;
+	int ret;
+
+	nc = to_nand_controller(nand->base.controller);
+
+	ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf);
+	if (ret)
+		return ret;
+
+	if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+		return 0;
+
+	cs = &nand->cs[csline];
+	cs->smcconf = smcconf;
+	atmel_smc_cs_conf_apply(nc->smc, cs->id, &cs->smcconf);
+
+	return 0;
+}
+
+static int atmel_hsmc_nand_setup_interface(struct atmel_nand *nand,
+					int csline,
+					const struct nand_interface_config *conf)
+{
+	struct atmel_hsmc_nand_controller *nc;
+	struct atmel_smc_cs_conf smcconf;
+	struct atmel_nand_cs *cs;
+	int ret;
+
+	nc = to_hsmc_nand_controller(nand->base.controller);
+
+	ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf);
+	if (ret)
+		return ret;
+
+	if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+		return 0;
+
+	cs = &nand->cs[csline];
+	cs->smcconf = smcconf;
+
+	if (cs->rb.type == ATMEL_NAND_NATIVE_RB)
+		cs->smcconf.timings |= ATMEL_HSMC_TIMINGS_RBNSEL(cs->rb.id);
+
+	atmel_hsmc_cs_conf_apply(nc->base.smc, nc->hsmc_layout, cs->id,
+				 &cs->smcconf);
+
+	return 0;
+}
+
+static int atmel_nand_setup_interface(struct nand_chip *chip, int csline,
+				      const struct nand_interface_config *conf)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	const struct nand_sdr_timings *sdr;
+	struct atmel_nand_controller *nc;
+
+	sdr = nand_get_sdr_timings(conf);
+	if (IS_ERR(sdr))
+		return PTR_ERR(sdr);
+
+	nc = to_nand_controller(nand->base.controller);
+
+	if (csline >= nand->numcs ||
+	    (csline < 0 && csline != NAND_DATA_IFACE_CHECK_ONLY))
+		return -EINVAL;
+
+	return nc->caps->ops->setup_interface(nand, csline, conf);
+}
+
+static int atmel_nand_exec_op(struct nand_chip *chip,
+			      const struct nand_operation *op,
+			      bool check_only)
+{
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct atmel_nand_controller *nc;
+
+	nc = to_nand_controller(nand->base.controller);
+
+	return nc->caps->ops->exec_op(nand, op, check_only);
+}
+
+static void atmel_nand_init(struct atmel_nand_controller *nc,
+			    struct atmel_nand *nand)
+{
+	struct nand_chip *chip = &nand->base;
+	struct mtd_info *mtd = nand_to_mtd(chip);
+
+	mtd->dev.parent = nc->dev;
+	nand->base.controller = &nc->base;
+
+	if (!nc->mck || !nc->caps->ops->setup_interface)
+		chip->options |= NAND_KEEP_TIMINGS;
+
+	/* Default to HW ECC if pmecc is available. */
+	if (nc->pmecc)
+		chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
+}
+
+static void atmel_smc_nand_init(struct atmel_nand_controller *nc,
+				struct atmel_nand *nand)
+{
+	struct nand_chip *chip = &nand->base;
+	struct atmel_smc_nand_controller *smc_nc;
+	int i;
+
+	atmel_nand_init(nc, nand);
+
+	smc_nc = to_smc_nand_controller(chip->controller);
+	if (!smc_nc->ebi_csa_regmap)
+		return;
+
+	/* Attach the CS to the NAND Flash logic. */
+	for (i = 0; i < nand->numcs; i++)
+		regmap_update_bits(smc_nc->ebi_csa_regmap,
+				   smc_nc->ebi_csa->offs,
+				   BIT(nand->cs[i].id), BIT(nand->cs[i].id));
+
+	if (smc_nc->ebi_csa->nfd0_on_d16)
+		regmap_update_bits(smc_nc->ebi_csa_regmap,
+				   smc_nc->ebi_csa->offs,
+				   smc_nc->ebi_csa->nfd0_on_d16,
+				   smc_nc->ebi_csa->nfd0_on_d16);
+}
+
+static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
+					    struct device_node *np,
+					    int reg_cells)
+{
+	struct atmel_nand *nand;
+	int gpio;
+	int numcs, ret, i;
+
+	numcs = of_property_count_elems_of_size(np, "reg",
+						reg_cells * sizeof(u32));
+	if (numcs < 1) {
+		dev_err(nc->dev, "Missing or invalid reg property\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	nand = kzalloc(struct_size(nand, cs, numcs), GFP_KERNEL);
+	if (!nand)
+		return ERR_PTR(-ENOMEM);
+
+	nand->cdgpio = -ENOENT;
+	nand->numcs = numcs;
+
+	gpio = dev_gpiod_get(nc->dev, np, "det", GPIOD_IN, "nand-det");
+	if (gpio < 0 && gpio != -ENOENT) {
+		ret = dev_err_probe(nc->dev, gpio, "Failed to get detect gpio\n");
+		return ERR_PTR(ret);
+	}
+
+	if (gpio < 0)
+		nand->cdgpio = gpio;
+
+	for (i = 0; i < numcs; i++) {
+		struct resource res;
+		u32 val;
+
+		ret = of_address_to_resource(np, 0, &res);
+		if (ret) {
+			dev_err(nc->dev, "Invalid reg property (err = %d)\n",
+				ret);
+			return ERR_PTR(ret);
+		}
+
+		ret = of_property_read_u32_index(np, "reg", i * reg_cells,
+						 &val);
+		if (ret) {
+			dev_err(nc->dev, "Invalid reg property (err = %d)\n",
+				ret);
+			return ERR_PTR(ret);
+		}
+
+		nand->cs[i].id = val;
+		nand->cs[i].csgpio = -ENOENT;
+
+		nand->cs[i].io.virt = IOMEM(res.start);
+		ret = dev_request_resource(nc->dev, &res);
+		if (ret < 0)
+			return ERR_PTR(ret);
+
+		if (!of_property_read_u32(np, "atmel,rb", &val)) {
+			if (val > ATMEL_NFC_MAX_RB_ID)
+				return ERR_PTR(-EINVAL);
+
+			nand->cs[i].rb.type = ATMEL_NAND_NATIVE_RB;
+			nand->cs[i].rb.id = val;
+		} else {
+			gpio = dev_gpiod_get_index(nc->dev, np, "rb", i, GPIOD_IN, "nand-rb");
+			if (gpio < 0 && gpio != -ENOENT) {
+				ret = dev_err_probe(nc->dev, gpio, "Failed to get R/B gpio\n");
+				return ERR_PTR(ret);
+			}
+
+			if (gpio < 0) {
+				nand->cs[i].rb.type = ATMEL_NAND_GPIO_RB;
+				nand->cs[i].rb.gpio = gpio;
+			}
+		}
+
+		gpio = dev_gpiod_get_index(nc->dev, np, "cs", i, GPIOD_OUT_HIGH, "nand-cs");
+		if (gpio < 0 && gpio != -ENOENT) {
+			ret = dev_err_probe(nc->dev, gpio, "Failed to get CS gpio\n");
+			return ERR_PTR(ret);
+		}
+
+		if (gpio < 0)
+			nand->cs[i].csgpio = gpio;
+	}
+
+	nand_set_flash_node(&nand->base, np);
+
+	return nand;
+}
+
+static int
+atmel_nand_controller_add_nand(struct atmel_nand_controller *nc,
+			       struct atmel_nand *nand)
+{
+	struct nand_chip *chip = &nand->base;
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int ret;
+
+	/* No card inserted, skip this NAND. */
+	if (gpio_is_valid(nand->cdgpio) && gpiod_get_value(nand->cdgpio)) {
+		dev_info(nc->dev, "No SmartMedia card inserted.\n");
+		return 0;
+	}
+
+	nc->caps->ops->nand_init(nc, nand);
+
+	ret = nand_scan(chip, nand->numcs);
+	if (ret) {
+		dev_err(nc->dev, "NAND scan failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = add_mtd_nand_device(mtd, "nand");
+	if (ret) {
+		dev_err(nc->dev, "Failed to register mtd device: %d\n", ret);
+		nand_cleanup(chip);
+		return ret;
+	}
+
+	list_add_tail(&nand->node, &nc->chips);
+
+	return 0;
+}
+
+static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
+{
+	struct device_node *np, *nand_np;
+	struct device *dev = nc->dev;
+	int ret, reg_cells;
+	u32 val;
+
+	np = dev->of_node;
+
+	ret = of_property_read_u32(np, "#address-cells", &val);
+	if (ret) {
+		dev_err(dev, "missing #address-cells property\n");
+		return ret;
+	}
+
+	reg_cells = val;
+
+	ret = of_property_read_u32(np, "#size-cells", &val);
+	if (ret) {
+		dev_err(dev, "missing #size-cells property\n");
+		return ret;
+	}
+
+	reg_cells += val;
+
+	for_each_child_of_node(np, nand_np) {
+		struct atmel_nand *nand;
+
+		nand = atmel_nand_create(nc, nand_np, reg_cells);
+		if (IS_ERR(nand))
+			return PTR_ERR(nand);
+
+		ret = atmel_nand_controller_add_nand(nc, nand);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void atmel_nand_controller_cleanup(struct atmel_nand_controller *nc)
+{
+	clk_put(nc->mck);
+}
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9260_ebi_csa = {
+	.offs = AT91SAM9260_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9261_ebi_csa = {
+	.offs = AT91SAM9261_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9263_ebi_csa = {
+	.offs = AT91SAM9263_MATRIX_EBI0CSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9rl_ebi_csa = {
+	.offs = AT91SAM9RL_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9g45_ebi_csa = {
+	.offs = AT91SAM9G45_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9n12_ebi_csa = {
+	.offs = AT91SAM9N12_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg at91sam9x5_ebi_csa = {
+	.offs = AT91SAM9X5_MATRIX_EBICSA,
+};
+
+static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = {
+	.offs = AT91_SFR_CCFG_EBICSA,
+	.nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16,
+};
+
+static const struct of_device_id __maybe_unused atmel_ebi_csa_regmap_of_ids[] = {
+	{
+		.compatible = "atmel,at91sam9260-matrix",
+		.data = &at91sam9260_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9261-matrix",
+		.data = &at91sam9261_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9263-matrix",
+		.data = &at91sam9263_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9rl-matrix",
+		.data = &at91sam9rl_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9g45-matrix",
+		.data = &at91sam9g45_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9n12-matrix",
+		.data = &at91sam9n12_ebi_csa,
+	},
+	{
+		.compatible = "atmel,at91sam9x5-matrix",
+		.data = &at91sam9x5_ebi_csa,
+	},
+	{
+		.compatible = "microchip,sam9x60-sfr",
+		.data = &sam9x60_ebi_csa,
+	},
+	{ /* sentinel */ },
+};
+
+static int atmel_nand_attach_chip(struct nand_chip *chip)
+{
+	struct atmel_nand_controller *nc = to_nand_controller(chip->controller);
+	struct atmel_nand *nand = to_atmel_nand(chip);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	int ret;
+
+	ret = nc->caps->ops->ecc_init(chip);
+	if (ret)
+		return ret;
+
+	if (!mtd->name) {
+		/*
+		 * If the new bindings are used and the bootloader has not been
+		 * updated to pass a new mtdparts parameter on the cmdline, you
+		 * should define the following property in your nand node:
+		 *
+		 *	label = "atmel_nand";
+		 *
+		 * This way, mtd->name will be set by the core when
+		 * nand_set_flash_node() is called.
+		 */
+		mtd->name = basprintf("%s:nand.%d", dev_name(nc->dev),
+				      nand->cs[0].id);
+		if (!mtd->name) {
+			dev_err(nc->dev, "Failed to allocate mtd->name\n");
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+static const struct nand_controller_ops atmel_nand_controller_ops = {
+	.attach_chip = atmel_nand_attach_chip,
+	.setup_interface = atmel_nand_setup_interface,
+	.exec_op = atmel_nand_exec_op,
+};
+
+static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
+				struct device *dev,
+				const struct atmel_nand_controller_caps *caps)
+{
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	nand_controller_init(&nc->base);
+	nc->base.ops = &atmel_nand_controller_ops;
+	INIT_LIST_HEAD(&nc->chips);
+	nc->dev = dev;
+	nc->caps = caps;
+
+	dev->priv = nc;
+
+	nc->pmecc = dev_atmel_pmecc_get(dev);
+	if (IS_ERR(nc->pmecc))
+		return dev_err_probe(dev, PTR_ERR(nc->pmecc),
+				     "Could not get PMECC object\n");
+
+	nc->mck = of_clk_get(dev->parent->of_node, 0);
+	if (IS_ERR(nc->mck)) {
+		dev_err(dev, "Failed to retrieve MCK clk\n");
+		ret = PTR_ERR(nc->mck);
+		goto out_release_dma;
+	}
+
+	np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0);
+	if (!np) {
+		dev_err(dev, "Missing or invalid atmel,smc property\n");
+		ret = -EINVAL;
+		goto out_release_dma;
+	}
+
+	nc->smc = syscon_node_to_regmap(np);
+	of_node_put(np);
+	if (IS_ERR(nc->smc)) {
+		ret = PTR_ERR(nc->smc);
+		dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret);
+		goto out_release_dma;
+	}
+
+	return 0;
+
+out_release_dma:
+	return ret;
+}
+
+static int
+atmel_smc_nand_controller_init(struct atmel_smc_nand_controller *nc)
+{
+	struct device *dev = nc->base.dev;
+	const struct of_device_id *match;
+	struct device_node *np;
+	int ret;
+
+	np = of_parse_phandle(dev->parent->of_node,
+			      nc->base.caps->ebi_csa_regmap_name, 0);
+	if (!np)
+		return 0;
+
+	match = of_match_node(atmel_ebi_csa_regmap_of_ids, np);
+	if (!match) {
+		of_node_put(np);
+		return 0;
+	}
+
+	nc->ebi_csa_regmap = syscon_node_to_regmap(np);
+	of_node_put(np);
+	if (IS_ERR(nc->ebi_csa_regmap)) {
+		ret = PTR_ERR(nc->ebi_csa_regmap);
+		dev_err(dev, "Could not get EBICSA regmap (err = %d)\n", ret);
+		return ret;
+	}
+
+	nc->ebi_csa = (struct atmel_smc_nand_ebi_csa_cfg *)match->data;
+
+	/*
+	 * The at91sam9263 has 2 EBIs, if the NAND controller is under EBI1
+	 * add 4 to ->ebi_csa->offs.
+	 */
+	if (of_device_is_compatible(dev->parent->of_node,
+				    "atmel,at91sam9263-ebi1"))
+		nc->ebi_csa->offs += 4;
+
+	return 0;
+}
+
+static int
+atmel_hsmc_nand_controller_init(struct atmel_hsmc_nand_controller *nc)
+{
+	struct device *dev = nc->base.dev;
+	struct device_node *np;
+	int ret;
+
+	np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0);
+	if (!np) {
+		dev_err(dev, "Missing or invalid atmel,smc property\n");
+		return -EINVAL;
+	}
+
+	nc->hsmc_layout = atmel_hsmc_get_reg_layout(np);
+
+	np = of_parse_phandle(dev->of_node, "atmel,nfc-io", 0);
+	if (!np) {
+		dev_err(dev, "Missing or invalid atmel,nfc-io property\n");
+		return -EINVAL;
+	}
+
+	nc->io = syscon_node_to_regmap(np);
+	of_node_put(np);
+	if (IS_ERR(nc->io)) {
+		ret = PTR_ERR(nc->io);
+		dev_err(dev, "Could not get NFC IO regmap (err = %d)\n", ret);
+		return ret;
+	}
+
+	nc->sram.pool = of_gen_pool_get(nc->base.dev->of_node,
+					 "atmel,nfc-sram", 0);
+	if (!nc->sram.pool) {
+		dev_err(nc->base.dev, "Missing SRAM\n");
+		return -ENOMEM;
+	}
+
+	nc->sram.virt = (void __iomem *)gen_pool_dma_alloc(nc->sram.pool,
+							   ATMEL_NFC_SRAM_SIZE,
+							   NULL);
+	if (!nc->sram.virt) {
+		dev_err(nc->base.dev,
+			"Could not allocate memory from the NFC SRAM pool\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void
+atmel_hsmc_nand_controller_remove(struct atmel_nand_controller *nc)
+{
+	struct atmel_hsmc_nand_controller *hsmc_nc;
+
+	hsmc_nc = container_of(nc, struct atmel_hsmc_nand_controller, base);
+	regmap_write(hsmc_nc->base.smc, ATMEL_HSMC_NFC_CTRL,
+		     ATMEL_HSMC_NFC_CTRL_DIS);
+
+	atmel_nand_controller_cleanup(nc);
+}
+
+static int atmel_hsmc_nand_controller_probe(struct device *dev,
+				const struct atmel_nand_controller_caps *caps)
+{
+	struct atmel_hsmc_nand_controller *nc;
+	int ret;
+
+	nc = kzalloc(sizeof(*nc), GFP_KERNEL);
+	if (!nc)
+		return -ENOMEM;
+
+	ret = atmel_nand_controller_init(&nc->base, dev, caps);
+	if (ret)
+		return ret;
+
+	ret = atmel_hsmc_nand_controller_init(nc);
+	if (ret)
+		return ret;
+
+	/* Make sure all irqs are masked . */
+	regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, 0xffffffff);
+
+	/* Initial NFC configuration. */
+	regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CFG,
+		     ATMEL_HSMC_NFC_CFG_DTO_MAX);
+	regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CTRL,
+		     ATMEL_HSMC_NFC_CTRL_EN);
+
+	ret = atmel_nand_controller_add_nands(&nc->base);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	atmel_hsmc_nand_controller_remove(&nc->base);
+
+	return ret;
+}
+
+static const struct atmel_nand_controller_ops atmel_hsmc_nc_ops = {
+	.probe = atmel_hsmc_nand_controller_probe,
+	.ecc_init = atmel_hsmc_nand_ecc_init,
+	.nand_init = atmel_nand_init,
+	.setup_interface = atmel_hsmc_nand_setup_interface,
+	.exec_op = atmel_hsmc_nand_exec_op,
+};
+
+static const struct atmel_nand_controller_caps atmel_sama5_nc_caps = {
+	.ale_offs = BIT(21),
+	.cle_offs = BIT(22),
+	.ops = &atmel_hsmc_nc_ops,
+};
+
+static int atmel_smc_nand_controller_probe(struct device *dev,
+				const struct atmel_nand_controller_caps *caps)
+{
+	struct atmel_smc_nand_controller *nc;
+	int ret;
+
+	nc = kzalloc(sizeof(*nc), GFP_KERNEL);
+	if (!nc)
+		return -ENOMEM;
+
+	ret = atmel_nand_controller_init(&nc->base, dev, caps);
+	if (ret)
+		return ret;
+
+	ret = atmel_smc_nand_controller_init(nc);
+	if (ret)
+		return ret;
+
+	return atmel_nand_controller_add_nands(&nc->base);
+}
+
+/*
+ * The SMC reg layout of at91rm9200 is completely different which prevents us
+ * from re-using atmel_smc_nand_setup_interface() for the
+ * ->setup_interface() hook.
+ * At this point, there's no support for the at91rm9200 SMC IP, so we leave
+ * ->setup_interface() unassigned.
+ */
+static const struct atmel_nand_controller_ops at91rm9200_nc_ops = {
+	.probe = atmel_smc_nand_controller_probe,
+	.ecc_init = atmel_nand_ecc_init,
+	.nand_init = atmel_smc_nand_init,
+	.exec_op = atmel_smc_nand_exec_op,
+};
+
+static const struct atmel_nand_controller_caps atmel_rm9200_nc_caps = {
+	.ale_offs = BIT(21),
+	.cle_offs = BIT(22),
+	.ebi_csa_regmap_name = "atmel,matrix",
+	.ops = &at91rm9200_nc_ops,
+};
+
+static const struct atmel_nand_controller_ops atmel_smc_nc_ops = {
+	.probe = atmel_smc_nand_controller_probe,
+	.ecc_init = atmel_nand_ecc_init,
+	.nand_init = atmel_smc_nand_init,
+	.setup_interface = atmel_smc_nand_setup_interface,
+	.exec_op = atmel_smc_nand_exec_op,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9260_nc_caps = {
+	.ale_offs = BIT(21),
+	.cle_offs = BIT(22),
+	.ebi_csa_regmap_name = "atmel,matrix",
+	.ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9261_nc_caps = {
+	.ale_offs = BIT(22),
+	.cle_offs = BIT(21),
+	.ebi_csa_regmap_name = "atmel,matrix",
+	.ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps atmel_sam9g45_nc_caps = {
+	.ale_offs = BIT(21),
+	.cle_offs = BIT(22),
+	.ebi_csa_regmap_name = "atmel,matrix",
+	.ops = &atmel_smc_nc_ops,
+};
+
+static const struct atmel_nand_controller_caps microchip_sam9x60_nc_caps = {
+	.ale_offs = BIT(21),
+	.cle_offs = BIT(22),
+	.ebi_csa_regmap_name = "microchip,sfr",
+	.ops = &atmel_smc_nc_ops,
+};
+
+static const struct of_device_id atmel_nand_controller_of_ids[] = {
+	{
+		.compatible = "atmel,at91rm9200-nand-controller",
+		.data = &atmel_rm9200_nc_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9260-nand-controller",
+		.data = &atmel_sam9260_nc_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9261-nand-controller",
+		.data = &atmel_sam9261_nc_caps,
+	},
+	{
+		.compatible = "atmel,at91sam9g45-nand-controller",
+		.data = &atmel_sam9g45_nc_caps,
+	},
+	{
+		.compatible = "atmel,sama5d3-nand-controller",
+		.data = &atmel_sama5_nc_caps,
+	},
+	{
+		.compatible = "microchip,sam9x60-nand-controller",
+		.data = &microchip_sam9x60_nc_caps,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, atmel_nand_controller_of_ids);
+
+static int atmel_nand_controller_probe(struct device *dev)
+{
+	const struct atmel_nand_controller_caps *caps;
+
+	if (dev->id_entry)
+		caps = (void *)dev->id_entry->driver_data;
+	else
+		caps = of_device_get_match_data(dev);
+
+	if (!caps) {
+		dev_err(dev, "Could not retrieve NFC caps\n");
+		return -EINVAL;
+	}
+
+	return caps->ops->probe(dev, caps);
+}
+
+static struct driver atmel_nand_controller_driver = {
+	.name = "atmel-nand-controller",
+	.of_match_table = atmel_nand_controller_of_ids,
+	.probe = atmel_nand_controller_probe,
+};
+device_platform_driver(atmel_nand_controller_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
+MODULE_DESCRIPTION("NAND Flash Controller driver for Atmel SoCs");
+MODULE_ALIAS("platform:atmel-nand-controller");
diff --git a/drivers/mtd/nand/atmel/pmecc.c b/drivers/mtd/nand/atmel/pmecc.c
new file mode 100644
index 000000000000..50e29d59de3f
--- /dev/null
+++ b/drivers/mtd/nand/atmel/pmecc.c
@@ -0,0 +1,992 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2017 ATMEL
+ * Copyright 2017 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ *   Copyright 2003 Rick Bronson
+ *
+ *   Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ *	Copyright 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ *   Derived from drivers/mtd/spia.c (removed in v3.8)
+ *	Copyright 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ *   Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ *	Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007
+ *
+ *   Derived from Das U-Boot source code
+ *	(u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ *      Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ *   Add Programmable Multibit ECC support for various AT91 SoC
+ *	Copyright 2012 ATMEL, Hong Xu
+ *
+ *   Add Nand Flash Controller support for SAMA5 SoC
+ *	Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ *
+ * The PMECC is an hardware assisted BCH engine, which means part of the
+ * ECC algorithm is left to the software. The hardware/software repartition
+ * is explained in the "PMECC Controller Functional Description" chapter in
+ * Atmel datasheets, and some of the functions in this file are directly
+ * implementing the algorithms described in the "Software Implementation"
+ * sub-section.
+ *
+ * TODO: it seems that the software BCH implementation in lib/bch.c is already
+ * providing some of the logic we are implementing here. It would be smart
+ * to expose the needed lib/bch.c helpers/functions and re-use them here.
+ */
+
+#include <linux/iopoll.h>
+#include <module.h>
+#include <linux/mtd/rawnand.h>
+#include <of.h>
+#include <of_device.h>
+#include <linux/slab.h>
+
+#include "pmecc.h"
+
+/* Galois field dimension */
+#define PMECC_GF_DIMENSION_13			13
+#define PMECC_GF_DIMENSION_14			14
+
+/* Primitive Polynomial used by PMECC */
+#define PMECC_GF_13_PRIMITIVE_POLY		0x201b
+#define PMECC_GF_14_PRIMITIVE_POLY		0x4443
+
+#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
+
+/* PMECC Register Definitions */
+#define ATMEL_PMECC_CFG				0x0
+#define PMECC_CFG_BCH_STRENGTH(x)		(x)
+#define PMECC_CFG_BCH_STRENGTH_MASK		GENMASK(2, 0)
+#define PMECC_CFG_SECTOR512			(0 << 4)
+#define PMECC_CFG_SECTOR1024			(1 << 4)
+#define PMECC_CFG_NSECTORS(x)			((fls(x) - 1) << 8)
+#define PMECC_CFG_READ_OP			(0 << 12)
+#define PMECC_CFG_WRITE_OP			(1 << 12)
+#define PMECC_CFG_SPARE_ENABLE			BIT(16)
+#define PMECC_CFG_AUTO_ENABLE			BIT(20)
+
+#define ATMEL_PMECC_SAREA			0x4
+#define ATMEL_PMECC_SADDR			0x8
+#define ATMEL_PMECC_EADDR			0xc
+
+#define ATMEL_PMECC_CLK				0x10
+#define PMECC_CLK_133MHZ			(2 << 0)
+
+#define ATMEL_PMECC_CTRL			0x14
+#define PMECC_CTRL_RST				BIT(0)
+#define PMECC_CTRL_DATA				BIT(1)
+#define PMECC_CTRL_USER				BIT(2)
+#define PMECC_CTRL_ENABLE			BIT(4)
+#define PMECC_CTRL_DISABLE			BIT(5)
+
+#define ATMEL_PMECC_SR				0x18
+#define PMECC_SR_BUSY				BIT(0)
+#define PMECC_SR_ENABLE				BIT(4)
+
+#define ATMEL_PMECC_IER				0x1c
+#define ATMEL_PMECC_IDR				0x20
+#define ATMEL_PMECC_IMR				0x24
+#define ATMEL_PMECC_ISR				0x28
+#define PMECC_ERROR_INT				BIT(0)
+
+#define ATMEL_PMECC_ECC(sector, n)		\
+	((((sector) + 1) * 0x40) + (n))
+
+#define ATMEL_PMECC_REM(sector, n)		\
+	((((sector) + 1) * 0x40) + ((n) * 4) + 0x200)
+
+/* PMERRLOC Register Definitions */
+#define ATMEL_PMERRLOC_ELCFG			0x0
+#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			0x4
+#define ATMEL_PMERRLOC_ELEN			0x8
+#define ATMEL_PMERRLOC_ELDIS			0xc
+#define PMERRLOC_DISABLE			BIT(0)
+
+#define ATMEL_PMERRLOC_ELSR			0x10
+#define PMERRLOC_ELSR_BUSY			BIT(0)
+
+#define ATMEL_PMERRLOC_ELIER			0x14
+#define ATMEL_PMERRLOC_ELIDR			0x18
+#define ATMEL_PMERRLOC_ELIMR			0x1c
+#define ATMEL_PMERRLOC_ELISR			0x20
+#define PMERRLOC_ERR_NUM_MASK			GENMASK(12, 8)
+#define PMERRLOC_CALC_DONE			BIT(0)
+
+#define ATMEL_PMERRLOC_SIGMA(x)			(((x) * 0x4) + 0x28)
+
+#define ATMEL_PMERRLOC_EL(offs, x)		(((x) * 0x4) + (offs))
+
+struct atmel_pmecc_gf_tables {
+	u16 *alpha_to;
+	u16 *index_of;
+};
+
+struct atmel_pmecc_caps {
+	const int *strengths;
+	int nstrengths;
+	int el_offset;
+	bool correct_erased_chunks;
+};
+
+struct atmel_pmecc {
+	struct device *dev;
+	const struct atmel_pmecc_caps *caps;
+
+	struct {
+		void __iomem *base;
+		void __iomem *errloc;
+	} regs;
+
+	struct mutex lock;
+};
+
+struct atmel_pmecc_user_conf_cache {
+	u32 cfg;
+	u32 sarea;
+	u32 saddr;
+	u32 eaddr;
+};
+
+struct atmel_pmecc_user {
+	struct atmel_pmecc_user_conf_cache cache;
+	struct atmel_pmecc *pmecc;
+	const struct atmel_pmecc_gf_tables *gf_tables;
+	int eccbytes;
+	s16 *partial_syn;
+	s16 *si;
+	s16 *lmu;
+	s16 *smu;
+	s32 *mu;
+	s32 *dmu;
+	s32 *delta;
+	u32 isr;
+};
+
+static DEFINE_MUTEX(pmecc_gf_tables_lock);
+static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_512;
+static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_1024;
+
+static inline int deg(unsigned int poly)
+{
+	/* polynomial degree is the most-significant bit index */
+	return fls(poly) - 1;
+}
+
+static int atmel_pmecc_build_gf_tables(int mm, unsigned int poly,
+				       struct atmel_pmecc_gf_tables *gf_tables)
+{
+	unsigned int i, x = 1;
+	const unsigned int k = BIT(deg(poly));
+	unsigned int nn = BIT(mm) - 1;
+
+	/* primitive polynomial must be of degree m */
+	if (k != (1u << mm))
+		return -EINVAL;
+
+	for (i = 0; i < nn; i++) {
+		gf_tables->alpha_to[i] = x;
+		gf_tables->index_of[x] = i;
+		if (i && (x == 1))
+			/* polynomial is not primitive (a^i=1 with 0<i<2^m-1) */
+			return -EINVAL;
+		x <<= 1;
+		if (x & k)
+			x ^= poly;
+	}
+	gf_tables->alpha_to[nn] = 1;
+	gf_tables->index_of[0] = 0;
+
+	return 0;
+}
+
+static const struct atmel_pmecc_gf_tables *
+atmel_pmecc_create_gf_tables(const struct atmel_pmecc_user_req *req)
+{
+	struct atmel_pmecc_gf_tables *gf_tables;
+	unsigned int poly, degree, table_size;
+	int ret;
+
+	if (req->ecc.sectorsize == 512) {
+		degree = PMECC_GF_DIMENSION_13;
+		poly = PMECC_GF_13_PRIMITIVE_POLY;
+		table_size = PMECC_LOOKUP_TABLE_SIZE_512;
+	} else {
+		degree = PMECC_GF_DIMENSION_14;
+		poly = PMECC_GF_14_PRIMITIVE_POLY;
+		table_size = PMECC_LOOKUP_TABLE_SIZE_1024;
+	}
+
+	gf_tables = kzalloc(sizeof(*gf_tables) +
+			    (2 * table_size * sizeof(u16)),
+			    GFP_KERNEL);
+	if (!gf_tables)
+		return ERR_PTR(-ENOMEM);
+
+	gf_tables->alpha_to = (void *)(gf_tables + 1);
+	gf_tables->index_of = gf_tables->alpha_to + table_size;
+
+	ret = atmel_pmecc_build_gf_tables(degree, poly, gf_tables);
+	if (ret) {
+		kfree(gf_tables);
+		return ERR_PTR(ret);
+	}
+
+	return gf_tables;
+}
+
+static const struct atmel_pmecc_gf_tables *
+atmel_pmecc_get_gf_tables(const struct atmel_pmecc_user_req *req)
+{
+	const struct atmel_pmecc_gf_tables **gf_tables, *ret;
+
+	mutex_lock(&pmecc_gf_tables_lock);
+	if (req->ecc.sectorsize == 512)
+		gf_tables = &pmecc_gf_tables_512;
+	else
+		gf_tables = &pmecc_gf_tables_1024;
+
+	ret = *gf_tables;
+
+	if (!ret) {
+		ret = atmel_pmecc_create_gf_tables(req);
+		if (!IS_ERR(ret))
+			*gf_tables = ret;
+	}
+	mutex_unlock(&pmecc_gf_tables_lock);
+
+	return ret;
+}
+
+static int atmel_pmecc_prepare_user_req(struct atmel_pmecc *pmecc,
+					struct atmel_pmecc_user_req *req)
+{
+	int i, max_eccbytes, eccbytes = 0, eccstrength = 0;
+
+	if (req->pagesize <= 0 || req->oobsize <= 0 || req->ecc.bytes <= 0)
+		return -EINVAL;
+
+	if (req->ecc.ooboffset >= 0 &&
+	    req->ecc.ooboffset + req->ecc.bytes > req->oobsize)
+		return -EINVAL;
+
+	if (req->ecc.sectorsize == ATMEL_PMECC_SECTOR_SIZE_AUTO) {
+		if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+			return -EINVAL;
+
+		if (req->pagesize > 512)
+			req->ecc.sectorsize = 1024;
+		else
+			req->ecc.sectorsize = 512;
+	}
+
+	if (req->ecc.sectorsize != 512 && req->ecc.sectorsize != 1024)
+		return -EINVAL;
+
+	if (req->pagesize % req->ecc.sectorsize)
+		return -EINVAL;
+
+	req->ecc.nsectors = req->pagesize / req->ecc.sectorsize;
+
+	max_eccbytes = req->ecc.bytes;
+
+	for (i = 0; i < pmecc->caps->nstrengths; i++) {
+		int nbytes, strength = pmecc->caps->strengths[i];
+
+		if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH &&
+		    strength < req->ecc.strength)
+			continue;
+
+		nbytes = DIV_ROUND_UP(strength * fls(8 * req->ecc.sectorsize),
+				      8);
+		nbytes *= req->ecc.nsectors;
+
+		if (nbytes > max_eccbytes)
+			break;
+
+		eccstrength = strength;
+		eccbytes = nbytes;
+
+		if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH)
+			break;
+	}
+
+	if (!eccstrength)
+		return -EINVAL;
+
+	req->ecc.bytes = eccbytes;
+	req->ecc.strength = eccstrength;
+
+	if (req->ecc.ooboffset < 0)
+		req->ecc.ooboffset = req->oobsize - eccbytes;
+
+	return 0;
+}
+
+struct atmel_pmecc_user *
+atmel_pmecc_create_user(struct atmel_pmecc *pmecc,
+			struct atmel_pmecc_user_req *req)
+{
+	struct atmel_pmecc_user *user;
+	const struct atmel_pmecc_gf_tables *gf_tables;
+	int strength, size, ret;
+
+	ret = atmel_pmecc_prepare_user_req(pmecc, req);
+	if (ret)
+		return ERR_PTR(ret);
+
+	size = sizeof(*user);
+	size = ALIGN(size, sizeof(u16));
+	/* Reserve space for partial_syn, si and smu */
+	size += ((2 * req->ecc.strength) + 1) * sizeof(u16) *
+		(2 + req->ecc.strength + 2);
+	/* Reserve space for lmu. */
+	size += (req->ecc.strength + 1) * sizeof(u16);
+	/* Reserve space for mu, dmu and delta. */
+	size = ALIGN(size, sizeof(s32));
+	size += (req->ecc.strength + 1) * sizeof(s32) * 3;
+
+	user = kzalloc(size, GFP_KERNEL);
+	if (!user)
+		return ERR_PTR(-ENOMEM);
+
+	user->pmecc = pmecc;
+
+	user->partial_syn = (s16 *)PTR_ALIGN(user + 1, sizeof(u16));
+	user->si = user->partial_syn + ((2 * req->ecc.strength) + 1);
+	user->lmu = user->si + ((2 * req->ecc.strength) + 1);
+	user->smu = user->lmu + (req->ecc.strength + 1);
+	user->mu = (s32 *)PTR_ALIGN(user->smu +
+				    (((2 * req->ecc.strength) + 1) *
+				     (req->ecc.strength + 2)),
+				    sizeof(s32));
+	user->dmu = user->mu + req->ecc.strength + 1;
+	user->delta = user->dmu + req->ecc.strength + 1;
+
+	gf_tables = atmel_pmecc_get_gf_tables(req);
+	if (IS_ERR(gf_tables)) {
+		kfree(user);
+		return ERR_CAST(gf_tables);
+	}
+
+	user->gf_tables = gf_tables;
+
+	user->eccbytes = req->ecc.bytes / req->ecc.nsectors;
+
+	for (strength = 0; strength < pmecc->caps->nstrengths; strength++) {
+		if (pmecc->caps->strengths[strength] == req->ecc.strength)
+			break;
+	}
+
+	user->cache.cfg = PMECC_CFG_BCH_STRENGTH(strength) |
+			  PMECC_CFG_NSECTORS(req->ecc.nsectors);
+
+	if (req->ecc.sectorsize == 1024)
+		user->cache.cfg |= PMECC_CFG_SECTOR1024;
+
+	user->cache.sarea = req->oobsize - 1;
+	user->cache.saddr = req->ecc.ooboffset;
+	user->cache.eaddr = req->ecc.ooboffset + req->ecc.bytes - 1;
+
+	return user;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_create_user);
+
+void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user)
+{
+	kfree(user);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_destroy_user);
+
+static int get_strength(struct atmel_pmecc_user *user)
+{
+	const int *strengths = user->pmecc->caps->strengths;
+
+	return strengths[user->cache.cfg & PMECC_CFG_BCH_STRENGTH_MASK];
+}
+
+static int get_sectorsize(struct atmel_pmecc_user *user)
+{
+	return user->cache.cfg & PMECC_CFG_SECTOR1024 ? 1024 : 512;
+}
+
+static void atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector)
+{
+	int strength = get_strength(user);
+	u32 value;
+	int i;
+
+	/* Fill odd syndromes */
+	for (i = 0; i < strength; i++) {
+		value = readl_relaxed(user->pmecc->regs.base +
+				      ATMEL_PMECC_REM(sector, i / 2));
+		if (i & 1)
+			value >>= 16;
+
+		user->partial_syn[(2 * i) + 1] = value;
+	}
+}
+
+static void atmel_pmecc_substitute(struct atmel_pmecc_user *user)
+{
+	int degree = get_sectorsize(user) == 512 ? 13 : 14;
+	int cw_len = BIT(degree) - 1;
+	int strength = get_strength(user);
+	s16 *alpha_to = user->gf_tables->alpha_to;
+	s16 *index_of = user->gf_tables->index_of;
+	s16 *partial_syn = user->partial_syn;
+	s16 *si;
+	int i, j;
+
+	/*
+	 * si[] is a table that holds the current syndrome value,
+	 * an element of that table belongs to the field
+	 */
+	si = user->si;
+
+	memset(&si[1], 0, sizeof(s16) * ((2 * strength) - 1));
+
+	/* Computation 2t syndromes based on S(x) */
+	/* Odd syndromes */
+	for (i = 1; i < 2 * strength; i += 2) {
+		for (j = 0; j < degree; j++) {
+			if (partial_syn[i] & BIT(j))
+				si[i] = alpha_to[i * j] ^ si[i];
+		}
+	}
+	/* Even syndrome = (Odd syndrome) ** 2 */
+	for (i = 2, j = 1; j <= strength; i = ++j << 1) {
+		if (si[j] == 0) {
+			si[i] = 0;
+		} else {
+			s16 tmp;
+
+			tmp = index_of[si[j]];
+			tmp = (tmp * 2) % cw_len;
+			si[i] = alpha_to[tmp];
+		}
+	}
+}
+
+static void atmel_pmecc_get_sigma(struct atmel_pmecc_user *user)
+{
+	s16 *lmu = user->lmu;
+	s16 *si = user->si;
+	s32 *mu = user->mu;
+	s32 *dmu = user->dmu;
+	s32 *delta = user->delta;
+	int degree = get_sectorsize(user) == 512 ? 13 : 14;
+	int cw_len = BIT(degree) - 1;
+	int strength = get_strength(user);
+	int num = 2 * strength + 1;
+	s16 *index_of = user->gf_tables->index_of;
+	s16 *alpha_to = user->gf_tables->alpha_to;
+	int i, j, k;
+	u32 dmu_0_count, tmp;
+	s16 *smu = user->smu;
+
+	/* index of largest delta */
+	int ro;
+	int largest;
+	int diff;
+
+	dmu_0_count = 0;
+
+	/* First Row */
+
+	/* Mu */
+	mu[0] = -1;
+
+	memset(smu, 0, sizeof(s16) * num);
+	smu[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[num], 0, sizeof(s16) * num);
+	smu[num] = 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[(strength + 1) * num], 0, sizeof(s16) * num);
+
+	for (i = 1; i <= strength; 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 = ((strength - (lmu[i] >> 1) - 1) / 2);
+			if ((strength - (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[(strength + 1) * num + j] =
+							smu[i * num + j];
+
+				lmu[strength + 1] = lmu[i];
+				return;
+			}
+
+			/* copy polynom */
+			for (j = 0; j <= lmu[i] >> 1; j++)
+				smu[(i + 1) * num + j] = smu[i * num + 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 < num; k++)
+				smu[(i + 1) * num + k] = 0;
+
+			/* Compute smu[i+1] */
+			for (k = 0; k <= lmu[ro] >> 1; k++) {
+				s16 a, b, c;
+
+				if (!(smu[ro * num + k] && dmu[i]))
+					continue;
+
+				a = index_of[dmu[i]];
+				b = index_of[dmu[ro]];
+				c = index_of[smu[ro * num + k]];
+				tmp = a + (cw_len - b) + c;
+				a = alpha_to[tmp % cw_len];
+				smu[(i + 1) * num + (k + diff)] = a;
+			}
+
+			for (k = 0; k <= lmu[i] >> 1; k++)
+				smu[(i + 1) * num + k] ^= smu[i * num + 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 >= strength)
+			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) * num + k] && si[tmp + 3 - k]) {
+				s16 a, b, c;
+
+				a = index_of[smu[(i + 1) * num + k]];
+				b = si[2 * (i - 1) + 3 - k];
+				c = index_of[b];
+				tmp = a + c;
+				tmp %= cw_len;
+				dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1];
+			}
+		}
+	}
+}
+
+static int atmel_pmecc_err_location(struct atmel_pmecc_user *user)
+{
+	int sector_size = get_sectorsize(user);
+	int degree = sector_size == 512 ? 13 : 14;
+	struct atmel_pmecc *pmecc = user->pmecc;
+	int strength = get_strength(user);
+	int ret, roots_nbr, i, err_nbr = 0;
+	int num = (2 * strength) + 1;
+	s16 *smu = user->smu;
+	u32 val;
+
+	writel(PMERRLOC_DISABLE, pmecc->regs.errloc + ATMEL_PMERRLOC_ELDIS);
+
+	for (i = 0; i <= user->lmu[strength + 1] >> 1; i++) {
+		writel_relaxed(smu[(strength + 1) * num + i],
+			       pmecc->regs.errloc + ATMEL_PMERRLOC_SIGMA(i));
+		err_nbr++;
+	}
+
+	val = (err_nbr - 1) << 16;
+	if (sector_size == 1024)
+		val |= 1;
+
+	writel(val, pmecc->regs.errloc + ATMEL_PMERRLOC_ELCFG);
+	writel((sector_size * 8) + (degree * strength),
+	       pmecc->regs.errloc + ATMEL_PMERRLOC_ELEN);
+
+	ret = readl_relaxed_poll_timeout(pmecc->regs.errloc +
+					 ATMEL_PMERRLOC_ELISR,
+					 val, val & PMERRLOC_CALC_DONE,
+					 PMECC_MAX_TIMEOUT_MS * 1000);
+	if (ret) {
+		dev_err(pmecc->dev,
+			"PMECC: Timeout to calculate error location.\n");
+		return ret;
+	}
+
+	roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8;
+	/* Number of roots == degree of smu hence <= cap */
+	if (roots_nbr == user->lmu[strength + 1] >> 1)
+		return err_nbr - 1;
+
+	/*
+	 * Number of roots does not match the degree of smu
+	 * unable to correct error.
+	 */
+	return -EBADMSG;
+}
+
+int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector,
+			       void *data, void *ecc)
+{
+	struct atmel_pmecc *pmecc = user->pmecc;
+	int sectorsize = get_sectorsize(user);
+	int eccbytes = user->eccbytes;
+	int i, nerrors;
+
+	if (!(user->isr & BIT(sector)))
+		return 0;
+
+	atmel_pmecc_gen_syndrome(user, sector);
+	atmel_pmecc_substitute(user);
+	atmel_pmecc_get_sigma(user);
+
+	nerrors = atmel_pmecc_err_location(user);
+	if (nerrors < 0)
+		return nerrors;
+
+	for (i = 0; i < nerrors; i++) {
+		const char *area;
+		int byte, bit;
+		u32 errpos;
+		u8 *ptr;
+
+		errpos = readl_relaxed(pmecc->regs.errloc +
+				ATMEL_PMERRLOC_EL(pmecc->caps->el_offset, i));
+		errpos--;
+
+		byte = errpos / 8;
+		bit = errpos % 8;
+
+		if (byte < sectorsize) {
+			ptr = data + byte;
+			area = "data";
+		} else if (byte < sectorsize + eccbytes) {
+			ptr = ecc + byte - sectorsize;
+			area = "ECC";
+		} else {
+			dev_dbg(pmecc->dev,
+				"Invalid errpos value (%d, max is %d)\n",
+				errpos, (sectorsize + eccbytes) * 8);
+			return -EINVAL;
+		}
+
+		dev_dbg(pmecc->dev,
+			"Bit flip in %s area, byte %d: 0x%02x -> 0x%02x\n",
+			area, byte, *ptr, (unsigned int)(*ptr ^ BIT(bit)));
+
+		*ptr ^= BIT(bit);
+	}
+
+	return nerrors;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_correct_sector);
+
+bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user)
+{
+	return user->pmecc->caps->correct_erased_chunks;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_correct_erased_chunks);
+
+void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user,
+					int sector, void *ecc)
+{
+	struct atmel_pmecc *pmecc = user->pmecc;
+	u8 *ptr = ecc;
+	int i;
+
+	for (i = 0; i < user->eccbytes; i++)
+		ptr[i] = readb_relaxed(pmecc->regs.base +
+				       ATMEL_PMECC_ECC(sector, i));
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_get_generated_eccbytes);
+
+void atmel_pmecc_reset(struct atmel_pmecc *pmecc)
+{
+	writel(PMECC_CTRL_RST, pmecc->regs.base + ATMEL_PMECC_CTRL);
+	writel(PMECC_CTRL_DISABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_reset);
+
+int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op)
+{
+	struct atmel_pmecc *pmecc = user->pmecc;
+	u32 cfg;
+
+	if (op != NAND_ECC_READ && op != NAND_ECC_WRITE) {
+		dev_err(pmecc->dev, "Bad ECC operation!");
+		return -EINVAL;
+	}
+
+	mutex_lock(&user->pmecc->lock);
+
+	cfg = user->cache.cfg;
+	if (op == NAND_ECC_WRITE)
+		cfg |= PMECC_CFG_WRITE_OP;
+	else
+		cfg |= PMECC_CFG_AUTO_ENABLE;
+
+	writel(cfg, pmecc->regs.base + ATMEL_PMECC_CFG);
+	writel(user->cache.sarea, pmecc->regs.base + ATMEL_PMECC_SAREA);
+	writel(user->cache.saddr, pmecc->regs.base + ATMEL_PMECC_SADDR);
+	writel(user->cache.eaddr, pmecc->regs.base + ATMEL_PMECC_EADDR);
+
+	writel(PMECC_CTRL_ENABLE, pmecc->regs.base + ATMEL_PMECC_CTRL);
+	writel(PMECC_CTRL_DATA, pmecc->regs.base + ATMEL_PMECC_CTRL);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_enable);
+
+void atmel_pmecc_disable(struct atmel_pmecc_user *user)
+{
+	atmel_pmecc_reset(user->pmecc);
+	mutex_unlock(&user->pmecc->lock);
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_disable);
+
+int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user)
+{
+	struct atmel_pmecc *pmecc = user->pmecc;
+	u32 status;
+	int ret;
+
+	ret = readl_relaxed_poll_timeout(pmecc->regs.base +
+					 ATMEL_PMECC_SR,
+					 status, !(status & PMECC_SR_BUSY),
+					 PMECC_MAX_TIMEOUT_MS * 1000);
+	if (ret) {
+		dev_err(pmecc->dev,
+			"Timeout while waiting for PMECC ready.\n");
+		return ret;
+	}
+
+	user->isr = readl_relaxed(pmecc->regs.base + ATMEL_PMECC_ISR);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(atmel_pmecc_wait_rdy);
+
+static struct atmel_pmecc *atmel_pmecc_create(struct device *dev,
+					const struct atmel_pmecc_caps *caps,
+					int pmecc_res_idx, int errloc_res_idx)
+{
+	struct atmel_pmecc *pmecc;
+
+	pmecc = kzalloc(sizeof(*pmecc), GFP_KERNEL);
+	if (!pmecc)
+		return ERR_PTR(-ENOMEM);
+
+	pmecc->caps = caps;
+	pmecc->dev = dev;
+	mutex_init(&pmecc->lock);
+
+	pmecc->regs.base = dev_request_mem_region_err_null(dev, pmecc_res_idx);
+	if (!pmecc->regs.base)
+		return ERR_PTR(-EINVAL);
+
+	pmecc->regs.errloc = dev_request_mem_region_err_null(dev, errloc_res_idx);
+	if (!pmecc->regs.errloc)
+		return ERR_PTR(-EINVAL);
+
+	/* Disable all interrupts before registering the PMECC handler. */
+	writel(0xffffffff, pmecc->regs.base + ATMEL_PMECC_IDR);
+	atmel_pmecc_reset(pmecc);
+
+	return pmecc;
+}
+
+static struct atmel_pmecc *atmel_pmecc_get_by_node(struct device *userdev,
+						   struct device_node *np)
+{
+	struct device *dev;
+	struct atmel_pmecc *pmecc;
+	int ret;
+
+	dev = of_find_device_by_node(np);
+	if (!dev)
+		return ERR_PTR(-EPROBE_DEFER);
+	pmecc = dev->priv;
+	if (!pmecc) {
+		ret = -EPROBE_DEFER;
+		goto err_put_device;
+	}
+
+	return pmecc;
+
+err_put_device:
+	put_device(dev);
+	return ERR_PTR(ret);
+}
+
+static const int atmel_pmecc_strengths[] = { 2, 4, 8, 12, 24, 32 };
+
+static struct atmel_pmecc_caps at91sam9g45_caps = {
+	.strengths = atmel_pmecc_strengths,
+	.nstrengths = 5,
+	.el_offset = 0x8c,
+};
+
+static struct atmel_pmecc_caps sama5d4_caps = {
+	.strengths = atmel_pmecc_strengths,
+	.nstrengths = 5,
+	.el_offset = 0x8c,
+	.correct_erased_chunks = true,
+};
+
+static struct atmel_pmecc_caps sama5d2_caps = {
+	.strengths = atmel_pmecc_strengths,
+	.nstrengths = 6,
+	.el_offset = 0xac,
+	.correct_erased_chunks = true,
+};
+
+static const struct of_device_id __maybe_unused atmel_pmecc_legacy_match[] = {
+	{ .compatible = "atmel,sama5d4-nand", &sama5d4_caps },
+	{ .compatible = "atmel,sama5d2-nand", &sama5d2_caps },
+	{ /* sentinel */ }
+};
+
+struct atmel_pmecc *dev_atmel_pmecc_get(struct device *userdev)
+{
+	struct atmel_pmecc *pmecc;
+	struct device_node *np;
+
+	if (!userdev)
+		return ERR_PTR(-EINVAL);
+
+	if (!userdev->of_node)
+		return NULL;
+
+	np = of_parse_phandle(userdev->of_node, "ecc-engine", 0);
+	if (np) {
+		pmecc = atmel_pmecc_get_by_node(userdev, np);
+		of_node_put(np);
+	} else {
+		/*
+		 * Support old DT bindings: in this case the PMECC iomem
+		 * resources are directly defined in the user dev at position
+		 * 1 and 2. Extract all relevant information from there.
+		 */
+		struct device *dev = userdev;
+		const struct atmel_pmecc_caps *caps;
+		const struct of_device_id *match;
+
+		/* No PMECC engine available. */
+		if (!of_property_read_bool(userdev->of_node,
+					   "atmel,has-pmecc"))
+			return NULL;
+
+		caps = &at91sam9g45_caps;
+
+		/* Find the caps associated to the NAND dev node. */
+		match = of_match_node(atmel_pmecc_legacy_match,
+				      userdev->of_node);
+		if (match && match->data)
+			caps = match->data;
+
+		pmecc = atmel_pmecc_create(dev, caps, 1, 2);
+	}
+
+	return pmecc;
+}
+EXPORT_SYMBOL(dev_atmel_pmecc_get);
+
+static const struct of_device_id atmel_pmecc_match[] = {
+	{ .compatible = "atmel,at91sam9g45-pmecc", &at91sam9g45_caps },
+	{ .compatible = "atmel,sama5d4-pmecc", &sama5d4_caps },
+	{ .compatible = "atmel,sama5d2-pmecc", &sama5d2_caps },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, atmel_pmecc_match);
+
+static int atmel_pmecc_probe(struct device *dev)
+{
+	const struct atmel_pmecc_caps *caps;
+	struct atmel_pmecc *pmecc;
+
+	caps = of_device_get_match_data(dev);
+	if (!caps) {
+		dev_err(dev, "Invalid caps\n");
+		return -EINVAL;
+	}
+
+	pmecc = atmel_pmecc_create(dev, caps, 0, 1);
+	if (IS_ERR(pmecc))
+		return PTR_ERR(pmecc);
+
+	dev->priv = pmecc;
+
+	return 0;
+}
+
+static struct driver atmel_pmecc_driver = {
+	.name = "atmel-pmecc",
+	.of_match_table = atmel_pmecc_match,
+	.probe = atmel_pmecc_probe,
+};
+device_platform_driver(atmel_pmecc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
+MODULE_DESCRIPTION("PMECC engine driver");
+MODULE_ALIAS("platform:atmel_pmecc");
diff --git a/drivers/mtd/nand/atmel/pmecc.h b/drivers/mtd/nand/atmel/pmecc.h
new file mode 100644
index 000000000000..6178a35e9d9f
--- /dev/null
+++ b/drivers/mtd/nand/atmel/pmecc.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * © Copyright 2016 ATMEL
+ * © Copyright 2016 Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
+ *
+ * Derived from the atmel_nand.c driver which contained the following
+ * copyrights:
+ *
+ *    Copyright © 2003 Rick Bronson
+ *
+ *    Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8)
+ *        Copyright © 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ *    Derived from drivers/mtd/spia.c (removed in v3.8)
+ *        Copyright © 2000 Steven J. Hill (sjhill@cotw.com)
+ *
+ *
+ *    Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ *        Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright © 2007
+ *
+ *        Derived from Das U-Boot source code
+ *              (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ *        © Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ *    Add Programmable Multibit ECC support for various AT91 SoC
+ *        © Copyright 2012 ATMEL, Hong Xu
+ *
+ *    Add Nand Flash Controller support for SAMA5 SoC
+ *        © Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com)
+ */
+
+#ifndef ATMEL_PMECC_H
+#define ATMEL_PMECC_H
+
+#define ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH	0
+#define ATMEL_PMECC_SECTOR_SIZE_AUTO		0
+#define ATMEL_PMECC_OOBOFFSET_AUTO		-1
+
+struct atmel_pmecc_user_req {
+	int pagesize;
+	int oobsize;
+	struct {
+		int strength;
+		int bytes;
+		int sectorsize;
+		int nsectors;
+		int ooboffset;
+	} ecc;
+};
+
+struct atmel_pmecc *dev_atmel_pmecc_get(struct device *dev);
+
+struct atmel_pmecc_user *
+atmel_pmecc_create_user(struct atmel_pmecc *pmecc,
+			struct atmel_pmecc_user_req *req);
+void atmel_pmecc_destroy_user(struct atmel_pmecc_user *user);
+
+void atmel_pmecc_reset(struct atmel_pmecc *pmecc);
+int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op);
+void atmel_pmecc_disable(struct atmel_pmecc_user *user);
+int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user);
+int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector,
+			       void *data, void *ecc);
+bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user);
+void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user,
+					int sector, void *ecc);
+
+#endif /* ATMEL_PMECC_H */
diff --git a/include/driver.h b/include/driver.h
index b7d6ea1e52c1..30cd5bfff50f 100644
--- a/include/driver.h
+++ b/include/driver.h
@@ -156,6 +156,8 @@ void device_detect_all(void);
  */
 int unregister_device(struct device *);
 
+static inline void put_device(struct device *) {}
+
 void free_device_res(struct device *dev);
 void free_device(struct device *dev);
 
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index 41eda79b763a..f511c45814e7 100644
--- a/include/linux/mutex.h
+++ b/include/linux/mutex.h
@@ -20,4 +20,6 @@
 #define mutex_is_locked(...)	0
 struct mutex { int i; };
 
+#define DEFINE_MUTEX(obj) struct mutex  __always_unused obj
+
 #endif /* __LINUX_MUTEX_H */
diff --git a/include/soc/at91/atmel-sfr.h b/include/soc/at91/atmel-sfr.h
index 8e75508165b7..1a909a3e06d6 100644
--- a/include/soc/at91/atmel-sfr.h
+++ b/include/soc/at91/atmel-sfr.h
@@ -19,6 +19,7 @@
 #define AT91_SFR_I2SCLKSEL	0x90	/* I2SC Register */
 
 /* Field definitions */
+#define AT91_SFR_CCFG_NFD0_ON_D16		BIT(24)
 #define AT91_OHCIICR_SUSPEND_A	BIT(8)
 #define AT91_OHCIICR_SUSPEND_B	BIT(9)
 #define AT91_OHCIICR_SUSPEND_C	BIT(10)
-- 
2.30.2




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

* [PATCH 13/15] ARM: AT91: sama5d3_xplained: switch to upstream binding
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (11 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 12/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 14/15] mtd: nand: drop DT support in legacy driver Ahmad Fatoum
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Now that we have an updated NAND driver in place that (exclusively)
supports the new binding, drop our override, so the new driver is
used instead.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 arch/arm/dts/at91-sama5d3_xplained.dts | 26 --------------------------
 arch/arm/dts/sama5d3.dtsi              | 16 ----------------
 2 files changed, 42 deletions(-)

diff --git a/arch/arm/dts/at91-sama5d3_xplained.dts b/arch/arm/dts/at91-sama5d3_xplained.dts
index cdb29ca6669e..b734457214eb 100644
--- a/arch/arm/dts/at91-sama5d3_xplained.dts
+++ b/arch/arm/dts/at91-sama5d3_xplained.dts
@@ -22,29 +22,3 @@
 
 /* Will be automatically read back from HW */
 /delete-node/ &{/memory@20000000};
-
-&nand_controller {
-	nand@3 {
-		compatible = "atmel,at91rm9200-nand";
-		#address-cells = <1>;
-		#size-cells = <1>;
-		reg = <	0x60000000 0x01000000	/* EBI CS3 */
-			0xffffc070 0x00000490	/* SMC PMECC regs */
-			0xffffc500 0x00000100	/* SMC PMECC Error Location regs */
-			0x00110000 0x00018000	/* ROM code */
-			>;
-		interrupts = <5 IRQ_TYPE_LEVEL_HIGH 6>;
-		atmel,nand-addr-offset = <21>;
-		atmel,nand-cmd-offset = <22>;
-		atmel,nand-has-dma;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_nand0_ale_cle>;
-		atmel,pmecc-lookup-table-offset = <0x10000 0x8000>;
-
-		atmel,has-pmecc;
-		atmel,pmecc-cap = <4>;
-		atmel,pmecc-sector-size = <512>;
-		status = "okay";
-	};
-};
-
diff --git a/arch/arm/dts/sama5d3.dtsi b/arch/arm/dts/sama5d3.dtsi
index 2301ad135b3b..84fc1d0ed431 100644
--- a/arch/arm/dts/sama5d3.dtsi
+++ b/arch/arm/dts/sama5d3.dtsi
@@ -5,19 +5,3 @@
 		mmc1 = &mmc1;
 	};
 };
-
-&ebi {
-	compatible = "simple-bus";
-	#address-cells = <1>;
-	#size-cells = <1>;
-	ranges;
-
-};
-
-&nand_controller {
-	compatible = "simple-bus";
-	#address-cells = <1>;
-	#size-cells = <1>;
-	ranges;
-
-};
-- 
2.30.2




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

* [PATCH 14/15] mtd: nand: drop DT support in legacy driver
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (12 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 13/15] ARM: AT91: sama5d3_xplained: switch to upstream binding Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-11 17:40 ` [PATCH 15/15] ARM: AT91: sama5d3: always read memory size from controller Ahmad Fatoum
  2023-01-12 14:22 ` [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Sascha Hauer
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

With the legacy binding removed from the barebox sama5d3_xplained DT,
the only remaining user of "atmel,at91rm9200-nand" is
dts/src/arm/at91rm9200.dtsi, which we don't use. Should DT support
ever be added to that platform, we should invest the time to update
the kernel DT to use the new EBI binding. Thus drop the now unused
DT support in the legacy driver, so only the new EBI binding is
supported with the new driver.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 drivers/mtd/nand/Kconfig        |   5 +-
 drivers/mtd/nand/atmel/legacy.c | 104 +-------------------------------
 2 files changed, 3 insertions(+), 106 deletions(-)

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 4aeb2b603b95..4a673e94406c 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -101,11 +101,10 @@ config NAND_ATMEL
 	depends on ARCH_AT91 || (OFDEVICE && COMPILE_TEST)
 
 config NAND_ATMEL_LEGACY
-	def_bool y
+	def_bool !AT91_MULTI_BOARDS
 	depends on NAND_ATMEL
 	help
-	  Select legacy driver for non-DT-enabled platforms
-	  and for the deprecated non-EBI binding.
+	  Select legacy non-device tree enabled driver.
 
 config NAND_ATMEL_PMECC
 	bool
diff --git a/drivers/mtd/nand/atmel/legacy.c b/drivers/mtd/nand/atmel/legacy.c
index bd8923f93dd5..1f804eb53720 100644
--- a/drivers/mtd/nand/atmel/legacy.c
+++ b/drivers/mtd/nand/atmel/legacy.c
@@ -23,10 +23,6 @@
 #include <init.h>
 #include <gpio.h>
 
-#include <of.h>
-#include <of_gpio.h>
-#include <of_mtd.h>
-
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/rawnand.h>
 #include <linux/mtd/nand.h>
@@ -1109,92 +1105,6 @@ static void atmel_nand_hwctl(struct nand_chip *nand_chip, int mode)
 {
 }
 
-static int atmel_nand_of_init(struct atmel_nand_host *host, struct device_node *np)
-{
-	u32 val;
-	u32 offset[2];
-	int ecc_mode;
-	struct atmel_nand_data *board = host->board;
-	enum of_gpio_flags flags = 0;
-
-	if (!IS_ENABLED(CONFIG_OFDEVICE))
-		return -ENOSYS;
-
-	if (of_property_read_u32(np, "atmel,nand-addr-offset", &val) == 0) {
-		if (val >= 32) {
-			dev_err(host->dev, "invalid addr-offset %u\n", val);
-			return -EINVAL;
-		}
-		board->ale = val;
-	}
-
-	if (of_property_read_u32(np, "atmel,nand-cmd-offset", &val) == 0) {
-		if (val >= 32) {
-			dev_err(host->dev, "invalid cmd-offset %u\n", val);
-			return -EINVAL;
-		}
-		board->cle = val;
-	}
-
-	ecc_mode = of_get_nand_ecc_mode(np);
-
-	board->ecc_mode = ecc_mode < 0 ? NAND_ECC_SOFT : ecc_mode;
-
-	board->on_flash_bbt = of_get_nand_on_flash_bbt(np);
-
-	if (of_get_nand_bus_width(np) == 16)
-		board->bus_width_16 = 1;
-
-	board->rdy_pin = of_get_gpio_flags(np, 0, &flags);
-	board->enable_pin = of_get_gpio(np, 1);
-	board->det_pin = of_get_gpio(np, 2);
-
-	board->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
-
-	if (!(board->ecc_mode == NAND_ECC_HW) || !board->has_pmecc)
-		return 0;	/* Not using PMECC */
-
-	/* use PMECC, get correction capability, sector size and lookup
-	* table offset.
-	* If correction bits and sector size are not specified, then
-	*   find
-	* them from NAND ONFI parameters.
-	*/
-	if (of_property_read_u32(np, "atmel,pmecc-cap", &val) == 0) {
-		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;
-		}
-
-		board->pmecc_corr_cap = (u8)val;
-	}
-
-	if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) == 0) {
-		if ((val != 512) && (val != 1024)) {
-				dev_err(host->dev, "Unsupported PMECC sector size: %d"
-					" should be 512 or 1024 bytes\n", val);
-			return -EINVAL;
-		}
-
-		board->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;
-	}
-
-	if (!offset[0] && !offset[1]) {
-		dev_err(host->dev, "Invalid PMECC lookup table offset\n");
-		return -EINVAL;
-	}
-
-	board->pmecc_lookup_table_offset = (board->pmecc_sector_size == 512) ? offset[0] : offset[1];
-
-	return 0;
-}
-
 static int atmel_hw_nand_init_params(struct device *dev,
 					 struct atmel_nand_host *host)
 {
@@ -1281,13 +1191,7 @@ static int __init atmel_nand_probe(struct device *dev)
 	host->board = pdata;
 	host->dev = dev;
 
-	if (dev->of_node) {
-		res = atmel_nand_of_init(host, dev->of_node);
-		if (res)
-			goto err_no_card;
-	} else {
-		memcpy(host->board, dev->platform_data, sizeof(struct atmel_nand_data));
-	}
+	memcpy(host->board, dev->platform_data, sizeof(struct atmel_nand_data));
 
 	nand_chip->priv = host;		/* link the private data structures */
 	mtd->dev.parent = dev;
@@ -1428,15 +1332,9 @@ err_no_card:
 	return res;
 }
 
-static struct of_device_id atmel_nand_dt_ids[] = {
-	{ .compatible = "atmel,at91rm9200-nand" },
-	{ /* sentinel */ }
-};
-
 static struct driver atmel_nand_driver = {
 	.name	= "atmel_nand",
 	.probe	= atmel_nand_probe,
-	.of_compatible	= DRV_OF_COMPAT(atmel_nand_dt_ids),
 };
 device_platform_driver(atmel_nand_driver);
 
-- 
2.30.2




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

* [PATCH 15/15] ARM: AT91: sama5d3: always read memory size from controller
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (13 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 14/15] mtd: nand: drop DT support in legacy driver Ahmad Fatoum
@ 2023-01-11 17:40 ` Ahmad Fatoum
  2023-01-12 14:22 ` [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Sascha Hauer
  15 siblings, 0 replies; 18+ messages in thread
From: Ahmad Fatoum @ 2023-01-11 17:40 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

No need to hardcode sizes, sama5d3.dtsi has an "atmel,sama5d3-ddramc"
node, which we have a driver for that does dynamic memory size readout.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 arch/arm/dts/at91-microchip-ksz9477-evb.dts | 4 ----
 arch/arm/dts/at91-sama5d3_xplained.dts      | 3 ---
 arch/arm/dts/sama5d3.dtsi                   | 3 +++
 3 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/arch/arm/dts/at91-microchip-ksz9477-evb.dts b/arch/arm/dts/at91-microchip-ksz9477-evb.dts
index 3762361b5362..60efc77f69bd 100644
--- a/arch/arm/dts/at91-microchip-ksz9477-evb.dts
+++ b/arch/arm/dts/at91-microchip-ksz9477-evb.dts
@@ -13,10 +13,6 @@
 	};
 };
 
-&{/memory@20000000} {
-	reg = <0x20000000 0x10000000>;
-};
-
 &reg_vcc_mmc0 {
 	status = "disabled";
 };
diff --git a/arch/arm/dts/at91-sama5d3_xplained.dts b/arch/arm/dts/at91-sama5d3_xplained.dts
index b734457214eb..1580bd29f814 100644
--- a/arch/arm/dts/at91-sama5d3_xplained.dts
+++ b/arch/arm/dts/at91-sama5d3_xplained.dts
@@ -19,6 +19,3 @@
 		};
 	};
 };
-
-/* Will be automatically read back from HW */
-/delete-node/ &{/memory@20000000};
diff --git a/arch/arm/dts/sama5d3.dtsi b/arch/arm/dts/sama5d3.dtsi
index 84fc1d0ed431..658292792fb7 100644
--- a/arch/arm/dts/sama5d3.dtsi
+++ b/arch/arm/dts/sama5d3.dtsi
@@ -5,3 +5,6 @@
 		mmc1 = &mmc1;
 	};
 };
+
+/* Will be automatically read back from HW */
+/delete-node/ &{/memory@20000000};
-- 
2.30.2




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

* Re: [PATCH 09/15] lib: provide stub Linux "generic" allocator API
  2023-01-11 17:40 ` [PATCH 09/15] lib: provide stub Linux "generic" allocator API Ahmad Fatoum
@ 2023-01-12 13:26   ` Sascha Hauer
  0 siblings, 0 replies; 18+ messages in thread
From: Sascha Hauer @ 2023-01-12 13:26 UTC (permalink / raw)
  To: Ahmad Fatoum; +Cc: barebox

On Wed, Jan 11, 2023 at 06:40:17PM +0100, Ahmad Fatoum wrote:
> We may want to port the whole of the Linux generic allocator
> implementation in future as it would come in handy in drivers that need
> special memory for DMA. For now, support just the use case of the
> incoming Atmel NAND driver, which is mapping the whole of a mmio-sram.
> 
> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
> ---
>  include/linux/genalloc.h |  36 ++++++++++++
>  lib/Kconfig              |   5 ++
>  lib/Makefile             |   1 +
>  lib/genalloc.c           | 118 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 160 insertions(+)
>  create mode 100644 include/linux/genalloc.h
>  create mode 100644 lib/genalloc.c
> 
> diff --git a/include/linux/genalloc.h b/include/linux/genalloc.h
> new file mode 100644
> index 000000000000..566e62d1965c
> --- /dev/null
> +++ b/include/linux/genalloc.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Basic general purpose allocator for managing special purpose
> + * memory, for example, memory that is not managed by the regular
> + * kmalloc/kfree interface.  Uses for this includes on-device special
> + * memory, uncached memory etc.
> + */
> +
> +
> +#ifndef __GENALLOC_H__
> +#define __GENALLOC_H__
> +
> +#include <linux/types.h>
> +
> +struct device_node;
> +
> +struct gen_pool;
> +
> +extern phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long);
> +
> +extern void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size,
> +		dma_addr_t *dma);
> +
> +extern void *gen_pool_dma_zalloc(struct gen_pool *pool, size_t size, dma_addr_t *dma);
> +
> +#ifdef CONFIG_OFDEVICE
> +extern struct gen_pool *of_gen_pool_get(struct device_node *np,
> +	const char *propname, int index);
> +#else
> +static inline struct gen_pool *of_gen_pool_get(struct device_node *np,
> +	const char *propname, int index)
> +{
> +	return NULL;
> +}
> +#endif
> +#endif /* __GENALLOC_H__ */
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 5af7ea33f27b..b8bc9d63d4f0 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -210,4 +210,9 @@ config ARCH_HAS_ZERO_PAGE
>  config HAVE_EFFICIENT_UNALIGNED_ACCESS
>  	bool
>  
> +config GENERIC_ALLOCATOR
> +	bool
> +	help
> +	  Support is curently limited to allocaing a complete mmio-sram at once.
> +
>  endmenu
> diff --git a/lib/Makefile b/lib/Makefile
> index 4717b8aec364..38478625423b 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -75,6 +75,7 @@ obj-$(CONFIG_CRC8)	+= crc8.o
>  obj-$(CONFIG_NLS)	+= nls_base.o
>  obj-$(CONFIG_FSL_QE_FIRMWARE) += fsl-qe-firmware.o
>  obj-$(CONFIG_UBSAN)	+= ubsan.o
> +obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o
>  
>  # GCC library routines
>  obj-$(CONFIG_GENERIC_LIB_ASHLDI3) += ashldi3.o
> diff --git a/lib/genalloc.c b/lib/genalloc.c
> new file mode 100644
> index 000000000000..906e2dd5f14b
> --- /dev/null
> +++ b/lib/genalloc.c
> @@ -0,0 +1,118 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// SPDX-FileCopyrightText: 2005 Jes Sorensen <jes@trained-monkey.org>
> +/*
> + * Basic general purpose allocator for managing special purpose
> + * memory, for example, memory that is not managed by the regular
> + * kmalloc/kfree interface.  Uses for this includes on-device special
> + * memory, uncached memory etc.
> + */
> +
> +#include <io.h>
> +#include <linux/ioport.h>
> +#include <linux/genalloc.h>
> +#include <linux/export.h>
> +#include <of.h>
> +#include <driver.h>
> +#include <linux/string.h>
> +
> +struct gen_pool {
> +	struct resource res;
> +};
> +
> +#define res_to_gen_pool(res) \
> +	container_of(res, struct gen_pool, res)
> +
> +/**
> + * gen_pool_virt_to_phys - return the physical address of memory
> + * @pool: pool to allocate from
> + * @addr: starting address of memory
> + *
> + * Returns the physical address on success, or -1 on error.
> + */
> +phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long addr)
> +{
> +	return virt_to_phys((void *)addr);
> +}
> +EXPORT_SYMBOL(gen_pool_virt_to_phys);
> +
> +/**
> + * gen_pool_dma_alloc - allocate special memory from the pool for DMA usage
> + * @pool: pool to allocate from
> + * @size: number of bytes to allocate from the pool
> + * @dma: dma-view physical address return value.  Use %NULL if unneeded.
> + *
> + * Allocate the requested number of bytes from the specified pool.
> + * Uses the pool allocation function (with first-fit algorithm by default).
> + * Can not be used in NMI handler on architectures without
> + * NMI-safe cmpxchg implementation.
> + *
> + * Return: virtual address of the allocated memory, or %NULL on failure
> + */

Maybe add a comment here that we currently only support allocating the
whole area?

Can be done as followup later.

Sascha

> +void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
> +{
> +	unsigned long vaddr;
> +
> +	if (!pool || resource_size(&pool->res) != size)
> +		return NULL;
> +
> +	vaddr = pool->res.start;
> +
> +	if (dma)
> +		*dma = gen_pool_virt_to_phys(pool, vaddr);
> +
> +	return (void *)vaddr;
> +}
> +EXPORT_SYMBOL(gen_pool_dma_alloc);
> +
> +/**
> + * gen_pool_dma_zalloc - allocate special zeroed memory from the pool for
> + * DMA usage
> + * @pool: pool to allocate from
> + * @size: number of bytes to allocate from the pool
> + * @dma: dma-view physical address return value.  Use %NULL if unneeded.
> + *
> + * Return: virtual address of the allocated zeroed memory, or %NULL on failure
> + */
> +void *gen_pool_dma_zalloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
> +{
> +	void *vaddr = gen_pool_dma_alloc(pool, size, dma);
> +
> +	if (vaddr)
> +		memset(vaddr, 0, size);
> +
> +	return vaddr;
> +}
> +EXPORT_SYMBOL(gen_pool_dma_zalloc);
> +
> +#ifdef CONFIG_OFDEVICE
> +/**
> + * of_gen_pool_get - find a pool by phandle property
> + * @np: device node
> + * @propname: property name containing phandle(s)
> + * @index: index into the phandle array
> + *
> + * Returns the pool that contains the chunk starting at the physical
> + * address of the device tree node pointed at by the phandle property,
> + * or NULL if not found.
> + */
> +struct gen_pool *of_gen_pool_get(struct device_node *np,
> +	const char *propname, int index)
> +{
> +	struct device *dev;
> +	struct device_node *np_pool;
> +
> +	np_pool = of_parse_phandle(np, propname, index);
> +	if (!np_pool)
> +		return NULL;
> +
> +	if (!of_device_is_compatible(np_pool, "mmio-sram"))
> +		return NULL;
> +
> +	dev = of_find_device_by_node(np_pool);
> +	if (!dev)
> +		return NULL;
> +
> +	return container_of(&dev->resource[0], struct gen_pool, res);
> +}
> +EXPORT_SYMBOL_GPL(of_gen_pool_get);
> +#endif /* CONFIG_OF */
> -- 
> 2.30.2
> 
> 
> 

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



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

* Re: [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver
  2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
                   ` (14 preceding siblings ...)
  2023-01-11 17:40 ` [PATCH 15/15] ARM: AT91: sama5d3: always read memory size from controller Ahmad Fatoum
@ 2023-01-12 14:22 ` Sascha Hauer
  15 siblings, 0 replies; 18+ messages in thread
From: Sascha Hauer @ 2023-01-12 14:22 UTC (permalink / raw)
  To: Ahmad Fatoum; +Cc: barebox

On Wed, Jan 11, 2023 at 06:40:08PM +0100, Ahmad Fatoum wrote:
> For a few years, Linux has been using the new EBI bindings for NAND
> controllers on all AT91 SoCs newer than the AT91RM2000. We have so far
> only supported the old bindings by hacking the DT, but this doesn't
> suffice for the SAMA5D4. Therefore import a new state of the Linux NAND
> controller driver. We still keep around the old barebox driver to
> support the non-DT enabled AT91 platforms.
> 
> Ahmad Fatoum (15):
>   asm-generic: io.h: sync with Linux
>   mtd: nand: base: implement nand_gpio_waitrdy
>   mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_
>   mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements
>   mtd: nand: define nand_get_(small|large)_page_ooblayout
>   mtd: nand: define nand_interface_is_sdr
>   mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type
>   driver: implement dev_request_resource
>   lib: provide stub Linux "generic" allocator API
>   memory: add Atmel EBI driver
>   mfd: add atmel-smc driver
>   mtd: nand: atmel: import Linux NAND controller driver
>   ARM: AT91: sama5d3_xplained: switch to upstream binding
>   mtd: nand: drop DT support in legacy driver
>   ARM: AT91: sama5d3: always read memory size from controller

Applied, thanks

Sascha

> 
>  arch/arm/dts/at91-microchip-ksz9477-evb.dts   |    4 -
>  arch/arm/dts/at91-sama5d3_xplained.dts        |   29 -
>  arch/arm/dts/sama5d3.dtsi                     |   17 +-
>  drivers/base/driver.c                         |   19 +-
>  drivers/memory/Kconfig                        |   14 +
>  drivers/memory/Makefile                       |    1 +
>  drivers/memory/atmel-ebi.c                    |  614 +++++
>  drivers/mfd/Kconfig                           |    4 +
>  drivers/mfd/Makefile                          |    1 +
>  drivers/mfd/atmel-smc.c                       |  352 +++
>  drivers/mtd/nand/Kconfig                      |   11 +-
>  drivers/mtd/nand/Makefile                     |    2 +-
>  drivers/mtd/nand/atmel/Makefile               |    3 +
>  drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h |    0
>  .../mtd/nand/{atmel_nand.c => atmel/legacy.c} |  106 +-
>  drivers/mtd/nand/atmel/nand-controller.c      | 2049 +++++++++++++++++
>  drivers/mtd/nand/atmel/pmecc.c                |  992 ++++++++
>  drivers/mtd/nand/atmel/pmecc.h                |   70 +
>  drivers/mtd/nand/nand_base.c                  |   86 +-
>  drivers/mtd/nand/nand_esmt.c                  |   10 +-
>  drivers/mtd/nand/nand_fsl_ifc.c               |    2 +-
>  drivers/mtd/nand/nand_hynix.c                 |   40 +-
>  drivers/mtd/nand/nand_jedec.c                 |    4 +-
>  drivers/mtd/nand/nand_micron.c                |   16 +-
>  drivers/mtd/nand/nand_onfi.c                  |    8 +-
>  drivers/mtd/nand/nand_samsung.c               |   18 +-
>  drivers/mtd/nand/nand_toshiba.c               |   12 +-
>  include/asm-generic/io.h                      |  401 +++-
>  include/driver.h                              |    5 +
>  include/linux/genalloc.h                      |   36 +
>  include/linux/mfd/syscon/atmel-matrix.h       |  112 +
>  include/linux/mfd/syscon/atmel-smc.h          |  119 +
>  include/linux/mtd/nand.h                      |   27 +-
>  include/linux/mtd/rawnand.h                   |   43 +-
>  include/linux/mutex.h                         |    2 +
>  include/soc/at91/atmel-sfr.h                  |    2 +
>  lib/Kconfig                                   |    5 +
>  lib/Makefile                                  |    1 +
>  lib/genalloc.c                                |  118 +
>  39 files changed, 5074 insertions(+), 281 deletions(-)
>  create mode 100644 drivers/memory/atmel-ebi.c
>  create mode 100644 drivers/mfd/atmel-smc.c
>  create mode 100644 drivers/mtd/nand/atmel/Makefile
>  rename drivers/mtd/nand/{ => atmel}/atmel_nand_ecc.h (100%)
>  rename drivers/mtd/nand/{atmel_nand.c => atmel/legacy.c} (92%)
>  create mode 100644 drivers/mtd/nand/atmel/nand-controller.c
>  create mode 100644 drivers/mtd/nand/atmel/pmecc.c
>  create mode 100644 drivers/mtd/nand/atmel/pmecc.h
>  create mode 100644 include/linux/genalloc.h
>  create mode 100644 include/linux/mfd/syscon/atmel-matrix.h
>  create mode 100644 include/linux/mfd/syscon/atmel-smc.h
>  create mode 100644 lib/genalloc.c
> 
> -- 
> 2.30.2
> 
> 
> 

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



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

end of thread, other threads:[~2023-01-12 14:24 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-11 17:40 [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 01/15] asm-generic: io.h: sync with Linux Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 02/15] mtd: nand: base: implement nand_gpio_waitrdy Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 03/15] mtd: nand: prefix enum nand_ecc_algo constants with NAND_ECC_ALGO_ Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 04/15] mtd: nand: rename nand_device::eccreq to Linux' ecc.requirements Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 05/15] mtd: nand: define nand_get_(small|large)_page_ooblayout Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 06/15] mtd: nand: define nand_interface_is_sdr Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 07/15] mtd: nand: provide Linux' struct nand_ecc_ctrl::engine_type Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 08/15] driver: implement dev_request_resource Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 09/15] lib: provide stub Linux "generic" allocator API Ahmad Fatoum
2023-01-12 13:26   ` Sascha Hauer
2023-01-11 17:40 ` [PATCH 10/15] memory: add Atmel EBI driver Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 11/15] mfd: add atmel-smc driver Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 12/15] mtd: nand: atmel: import Linux NAND controller driver Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 13/15] ARM: AT91: sama5d3_xplained: switch to upstream binding Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 14/15] mtd: nand: drop DT support in legacy driver Ahmad Fatoum
2023-01-11 17:40 ` [PATCH 15/15] ARM: AT91: sama5d3: always read memory size from controller Ahmad Fatoum
2023-01-12 14:22 ` [PATCH 00/15] mtd: nand: atmel: import Linux NAND controller driver Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox