diff mbox

[U-Boot,1/4] sunxi: nand: Add basic sunxi NAND driver with DMA support

Message ID 1437045915-25755-2-git-send-email-pzierhoffer@antmicro.com
State Superseded
Delegated to: Hans de Goede
Headers show

Commit Message

Piotr Zierhoffer July 16, 2015, 11:25 a.m. UTC
From: Piotr Zierhoffer <piotr.zierhoffer@cs.put.poznan.pl>

This driver adds NAND support to both SPL and full U-Boot.
It was tested on AllWinner A20.

Signed-off-by: Peter Gielda <pgielda@antmicro.com>
Signed-off-by: Tomasz Gorochowik <tgorochowik@antmicro.com>
Signed-off-by: Mateusz Holenko <mholenko@antmicro.com>
Signed-off-by: Piotr Zierhoffer <pzierhoffer@antmicro.com>
---

 board/sunxi/Makefile |   1 +
 board/sunxi/nand.c   | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++
 board/sunxi/nand.h   | 146 ++++++++++++++++++++++++
 3 files changed, 462 insertions(+)
 create mode 100644 board/sunxi/nand.c
 create mode 100644 board/sunxi/nand.h

Comments

Scott Wood July 16, 2015, 9:15 p.m. UTC | #1
On Thu, 2015-07-16 at 13:25 +0200, Piotr Zierhoffer wrote:
> From: Piotr Zierhoffer <piotr.zierhoffer@cs.put.poznan.pl>
> 
> This driver adds NAND support to both SPL and full U-Boot.
> It was tested on AllWinner A20.

It looks a lot like an SPL-only driver to me...

> 
> Signed-off-by: Peter Gielda <pgielda@antmicro.com>
> Signed-off-by: Tomasz Gorochowik <tgorochowik@antmicro.com>
> Signed-off-by: Mateusz Holenko <mholenko@antmicro.com>
> Signed-off-by: Piotr Zierhoffer <pzierhoffer@antmicro.com>
> ---
> 
>  board/sunxi/Makefile |   1 +
>  board/sunxi/nand.c   | 315 
> +++++++++++++++++++++++++++++++++++++++++++++++++++
>  board/sunxi/nand.h   | 146 ++++++++++++++++++++++++
>  3 files changed, 462 insertions(+)
>  create mode 100644 board/sunxi/nand.c
>  create mode 100644 board/sunxi/nand.h

Please keep NAND drivers in drivers/mtd/nand/.

> 
> diff --git a/board/sunxi/Makefile b/board/sunxi/Makefile
> index 43766e0..6f1d571 100644
> --- a/board/sunxi/Makefile
> +++ b/board/sunxi/Makefile
> @@ -9,6 +9,7 @@
>  # SPDX-License-Identifier:   GPL-2.0+
>  #
>  obj-y        += board.o
> +obj-$(CONFIG_SUNXI_NAND)     += nand.o
>  obj-$(CONFIG_SUNXI_GMAC)     += gmac.o
>  obj-$(CONFIG_SUNXI_AHCI)     += ahci.o
>  obj-$(CONFIG_MACH_SUN4I)     += dram_sun4i_auto.o
> diff --git a/board/sunxi/nand.c b/board/sunxi/nand.c
> new file mode 100644
> index 0000000..16e8e4d
> --- /dev/null
> +++ b/board/sunxi/nand.c
> @@ -0,0 +1,315 @@
> +/*
> + * Copyright (c) 2014-2015, Antmicro Ltd <www.antmicro.com>
> + * Copyright (c) 2015, AW-SOM Technologies <www.aw-som.com>
> + *
> + * SPDX-License-Identifier:     GPL-2.0+
> + */
> +
> +#include <common.h>
> +#include <config.h>
> +#include <asm/io.h>
> +#include <nand.h>
> +#include "nand.h" /* sunxi specific header */
> +
> +/* minimal "boot0" style NAND support for Allwinner A20 */
> +
> +/* temporary buffer in internal ram */
> +#ifdef CONFIG_SPL_BUILD
> +/* in SPL temporary buffer cannot be @ 0x0 */
> +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10) __section(".text#");
> +#else
> +/* put temporary buffer @ 0x0 */
> +unsigned char *temp_buf = (unsigned char *)0x0;
> +#endif

If 0x0 is the address of an SRAM, its address should be symbolically defined. 
 Also consider mapping it to a different virtual address, to avoid potential 
compiler mischief.

> +#define MAX_RETRIES 10
> +
> +static int check_value_inner(int offset, int expected_bits,
> +                             int max_number_of_retries, int negation)
> +{
> +     int retries = 0;
> +     do {
> +             int val = readl(offset) & expected_bits;
> +             if (negation ? !val : val)
> +                     return 1;
> +             mdelay(1);
> +             retries++;
> +     } while (retries < max_number_of_retries);
> +
> +     return 0;
> +}
> +
> +static inline int check_value(int offset, int expected_bits,
> +                             int max_number_of_retries)
> +{
> +     return check_value_inner(offset, expected_bits,
> +                                     max_number_of_retries, 0);
> +}
> +
> +static inline int check_value_negated(int offset, int unexpected_bits,
> +                                     int max_number_of_retries)
> +{
> +     return check_value_inner(offset, unexpected_bits,
> +                                     max_number_of_retries, 1);
> +}
> +
> +void nand_set_clocks(void)
> +{

Here and elsewhere, please either use static or less generic name.

+int nand_initialized;

static bool nand_initialized;

Or better yet, get rid of this and stop using init-on-first-use.

> +
> +void nand_init(void)
> +{
> +     board_nand_init(NULL);
> +}

nand_init() is defined in drivers/mtd/nand/nand.c, which is only optional for 
SPL -- and I don't see an SPL ifdef here.

> +uint32_t ecc_errors;

static

> +
> +/* read 0x400 bytes from real_addr to temp_buf */
> +void nand_read_block(unsigned int real_addr, int syndrome)

I don't know of any NAND chips whose erase blocks are as small as 0x400 
bytes.  The read/program unit is called a page, not a block.

Is there a reason to hardcode the page size here?  Especially since the 
comment doesn't appear to match the code, which uses SPL_WRITE_SIZE.

> +{
> +     uint32_t val;
> +     int ECC_OFF = 0;

Why is this capitalized?

> +     uint16_t ecc_mode = 0;
> +     uint16_t rand_seed;
> +     uint32_t page;
> +     uint16_t column;
> +     uint32_t oob_offset;
> +
> +     if (!nand_initialized)
> +             board_nand_init(NULL);

board_nand_init() is called from nand_init().  Why are you calling it here?

> +     switch (CONFIG_SUNXI_ECC_STRENGTH) {

No documentation for this symbol.

If it's an attribute of the hardware rather than something the user selects, 
it should be CONFIG_SYS_SUNXI_ECC_STRENGTH, or just SUNXI_ECC_STRENGTH if it 
doesn't need to integrate with kconfig or makefiles.


> +     /* clear temp_buf */
> +     memset((void *)temp_buf, 0, SPL_WRITE_SIZE);

Unnecessary cast.

> +
> +     /* set CMD  */
> +     writel(NFC_SEND_CMD1 | NFC_WAIT_FLAG | 0xff,
> +            NANDFLASHC_BASE + NANDFLASHC_CMD);
> +
> +     if (!check_value(NANDFLASHC_BASE + NANDFLASHC_ST, (1 << 1),
> +                      MAX_RETRIES)) {
> +             printf("Error while initilizing command interrupt\n");
> +             return;
> +     }
> +
> +     page = (real_addr / BLOCK_SIZE);

Unnecessary parens (and inconsistent with the following line).

> +     column = real_addr % BLOCK_SIZE;
> +
> +     if (syndrome) {
> +             /* shift every 1kB in syndrome */
> +             column += (column / SPL_WRITE_SIZE) * ECC_OFF;
> +     }
> +
> +     /* clear ecc status */
> +     writel(0, NANDFLASHC_BASE + NANDFLASHC_ECC_ST);
> +
> +     /* Choose correct seed */
> +     if (syndrome)
> +             rand_seed = random_seed_syndrome;
> +     else
> +             rand_seed = random_seed[page % 128];
> +
> +     writel((rand_seed << 16) | NFC_ECC_RANDOM_EN | NFC_ECC_EN
> +             | NFC_ECC_PIPELINE | (ecc_mode << 12),
> +             NANDFLASHC_BASE + NANDFLASHC_ECC_CTL);
> +
> +     val = readl(NANDFLASHC_BASE + NANDFLASHC_CTL);
> +     writel(val | NFC_CTL_RAM_METHOD, NANDFLASHC_BASE + NANDFLASHC_CTL);
> +
> +     if (syndrome) {
> +             writel(SPL_WRITE_SIZE, NANDFLASHC_BASE + NANDFLASHC_SPARE_AREA);
> +     } else {
> +             oob_offset = BLOCK_SIZE + (column / SPL_WRITE_SIZE) * ECC_OFF;
> +             writel(oob_offset, NANDFLASHC_BASE + NANDFLASHC_SPARE_AREA);
> +     }
> +
> +     /* DMAC */
> +     writel(0x0, DMAC_BASE + DMAC_CFG_REG0); /* clr dma cmd */
> +     /* read from REG_IO_DATA */
> +     writel(NANDFLASHC_BASE + NANDFLASHC_IO_DATA,
> +            DMAC_BASE + DMAC_SRC_START_ADDR_REG0);
> +     writel((uint32_t)temp_buf,
> +            DMAC_BASE + DMAC_DEST_START_ADDRR_REG0); /* read to RAM */
> +     writel(DMAC_DDMA_PARA_REG_SRC_WAIT_CYC
> +                     | DMAC_DDMA_PARA_REG_SRC_BLK_SIZE,
> +                     DMAC_BASE + DMAC_DDMA_PARA_REG0);
> +     writel(SPL_WRITE_SIZE, DMAC_BASE + DMAC_DDMA_BC_REG0); /* 1kB */
> +     writel(DMAC_DDMA_CFG_REG_LOADING
> +             | DMAC_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32
> +             | DMAC_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32
> +             | DMAC_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO
> +             | DMAC_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
> +             DMAC_BASE + DMAC_CFG_REG0);
> +
> +     writel(0x00E00530, NANDFLASHC_BASE + NANDFLASHC_RCMD_SET); /* READ */

What is 0x00e00530?  Define it symbolically.


> +     writel(1, NANDFLASHC_BASE + NANDFLASHC_SECTOR_NUM);
> +     writel(((page & 0xFFFF) << 16) | column,
> +            NANDFLASHC_BASE + NANDFLASHC_ADDR_LOW);
> +     writel((page >> 16) & 0xFF, NANDFLASHC_BASE + NANDFLASHC_ADDR_HIGH);
> +     writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_DATA_TRANS |
> +             NFC_PAGE_CMD | NFC_WAIT_FLAG | /* ADDR_CYCLE */ (4 << 16) |

Why is ADDR_CYCLE commented out, and a magic number used in its place?

> +int helper_load(uint32_t offs, unsigned int size, void *dest)

This name is not clear (in addition to being way too vague for a global 
symbol).

> +{
> +     uint32_t dst;

Don't have both "dest" and "dst".  It's confusing.

> +     uint32_t count;
> +     uint32_t count_c;

What does the "_c" mean?

> +     uint32_t adr = offs;

So address is actually a NAND offset, not a memory address?  Confusing.

Don't put addresses in uint32_t (even if it doesn't make a difference on this 
platform, it's a bad habit and a bad example to others).  Use a pointer for 
virtual addresses, and phys_addr_t for physical addresses.

> +
> +     memset((void *)dest, 0x0, size); /* clean destination memory */

Unnecessary cast.

> +     ecc_errors = 0;
> +     for (dst = (uint32_t)dest;
> +                     dst < ((uint32_t)dest + size);
> +                     dst += SPL_WRITE_SIZE) {
> +             /* if < 0x400000 then syndrome read */
> +             nand_read_block(adr, adr < 0x400000);

What does 0x400000 represent?

> +int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
> +{
> +     helper_load(offs, size, dest);
> +     return 0;
> +}

Why didn't you just call helper_load() nand_spl_load_image()?


> +
> +void nand_deselect(void) {}
> diff --git a/board/sunxi/nand.h b/board/sunxi/nand.h
> new file mode 100644
> index 0000000..b3c1e93
> --- /dev/null
> +++ b/board/sunxi/nand.h
> @@ -0,0 +1,146 @@
> +#ifndef NAND_H
> +
> +#define NAND_H

Don't use such a generic ifdef protector in a non-generic file.  It's just 
chance that include/nand.h is using _NAND_H_ instead of NAND_H...

-Scott
Marek Vasut July 16, 2015, 9:26 p.m. UTC | #2
On Thursday, July 16, 2015 at 11:15:58 PM, Scott Wood wrote:

[...]

> > +/* temporary buffer in internal ram */
> > +#ifdef CONFIG_SPL_BUILD
> > +/* in SPL temporary buffer cannot be @ 0x0 */
> > +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10)
> > __section(".text#"); +#else
> > +/* put temporary buffer @ 0x0 */
> > +unsigned char *temp_buf = (unsigned char *)0x0;
> > +#endif
> 
> If 0x0 is the address of an SRAM, its address should be symbolically
> defined. Also consider mapping it to a different virtual address, to avoid
> potential compiler mischief.

The DMA I believe accesses it via PA anyway, so mapping it elsewhere would
only confuse everyone who's hacking on the driver. Just my 5 cents ;-)

[...]

Best regards,
Marek Vasut
Scott Wood July 16, 2015, 9:36 p.m. UTC | #3
On Thu, 2015-07-16 at 23:26 +0200, Marek Vasut wrote:
> On Thursday, July 16, 2015 at 11:15:58 PM, Scott Wood wrote:
> 
> [...]
> 
> > > +/* temporary buffer in internal ram */
> > > +#ifdef CONFIG_SPL_BUILD
> > > +/* in SPL temporary buffer cannot be @ 0x0 */
> > > +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10)
> > > __section(".text#"); +#else
> > > +/* put temporary buffer @ 0x0 */
> > > +unsigned char *temp_buf = (unsigned char *)0x0;
> > > +#endif
> > 
> > If 0x0 is the address of an SRAM, its address should be symbolically
> > defined. Also consider mapping it to a different virtual address, to avoid
> > potential compiler mischief.
> 
> The DMA I believe accesses it via PA anyway, so mapping it elsewhere would
> only confuse everyone who's hacking on the driver. Just my 5 cents ;-)

Hey, if it wakes people up to the fact that they ought to be using 
virt_to_phys() (or better yet, something that distinguishes DMA addresses 
from physical), great! :-)

And yes, people go on to do the same "everything is u32" crap in non-platform-
specific files.  Just look at drivers/block/ahci.c.

-Scott
Marek Vasut July 16, 2015, 10:06 p.m. UTC | #4
On Thursday, July 16, 2015 at 11:36:08 PM, Scott Wood wrote:
> On Thu, 2015-07-16 at 23:26 +0200, Marek Vasut wrote:
> > On Thursday, July 16, 2015 at 11:15:58 PM, Scott Wood wrote:
> > 
> > [...]
> > 
> > > > +/* temporary buffer in internal ram */
> > > > +#ifdef CONFIG_SPL_BUILD
> > > > +/* in SPL temporary buffer cannot be @ 0x0 */
> > > > +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10)
> > > > __section(".text#"); +#else
> > > > +/* put temporary buffer @ 0x0 */
> > > > +unsigned char *temp_buf = (unsigned char *)0x0;
> > > > +#endif
> > > 
> > > If 0x0 is the address of an SRAM, its address should be symbolically
> > > defined. Also consider mapping it to a different virtual address, to
> > > avoid potential compiler mischief.
> > 
> > The DMA I believe accesses it via PA anyway, so mapping it elsewhere
> > would only confuse everyone who's hacking on the driver. Just my 5 cents
> > ;-)
> 
> Hey, if it wakes people up to the fact that they ought to be using
> virt_to_phys() (or better yet, something that distinguishes DMA addresses
> from physical), great! :-)

Not really useful here, but whatever.

> And yes, people go on to do the same "everything is u32" crap in
> non-platform- specific files.  Just look at drivers/block/ahci.c.

Errr, this file is platform specific.

Best regards,
Marek Vasut
Scott Wood July 16, 2015, 10:12 p.m. UTC | #5
On Fri, 2015-07-17 at 00:06 +0200, Marek Vasut wrote:
> On Thursday, July 16, 2015 at 11:36:08 PM, Scott Wood wrote:
> > On Thu, 2015-07-16 at 23:26 +0200, Marek Vasut wrote:
> > > On Thursday, July 16, 2015 at 11:15:58 PM, Scott Wood wrote:
> > > 
> > > [...]
> > > 
> > > > > +/* temporary buffer in internal ram */
> > > > > +#ifdef CONFIG_SPL_BUILD
> > > > > +/* in SPL temporary buffer cannot be @ 0x0 */
> > > > > +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10)
> > > > > __section(".text#"); +#else
> > > > > +/* put temporary buffer @ 0x0 */
> > > > > +unsigned char *temp_buf = (unsigned char *)0x0;
> > > > > +#endif
> > > > 
> > > > If 0x0 is the address of an SRAM, its address should be symbolically
> > > > defined. Also consider mapping it to a different virtual address, to
> > > > avoid potential compiler mischief.
> > > 
> > > The DMA I believe accesses it via PA anyway, so mapping it elsewhere
> > > would only confuse everyone who's hacking on the driver. Just my 5 cents
> > > ;-)
> > 
> > Hey, if it wakes people up to the fact that they ought to be using
> > virt_to_phys() (or better yet, something that distinguishes DMA addresses
> > from physical), great! :-)
> 
> Not really useful here, but whatever.
> 
> > And yes, people go on to do the same "everything is u32" crap in
> > non-platform- specific files.  Just look at drivers/block/ahci.c.
> 
> Errr, this file is platform specific.

(I'm assuming that by "this file" you mean the sunxi file, not the ahci file.)

The point is that establishing good habits everywhere reduces the likelihood 
of the bad habits migrating to places where things break.

And no, I don't seriously expect my suggestion of remapping the virtual 
address to be implemented, but GCC has been known to do all sorts of weird 
stuff when it thinks it knows a NULL pointer is being dereferenced.

-Scott
Marek Vasut July 16, 2015, 10:13 p.m. UTC | #6
On Friday, July 17, 2015 at 12:12:15 AM, Scott Wood wrote:
> On Fri, 2015-07-17 at 00:06 +0200, Marek Vasut wrote:
> > On Thursday, July 16, 2015 at 11:36:08 PM, Scott Wood wrote:
> > > On Thu, 2015-07-16 at 23:26 +0200, Marek Vasut wrote:
> > > > On Thursday, July 16, 2015 at 11:15:58 PM, Scott Wood wrote:
> > > > 
> > > > [...]
> > > > 
> > > > > > +/* temporary buffer in internal ram */
> > > > > > +#ifdef CONFIG_SPL_BUILD
> > > > > > +/* in SPL temporary buffer cannot be @ 0x0 */
> > > > > > +unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10)
> > > > > > __section(".text#"); +#else
> > > > > > +/* put temporary buffer @ 0x0 */
> > > > > > +unsigned char *temp_buf = (unsigned char *)0x0;
> > > > > > +#endif
> > > > > 
> > > > > If 0x0 is the address of an SRAM, its address should be
> > > > > symbolically defined. Also consider mapping it to a different
> > > > > virtual address, to avoid potential compiler mischief.
> > > > 
> > > > The DMA I believe accesses it via PA anyway, so mapping it elsewhere
> > > > would only confuse everyone who's hacking on the driver. Just my 5
> > > > cents ;-)
> > > 
> > > Hey, if it wakes people up to the fact that they ought to be using
> > > virt_to_phys() (or better yet, something that distinguishes DMA
> > > addresses from physical), great! :-)
> > 
> > Not really useful here, but whatever.
> > 
> > > And yes, people go on to do the same "everything is u32" crap in
> > > non-platform- specific files.  Just look at drivers/block/ahci.c.
> > 
> > Errr, this file is platform specific.
> 
> (I'm assuming that by "this file" you mean the sunxi file, not the ahci
> file.)
> 
> The point is that establishing good habits everywhere reduces the
> likelihood of the bad habits migrating to places where things break.
> 
> And no, I don't seriously expect my suggestion of remapping the virtual
> address to be implemented, but GCC has been known to do all sorts of weird
> stuff when it thinks it knows a NULL pointer is being dereferenced.

OK, you have a point there.

Best regards,
Marek Vasut
Piotr Zierhoffer July 17, 2015, 2:39 p.m. UTC | #7
2015-07-16 23:15 GMT+02:00 Scott Wood <scottwood@freescale.com>:
> On Thu, 2015-07-16 at 13:25 +0200, Piotr Zierhoffer wrote:
>> From: Piotr Zierhoffer <piotr.zierhoffer@cs.put.poznan.pl>
>>
>> This driver adds NAND support to both SPL and full U-Boot.
>> It was tested on AllWinner A20.
>
> It looks a lot like an SPL-only driver to me...
>

I am preparing a v2 patch set, that will have SPL-only code.

>> +     writel(0x00E00530, NANDFLASHC_BASE + NANDFLASHC_RCMD_SET); /* READ */
>
> What is 0x00e00530?  Define it symbolically.
>

It's difficult because we do not have much explanation for this
constant. This is how BROM operates.
We have tuned it according to http://linux-sunxi.org/NFC_Register_Guide

We have fixed the code according to your comments and will resubmit it shortly.

Best regards

Piotr Zierhoffer
Antmicro Ltd | www.antmicro.com
diff mbox

Patch

diff --git a/board/sunxi/Makefile b/board/sunxi/Makefile
index 43766e0..6f1d571 100644
--- a/board/sunxi/Makefile
+++ b/board/sunxi/Makefile
@@ -9,6 +9,7 @@ 
 # SPDX-License-Identifier:	GPL-2.0+
 #
 obj-y	+= board.o
+obj-$(CONFIG_SUNXI_NAND)	+= nand.o
 obj-$(CONFIG_SUNXI_GMAC)	+= gmac.o
 obj-$(CONFIG_SUNXI_AHCI)	+= ahci.o
 obj-$(CONFIG_MACH_SUN4I)	+= dram_sun4i_auto.o
diff --git a/board/sunxi/nand.c b/board/sunxi/nand.c
new file mode 100644
index 0000000..16e8e4d
--- /dev/null
+++ b/board/sunxi/nand.c
@@ -0,0 +1,315 @@ 
+/*
+ * Copyright (c) 2014-2015, Antmicro Ltd <www.antmicro.com>
+ * Copyright (c) 2015, AW-SOM Technologies <www.aw-som.com>
+ *
+ * SPDX-License-Identifier:     GPL-2.0+
+ */
+
+#include <common.h>
+#include <config.h>
+#include <asm/io.h>
+#include <nand.h>
+#include "nand.h" /* sunxi specific header */
+
+/* minimal "boot0" style NAND support for Allwinner A20 */
+
+/* temporary buffer in internal ram */
+#ifdef CONFIG_SPL_BUILD
+/* in SPL temporary buffer cannot be @ 0x0 */
+unsigned char temp_buf[SPL_WRITE_SIZE] __aligned(0x10) __section(".text#");
+#else
+/* put temporary buffer @ 0x0 */
+unsigned char *temp_buf = (unsigned char *)0x0;
+#endif
+
+#define MAX_RETRIES 10
+
+static int check_value_inner(int offset, int expected_bits,
+				int max_number_of_retries, int negation)
+{
+	int retries = 0;
+	do {
+		int val = readl(offset) & expected_bits;
+		if (negation ? !val : val)
+			return 1;
+		mdelay(1);
+		retries++;
+	} while (retries < max_number_of_retries);
+
+	return 0;
+}
+
+static inline int check_value(int offset, int expected_bits,
+				int max_number_of_retries)
+{
+	return check_value_inner(offset, expected_bits,
+					max_number_of_retries, 0);
+}
+
+static inline int check_value_negated(int offset, int unexpected_bits,
+					int max_number_of_retries)
+{
+	return check_value_inner(offset, unexpected_bits,
+					max_number_of_retries, 1);
+}
+
+void nand_set_clocks(void)
+{
+	uint32_t val;
+
+	writel(PORTC_PC_CFG0_NRB1 |
+		PORTC_PC_CFG0_NRB0 |
+		PORTC_PC_CFG0_NRE  |
+		PORTC_PC_CFG0_NCE0 |
+		PORTC_PC_CFG0_NCE1 |
+		PORTC_PC_CFG0_NCLE |
+		PORTC_PC_CFG0_NALE |
+		PORTC_PC_CFG0_NWE, PORTC_BASE + PORTC_PC_CFG0);
+
+	writel(PORTC_PC_CFG1_NDQ7 |
+		PORTC_PC_CFG1_NDQ6 |
+		PORTC_PC_CFG1_NDQ5 |
+		PORTC_PC_CFG1_NDQ4 |
+		PORTC_PC_CFG1_NDQ3 |
+		PORTC_PC_CFG1_NDQ2 |
+		PORTC_PC_CFG1_NDQ1 |
+		PORTC_PC_CFG1_NDQ0, PORTC_BASE + PORTC_PC_CFG1);
+
+	writel(PORTC_PC_CFG2_NCE7 |
+		PORTC_PC_CFG2_NCE6 |
+		PORTC_PC_CFG2_NCE5 |
+		PORTC_PC_CFG2_NCE4 |
+		PORTC_PC_CFG2_NCE3 |
+		PORTC_PC_CFG2_NCE2 |
+		PORTC_PC_CFG2_NWP, PORTC_BASE + PORTC_PC_CFG2);
+
+	writel(PORTC_PC_CFG3_NDQS, PORTC_BASE + PORTC_PC_CFG3);
+
+	val = readl(CCU_BASE + CCU_AHB_GATING_REG0);
+	writel(CCU_AHB_GATING_REG0_NAND | val, CCU_BASE + CCU_AHB_GATING_REG0);
+
+	val = readl(CCU_BASE + CCU_NAND_SCLK_CFG_REG);
+	writel(val | CCU_NAND_SCLK_CFG_REG_SCLK_GATING
+		| CCU_NAND_SCLK_CFG_REG_CLK_DIV_RATIO,
+		CCU_BASE + CCU_NAND_SCLK_CFG_REG);
+}
+
+int nand_initialized;
+
+void nand_init(void)
+{
+	board_nand_init(NULL);
+}
+
+uint32_t ecc_errors;
+
+/* read 0x400 bytes from real_addr to temp_buf */
+void nand_read_block(unsigned int real_addr, int syndrome)
+{
+	uint32_t val;
+	int ECC_OFF = 0;
+	uint16_t ecc_mode = 0;
+	uint16_t rand_seed;
+	uint32_t page;
+	uint16_t column;
+	uint32_t oob_offset;
+
+	if (!nand_initialized)
+		board_nand_init(NULL);
+
+	switch (CONFIG_SUNXI_ECC_STRENGTH) {
+	case 16:
+		ecc_mode = 0;
+		ECC_OFF = 0x20;
+		break;
+	case 24:
+		ecc_mode = 1;
+		ECC_OFF = 0x2e;
+		break;
+	case 28:
+		ecc_mode = 2;
+		ECC_OFF = 0x32;
+		break;
+	case 32:
+		ecc_mode = 3;
+		ECC_OFF = 0x3c;
+		break;
+	case 40:
+		ecc_mode = 4;
+		ECC_OFF = 0x4a;
+		break;
+	case 48:
+		ecc_mode = 4;
+		ECC_OFF = 0x52;
+		break;
+	case 56:
+		ecc_mode = 4;
+		ECC_OFF = 0x60;
+		break;
+	case 60:
+		ecc_mode = 4;
+		ECC_OFF = 0x0;
+		break;
+	case 64:
+		ecc_mode = 4;
+		ECC_OFF = 0x0;
+		break;
+	default:
+		ecc_mode = 0;
+		ECC_OFF = 0;
+	}
+
+	if (ECC_OFF == 0) {
+		printf("Unsupported ECC strength (%d)!\n",
+		       CONFIG_SUNXI_ECC_STRENGTH);
+		return;
+	}
+
+	/* clear temp_buf */
+	memset((void *)temp_buf, 0, SPL_WRITE_SIZE);
+
+	/* set CMD  */
+	writel(NFC_SEND_CMD1 | NFC_WAIT_FLAG | 0xff,
+	       NANDFLASHC_BASE + NANDFLASHC_CMD);
+
+	if (!check_value(NANDFLASHC_BASE + NANDFLASHC_ST, (1 << 1),
+			 MAX_RETRIES)) {
+		printf("Error while initilizing command interrupt\n");
+		return;
+	}
+
+	page = (real_addr / BLOCK_SIZE);
+	column = real_addr % BLOCK_SIZE;
+
+	if (syndrome) {
+		/* shift every 1kB in syndrome */
+		column += (column / SPL_WRITE_SIZE) * ECC_OFF;
+	}
+
+	/* clear ecc status */
+	writel(0, NANDFLASHC_BASE + NANDFLASHC_ECC_ST);
+
+	/* Choose correct seed */
+	if (syndrome)
+		rand_seed = random_seed_syndrome;
+	else
+		rand_seed = random_seed[page % 128];
+
+	writel((rand_seed << 16) | NFC_ECC_RANDOM_EN | NFC_ECC_EN
+		| NFC_ECC_PIPELINE | (ecc_mode << 12),
+		NANDFLASHC_BASE + NANDFLASHC_ECC_CTL);
+
+	val = readl(NANDFLASHC_BASE + NANDFLASHC_CTL);
+	writel(val | NFC_CTL_RAM_METHOD, NANDFLASHC_BASE + NANDFLASHC_CTL);
+
+	if (syndrome) {
+		writel(SPL_WRITE_SIZE, NANDFLASHC_BASE + NANDFLASHC_SPARE_AREA);
+	} else {
+		oob_offset = BLOCK_SIZE + (column / SPL_WRITE_SIZE) * ECC_OFF;
+		writel(oob_offset, NANDFLASHC_BASE + NANDFLASHC_SPARE_AREA);
+	}
+
+	/* DMAC */
+	writel(0x0, DMAC_BASE + DMAC_CFG_REG0); /* clr dma cmd */
+	/* read from REG_IO_DATA */
+	writel(NANDFLASHC_BASE + NANDFLASHC_IO_DATA,
+	       DMAC_BASE + DMAC_SRC_START_ADDR_REG0);
+	writel((uint32_t)temp_buf,
+	       DMAC_BASE + DMAC_DEST_START_ADDRR_REG0); /* read to RAM */
+	writel(DMAC_DDMA_PARA_REG_SRC_WAIT_CYC
+			| DMAC_DDMA_PARA_REG_SRC_BLK_SIZE,
+			DMAC_BASE + DMAC_DDMA_PARA_REG0);
+	writel(SPL_WRITE_SIZE, DMAC_BASE + DMAC_DDMA_BC_REG0); /* 1kB */
+	writel(DMAC_DDMA_CFG_REG_LOADING
+		| DMAC_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32
+		| DMAC_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32
+		| DMAC_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO
+		| DMAC_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
+		DMAC_BASE + DMAC_CFG_REG0);
+
+	writel(0x00E00530, NANDFLASHC_BASE + NANDFLASHC_RCMD_SET); /* READ */
+	writel(1, NANDFLASHC_BASE + NANDFLASHC_SECTOR_NUM);
+	writel(((page & 0xFFFF) << 16) | column,
+	       NANDFLASHC_BASE + NANDFLASHC_ADDR_LOW);
+	writel((page >> 16) & 0xFF, NANDFLASHC_BASE + NANDFLASHC_ADDR_HIGH);
+	writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_DATA_TRANS |
+		NFC_PAGE_CMD | NFC_WAIT_FLAG | /* ADDR_CYCLE */ (4 << 16) |
+		NFC_SEND_ADR | NFC_DATA_SWAP_METHOD | (syndrome ? NFC_SEQ : 0),
+		NANDFLASHC_BASE + NANDFLASHC_CMD);
+
+	if (!check_value(NANDFLASHC_BASE + NANDFLASHC_ST, (1 << 2),
+			 MAX_RETRIES)) {
+		printf("Error while initializing dma interrupt\n");
+		return;
+	}
+
+	if (!check_value_negated(DMAC_BASE + DMAC_CFG_REG0,
+				 DMAC_DDMA_CFG_REG_LOADING, MAX_RETRIES)) {
+		printf("Error while waiting for dma transfer to finish\n");
+		return;
+	}
+
+	if (readl(NANDFLASHC_BASE + NANDFLASHC_ECC_ST))
+		ecc_errors++;
+}
+
+int helper_load(uint32_t offs, unsigned int size, void *dest)
+{
+	uint32_t dst;
+	uint32_t count;
+	uint32_t count_c;
+	uint32_t adr = offs;
+
+	memset((void *)dest, 0x0, size); /* clean destination memory */
+	ecc_errors = 0;
+	for (dst = (uint32_t)dest;
+			dst < ((uint32_t)dest + size);
+			dst += SPL_WRITE_SIZE) {
+		/* if < 0x400000 then syndrome read */
+		nand_read_block(adr, adr < 0x400000);
+		count = dst - (uint32_t)dest;
+
+		if (size - count > SPL_WRITE_SIZE)
+			count_c = SPL_WRITE_SIZE;
+		else
+			count_c = size - count;
+
+		memcpy((void *)dst,
+		       (void *)temp_buf,
+		       count_c);
+		adr += SPL_WRITE_SIZE;
+	}
+	return ecc_errors;
+}
+
+int board_nand_init(struct nand_chip *nand)
+{
+	uint32_t val;
+
+	if (nand_initialized) {
+		printf("NAND already initialized, should not be here...\n");
+		return 0;
+	}
+	nand_initialized = 1;
+	nand_set_clocks();
+	val = readl(NANDFLASHC_BASE + NANDFLASHC_CTL);
+	/* enable and reset CTL */
+	writel(val | NFC_CTL_EN | NFC_CTL_RESET,
+	       NANDFLASHC_BASE + NANDFLASHC_CTL);
+
+	if (!check_value_negated(NANDFLASHC_BASE + NANDFLASHC_CTL,
+				 NFC_CTL_RESET, MAX_RETRIES)) {
+		printf("Couldn't initialize nand\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
+{
+	helper_load(offs, size, dest);
+	return 0;
+}
+
+void nand_deselect(void) {}
diff --git a/board/sunxi/nand.h b/board/sunxi/nand.h
new file mode 100644
index 0000000..b3c1e93
--- /dev/null
+++ b/board/sunxi/nand.h
@@ -0,0 +1,146 @@ 
+#ifndef NAND_H
+
+#define NAND_H
+
+#define PORTC_BASE                 0x01c20800
+#define CCU_BASE                   0x01c20000
+#define NANDFLASHC_BASE            0x01c03000
+#define DMAC_BASE                  0x01c02000
+
+#define CCU_AHB_GATING_REG0        0x60
+#define CCU_NAND_SCLK_CFG_REG      0x80
+#define CCU_AHB_GATING_REG0_NAND   (1 << 13)
+
+#define CCU_NAND_SCLK_CFG_REG_SCLK_GATING (1 << 31)
+#define CCU_NAND_SCLK_CFG_REG_CLK_DIV_RATIO (1 << 0)
+
+#define PORTC_PC_CFG0              0x48
+#define PORTC_PC_CFG1              0x4C
+#define PORTC_PC_CFG2              0x50
+#define PORTC_PC_CFG3              0x54
+
+#define PORTC_PC_CFG0_NRB1         (2 << 28)
+#define PORTC_PC_CFG0_NRB0         (2 << 24)
+#define PORTC_PC_CFG0_NRE          (2 << 20)
+#define PORTC_PC_CFG0_NCE0         (2 << 16)
+#define PORTC_PC_CFG0_NCE1         (2 << 12)
+#define PORTC_PC_CFG0_NCLE         (2 << 8)
+#define PORTC_PC_CFG0_NALE         (2 << 4)
+#define PORTC_PC_CFG0_NWE          (2 << 0)
+
+#define PORTC_PC_CFG1_NDQ7         (2 << 28)
+#define PORTC_PC_CFG1_NDQ6         (2 << 24)
+#define PORTC_PC_CFG1_NDQ5         (2 << 20)
+#define PORTC_PC_CFG1_NDQ4         (2 << 16)
+#define PORTC_PC_CFG1_NDQ3         (2 << 12)
+#define PORTC_PC_CFG1_NDQ2         (2 << 8)
+#define PORTC_PC_CFG1_NDQ1         (2 << 4)
+#define PORTC_PC_CFG1_NDQ0         (2 << 0)
+
+#define PORTC_PC_CFG2_NCE7         (2 << 24)
+#define PORTC_PC_CFG2_NCE6         (2 << 20)
+#define PORTC_PC_CFG2_NCE5         (2 << 16)
+#define PORTC_PC_CFG2_NCE4         (2 << 12)
+#define PORTC_PC_CFG2_NCE3         (2 << 8)
+#define PORTC_PC_CFG2_NCE2         (2 << 4)
+#define PORTC_PC_CFG2_NWP          (2 << 0)
+
+#define PORTC_PC_CFG3_NDQS         (2 << 0)
+
+#define BLOCK_SIZE                 0x2000
+
+#define DMAC_CFG_REG0              0x300
+#define DMAC_SRC_START_ADDR_REG0   0x304
+#define DMAC_DEST_START_ADDRR_REG0 0x308
+#define DMAC_DDMA_BC_REG0          0x30C
+#define DMAC_DDMA_PARA_REG0        0x318
+
+#define DMAC_DDMA_CFG_REG_LOADING  (1 << 31)
+#define DMAC_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32 (2 << 25)
+#define DMAC_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32 (2 << 9)
+#define DMAC_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO (1 << 5)
+#define DMAC_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC (3 << 0)
+
+#define DMAC_DDMA_PARA_REG_SRC_WAIT_CYC (0x0F << 0)
+#define DMAC_DDMA_PARA_REG_SRC_BLK_SIZE (0x7F << 8)
+
+#define NANDFLASHC_CTL             0x00000000
+
+#define NFC_CTL_EN                 (1 << 0)
+#define NFC_CTL_RESET              (1 << 1)
+#define NFC_CTL_RAM_METHOD         (1 << 14)
+
+#define NANDFLASHC_ST              0x00000004
+#define NANDFLASHC_INT             0x00000008
+#define NANDFLASHC_TIMING_CTL      0x0000000C
+#define NANDFLASHC_TIMING_CFG      0x00000010
+#define NANDFLASHC_ADDR_LOW        0x00000014
+#define NANDFLASHC_ADDR_HIGH       0x00000018
+#define NANDFLASHC_SECTOR_NUM      0x0000001C
+#define NANDFLASHC_CNT             0x00000020
+#define NANDFLASHC_CMD             0x00000024
+#define NANDFLASHC_RCMD_SET        0x00000028
+#define NANDFLASHC_WCMD_SET        0x0000002C
+#define NANDFLASHC_IO_DATA         0x00000030
+#define NANDFLASHC_ECC_CTL         0x00000034
+
+#define NFC_ECC_EN                 (1 << 0)
+#define NFC_ECC_PIPELINE           (1 << 3)
+#define NFC_ECC_EXCEPTION          (1 << 4)
+#define NFC_ECC_BLOCK_SIZE         (1 << 5)
+#define NFC_ECC_RANDOM_EN          (1 << 9)
+#define NFC_ECC_RANDOM_DIRECTION   (1 << 10)
+
+#define NANDFLASHC_ECC_ST          0x00000038
+#define NANDFLASHC_DEBUG           0x0000003C
+#define NANDFLASHC_ECC_CNT0        0x00000040
+#define NANDFLASHC_ECC_CNT1        0x00000044
+#define NANDFLASHC_ECC_CNT2        0x00000048
+#define NANDFLASHC_ECC_CNT3        0x0000004C
+#define NANDFLASHC_USER_DATA_BASE  0x00000050
+#define NANDFLASHC_EFNAND_STATUS   0x00000090
+#define NANDFLASHC_SPARE_AREA      0x000000A0
+#define NANDFLASHC_PATTERN_ID      0x000000A4
+#define NANDFLASHC_RAM0_BASE       0x00000400
+#define NANDFLASHC_RAM1_BASE       0x00000800
+
+#define NFC_SEND_ADR               (1 << 19)
+#define NFC_ACCESS_DIR             (1 << 20)
+#define NFC_DATA_TRANS             (1 << 21)
+#define NFC_SEND_CMD1              (1 << 22)
+#define NFC_WAIT_FLAG              (1 << 23)
+#define NFC_SEND_CMD2              (1 << 24)
+#define NFC_SEQ                    (1 << 25)
+#define NFC_DATA_SWAP_METHOD       (1 << 26)
+#define NFC_ROW_AUTO_INC           (1 << 27)
+#define NFC_SEND_CMD3              (1 << 28)
+#define NFC_SEND_CMD4              (1 << 29)
+
+#define NFC_PAGE_CMD               (2 << 30)
+
+#define SPL_WRITE_SIZE             CONFIG_CMD_SPL_WRITE_SIZE
+
+/* random seed used by linux */
+const uint16_t random_seed[128] = {
+	0x2b75, 0x0bd0, 0x5ca3, 0x62d1, 0x1c93, 0x07e9, 0x2162, 0x3a72,
+	0x0d67, 0x67f9, 0x1be7, 0x077d, 0x032f, 0x0dac, 0x2716, 0x2436,
+	0x7922, 0x1510, 0x3860, 0x5287, 0x480f, 0x4252, 0x1789, 0x5a2d,
+	0x2a49, 0x5e10, 0x437f, 0x4b4e, 0x2f45, 0x216e, 0x5cb7, 0x7130,
+	0x2a3f, 0x60e4, 0x4dc9, 0x0ef0, 0x0f52, 0x1bb9, 0x6211, 0x7a56,
+	0x226d, 0x4ea7, 0x6f36, 0x3692, 0x38bf, 0x0c62, 0x05eb, 0x4c55,
+	0x60f4, 0x728c, 0x3b6f, 0x2037, 0x7f69, 0x0936, 0x651a, 0x4ceb,
+	0x6218, 0x79f3, 0x383f, 0x18d9, 0x4f05, 0x5c82, 0x2912, 0x6f17,
+	0x6856, 0x5938, 0x1007, 0x61ab, 0x3e7f, 0x57c2, 0x542f, 0x4f62,
+	0x7454, 0x2eac, 0x7739, 0x42d4, 0x2f90, 0x435a, 0x2e52, 0x2064,
+	0x637c, 0x66ad, 0x2c90, 0x0bad, 0x759c, 0x0029, 0x0986, 0x7126,
+	0x1ca7, 0x1605, 0x386a, 0x27f5, 0x1380, 0x6d75, 0x24c3, 0x0f8e,
+	0x2b7a, 0x1418, 0x1fd1, 0x7dc1, 0x2d8e, 0x43af, 0x2267, 0x7da3,
+	0x4e3d, 0x1338, 0x50db, 0x454d, 0x764d, 0x40a3, 0x42e6, 0x262b,
+	0x2d2e, 0x1aea, 0x2e17, 0x173d, 0x3a6e, 0x71bf, 0x25f9, 0x0a5d,
+	0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db,
+};
+
+/* random seed used for syndrome calls */
+const uint16_t random_seed_syndrome = 0x4a80;
+
+#endif /* end of include guard: NAND_H */