Commit of aml rawnand controller driver changes so far.

Change-Id: Ib97a23a84ef448ddbe35c1b1365540808d016cfb
diff --git a/system/core/devmgr/devmgr-coordinator.c b/system/core/devmgr/devmgr-coordinator.c
index ec0c1aa..ece7018 100644
--- a/system/core/devmgr/devmgr-coordinator.c
+++ b/system/core/devmgr/devmgr-coordinator.c
@@ -585,7 +585,7 @@
     // Inherit devmgr's environment (including kernel cmdline)
     launchpad_clone(lp, LP_CLONE_ENVIRON);
 
-    const char* nametable[2] = { "/boot", "/svc", };
+    const char* nametable[3] = { "/boot", "/svc", "/tmp"};
     size_t name_count = 0;
 
     //TODO: eventually devhosts should not have vfs access
@@ -597,6 +597,10 @@
         launchpad_add_handle(lp, h, PA_HND(PA_NS_DIR, name_count++));
     }
 
+    if ((h = fs_clone("tmp")) != ZX_HANDLE_INVALID) {
+        launchpad_add_handle(lp, h, PA_HND(PA_NS_DIR, name_count++));
+    }
+
     launchpad_set_nametable(lp, name_count, nametable);
 
     //TODO: limit root job access to root devhost only
diff --git a/system/dev/block/rawnand/aml-rawnand.c b/system/dev/block/rawnand/aml-rawnand.c
new file mode 100644
index 0000000..df46639
--- /dev/null
+++ b/system/dev/block/rawnand/aml-rawnand.c
@@ -0,0 +1,1134 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <bits/limits.h>
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <ddk/protocol/platform-device.h>
+#include <ddk/protocol/rawnand.h>
+#include <ddk/io-buffer.h>
+#include <hw/reg.h>
+
+#include <zircon/assert.h>
+#include <zircon/threads.h>
+#include <zircon/types.h>
+#include <zircon/status.h>
+#include <sync/completion.h>
+
+#include <string.h>
+
+#include <soc/aml-common/aml-rawnand.h>
+#include "nand.h"
+
+#include "aml_rawnand_api.h"
+
+static const uint32_t chipsel[2] = {NAND_CE0, NAND_CE1};
+
+struct aml_controller_params aml_params = {
+    8,
+    2,
+    /* The 2 following values are overwritten by page0 contents */
+    1,                  /* rand-mode is 1 for page0 */
+    AML_ECC_BCH60_1K,   /* This is the BCH setting for page0 */
+};
+
+uint32_t
+aml_get_ecc_pagesize(aml_rawnand_t *rawnand, uint32_t ecc_mode)
+{
+    uint32_t ecc_page;
+    
+    switch (ecc_mode) {
+    case AML_ECC_BCH8:
+        ecc_page = 512;
+        break;
+    case AML_ECC_BCH8_1K:        
+        ecc_page = 1024;
+        break;
+    default:
+        ecc_page = 0;
+        break;
+    }
+    return ecc_page;
+}
+
+static void aml_cmd_idle(aml_rawnand_t *rawnand, uint32_t time)
+{
+    uint32_t cmd = 0;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    cmd = rawnand->chip_select | AML_CMD_IDLE | (time & 0x3ff);
+    writel(cmd, reg + P_NAND_CMD);
+}
+
+static zx_status_t aml_wait_cmd_finish(aml_rawnand_t *rawnand,
+                                       unsigned int timeout_ms)
+{
+    uint32_t cmd_size = 0;
+    zx_status_t ret = ZX_OK;
+    uint64_t total_time = 0;
+    uint32_t numcmds;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    /* wait until cmd fifo is empty */
+    while (true) {
+        cmd_size = readl(reg + P_NAND_CMD);
+        numcmds = (cmd_size >> 22) & 0x1f;
+        if (numcmds == 0)
+            break;
+//        zxlogf(ERROR, "aml_wait_cmd_finish: numcmds =  %u\n",
+//               numcmds);            
+        usleep(10);
+        total_time += 10;
+        if (total_time > (timeout_ms * 1000)) {
+            ret = ZX_ERR_TIMED_OUT;
+            break;
+        }
+    }
+//    zxlogf(ERROR, "aml_wait_cmd_finish: Waited for %lu usecs, numcmds = %d\n",
+//           total_time, numcmds);    
+    if (ret == ZX_ERR_TIMED_OUT)
+        zxlogf(ERROR, "wait for empty cmd FIFO time out\n");
+    return ret;
+}
+
+static void aml_cmd_seed(aml_rawnand_t *rawnand, uint32_t seed)
+{       
+    uint32_t cmd;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    cmd = AML_CMD_SEED | (0xc2 + (seed & 0x7fff));
+    writel(cmd, reg + P_NAND_CMD);
+}
+
+static void aml_cmd_n2m(aml_rawnand_t *rawnand, uint32_t ecc_pages,
+                        uint32_t ecc_pagesize)
+{
+    uint32_t cmd;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    cmd = CMDRWGEN(AML_CMD_N2M,
+                   rawnand->controller_params.rand_mode,
+                   rawnand->controller_params.bch_mode,
+                   0,
+		   ecc_pagesize,
+                   ecc_pages);
+    writel(cmd, reg + P_NAND_CMD);    
+}
+
+static void aml_cmd_m2n_page0(aml_rawnand_t *rawnand)
+{
+    /* TODO */
+}
+
+static void aml_cmd_m2n(aml_rawnand_t *rawnand, uint32_t ecc_pages,
+                        uint32_t ecc_pagesize)
+{
+    uint32_t cmd;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    cmd = CMDRWGEN(AML_CMD_M2N,
+                   rawnand->controller_params.rand_mode,
+                   rawnand->controller_params.bch_mode,
+                   0, ecc_pagesize,
+                   ecc_pages);
+    writel(cmd, reg + P_NAND_CMD);    
+}
+
+static void aml_cmd_n2m_page0(aml_rawnand_t *rawnand)
+{
+    uint32_t cmd;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    /*
+     * For page0 reads, we must use AML_ECC_BCH60_1K,
+     * and rand-mode == 1.
+     */
+    cmd = CMDRWGEN(AML_CMD_N2M,
+                   1,                   /* force rand_mode */
+                   AML_ECC_BCH60_1K,    /* force bch_mode  */
+                   1,                   /* shortm == 1     */
+                   384 >> 3,
+                   1);
+    writel(cmd, reg + P_NAND_CMD);    
+}
+
+static zx_status_t aml_wait_dma_finish(aml_rawnand_t *rawnand) 
+{
+    aml_cmd_idle(rawnand, 0);
+    aml_cmd_idle(rawnand, 0);
+    return aml_wait_cmd_finish(rawnand, DMA_BUSY_TIMEOUT);
+}
+
+/*
+ * Return the aml_info_format struct corresponding to the i'th 
+ * ECC page. THIS ASSUMES user_mode == 2 (2 OOB bytes per ECC page).
+ */
+static struct aml_info_format *aml_info_ptr(aml_rawnand_t *rawnand,
+                                            int i)
+{
+    struct aml_info_format *p;
+
+    p = (struct aml_info_format *)rawnand->info_buf;
+    return &p[i];
+}
+
+/*
+ * In the case where user_mode == 2, info_buf contains one nfc_info_format 
+ * struct per ECC page on completion of a read. This 8 byte structure has 
+ * the 2 OOB bytes and ECC/error status
+ */
+static zx_status_t aml_get_oob_byte(aml_rawnand_t *rawnand,
+                                    uint8_t *oob_buf)
+{
+    struct aml_info_format *info;
+    int count = 0;
+    uint32_t i, ecc_pagesize, ecc_pages;
+
+    ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+    ecc_pages = rawnand->writesize / ecc_pagesize;
+    /*
+     * user_mode is 2 in our case - 2 bytes of OOB for every
+     * ECC page.
+     */
+    if (rawnand->controller_params.user_mode != 2)
+        return ZX_ERR_NOT_SUPPORTED;
+    for (i = 0;
+         i < ecc_pages;
+         i++) {
+        info = aml_info_ptr(rawnand, i);
+        oob_buf[count++] = info->info_bytes & 0xff;
+        oob_buf[count++] = (info->info_bytes >> 8) & 0xff;
+    }
+    return ZX_OK;
+}
+
+static zx_status_t aml_set_oob_byte(aml_rawnand_t *rawnand,
+                                    uint8_t *oob_buf,
+                                    uint32_t ecc_pages)
+{
+    struct aml_info_format *info;
+    int count = 0;
+    uint32_t i;
+
+    /*
+     * user_mode is 2 in our case - 2 bytes of OOB for every
+     * ECC page.
+     */
+    if (rawnand->controller_params.user_mode != 2)
+        return ZX_ERR_NOT_SUPPORTED;
+    for (i = 0; i < ecc_pages; i++) {
+        info = aml_info_ptr(rawnand, i);
+        info->info_bytes = oob_buf[count] | (oob_buf[count + 1] << 8);
+        count += 2;
+    }
+    return ZX_OK;
+}
+
+/*
+ * Returns the maximum bitflips corrected on this NAND page
+ * (the maximum bitflips across all of the ECC pages in this page).
+ */
+static int aml_get_ecc_corrections(aml_rawnand_t *rawnand, int ecc_pages)
+{
+    struct aml_info_format *info;
+    int bitflips = 0, i;
+    uint8_t zero_cnt;
+
+    for (i = 0; i < ecc_pages; i++) {
+        info = aml_info_ptr(rawnand, i);
+        if (info->ecc.eccerr_cnt == 0x3f) {
+            /*
+             * Why are we checking for zero_cnt here ?
+             * Per Amlogic HW architect, this is to deal with 
+             * blank NAND pages. The entire blank page is 0xff.
+             * When read with scrambler, the page will be ECC
+             * uncorrectable, but if the total of zeroes in this
+             * page is less than a threshold, then we know this is
+             * blank page.
+             */
+            zero_cnt = info->zero_cnt & 0x3f;
+#if 0
+            zxlogf(ERROR, "%s: zero_cnt = %x\n",
+                   __func__, zero_cnt);
+#endif
+            if (rawnand->controller_params.rand_mode &&
+                (zero_cnt < rawnand->controller_params.ecc_strength)) {
+                zxlogf(ERROR, "%s: Returning ECC failure\n",
+                       __func__);                    
+                return ECC_CHECK_RETURN_FF;
+            }
+            rawnand->stats.failed++;
+            continue;
+        }
+        rawnand->stats.ecc_corrected += info->ecc.eccerr_cnt;
+        bitflips = MAX(bitflips, info->ecc.eccerr_cnt);
+    }
+    return bitflips;
+}
+
+static zx_status_t aml_check_ecc_pages(aml_rawnand_t *rawnand, int ecc_pages)
+{
+    struct aml_info_format *info;
+    int i;
+
+    for (i = 0; i < ecc_pages; i++) {
+        info = aml_info_ptr(rawnand, i);
+        if (info->ecc.completed == 0) {
+#if 1
+            zxlogf(ERROR, "%s: READ not completed/valid info_bytes = %x zero_cnt = %x ecc = %x\n",
+                   __func__, info->info_bytes, info->zero_cnt, *(uint8_t *)&info->ecc);
+#endif
+            return ZX_ERR_IO;
+        }
+    }
+    return ZX_OK;
+}
+
+static zx_status_t aml_queue_rb(aml_rawnand_t *rawnand)
+{
+    uint32_t cmd, cfg;
+    zx_status_t status;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    rawnand->req_completion = COMPLETION_INIT;    
+    cfg = readl(reg + P_NAND_CFG);
+    cfg |= (1 << 21);
+    writel(cfg, reg + P_NAND_CFG);
+    aml_cmd_idle(rawnand, NAND_TWB_TIME_CYCLE);
+    cmd = rawnand->chip_select | AML_CMD_CLE | (NAND_CMD_STATUS & 0xff);
+    writel(cmd, reg + P_NAND_CMD);
+    aml_cmd_idle(rawnand, NAND_TWB_TIME_CYCLE);
+    cmd = AML_CMD_RB | AML_CMD_IO6 | (1 << 16) | (0x18 & 0x1f);
+    writel(cmd, reg + P_NAND_CMD);
+    aml_cmd_idle(rawnand, 2);
+    status = completion_wait(&rawnand->req_completion,
+                             ZX_SEC(1));
+    if (status == ZX_ERR_TIMED_OUT) {
+        zxlogf(ERROR, "%s: Request timed out, not woken up from irq\n",
+               __func__);
+    }
+    return status;
+}
+
+/*
+ * This function just stuffs the parameters needed into the 
+ * rawnand controller struct, so they can be retrieved later.
+ */
+static void aml_select_chip(aml_rawnand_t *rawnand, int chip)
+{
+    rawnand->chip_select = chipsel[chip];
+}
+
+void aml_cmd_ctrl(aml_rawnand_t *rawnand,
+                  int cmd, unsigned int ctrl)
+{
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    if (cmd == NAND_CMD_NONE)
+        return;
+    if (ctrl & NAND_CLE)
+        cmd = rawnand->chip_select | AML_CMD_CLE | (cmd & 0xff);
+    else
+        cmd = rawnand->chip_select | AML_CMD_ALE | (cmd & 0xff);
+    writel(cmd, reg + P_NAND_CMD);
+}
+
+/* Read statys byte */
+uint8_t aml_read_byte(aml_rawnand_t *rawnand)
+{
+    uint32_t cmd;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    cmd = rawnand->chip_select | AML_CMD_DRD | 0;
+    nandctrl_send_cmd(rawnand, cmd);
+
+    aml_cmd_idle(rawnand, NAND_TWB_TIME_CYCLE);
+    
+    aml_cmd_idle(rawnand, 0);
+    aml_cmd_idle(rawnand, 0);
+    aml_wait_cmd_finish(rawnand, 1000);
+    return readb(reg + P_NAND_BUF);
+}
+
+static void aml_set_clock_rate(aml_rawnand_t* rawnand,
+                               uint32_t clk_freq)
+{
+    uint32_t always_on = 0x1 << 24;
+    uint32_t clk;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[CLOCKREG_WINDOW]);
+    
+    /* For Amlogic type  AXG */
+    always_on = 0x1 << 28;
+    switch (clk_freq) {
+    case 24:
+        clk = 0x80000201;
+        break;
+    case 112:
+        clk = 0x80000249;
+        break;
+    case 200:
+        clk = 0x80000245;
+        break;
+    case 250:
+        clk = 0x80000244;
+        break;
+    default:
+        clk = 0x80000245;
+        break;
+    } 
+    clk |= always_on;
+    writel(clk, reg);
+}
+
+static void aml_clock_init(aml_rawnand_t* rawnand)
+{
+    uint32_t sys_clk_rate, bus_cycle, bus_timing;
+
+    sys_clk_rate = 200;
+    aml_set_clock_rate(rawnand, sys_clk_rate);
+    bus_cycle  = 6;
+    bus_timing = bus_cycle + 1;
+    nandctrl_set_cfg(rawnand, 0);
+    nandctrl_set_timing_async(rawnand, bus_timing, (bus_cycle - 1));
+    nandctrl_send_cmd(rawnand, 1<<31);
+}
+
+static void aml_adjust_timings(aml_rawnand_t* rawnand,
+                               uint32_t tRC_min, uint32_t tREA_max,
+                               uint32_t RHOH_min)
+{
+    int sys_clk_rate, bus_cycle, bus_timing;
+
+    if (!tREA_max)
+        tREA_max = 20;
+    if (!RHOH_min)
+        RHOH_min = 15;
+    if (tREA_max > 30)
+        sys_clk_rate = 112;
+    else if (tREA_max > 16)
+        sys_clk_rate = 200;
+    else
+        sys_clk_rate = 250;
+    aml_set_clock_rate(rawnand, sys_clk_rate);
+    bus_cycle  = 6;
+    bus_timing = bus_cycle + 1;
+    nandctrl_set_cfg(rawnand, 0);
+    nandctrl_set_timing_async(rawnand, bus_timing, (bus_cycle - 1));
+    nandctrl_send_cmd(rawnand, 1<<31);
+}
+
+zx_status_t aml_read_page_hwecc(aml_rawnand_t *rawnand,
+                                void *data,
+                                void *oob,
+                                uint32_t nandpage,
+                                int *ecc_correct,
+                                bool page0)
+{
+    uint32_t cmd;
+    zx_status_t status;
+    uint64_t daddr = rawnand->data_buf_paddr;
+    uint64_t iaddr = rawnand->info_buf_paddr;
+    int ecc_c;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    uint32_t ecc_pagesize, ecc_pages;
+
+    if (!page0) {
+        ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+        ecc_pages = rawnand->writesize / ecc_pagesize;
+    } else
+        ecc_pages = 1;
+    aml_select_chip(rawnand, 0);
+    /*
+     * Flush and invalidate (only invalidate is really needed), the
+     * info and data buffers before kicking off DMA into them.
+     */
+    io_buffer_cache_flush_invalidate(&rawnand->data_buffer, 0,
+                                     rawnand->writesize);
+    io_buffer_cache_flush_invalidate(&rawnand->info_buffer, 0,
+           ecc_pages * sizeof(struct aml_info_format));
+    /* Send the page address into the controller */
+    nand_command(rawnand, NAND_CMD_READ0, 0x00, nandpage);
+    cmd = GENCMDDADDRL(AML_CMD_ADL, daddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDDADDRH(AML_CMD_ADH, daddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDIADDRL(AML_CMD_AIL, iaddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDIADDRH(AML_CMD_AIH, iaddr);
+    writel(cmd, reg + P_NAND_CMD);
+    /* page0 needs randomization. so force it for page0 */
+    if (page0 || rawnand->controller_params.rand_mode)
+        /*
+         * Only need to set the seed if randomizing 
+         * is enabled.
+         */
+	aml_cmd_seed(rawnand, nandpage);
+    if (!page0)
+        aml_cmd_n2m(rawnand, ecc_pages, ecc_pagesize);
+    else
+        aml_cmd_n2m_page0(rawnand);
+    status = aml_wait_dma_finish(rawnand);
+    if (status != ZX_OK)
+        return status;
+    aml_queue_rb(rawnand);
+#if 0
+    {
+        struct aml_info_format *info_blk;
+
+        info_blk = (struct aml_info_format *)rawnand->info_buf;
+        for (uint32_t i = 0; i < ecc_pages; i++) {
+            zxlogf(ERROR, "info_bytes = %x\n",
+                   info_blk->info_bytes);
+            zxlogf(ERROR, "zero_cnt = %x\n",
+                   info_blk->zero_cnt);
+            zxlogf(ERROR, "ecc = %x\n",
+                   *(uint8_t *)&(info_blk->ecc));
+            zxlogf(ERROR, "reserved = %x\n",
+                   info_blk->reserved);
+            info_blk++;
+        }
+    }
+#endif
+    status = aml_check_ecc_pages(rawnand, ecc_pages);
+    if (status != ZX_OK)
+        return status;
+    /*
+     * Finally copy out the data and oob as needed
+     */
+    if (data != NULL) {
+        if (!page0)
+            memcpy(data, rawnand->data_buf, rawnand->writesize);
+        else
+            memcpy(data, rawnand->data_buf, 384);         /* XXX - FIXME */
+    }
+    if (oob != NULL)
+        status = aml_get_oob_byte(rawnand, oob);
+    ecc_c = aml_get_ecc_corrections(rawnand, ecc_pages);
+    if (ecc_c < 0) {
+            zxlogf(ERROR, "%s: Uncorrectable ECC error on read\n",
+                   __func__);
+            status = ZX_ERR_IO;
+    }
+    *ecc_correct = ecc_c;
+    return status;
+}
+
+/*
+ * TODO : Right now, the driver uses a buffer for DMA, which 
+ * is not needed. We should initiate DMA to/from pages passed in.
+ */
+zx_status_t aml_write_page_hwecc(aml_rawnand_t *rawnand,
+                                 void *data,
+                                 void *oob,
+                                 uint32_t nandpage,
+                                 bool page0)
+{
+    uint32_t cmd;
+    uint64_t daddr = rawnand->data_buf_paddr;
+    uint64_t iaddr = rawnand->info_buf_paddr;
+    zx_status_t status;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    uint32_t ecc_pages, ecc_pagesize;
+
+    if (!page0) {
+        ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+        ecc_pages = rawnand->writesize / ecc_pagesize;
+    } else
+        ecc_pages = 1;
+    aml_select_chip(rawnand, 0);
+    if (data != NULL) {
+        memcpy(rawnand->data_buf, data, rawnand->writesize);
+        io_buffer_cache_flush(&rawnand->data_buffer, 0,
+                              rawnand->writesize);
+    }
+    if (oob != NULL) {
+        aml_set_oob_byte(rawnand, oob, ecc_pages);
+        io_buffer_cache_flush_invalidate(&rawnand->info_buffer, 0,
+            ecc_pages * sizeof(struct aml_info_format));
+    }
+    nand_command(rawnand, NAND_CMD_SEQIN, 0x00, nandpage);
+    cmd = GENCMDDADDRL(AML_CMD_ADL, daddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDDADDRH(AML_CMD_ADH, daddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDIADDRL(AML_CMD_AIL, iaddr);
+    writel(cmd, reg + P_NAND_CMD);
+    cmd = GENCMDIADDRH(AML_CMD_AIH, iaddr);
+    writel(cmd, reg + P_NAND_CMD);
+    /* page0 needs randomization. so force it for page0 */
+    if (page0 || rawnand->controller_params.rand_mode)
+        /*
+         * Only need to set the seed if randomizing 
+         * is enabled.
+         */
+	aml_cmd_seed(rawnand, nandpage);
+    if (!page0)    
+        aml_cmd_m2n(rawnand, ecc_pages, ecc_pagesize);
+    else
+        aml_cmd_m2n_page0(rawnand);
+    status = aml_wait_dma_finish(rawnand);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: error from wait_dma_finish\n",
+               __func__);        
+        return status;
+    }
+    nand_command(rawnand, NAND_CMD_PAGEPROG, -1, -1);    
+    return nand_wait(rawnand, 20);
+}
+
+/*
+ * Erase entry point into the Amlogic driver.
+ * nandblock : NAND erase block address.
+ */
+zx_status_t aml_erase_block(aml_rawnand_t *rawnand,
+                            uint32_t nandpage)
+{
+    /* nandblock has to be erasesize aligned */
+    if (nandpage % rawnand->erasesize_pages) {
+        zxlogf(ERROR, "%s: NAND block %u must be a erasesize_pages (%u) multiple\n",
+               __func__, nandpage, rawnand->erasesize_pages);
+        return ZX_ERR_INVALID_ARGS;
+    }
+    aml_select_chip(rawnand, 0);
+    nand_command(rawnand, NAND_CMD_ERASE1, -1, nandpage);
+    nand_command(rawnand, NAND_CMD_ERASE2, -1, -1);
+    return nand_wait(rawnand, 400);
+}
+
+static zx_status_t aml_get_flash_type(aml_rawnand_t *rawnand)
+{
+    uint8_t nand_maf_id, nand_dev_id;
+    uint8_t id_data[8];
+    struct nand_chip_table *nand_chip;
+    uint32_t i;
+    
+    aml_select_chip(rawnand, 0);
+    nand_command(rawnand,  NAND_CMD_RESET, -1, -1);
+    nand_command(rawnand, NAND_CMD_READID, 0x00, -1);
+    /* Read manufacturer and device IDs */
+    nand_maf_id = aml_read_byte(rawnand);
+    nand_dev_id = aml_read_byte(rawnand);
+    /* Read again */
+    nand_command(rawnand, NAND_CMD_READID, 0x00, -1);
+    /* Read entire ID string */
+    for (i = 0; i < 8; i++)
+        id_data[i] = aml_read_byte(rawnand);
+    if (id_data[0] != nand_maf_id || id_data[1] != nand_dev_id) {
+        zxlogf(ERROR, "second ID read did not match %02x,%02x against %02x,%02x\n",
+               nand_maf_id, nand_dev_id, id_data[0], id_data[1]);
+    }
+
+    zxlogf(ERROR, "Mfg id/Dev id = %02x %02x\n", nand_maf_id, nand_dev_id);
+            
+    nand_chip = find_nand_chip_table(nand_maf_id, nand_dev_id);
+    if (nand_chip == NULL) {
+        
+#if 0
+        nand_chip = &default_chip;
+#else
+        zxlogf(ERROR, "%s: Cound not find matching NAND chip. NAND chip unsupported."
+               " This is FATAL\n",
+               __func__);
+        return ZX_ERR_UNAVAILABLE;
+#endif
+    }
+#if 0
+    zxlogf(ERROR, "Found matching device %s:%s\n",
+           nand_chip->manufacturer_name,
+           nand_chip->device_name);
+#endif
+    if (true /* nand_chip->extended_id_nand */) {
+	/*
+	 * Initialize pagesize, eraseblk size, oobsize and 
+	 * buswidth from extended parameters queried just now.
+	 */
+	uint8_t extid = id_data[3];
+
+        zxlogf(ERROR, "%s: Found extended id NAND extid = 0x%x\n",
+               __func__, extid);
+	rawnand->writesize = 1024 << (extid & 0x03);
+	extid >>= 2;
+	/* Calc oobsize */
+	rawnand->oobsize = (8 << (extid & 0x01)) *
+	    (rawnand->writesize >> 9);
+	extid >>= 2;
+	/* Calc blocksize. Blocksize is multiples of 64KiB */
+	rawnand->erasesize = (64 * 1024) << (extid & 0x03);
+	extid >>= 2;
+	/* Get buswidth information */
+	rawnand->bus_width = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
+        zxlogf(ERROR, "%s: writesize = %u, oobsize = %u, erasesize = %u bus_width = %u\n",
+               __func__, rawnand->writesize, rawnand->oobsize,
+	       rawnand->erasesize, rawnand->bus_width);
+#if 0
+	/*                                                              
+	 * Toshiba 24nm raw SLC (i.e., not BENAND) have 32B OOB per     
+	 * 512B page. For Toshiba SLC, we decode the 5th/6th byte as    
+	 * follows:                                                     
+	 * - ID byte 6, bits[2:0]: 100b -> 43nm, 101b -> 32nm,          
+	 *                         110b -> 24nm                         
+	 * - ID byte 5, bit[7]:    1 -> BENAND, 0 -> raw SLC            
+	 */
+	if (id_len >= 6 && id_data[0] == NAND_MFR_TOSHIBA &&
+	    nand_is_slc(chip) &&
+	    (id_data[5] & 0x7) == 0x6 /* 24nm */ &&
+	    !(id_data[4] & 0x80) /* !BENAND */) {
+	    mtd->oobsize = 32 * mtd->writesize >> 9;
+	}
+#endif
+    } else {
+	/*
+	 * Initialize pagesize, eraseblk size, oobsize and 
+	 * buswidth from values in table.
+	 */
+	rawnand->writesize = nand_chip->page_size;
+	rawnand->oobsize = nand_chip->oobsize;
+	rawnand->erasesize = nand_chip->erase_block_size;
+	rawnand->bus_width = nand_chip->bus_width;
+    }
+    rawnand->erasesize_pages =
+        rawnand->erasesize / rawnand->writesize;
+    rawnand->chipsize = nand_chip->chipsize;
+    rawnand->page_shift = ffs(rawnand->writesize) - 1;
+
+    zxlogf(ERROR, "%s: chipsize = %lu, page_shift = %u\n",
+           __func__, rawnand->chipsize, rawnand->page_shift);
+    
+    /*
+     * We found a matching device in our database, use it to 
+     * initialize. Adjust timings and set various parameters.
+     */
+    zxlogf(ERROR, "Adjusting timings based on datasheet values\n");
+    aml_adjust_timings(rawnand,
+                       nand_chip->timings.tRC_min,
+                       nand_chip->timings.tREA_max, 
+                       nand_chip->timings.RHOH_min);
+    return ZX_OK;
+}
+
+static int aml_rawnand_irq_thread(void *arg) {
+    zxlogf(INFO, "aml_rawnand_irq_thread start\n");
+
+    aml_rawnand_t* rawnand = arg;
+
+    while (1) {
+        uint64_t slots;
+
+        zx_status_t result = zx_interrupt_wait(rawnand->irq_handle, &slots);
+        if (result != ZX_OK) {
+            zxlogf(ERROR,
+                   "aml_rawnand_irq_thread: zx_interrupt_wait got %d\n",
+                   result);
+            break;
+        }
+        /*
+         * Wakeup blocked requester on 
+         * completion_wait(&rawnand->req_completion, ZX_TIME_INFINITE);
+         */
+        completion_signal(&rawnand->req_completion);
+    }
+
+    return 0;
+}
+
+static void
+aml_rawnand_query(void *ctx, nand_info_t *info_out,
+                  size_t *nand_op_size_out)
+{
+
+}
+
+/*
+ * Queue up a read/write request to the rawnand.
+ */
+static void
+aml_rawnand_queue(void *ctx, nand_op_t *op)
+{
+
+}
+
+static void
+aml_rawnand_bad_block_list(void *ctx, size_t *num_bad_blocks,
+                           uint64_t **blocklist)
+{
+
+}
+
+static nand_protocol_ops_t rawnand_ops = {
+    .query = aml_rawnand_query,
+    .queue = aml_rawnand_queue,
+    .get_bad_block_list = aml_rawnand_bad_block_list
+};
+
+static void aml_rawnand_release(void* ctx) {
+    aml_rawnand_t* rawnand = ctx;
+
+    for (rawnand_addr_window_t wnd = 0 ;
+         wnd < ADDR_WINDOW_COUNT ;
+         wnd++)
+        io_buffer_release(&rawnand->mmio[wnd]);
+    zx_handle_close(rawnand->irq_handle);
+    /*
+     * XXX -  We need to make sure that the irq thread and
+     * the worker thread exit cleanly.
+     */
+    free(rawnand);
+}
+
+static void aml_set_encryption(aml_rawnand_t *rawnand)
+{
+    uint32_t cfg;
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    cfg = readl(reg + P_NAND_CFG);
+    cfg |= (1 << 17);
+    writel(cfg, reg + P_NAND_CFG);
+}
+
+/* 
+ * Read one of the page0 pages, and use the result to init
+ * ECC algorithm and rand-mode.
+ */
+static zx_status_t aml_nand_init_from_page0(aml_rawnand_t* rawnand)
+{
+    uint32_t i;
+    zx_status_t status;
+    char *data;
+    nand_page0_t *page0;
+    int ecc_correct;
+    
+    data = malloc(rawnand->writesize);    
+    /*
+     * There are 8 copies of page0 spaced apart by 128 pages
+     * starting at Page 0. Read the first we can.
+     */
+    for (i = 0 ; i < 7 ; i++) {
+        status = nand_read_page0(rawnand, data, NULL, i * 128,
+                                 &ecc_correct, 3);
+        if (status == ZX_OK)
+            break;
+    }
+    if (status != ZX_OK) {
+        /* 
+         * Could not read any of the page0 copies. This is a fatal
+         * error.
+         */
+        free(data);
+        zxlogf(ERROR, "%s: Page0 Read (all copies) failed\n", __func__);        
+        return status;
+    }
+    page0 = (nand_page0_t *)data;
+    rawnand->controller_params.rand_mode =
+        (page0->nand_setup.cfg.d32 >> 19) & 0x1;
+    rawnand->controller_params.bch_mode =
+        (page0->nand_setup.cfg.d32 >> 14) & 0x7;
+    zxlogf(ERROR, "%s: Initializing BCH mode (%d) and rand-mode (%d) from Page0@%u\n",
+           __func__, rawnand->controller_params.bch_mode,
+           rawnand->controller_params.rand_mode, i * 128);
+
+    zxlogf(ERROR, "cfg.d32 = 0x%x\n", page0->nand_setup.cfg.d32);
+    uint32_t val = page0->nand_setup.cfg.d32 & 0x3f;
+    zxlogf(ERROR, "ecc_step = %u\n", val);
+    val = (page0->nand_setup.cfg.d32 >> 6) & 0x7f;
+    zxlogf(ERROR, "pagesize = %u\n", val);
+    
+    zxlogf(ERROR, "ext_info.read_info = 0x%x\n", page0->ext_info.read_info);
+    zxlogf(ERROR, "ext_info.page_per_blk = 0x%x\n", page0->ext_info.page_per_blk);
+    zxlogf(ERROR, "ext_info.boot_num = 0x%x\n", page0->ext_info.boot_num);
+    zxlogf(ERROR, "ext_info.each_boot_pages = 0x%x\n", page0->ext_info.each_boot_pages);
+    zxlogf(ERROR, "ext_info.bbt_occupy_pages = 0x%x\n", page0->ext_info.bbt_occupy_pages);
+    zxlogf(ERROR, "ext_info.bbt_start_block = 0x%x\n", page0->ext_info.bbt_start_block);
+    
+    free(data);
+    return ZX_OK;
+}
+
+static zx_status_t aml_rawnand_allocbufs(aml_rawnand_t* rawnand)
+{
+    zx_status_t status;
+
+    status = pdev_get_bti(&rawnand->pdev, 0, &rawnand->bti_handle);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "rawnand_test_allocbufs: pdev_get_bti failed (%d)\n",
+            status);
+        return status;
+    }
+    status = io_buffer_init(&rawnand->data_buffer,
+                            rawnand->bti_handle,
+                            4096,
+                            IO_BUFFER_RW | IO_BUFFER_CONTIG);
+    if (status != ZX_OK) {
+        zxlogf(ERROR,
+               "rawnand_test_allocbufs: io_buffer_init(data_buffer) failed\n");
+        zx_handle_close(rawnand->bti_handle);        
+        return status;
+    }
+    status = io_buffer_init(&rawnand->info_buffer,
+                            rawnand->bti_handle,
+                            4096,
+                            IO_BUFFER_RW | IO_BUFFER_CONTIG);
+    if (status != ZX_OK) {
+        zxlogf(ERROR,
+               "rawnand_test_allocbufs: io_buffer_init(info_buffer) failed\n");
+        io_buffer_release(&rawnand->data_buffer);
+        zx_handle_close(rawnand->bti_handle);
+        return status;        
+    }
+    rawnand->data_buf = io_buffer_virt(&rawnand->data_buffer);    
+    rawnand->info_buf = io_buffer_virt(&rawnand->info_buffer);
+    rawnand->data_buf_paddr = io_buffer_phys(&rawnand->data_buffer);
+    rawnand->info_buf_paddr = io_buffer_phys(&rawnand->info_buffer);
+    return ZX_OK;
+}
+
+
+static void aml_rawnand_freebufs(aml_rawnand_t* rawnand)
+{
+    io_buffer_release(&rawnand->data_buffer);
+    io_buffer_release(&rawnand->info_buffer);
+    zx_handle_close(rawnand->bti_handle);
+}
+
+static zx_status_t aml_nand_init(aml_rawnand_t* rawnand)
+{
+    zx_status_t status;
+    
+    /*
+     * Do nand scan to get manufacturer and other info
+     */
+    status = aml_get_flash_type(rawnand);
+    if (status != ZX_OK)
+        return status;
+    /*
+     * Again: All of the following crap should be read and
+     * parsed from the equivalent of the device tree.
+     */
+    rawnand->controller_params.ecc_strength = aml_params.ecc_strength;
+    rawnand->controller_params.user_mode = aml_params.user_mode;
+    rawnand->controller_params.rand_mode = aml_params.rand_mode;
+    rawnand->controller_params.options = NAND_USE_BOUNCE_BUFFER;
+    rawnand->controller_params.bch_mode = aml_params.bch_mode;
+    rawnand->controller_delay = 200;
+
+    /*
+     * Note on OOB byte settings.
+     * The default config for OOB is 2 bytes per OOB page. This is the
+     * settings we use. So nothing to be done for OOB. If we ever need
+     * to switch to 16 bytes of OOB per NAND page, we need to set the 
+     * right bits in the CFG register/
+     */
+
+    status = aml_rawnand_allocbufs(rawnand);
+    if (status != ZX_OK)
+        return status;
+        
+    /* 
+     * Read one of the copies of page0, and use that to initialize 
+     * ECC algorithm and rand-mode.
+     */
+    status = aml_nand_init_from_page0(rawnand);
+    
+    return status;
+}
+
+static void aml_rawnand_unbind(void* ctx) {
+    aml_rawnand_t* rawnand = ctx;
+
+    aml_rawnand_freebufs(rawnand);
+    
+    /* Terminate our worker thread here */
+#if 0
+    mtx_lock(&rawnand->txn_lock);
+    rawnand->dead = true;
+    mtx_unlock(&rawnand->txn_lock);
+    completion_signal(&rawnand->txn_completion);
+    // wait for worker thread to finish before removing devices
+    thrd_join(rawnand->worker_thread, NULL);
+
+    /* 
+     * Then similarly, terminate the irq thread 
+     * and wait for it to exit
+     */
+#endif
+    /* Then remove the device */
+    device_remove(rawnand->zxdev);
+}
+
+bool aml_check_write_protect(aml_rawnand_t *rawnand)
+{
+    uint8_t cmd_status;
+
+    aml_select_chip(rawnand, 0);
+    nand_command(rawnand, NAND_CMD_STATUS, -1, -1);
+    cmd_status = aml_read_byte(rawnand);
+    if (!(cmd_status & NAND_STATUS_WP))
+        /* If clear, the device is WP */
+        return true;
+    else
+        /* If set, the device is not-WP */        
+        return false;
+}
+
+static zx_protocol_device_t rawnand_device_proto = {
+    .version = DEVICE_OPS_VERSION,
+        .unbind = aml_rawnand_unbind,
+        .release = aml_rawnand_release,
+};
+
+static zx_status_t aml_rawnand_bind(void* ctx, zx_device_t* parent)
+{
+    zx_status_t status;
+
+    aml_rawnand_t* rawnand = calloc(1, sizeof(aml_rawnand_t));
+    
+    if (!rawnand) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    rawnand->req_completion = COMPLETION_INIT;
+
+    if ((status = device_get_protocol(parent,
+                                      ZX_PROTOCOL_PLATFORM_DEV,
+                                      &rawnand->pdev)) != ZX_OK) {
+        zxlogf(ERROR,
+               "aml_rawnand_bind: ZX_PROTOCOL_PLATFORM_DEV not available\n");
+        goto fail;
+    }
+
+    pdev_device_info_t info;
+    status = pdev_get_device_info(&rawnand->pdev, &info);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_rawnand_bind: pdev_get_device_info failed\n");
+        goto fail;
+    }
+
+    /* Map all of the mmio windows that we need */
+    for (rawnand_addr_window_t wnd = 0 ;
+         wnd < ADDR_WINDOW_COUNT ;
+         wnd++) {
+        status = pdev_map_mmio_buffer(&rawnand->pdev,
+                                      wnd,
+                                      ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                      &rawnand->mmio[wnd]);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "aml_rawnand_bind: pdev_map_mmio_buffer failed %d\n",
+                   status);
+            goto fail;
+        }
+    }
+
+    status = pdev_map_interrupt(&rawnand->pdev, 0, &rawnand->irq_handle);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_rawnand_bind: pdev_map_interrupt failed %d\n",
+               status);
+        for (rawnand_addr_window_t wnd = 0 ;
+             wnd < ADDR_WINDOW_COUNT ;
+             wnd++)
+            io_buffer_release(&rawnand->mmio[wnd]);            
+        goto fail;
+    }
+
+    int rc = thrd_create_with_name(&rawnand->irq_thread,
+				   aml_rawnand_irq_thread,
+                                   rawnand, "aml_rawnand_irq_thread");
+    if (rc != thrd_success) {
+        zx_handle_close(rawnand->irq_handle);
+        for (rawnand_addr_window_t wnd = 0 ;
+             wnd < ADDR_WINDOW_COUNT ;
+             wnd++)
+            io_buffer_release(&rawnand->mmio[wnd]);            
+        status = thrd_status_to_zx_status(rc);
+        goto fail;
+    }
+
+    /*
+     * This creates a device that a top level (controller independent)
+     * rawnand driver can bind to.
+     */
+    device_add_args_t args = {
+        .version = DEVICE_ADD_ARGS_VERSION,
+        .name = "aml-rawnand",
+        .ctx = rawnand,
+        .ops = &rawnand_device_proto,
+        .proto_id = ZX_PROTOCOL_NAND,
+        .proto_ops = &rawnand_ops,
+    };
+
+    status = device_add(parent, &args, &rawnand->zxdev);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_rawnand_bind: device_add failed\n");
+        zx_handle_close(rawnand->irq_handle);
+        for (rawnand_addr_window_t wnd = 0 ;
+             wnd < ADDR_WINDOW_COUNT ;
+             wnd++)
+            io_buffer_release(&rawnand->mmio[wnd]);
+        goto fail;
+    }
+
+    /*
+     * For the Toshiba chip we are using/plan to use with Estelle
+     * (Toshiba TC58NVG2S0HBAI4)
+     * tRC_min = 25ns
+     * tREA_max = 20ns
+     * RHOH_min = 25ns 
+     */
+
+    aml_clock_init(rawnand);                    
+    status = aml_nand_init(rawnand);
+
+    if (status != ZX_OK) {
+        zxlogf(ERROR,
+               "aml_rawnand_bind: aml_nand_init() failed - This is FATAL\n");
+        goto fail;
+    }
+
+#ifdef AML_RAWNAND_TEST    
+//        rawnand_dump_bbt(rawnand);
+//        rawnand_test_page0(rawnand);
+//        rawnand_test(rawnand);
+//        rawnand_erasetest(rawnand);
+//    rawnand_writetest_one_eraseblock(rawnand, 4093*64);
+    rawnand_erase_write_test(rawnand, 0x000010c00000, 0x000020000000);
+#endif
+
+    return status;
+
+fail:
+    free(rawnand);
+    return status;
+}
+
+static zx_driver_ops_t aml_rawnand_driver_ops = {
+    .version = DRIVER_OPS_VERSION,
+    .bind = aml_rawnand_bind,
+};
+
+ZIRCON_DRIVER_BEGIN(aml_rawnand, aml_rawnand_driver_ops, "zircon", "0.1", 3)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_RAWNAND),
+ZIRCON_DRIVER_END(aml_rawnand)
diff --git a/system/dev/block/rawnand/aml_rawnand_api.h b/system/dev/block/rawnand/aml_rawnand_api.h
new file mode 100644
index 0000000..89c0af0
--- /dev/null
+++ b/system/dev/block/rawnand/aml_rawnand_api.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* These are only used by the rawnand test code */
+uint32_t aml_get_ecc_pagesize(aml_rawnand_t *rawnand, uint32_t ecc_mode);
+bool aml_check_write_protect(aml_rawnand_t *rawnand);
+
+/* 
+ * These are used by the NAND entry functions. These should move to a 
+ * switch table, at a later point.
+ */
+void aml_cmd_ctrl(aml_rawnand_t *rawnand, int cmd, unsigned int ctrl);
+uint8_t aml_read_byte(aml_rawnand_t *rawnand);
+zx_status_t aml_read_page_hwecc(aml_rawnand_t *rawnand, void *data,
+                                void *oob, uint32_t nandpage,
+                                int *ecc_correct, bool page0);
+zx_status_t aml_write_page_hwecc(aml_rawnand_t *rawnand,
+                                 void *data, void *oob,
+                                 uint32_t nandpage, bool page0);
+zx_status_t aml_erase_block(aml_rawnand_t *rawnand, uint32_t nandpage);
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/system/dev/block/rawnand/nand.c b/system/dev/block/rawnand/nand.c
new file mode 100644
index 0000000..865b8d8
--- /dev/null
+++ b/system/dev/block/rawnand/nand.c
@@ -0,0 +1,269 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <bits/limits.h>
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <ddk/protocol/platform-device.h>
+#include <ddk/protocol/rawnand.h>
+#include <ddk/io-buffer.h>
+#include <hw/reg.h>
+
+#include <zircon/assert.h>
+#include <zircon/threads.h>
+#include <zircon/types.h>
+#include <zircon/status.h>
+#include <sync/completion.h>
+
+#include <string.h>
+
+#include <soc/aml-common/aml-rawnand.h>
+#include "nand.h"
+
+#include "aml_rawnand_api.h"
+
+/*
+ * Database of settings for the NAND flash devices we support 
+ */
+struct nand_chip_table nand_chip_table[] = {
+    { 0xEC, 0xDC, "Samsung", "K9F4G08U0F", { 25, 20, 15 }, true, 512,
+      0, 0, 0, 0},
+};
+
+struct nand_chip_table default_chip =
+{
+    0x00, 0x00, "DEFAULT", "UNKNOWN", { 25, 20, 15 }, true, 512,
+    0, 0, 0, 0
+};
+
+#define NAND_CHIP_TABLE_SIZE					\
+    (sizeof(nand_chip_table)/sizeof(struct nand_chip_table))
+
+/*
+ * Find the entry in the NAND chip table database based on manufacturer
+ * id and device id
+ */
+struct nand_chip_table *
+find_nand_chip_table(uint8_t manuf_id, uint8_t device_id)
+{
+    uint32_t i;
+
+    for (i = 0 ; i < NAND_CHIP_TABLE_SIZE ; i++)
+        if (manuf_id == nand_chip_table[i].manufacturer_id &&
+            device_id == nand_chip_table[i].device_id)
+            return &nand_chip_table[i];
+    return NULL;
+}
+
+/*
+ * Generic wait function used by both program (write) and erase
+ * functionality.
+ */
+zx_status_t nand_wait(aml_rawnand_t *rawnand, uint32_t timeout_ms)
+{
+    uint64_t total_time = 0;
+    uint8_t cmd_status;
+
+#if 0
+    nand_command(rawnand, NAND_CMD_STATUS, -1, -1);
+#else
+    aml_cmd_ctrl(rawnand, NAND_CMD_STATUS,
+                 NAND_CTRL_CLE | NAND_CTRL_CHANGE);
+    aml_cmd_ctrl(rawnand, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
+#endif
+    while (!((cmd_status = aml_read_byte(rawnand)) & NAND_STATUS_READY)) {
+            usleep(10);
+            total_time += 10;
+            if (total_time > (timeout_ms * 1000)) {
+                break;
+            }
+    }
+#if 0
+    zxlogf(ERROR, "%s: waited %lu usecs\n", __func__, total_time);
+    zxlogf(ERROR, "%s: cmd_status = %u\n", __func__, cmd_status);
+#endif
+    if (!(cmd_status & NAND_STATUS_READY)) {
+        zxlogf(ERROR, "nand command wait timed out\n");
+        return ZX_ERR_TIMED_OUT;
+    }
+    if (cmd_status & NAND_STATUS_FAIL) {
+        zxlogf(ERROR, "%s: nand command returns error\n", __func__);
+        return ZX_ERR_IO;
+    }
+    return ZX_OK;
+}
+
+/* 
+ * Send command down to the controller.
+ */
+void nand_command(aml_rawnand_t *rawnand, unsigned int command,
+                  int column, int page_addr)
+{
+    /* Emulate NAND_CMD_READOOB */
+    if (command == NAND_CMD_READOOB) {
+        column += rawnand->writesize;
+        command = NAND_CMD_READ0;
+    }
+
+    /* Command latch cycle */
+    aml_cmd_ctrl(rawnand, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+    if (column != -1 || page_addr != -1) {
+        int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
+
+        /* Serially input address */
+        if (column != -1) {
+            /* Adjust columns for 16 bit buswidth */
+            if (rawnand->controller_params.options & NAND_BUSWIDTH_16)
+                column >>= 1;
+            aml_cmd_ctrl(rawnand, column, ctrl);
+            ctrl &= ~NAND_CTRL_CHANGE;
+            aml_cmd_ctrl(rawnand, column >> 8, ctrl);
+        }
+        if (page_addr != -1) {
+            aml_cmd_ctrl(rawnand, page_addr, ctrl);
+            aml_cmd_ctrl(rawnand, page_addr >> 8,
+                         NAND_NCE | NAND_ALE);
+            /* One more address cycle for devices > 128MiB */
+            if (rawnand->chipsize > 128)
+                aml_cmd_ctrl(rawnand, page_addr >> 16,
+                             NAND_NCE | NAND_ALE);
+        }
+    }
+    aml_cmd_ctrl(rawnand, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
+
+    switch (command) {
+    case NAND_CMD_ERASE1:
+    case NAND_CMD_ERASE2:
+    case NAND_CMD_SEQIN:
+    case NAND_CMD_PAGEPROG:
+        return;
+    case NAND_CMD_RESET:
+        usleep(rawnand->controller_delay);
+        aml_cmd_ctrl(rawnand, NAND_CMD_STATUS,
+                     NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+        aml_cmd_ctrl(rawnand, NAND_CMD_NONE,
+                     NAND_NCE | NAND_CTRL_CHANGE);
+        while (!(aml_read_byte(rawnand) & NAND_STATUS_READY))
+            ;
+        return;
+
+    case NAND_CMD_READ0:
+        aml_cmd_ctrl(rawnand, NAND_CMD_READSTART,
+                     NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+        aml_cmd_ctrl(rawnand, NAND_CMD_NONE,
+                       NAND_NCE | NAND_CTRL_CHANGE);
+
+        /* This applies to read commands */
+    default:
+        usleep(rawnand->controller_delay);
+        return;
+    }
+}
+
+/*
+ * Main Read entry point into the NAND. Calls controller specific
+ * read function.
+ * data, oob: pointers to user oob/data buffers.
+ * nandpage : NAND page address to read.
+ * ecc_correct : Number of ecc corrected bitflips (< 0 indicates
+ * ecc could not correct all bitflips - caller needs to check that).
+ * retries : I see read errors (read not completing) reported occasionally
+ * on different NAND pages. Retry of 3 works around this, in
+ * fact the first retry addresses the vast majority of the read
+ * non-completions.
+ */
+zx_status_t nand_read_page(aml_rawnand_t *rawnand,
+                           void *data,
+                           void *oob,
+                           uint32_t nandpage,
+                           int *ecc_correct,
+                           int retries)
+{
+    zx_status_t status;
+    
+    retries++;
+    do {
+        status = aml_read_page_hwecc(rawnand, data, oob, nandpage,
+                                     ecc_correct, false);
+        if (status != ZX_OK)
+            zxlogf(ERROR, "%s: Retrying Read@%u\n",
+                   __func__, nandpage);        
+    } while (status != ZX_OK && --retries > 0);
+    if (status != ZX_OK)
+        zxlogf(ERROR, "%s: Read error\n", __func__);
+    return status;
+}
+
+zx_status_t nand_read_page0(aml_rawnand_t *rawnand,
+                            void *data,
+                            void *oob,
+                            uint32_t nandpage,                                  
+                            int *ecc_correct,
+                            int retries)
+{
+    zx_status_t status;
+    
+    retries++;
+    do {
+        status = aml_read_page_hwecc(rawnand, data, oob,
+                                     nandpage, ecc_correct, true);
+    } while (status != ZX_OK && --retries > 0);
+    if (status != ZX_OK)
+        zxlogf(ERROR, "%s: Read error\n", __func__);                
+    return status;
+}
+
+/*
+ * Main Write entry point into the NAND. Calls controller specific 
+ * write function.
+ * data, oob: pointers to user oob/data buffers.
+ * nandpage : NAND page address to read.
+ */
+zx_status_t nand_write_page(aml_rawnand_t *rawnand,
+                            void *data,
+                            void *oob,
+                            uint32_t nandpage)
+{
+    zx_status_t status;
+    
+    if (aml_check_write_protect(rawnand)) {
+        zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
+               __func__);
+        /* Zircon doesn't have EROFS, this seems closest */
+        status = ZX_ERR_ACCESS_DENIED;
+    } else
+        status = aml_write_page_hwecc(rawnand, data, oob, nandpage, false);
+    return status;
+}
+
+/*
+ * Main Erase entry point into NAND. Calls controller specific 
+ * erase function.
+ * nandblock : NAND erase block address.
+ */
+zx_status_t nand_erase_block(aml_rawnand_t *rawnand,
+                             uint32_t nandpage)
+{
+    zx_status_t status;
+
+    if (aml_check_write_protect(rawnand)) {
+        zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
+               __func__);
+        /* Zircon doesn't have EROFS, this seems closest */
+        status = ZX_ERR_ACCESS_DENIED;
+    } else
+        status = aml_erase_block(rawnand, nandpage);
+    return status;
+}
+
diff --git a/system/dev/block/rawnand/nand.h b/system/dev/block/rawnand/nand.h
new file mode 100644
index 0000000..2a0b94c
--- /dev/null
+++ b/system/dev/block/rawnand/nand.h
@@ -0,0 +1,106 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define NAND_CE0        (0xe<<10)
+#define NAND_CE1        (0xd<<10)
+
+#define NAND_NCE        0x01
+#define NAND_CLE        0x02
+#define NAND_ALE        0x04
+
+#define NAND_CTRL_CLE   (NAND_NCE | NAND_CLE)
+#define NAND_CTRL_ALE   (NAND_NCE | NAND_ALE)
+#define NAND_CTRL_CHANGE        0x80
+
+#define NAND_CMD_READ0          0
+#define NAND_CMD_READ1          1
+#define NAND_CMD_PAGEPROG       0x10
+#define NAND_CMD_READOOB        0x50
+#define NAND_CMD_ERASE1         0x60
+#define NAND_CMD_STATUS         0x70
+#define NAND_CMD_SEQIN          0x80    
+#define NAND_CMD_READID         0x90
+#define NAND_CMD_ERASE2         0xd0
+#define NAND_CMD_RESET          0xff
+#define NAND_CMD_NONE           -1
+
+/* Extended commands for large page devices */
+#define NAND_CMD_READSTART      0x30
+
+/* Status */
+#define NAND_STATUS_FAIL        0x01
+#define NAND_STATUS_FAIL_N1     0x02
+#define NAND_STATUS_TRUE_READY  0x20
+#define NAND_STATUS_READY       0x40
+#define NAND_STATUS_WP          0x80
+
+struct nand_timings {
+    uint32_t tRC_min;
+    uint32_t tREA_max;
+    uint32_t RHOH_min;
+};
+
+struct nand_chip_table {
+    uint8_t             manufacturer_id;
+    uint8_t             device_id;    
+    const char          *manufacturer_name;
+    const char          *device_name;
+    struct nand_timings timings;
+    /* 
+     * extended_id_nand -> pagesize, erase blocksize, OOB size
+     * could vary given the same device id.
+     */
+    bool                extended_id_nand;
+    uint64_t            chipsize;               /* MiB */
+    /* Valid only if extended_id_nand is false */
+    uint32_t            page_size;              /* bytes */
+    uint32_t		oobsize;		/* bytes */
+    uint32_t            erase_block_size;       /* bytes */
+    uint32_t            bus_width;       	/* 8 vs 16 bit */    
+};
+
+#define MAX(A, B)               ((A > B) ? A : B)
+#define MIN(A, B)               ((A < B) ? A : B)
+
+void nand_command(aml_rawnand_t *rawnand, unsigned int command,
+                  int column, int page_addr);
+struct nand_chip_table *find_nand_chip_table(uint8_t manuf_id,
+                                             uint8_t device_id);
+zx_status_t nand_wait(aml_rawnand_t *rawnand, uint32_t timeout_ms);
+void nand_command(aml_rawnand_t *rawnand, unsigned int command,
+                  int column, int page_addr);
+zx_status_t nand_read_page(aml_rawnand_t *rawnand, void *data,
+                           void *oob, uint32_t nandpage,
+                           int *ecc_correct, int retries);
+zx_status_t nand_read_page0(aml_rawnand_t *rawnand, void *data,
+                            void *oob, uint32_t nandpage,                                  
+                            int *ecc_correct, int retries);
+zx_status_t nand_write_page(aml_rawnand_t *rawnand, void *data,
+                            void *oob, uint32_t nandpage);
+zx_status_t nand_erase_block(aml_rawnand_t *rawnand, uint32_t nandpage);
+
+/*
+ * Test functions.
+ */
+#define AML_RAWNAND_TEST
+
+#ifdef AML_RAWNAND_TEST
+void rawnand_read_device_test(aml_rawnand_t* rawnand);
+void rawnand_dump_bbt(aml_rawnand_t *rawnand);
+void rawnand_test_page0(aml_rawnand_t* rawnand);
+void rawnand_erasetest_one_block(aml_rawnand_t* rawnand);
+void rawnand_writetest_one_eraseblock(aml_rawnand_t* rawnand,
+                                      uint32_t nandpage);
+void rawnand_erase_write_test(aml_rawnand_t* rawnand,
+                              uint64_t start_byte,
+                              uint64_t end_byte);
+#endif
+
+
+
+
+
+
+
+
diff --git a/system/dev/block/rawnand/rawnand_tests.c b/system/dev/block/rawnand/rawnand_tests.c
new file mode 100644
index 0000000..b65b9d5
--- /dev/null
+++ b/system/dev/block/rawnand/rawnand_tests.c
@@ -0,0 +1,968 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <bits/limits.h>
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <ddk/protocol/platform-device.h>
+#include <ddk/protocol/rawnand.h>
+#include <ddk/io-buffer.h>
+#include <hw/reg.h>
+
+#include <zircon/assert.h>
+#include <zircon/threads.h>
+#include <zircon/types.h>
+#include <zircon/status.h>
+#include <sync/completion.h>
+
+#include <string.h>
+
+#include <soc/aml-common/aml-rawnand.h>
+#include "nand.h"
+#include "aml_rawnand_api.h"
+
+#ifdef AML_RAWNAND_TEST
+uint32_t num_bad_blocks = 0;
+uint32_t bad_blocks[4096];
+
+static bool
+is_block_bad(uint32_t nandblock)
+{
+    uint32_t i;
+
+    for (i = 0 ; i < num_bad_blocks; i++) {
+        if (nandblock == bad_blocks[i]) {
+            zxlogf(ERROR, "%s: Found bad block @%u, skipping\n",
+                   __func__, nandblock);
+            return true;
+        }
+    }
+    return false;
+}
+
+#define WRITEBUF_SIZE           (512*1024)
+
+/*
+ * This file creates the data that we will write to the NAND device.
+ * The file is created in /tmp. The idea here is that we can scp 
+ * this file out and then compare the data we wrote to NAND.
+ */
+static zx_status_t
+create_data_file(aml_rawnand_t* rawnand, uint64_t size)
+{
+    int fd;
+    char *data;
+    zx_status_t status;
+    uint8_t *p;
+    uint64_t to_write;
+    
+    fd = open("/tmp/nand-data-to-write", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-data-to-write\n",
+               __func__);
+        return ZX_ERR_IO;
+    }
+    data = malloc(WRITEBUF_SIZE);
+    srand(zx_ticks_get());
+    while (size > 0) {
+        int write_bytes;
+        
+        write_bytes = to_write = MIN(WRITEBUF_SIZE, size);
+        p = (uint8_t *)data;
+        while (to_write > 0) {
+            if (to_write >= sizeof(uint32_t)) {
+                *(uint32_t *)p = random();
+                to_write -= sizeof(uint32_t);
+                p += sizeof(uint32_t);
+            } else {
+                *p++ = (uint8_t)random();
+                to_write--;
+            }
+        }
+        if (write(fd, data, write_bytes) != write_bytes) {
+            zxlogf(ERROR, "%s: Could not open file /tmp/nand-data-to-write\n",
+                   __func__);
+            status = ZX_ERR_IO;
+            break;
+        } else
+            status = ZX_OK;
+        size -= write_bytes;
+    }
+    free(data);    
+    fsync(fd);
+    close(fd);
+    return status;
+}
+
+static zx_status_t
+erase_blocks_range(aml_rawnand_t* rawnand,
+                   uint64_t start_erase_block,
+                   uint64_t end_erase_block)
+{
+    uint64_t cur;
+    zx_status_t status;
+    
+    for (cur = start_erase_block; cur < end_erase_block; cur++) {
+        if (!is_block_bad(cur)) {
+            status = nand_erase_block(rawnand,
+                                      cur * rawnand->erasesize_pages);
+            if (status != ZX_OK) {
+                zxlogf(ERROR, "%s: Count not ERASE block @%lu\n",
+                       __func__, cur);
+                return status;
+            }
+            zxlogf(ERROR, "%s: ERASED block @%lu successfully\n",
+                       __func__, cur);
+        } else
+            zxlogf(ERROR, "%s: SKIPPING erase of bad block Count @%lu\n",
+                   __func__, cur);
+            
+    }
+    return ZX_OK;
+}
+
+static zx_status_t
+read_single_erase_block(aml_rawnand_t* rawnand,
+                        uint64_t erase_block,
+                        char *data,
+                        char *oob)
+{
+    uint64_t *oob_long = (uint64_t *)oob;
+    uint64_t start_page = erase_block * rawnand->erasesize_pages;
+    zx_status_t status;
+    int ecc_correct;
+    uint64_t i;
+    
+    for (i = 0; i < rawnand->erasesize_pages; i++) {
+        status = nand_read_page(rawnand,
+                                data + (i * rawnand->writesize),
+                                &oob_long[i],
+                                start_page + i,
+                                &ecc_correct, 3);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at @%lu\n",
+                   __func__, start_page + i);        
+            return status;
+        }
+        if (ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %lu\n",
+                   __func__, ecc_correct, start_page + i);
+    }
+    return ZX_OK;
+}
+
+static zx_status_t
+read_blocks_range(aml_rawnand_t* rawnand,
+                  const char *filename,
+                  uint64_t start_erase_block,
+                  uint64_t end_erase_block,
+                  bool (*verify_pattern)(uint64_t cur_erase_block, uint64_t oob_data))
+{
+    int fd;
+    uint64_t cur;
+    char *data, *oob;
+    uint64_t *oob_long;
+    zx_status_t status = ZX_OK;
+    uint64_t i;
+    
+    fd = open(filename, O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file %s\n",
+               __func__, filename);
+        return ZX_ERR_IO;
+    }
+    data = malloc(rawnand->erasesize);
+    oob = malloc(rawnand->erasesize_pages * 8); /* XXX 8 bytes OOB per page */
+    for (cur = start_erase_block; cur < end_erase_block; cur++) {
+        if (is_block_bad(cur))
+            continue;
+        status = read_single_erase_block(rawnand, cur, data, oob);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Count not Read block @%lu from %s\n",
+                   __func__, cur, filename);
+            goto out;
+        }
+        /* Compare the 8 bytes of OOB per page to the pattern passed in */
+        oob_long = (uint64_t *)oob;
+        for (i = 0; i < rawnand->erasesize_pages; i++) {
+            if (!verify_pattern(cur, oob_long[i])) {
+                zxlogf(ERROR, "%s: OOB post-Erase failed at %lu ! found %lu\n",
+                       __func__, cur, oob_long[i]);
+                status = ZX_ERR_IO;
+                goto out;
+            }
+        }
+        /* Write the entire eraseblock out to the file */
+        if (write(fd, data, rawnand->erasesize) != rawnand->erasesize) {
+            zxlogf(ERROR, "%s: Could not write to %s\n",
+                   __func__, filename);
+            status = ZX_ERR_IO;
+            goto out;            
+        }
+    }
+out:
+    free(data);
+    free(oob);
+    close(fd);
+    return status;
+}
+
+static zx_status_t
+write_single_erase_block(aml_rawnand_t* rawnand,
+                         uint64_t erase_block, char *data,
+                         char *oob)
+{
+    uint64_t *oob_long = (uint64_t *)oob;
+    uint64_t start_page = erase_block * rawnand->erasesize_pages;
+    zx_status_t status;
+    uint64_t i;
+    
+    for (i = 0; i < rawnand->erasesize_pages; i++) {
+        status = nand_write_page(rawnand,
+                                 data + (i * rawnand->writesize),
+                                 &oob_long[i],                                 
+                                 start_page + i);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Write failed at @%lu\n",
+                   __func__, start_page + i);        
+            return status;
+        }
+    }
+    return ZX_OK;
+}
+
+
+static zx_status_t
+write_blocks_range(aml_rawnand_t* rawnand,
+                   const char *filename,
+                   uint64_t start_erase_block,
+                   uint64_t end_erase_block)
+{
+    int fd;
+    uint64_t cur;
+    char *data, *oob;
+    uint64_t *oob_long;
+    zx_status_t status = ZX_OK;
+    uint64_t i;
+    
+    fd = open(filename, 0);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file %s\n",
+               __func__, filename);
+        return ZX_ERR_IO;
+    }
+    data = malloc(rawnand->erasesize);
+    oob = malloc(rawnand->erasesize_pages * 8); /* XXX 8 bytes OOB per page */    
+    for (cur = start_erase_block; cur < end_erase_block; cur++) {
+        if (is_block_bad(cur))
+            continue;
+        if (read(fd, data, rawnand->erasesize) != rawnand->erasesize) {
+            zxlogf(ERROR, "%s: Could not read file %s\n",
+                   __func__, filename);
+            status = ZX_ERR_IO;
+            goto out;
+        }
+        oob_long = (uint64_t *)oob;
+        for (i = 0; i < rawnand->erasesize_pages; i++)
+            oob_long[i] = cur; /* All 8 oob bytes for every page have same signature */
+        status = write_single_erase_block(rawnand, cur, data, oob);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Count not write block @%lu\n",
+                   __func__, cur);
+            goto out;
+        }
+    }
+out:
+    free(data);
+    free(oob);
+    close(fd);
+    return status;
+}
+
+/*
+ * After a block has been erased, OOB bytes should be all 0xff
+ */
+static bool
+verify_pattern_erased(uint64_t curblock, uint64_t oob_data)
+{
+    if (oob_data == ~((uint64_t)0)) {
+#if 0
+        zxlogf(ERROR, "%s: Erase Block %lu OOB signature OK !\n",
+               __func__, curblock);
+#endif
+        return true;
+    }
+    return false;
+}
+
+/*
+ * We write 8 OOB bytes for every page with the index of the *eraseblock*
+ * this verifies the signature.
+ */
+static bool
+verify_pattern_written(uint64_t cur_erase_block, uint64_t oob_data)
+{
+    if (oob_data == cur_erase_block)
+        return true;
+    return false;
+}
+
+/*
+ * Given the range. Erase every block in the range and write the entire
+ * range. Once the range is written, read the range back and save the 
+ * data so we can compare the data that we wrote and the data we read back
+ * 
+ * This function creates 2 files in tmp: one file is the data we are writing
+ * the range, and the second file is the data we read back (post write) for
+ * the range. We expect these to be identical. As a side effect, the write
+ * also encodes the (8 byte) page offset in the OOB bytes for every page
+ * written and sanity checks this on the read.
+ * 
+ * Finally, need to figure out how many bad blocks are in the range, and 
+ * we need to skip those.
+ */
+void rawnand_erase_write_test(aml_rawnand_t* rawnand,
+                              uint64_t start_byte,
+                              uint64_t end_byte)
+{
+    uint64_t start_erase_block, end_erase_block;
+    uint64_t cur;
+    uint64_t num_erase_blocks;
+    zx_status_t status;
+
+    /* Populate the bad block list, so we don't stomp on it */
+    rawnand_dump_bbt(rawnand);
+    /*
+     * start_byte and end_byte must be erase block aligned.
+     * If they are not, make them so.
+     */
+    if (start_byte % rawnand->erasesize)
+        start_byte += rawnand->erasesize -
+            (start_byte % rawnand->erasesize);
+    if (end_byte % rawnand->erasesize)
+        end_byte -= (end_byte % rawnand->erasesize);
+    start_erase_block = start_byte / rawnand->erasesize;
+    end_erase_block = end_byte / rawnand->erasesize;
+    zxlogf(ERROR, "%s: start erase block = %lu\n",
+           __func__, start_erase_block);
+    zxlogf(ERROR, "%s: end erase block = %lu\n",
+           __func__, end_erase_block);
+    /*
+     * How many bad blocks are in the range of erase blocks we
+     * are going to erase + write to ?
+     */
+    num_erase_blocks = end_erase_block - start_erase_block;
+    for (cur = start_erase_block; cur < end_erase_block; cur++)
+        if (is_block_bad(cur)) {
+            zxlogf(ERROR, "%s: Skipping erase block %lu\n",
+                   __func__, cur);
+            num_erase_blocks--;
+        }
+    zxlogf(ERROR, "%s: num erase block = %lu\n",
+           __func__, num_erase_blocks);
+    /* 
+     * Create a file with known data for the size we are going
+     * to write out.
+     */
+    status = create_data_file(rawnand,
+                              num_erase_blocks * rawnand->erasesize);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Count not create data file \n",
+               __func__);
+        return;
+    }
+#if 0
+    /*
+     * Next erase every block in the range (skipping over known bad 
+     * blocks).
+     */
+    status = erase_blocks_range(rawnand, start_erase_block, end_erase_block);
+    if (status != ZX_OK)
+        return;
+
+    /*
+     * At this point, the data in the range should be all 0xff !
+     * Let's read the data and save it away (so we can verify).
+     */
+    status = read_blocks_range(rawnand, "/tmp/nand-dump-before-write",
+                               start_erase_block, end_erase_block,
+                               verify_pattern_erased);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Reading blocks before write (after erase) failed\n",
+               __func__);
+        return;
+    }
+    /*
+     * Write blocks in the range reading data from the given file.
+     */
+    status = write_blocks_range(rawnand, "/tmp/nand-data-to-write",
+                                start_erase_block, end_erase_block);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Writing of blocks failed\n",
+               __func__);
+        return;
+    }        
+#endif
+    /*
+     * At this point, the data in the range has been completely written
+     * Let's read the data and save it away (so we can verify).
+     */
+    status = read_blocks_range(rawnand, "/tmp/nand-dump-after-write",
+                               start_erase_block, end_erase_block,
+                               verify_pattern_written);
+    if (status != ZX_OK)
+        zxlogf(ERROR, "%s: Reading blocks after write failed\n",
+               __func__);
+}
+
+/* 
+ * Write the given page and read back the data before and after the
+ * write.
+ * It is assumed the page to be written has already been erased.
+ * The page passed in must be eraseblock aligned.
+ * The function snapshots the contents before and after the write.
+ */
+void rawnand_writetest_one_eraseblock(aml_rawnand_t* rawnand,
+                                      uint32_t nandpage)
+{
+    int ecc_correct;
+    zx_status_t status;
+    char *data, *oob;
+    uint32_t i;
+    int fd;
+    int ecc_pages_per_nand_page;
+    uint32_t ecc_pagesize;
+
+    /* nandpage must be aligned to a eraseblock boundary */
+    if (nandpage % rawnand->erasesize_pages) {
+        zxlogf(ERROR, "%s: NAND block %u must be a erasesize_pages (%u) multiple\n",
+               __func__, nandpage, rawnand->erasesize_pages);
+        return;
+    }
+    /* Populate the bad block list, so we don't stomp on it */
+    rawnand_dump_bbt(rawnand);
+    ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+    ecc_pages_per_nand_page =
+        rawnand->writesize / ecc_pagesize;
+    if (aml_check_write_protect(rawnand)) {
+        zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
+               __func__);
+        return;
+    } else
+        zxlogf(ERROR, "%s: Device is not-Write Protected\n",
+               __func__);
+    if (is_block_bad(nandpage)) {
+        zxlogf(ERROR, "%s: Cannot write to @%u - part of bad block\n",
+                   __func__, nandpage);
+        return;
+    }
+    data = malloc(rawnand->writesize);
+    oob = malloc(rawnand->writesize);    
+    /* 
+     * Read existing data and dump that out to a file 
+     */
+    fd = open("/tmp/nand-dump-before-write", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump-before-write\n",
+               __func__);
+        return;
+    }
+    for (i = 0 ; i < rawnand->erasesize_pages ; i++) {
+        ecc_correct = 0;
+        memset((char *)rawnand->data_buf, 0, rawnand->writesize);
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        status = nand_read_page(rawnand, data, oob, nandpage + i,
+                                &ecc_correct, 3);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at %u\n",
+                   __func__, nandpage + i);
+            return;
+        }
+        zxlogf(ERROR,
+               "%s: OOB bytes for page %u: \n", __func__, nandpage + i);
+        for (int j = 0; j < 8; j++)
+            zxlogf(ERROR, " 0x%x", oob[j]);
+        zxlogf(ERROR, "\n");            
+        if (ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %u\n",
+                   __func__, ecc_correct, nandpage);
+        if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not write to file /tmp/nand-dump-before-write\n",
+                   __func__);
+        }
+    }
+    fsync(fd);    
+    close(fd);
+    if (create_data_file(rawnand, rawnand->writesize * rawnand->erasesize_pages) != ZX_OK) {
+        zxlogf(ERROR, "%s: Could not create data file /tmp/nand-data-to-write\n",
+               __func__);
+        free(data);
+        free(oob);
+        return;
+    }
+    /*
+     * Do the program operation, then re-read the pages, storing it in "after".
+     */
+    fd = open("/tmp/nand-data-to-write", 0);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-data-to-write\n",
+               __func__);
+        return;
+    }
+    for (i = 0 ; i < rawnand->erasesize_pages ; i++) {
+        if (read(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not read file /tmp/nand-data-to-write\n",
+                   __func__);
+        }
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        *(uint32_t *)oob = (nandpage + i);
+        status = nand_write_page(rawnand, data, oob, nandpage + i);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Write failed to page %u\n",
+                   __func__, nandpage + i);
+            return;
+        }
+    }
+    close(fd);
+    fd = open("/tmp/nand-dump-after-write", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump-after-write\n",
+               __func__);
+        return;
+    }
+    for (i = 0 ; i < rawnand->erasesize_pages ; i++) {
+        ecc_correct = 0;
+        memset((char *)rawnand->data_buf, 0, rawnand->writesize);
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        status = nand_read_page(rawnand, data, oob, nandpage + i,
+                                &ecc_correct, 3);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at %u\n",
+                   __func__, nandpage + i);
+            return;
+        }
+        if (ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %u\n",
+                   __func__, ecc_correct, nandpage + i);
+        if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not write to file /tmp/nand-dump-after-write\n",
+                   __func__);
+        }
+        zxlogf(ERROR, "%s: OOB bytes for page @%u = %u\n",
+               __func__, nandpage + i, *(uint32_t *)oob);
+    }
+    fsync(fd);
+    close(fd);
+    free(data);
+    free(oob);
+}
+
+/*
+ * Erase a block, read it back and make sure entire block has been
+ * erased.
+ * The function finds the last block we can erase (that is not in the 
+ * bad block table) and erases that block.
+ */
+void rawnand_erasetest_one_block(aml_rawnand_t* rawnand)
+{
+    uint32_t num_nand_pages;
+    uint32_t cur_nand_page;
+    int ecc_correct;
+    zx_status_t status;
+    char *data, *oob;
+    uint32_t i;
+    int fd;
+    int ecc_pages_per_nand_page;
+    uint32_t ecc_pagesize;
+
+    /* Populate the bad block list, so we don't stomp on it */
+    rawnand_dump_bbt(rawnand);
+    ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+    if (aml_check_write_protect(rawnand)) {
+        zxlogf(ERROR, "%s: Device is Write Protected, Cannot Erase\n",
+               __func__);
+        return;
+    } else
+        zxlogf(ERROR, "%s: Device is not-Write Protected\n",
+               __func__);
+    ecc_pages_per_nand_page =
+        rawnand->writesize / ecc_pagesize;
+    num_nand_pages = (rawnand->chipsize * (1024 * 1024)) /
+        rawnand->writesize;
+    zxlogf(ERROR, "%s: num_nand_pages = %u\n",
+           __func__, num_nand_pages);        
+    /* Let's pick the very last erase block */
+    cur_nand_page = num_nand_pages - 1;
+    zxlogf(ERROR, "%s: rawnand->erasesize_pages = %u\n",
+           __func__, rawnand->erasesize_pages);
+    zxlogf(ERROR, "%s: Number of Bad blocks %u\n", __func__,
+           num_bad_blocks);    
+    for (i = 0; i < num_bad_blocks; i++)
+        zxlogf(ERROR, "%s: Bad block at %u\n", __func__, bad_blocks[i]);
+    while (cur_nand_page > 0) {
+        uint32_t nandblock = cur_nand_page / rawnand->erasesize_pages;
+        
+        cur_nand_page &= ~(rawnand->erasesize_pages - 1);
+        if (!is_block_bad(nandblock)) {
+            zxlogf(ERROR, "%s: About to erase block @%u\n",
+                   __func__, nandblock);
+            break;
+        }
+        cur_nand_page -= MIN(rawnand->erasesize_pages, cur_nand_page);
+    }
+    if (cur_nand_page == 0) {
+        zxlogf(ERROR, "%s: Could not find a block to erase\n",
+               __func__);
+        return;
+    }
+    data = malloc(rawnand->writesize);
+    oob = malloc(rawnand->writesize);    
+    /* 
+     * Read existing data and dump that out to a file 
+     */
+    fd = open("/tmp/nand-dump-before-erase", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump-before-erase\n",
+               __func__);
+        return;
+    }
+    for (i = 0 ; i < rawnand->erasesize_pages ; i++) {
+        ecc_correct = 0;
+        memset((char *)rawnand->data_buf, 0, rawnand->writesize);
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        status = nand_read_page(rawnand, data, NULL, cur_nand_page + i,
+                                &ecc_correct, 3);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at %u\n",
+                   __func__, cur_nand_page + i);
+            return;
+        }
+        if (ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %u\n",
+                   __func__, ecc_correct, cur_nand_page);
+        if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not write to file /tmp/nand-dump-before-erase\n",
+                   __func__);
+        }
+    }
+    fsync(fd);    
+    close(fd);
+    /*
+     * Do the erase operation, then re-read the pages, storing it in "after".
+     */
+    status = nand_erase_block(rawnand, cur_nand_page);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Erase block failed %u\n",
+               __func__, cur_nand_page);
+        return;
+    }
+    fd = open("/tmp/nand-dump-after-erase", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump-after-erase\n",
+               __func__);
+        return;
+    }
+    for (i = 0 ; i < rawnand->erasesize_pages ; i++) {
+        ecc_correct = 0;
+        memset((char *)rawnand->data_buf, 0, rawnand->writesize);        
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        memset(oob, 0, rawnand->writesize);        
+        status = nand_read_page(rawnand, data, oob, cur_nand_page + i,
+                                &ecc_correct, 3);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at %u\n",
+                   __func__, cur_nand_page + i);
+            return;
+        }
+        if (ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %u\n",
+                   __func__, ecc_correct, cur_nand_page + i);
+        zxlogf(ERROR,
+               "%s: OOB bytes for page %u: \n", __func__, cur_nand_page + i);
+        for (int j = 0; j < 8; j++)
+            zxlogf(ERROR, " 0x%x", oob[j]);
+        zxlogf(ERROR, "\n");        
+        if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not write to file /tmp/nand-dump-after-erase\n",
+                   __func__);
+        }
+    }
+    fsync(fd);
+    close(fd);
+    free(data);
+    free(oob);    
+}
+
+static void rawnand_test_page0_sub(aml_rawnand_t *rawnand,
+                                   uint32_t nandpage)
+{
+    int ecc_correct;
+    zx_status_t status;
+    char *data;
+    uint8_t *oob;
+    nand_page0_t *page0;
+    
+    data = malloc(rawnand->writesize);
+    memset(data, 0xff, rawnand->writesize);
+    oob = malloc(rawnand->writesize);
+    memset(oob, 0xff, rawnand->writesize);    
+    zxlogf(ERROR, "%s: Reading page0@%u \n", __func__, nandpage);    
+    status = nand_read_page0(rawnand, data, oob, nandpage, &ecc_correct, 3);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Read page0 failed\n", __func__);
+        goto out;
+    }
+    zxlogf(ERROR, "%s: Read page0 succeeded\n", __func__);
+    if (ecc_correct < 0) {
+        zxlogf(ERROR,
+               "%s: Un-Correctable ECC errors on reading page0\n",
+               __func__);
+        goto out;
+    }
+    if (ecc_correct > 0)
+        zxlogf(ERROR,
+               "%s: Corrected ECC errors %d on reading page0\n",
+               __func__, ecc_correct);
+    if (memcmp(data, rawnand->data_buf, 384)) {
+        zxlogf(ERROR, "%s: Something went wrong reading page0\n",
+               __func__);
+        goto out;
+    }
+    page0 = (nand_page0_t *)data;
+    zxlogf(ERROR, "cfg.d32 = 0x%x\n", page0->nand_setup.cfg.d32);
+    uint32_t val = page0->nand_setup.cfg.d32 & 0x3f;
+    zxlogf(ERROR, "ecc_step = %u\n", val);
+    val = (page0->nand_setup.cfg.d32 >> 6) & 0x7f;
+    zxlogf(ERROR, "pagesize = %u\n", val);
+    
+    zxlogf(ERROR, "ext_info.read_info = 0x%x\n", page0->ext_info.read_info);
+    zxlogf(ERROR, "ext_info.page_per_blk = 0x%x\n", page0->ext_info.page_per_blk);
+    zxlogf(ERROR, "ext_info.boot_num = 0x%x\n", page0->ext_info.boot_num);
+    zxlogf(ERROR, "ext_info.each_boot_pages = 0x%x\n", page0->ext_info.each_boot_pages);
+    zxlogf(ERROR, "ext_info.bbt_occupy_pages = 0x%x\n", page0->ext_info.bbt_occupy_pages);
+    zxlogf(ERROR, "ext_info.bbt_start_block = 0x%x\n", page0->ext_info.bbt_start_block);
+    zxlogf(ERROR, "OOB bytes =  0x%x 0x%x\n", *oob, *(oob + 1));
+    
+out:
+    free(data);
+    free(oob);
+}
+
+/*
+ * Read all copies of page0 and dump them out 
+ */
+void rawnand_test_page0(aml_rawnand_t* rawnand)
+{
+    uint32_t i;
+
+    for (i = 0; i < 7; i++)
+        rawnand_test_page0_sub(rawnand, 128 * i);
+}
+
+struct bbt_oobinfo {
+    char name[4];
+    int16_t ec;
+    unsigned timestamp: 15;
+    unsigned status_page: 1;
+};
+
+/*
+ * Dump pages 1024/1025/1026 to see if the Linux MTD bbt is present
+ * among those pages.
+ */
+void rawnand_dump_bbt(aml_rawnand_t *rawnand)
+{
+    uint32_t cur_nand_page;
+    int ecc_correct;
+    zx_status_t status;
+    char *data;
+    uint8_t *oob;
+    int ecc_pages_per_nand_page;
+    int fd;
+    char filename[512];
+    uint32_t ecc_pagesize;
+    struct bbt_oobinfo *bbt_oob;
+    char name[5];
+    
+    ecc_pagesize = aml_get_ecc_pagesize(rawnand, AML_ECC_BCH8);
+    ecc_pages_per_nand_page =
+        rawnand->writesize / ecc_pagesize;
+    data = malloc(rawnand->writesize);
+    oob = malloc(rawnand->writesize);
+    cur_nand_page = (20*64);
+    sprintf(filename, "/tmp/bbt");
+    fd = open(filename, O_CREAT | O_WRONLY);
+    if (fd < 0) {
+	zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump\n",
+	       __func__);
+	return;
+    }
+    memset(oob, 0xff, rawnand->writesize);
+    ecc_correct = 0;
+    memset((char *)rawnand->info_buf, 0,
+	   ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+    status = nand_read_page(rawnand, data, oob, cur_nand_page,
+			   &ecc_correct, 3);
+    if (status != ZX_OK) {
+	zxlogf(ERROR, "%s: Read failed at %u\n",
+	       __func__, cur_nand_page);
+	return;
+    }
+    if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+	zxlogf(ERROR, "%s: Could not write to file %s\n", __func__, filename);
+    }
+    if (ecc_correct > 0)
+	zxlogf(ERROR,
+	       "%s: Corrected ECC errors %d on reading at %u\n",
+	       __func__, ecc_correct, cur_nand_page);
+
+    cur_nand_page++;
+    memset(oob, 0xff, rawnand->writesize);    
+    ecc_correct = 0;
+    memset((char *)rawnand->info_buf, 0,
+	   ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+    status = nand_read_page(rawnand, data, oob, cur_nand_page,
+                            &ecc_correct, 3);
+    if (status != ZX_OK) {
+	zxlogf(ERROR, "%s: Read failed at %u\n",
+	       __func__, cur_nand_page);
+	return;
+    }
+    if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+	zxlogf(ERROR, "%s: Could not write to file %s\n", __func__, filename);
+    }
+    if (ecc_correct > 0)
+	zxlogf(ERROR,
+	       "%s: Corrected ECC errors %d on reading at %u\n",
+	       __func__, ecc_correct, cur_nand_page);
+
+    bbt_oob = (struct bbt_oobinfo *)oob;
+    strncpy(name, bbt_oob->name, 4);
+    name[4] = '\0';
+    zxlogf(ERROR, "%s: bbt_oob: name=%s ec=%d timestamp=%d status_page=%d\n",
+	   __func__, name, bbt_oob->ec, bbt_oob->timestamp, bbt_oob->status_page);
+    
+    fsync(fd);    
+    close(fd);
+    free(data);
+    free(oob);
+    data = malloc(4096);
+    fd = open(filename, O_CREAT | O_RDONLY);
+    if (fd < 0) {
+	zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump\n",
+	       __func__);
+	return;
+    }
+    if (read(fd, data, 4096) != 4096) {
+	zxlogf(ERROR, "%s: Could not write to file %s\n", __func__, filename);
+    }
+    char *p = data;
+    for (uint32_t i = 0; i < 4096; i++) {
+        if (*p++ != 0) {
+            zxlogf(ERROR, "%s: Bad block at %u\n", __func__, i);
+            bad_blocks[num_bad_blocks++] = i;
+        }
+    }
+    close(fd);
+    free(data);        
+}
+
+/* 
+ * Read all pages and dump them all out to a file for analysis
+ */
+void rawnand_read_device_test(aml_rawnand_t* rawnand)
+{
+    uint32_t num_nand_pages;
+    uint32_t cur_nand_page;
+    int ecc_correct;
+    zx_status_t status;
+    char *data;
+    uint32_t num_pages_read = 0;
+    uint32_t ecc_pages_per_nand_page;
+    uint64_t checksum = 0;
+    int fd;
+    uint32_t ecc_pagesize;
+
+    fd = open("/tmp/nand-dump", O_CREAT | O_WRONLY);
+    if (fd < 0) {
+        zxlogf(ERROR, "%s: Could not open file /tmp/nand-dump\n",
+               __func__);
+        return;
+    }
+    ecc_pagesize = aml_get_ecc_pagesize(rawnand,
+                    rawnand->controller_params.bch_mode);
+    ecc_pages_per_nand_page =
+        rawnand->writesize / ecc_pagesize;
+    zxlogf(ERROR, "%s: ECC pages per NAND page = %d\n",
+           __func__, ecc_pages_per_nand_page);
+    num_nand_pages = (rawnand->chipsize * (1024 * 1024)) /
+        rawnand->writesize;
+    data = malloc(rawnand->writesize);
+    for (cur_nand_page = 0;
+         cur_nand_page < num_nand_pages;
+         cur_nand_page++) {
+        ecc_correct = 0;
+        memset(data, 0xff, rawnand->writesize);
+        memset((char *)rawnand->info_buf, 0,
+               ecc_pages_per_nand_page * sizeof(struct aml_info_format));
+        if ((cur_nand_page <= 896) && ((cur_nand_page % 128) == 0)) {
+            status = nand_read_page0(rawnand, data, NULL, cur_nand_page,
+                                     &ecc_correct, 3);
+            /* Fill up the rest of the page with 0xff */
+            memset(data + 384, 0xff, rawnand->writesize - 384);
+        } else {
+            status = nand_read_page(rawnand, data, NULL, cur_nand_page,
+                                    &ecc_correct, 3);
+        }
+        /*
+         * If the write fails, we write 0xff for that page, instead of
+         * skipping that write, so the offsets don't get messed up.
+         */
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s: Read failed at %u\n",
+                   __func__, cur_nand_page);
+            memset(data, 0xff, rawnand->writesize);
+        }
+        uint32_t i;
+        uint64_t *p;
+            
+        num_pages_read++;
+        p = (uint64_t *)rawnand->data_buf;
+        for (i = 0; i < rawnand->writesize/sizeof(uint64_t); i++)
+            checksum += *p++;
+        if (write(fd, data, rawnand->writesize) != rawnand->writesize) {
+            zxlogf(ERROR, "%s: Could not write to file /tmp/nand-dump\n",
+                   __func__);
+        }
+        if (status == ZX_OK && ecc_correct > 0)
+            zxlogf(ERROR,
+                   "%s: Corrected ECC errors %d on reading at %u\n",
+                   __func__, ecc_correct, cur_nand_page);
+    }
+    zxlogf(ERROR, "%s: pages_read = %u\n", __func__, num_pages_read);
+    zxlogf(ERROR, "%s: checksum = 0x%lx\n", __func__, checksum);
+    fsync(fd);    
+    close(fd);
+}
+#endif
diff --git a/system/dev/block/rawnand/rules.mk b/system/dev/block/rawnand/rules.mk
new file mode 100644
index 0000000..00b4cec
--- /dev/null
+++ b/system/dev/block/rawnand/rules.mk
@@ -0,0 +1,27 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := driver
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/aml-rawnand.c \
+    $(LOCAL_DIR)/rawnand_tests.c \
+    $(LOCAL_DIR)/nand.c \
+
+MODULE_STATIC_LIBS := \
+    system/ulib/ddk \
+    system/ulib/sync \
+
+MODULE_LIBS := \
+    system/ulib/driver \
+    system/ulib/c \
+    system/ulib/zircon \
+
+MODULE_HEADER_DEPS := system/dev/soc/amlogic
+
+include make/module.mk
diff --git a/system/dev/board/aml-s905d2/aml-rawnand.c b/system/dev/board/aml-s905d2/aml-rawnand.c
new file mode 100644
index 0000000..383dbfa
--- /dev/null
+++ b/system/dev/board/aml-s905d2/aml-rawnand.c
@@ -0,0 +1,107 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddk/protocol/gpio.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <hw/reg.h>
+#include <soc/aml-s905d2/s905d2-hw.h>
+#include <soc/aml-s905d2/s905d2-gpio.h>
+#include <unistd.h>
+
+#include "aml.h"
+
+static const pbus_mmio_t rawnand_mmios[] = {
+    {   /* nandreg : Registers for NAND controller */
+        /* 
+         * From the Linux devicetree this seems the right
+         * address. The data sheet is WRONG.
+         */
+        .base = 0xffe07800,
+        .length = 0x2000,
+    },
+    {   /* clockreg : Clock Register for NAND controller */
+        /*
+         * From the Linux devicetree. This is the base SD_EMMC_CLOCK
+         * register (for port C)
+         */
+        .base = /* 0xd0070000 */ 0xffe07000,
+        .length = 0x4,  /* Just 4 bytes */
+    },
+};
+
+static const pbus_irq_t rawnand_irqs[] = {
+    {
+        .irq = 66,
+    },
+};
+
+static const pbus_bti_t rawnand_btis[] = {
+    {
+        .iommu_index = 0,
+        .bti_id = BTI_AML_RAWNAND,
+    },
+};
+
+static const pbus_dev_t rawnand_dev = {
+    .name = "aml_rawnand",
+    .vid = PDEV_VID_AMLOGIC,
+    .pid = PDEV_PID_GENERIC,
+    .did = PDEV_DID_AMLOGIC_RAWNAND,
+    .mmios = rawnand_mmios,
+    .mmio_count = countof(rawnand_mmios),
+    .irqs = rawnand_irqs,
+    .irq_count = countof(rawnand_irqs),
+    .btis = rawnand_btis,
+    .bti_count = countof(rawnand_btis),
+};
+
+
+zx_status_t aml_rawnand_init(aml_bus_t* bus) 
+{
+    zx_status_t status;
+
+    // set alternate functions to enable rawnand
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(8),
+                                   2);
+    if (status != ZX_OK)
+        return status;    
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(9),
+                                   2);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(10),
+                                   2);    
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(11),
+                                   2);        
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(12),
+                                   2);            
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(14),
+                                   2);                
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(15),
+                                   2);                
+    if (status != ZX_OK)
+        return status;
+
+    status = pbus_device_add(&bus->pbus, &rawnand_dev, 0);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: pbus_device_add failed: %d\n",
+               __func__, status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
diff --git a/system/dev/board/aml-s905d2/aml.c b/system/dev/board/aml-s905d2/aml.c
index 9d92a73..c73d8f2 100644
--- a/system/dev/board/aml-s905d2/aml.c
+++ b/system/dev/board/aml-s905d2/aml.c
@@ -57,6 +57,11 @@
         goto fail;
     }
 
+    if ((status = aml_rawnand_init(bus)) != ZX_OK) {
+        zxlogf(ERROR, "aml_rawnand_init failed: %d\n", status);
+        goto fail;
+    }
+
     return ZX_OK;
 fail:
     zxlogf(ERROR, "aml_start_thread failed, not all devices have been initialized\n");
diff --git a/system/dev/board/aml-s905d2/aml.h b/system/dev/board/aml-s905d2/aml.h
index f5439f6..2f26e60 100644
--- a/system/dev/board/aml-s905d2/aml.h
+++ b/system/dev/board/aml-s905d2/aml.h
@@ -16,6 +16,7 @@
 enum {
     BTI_BOARD,
     BTI_USB_XHCI,
+    BTI_AML_RAWNAND,    
 };
 
 typedef struct {
@@ -35,3 +36,7 @@
 
 // aml-usb.c
 zx_status_t aml_usb_init(aml_bus_t* bus);
+
+// aml-rawnand.c
+zx_status_t aml_rawnand_init(aml_bus_t* bus);
+
diff --git a/system/dev/board/aml-s905d2/rules.mk b/system/dev/board/aml-s905d2/rules.mk
index a5d4c82..23ef33d 100644
--- a/system/dev/board/aml-s905d2/rules.mk
+++ b/system/dev/board/aml-s905d2/rules.mk
@@ -13,6 +13,7 @@
     $(LOCAL_DIR)/aml-gpio.c \
     $(LOCAL_DIR)/aml-i2c.c \
     $(LOCAL_DIR)/aml-usb.c \
+    $(LOCAL_DIR)/aml-rawnand.c \
 
 MODULE_STATIC_LIBS := \
     system/dev/soc/amlogic \
diff --git a/system/dev/board/astro/astro-rawnand.c b/system/dev/board/astro/astro-rawnand.c
new file mode 100644
index 0000000..dd42a7d
--- /dev/null
+++ b/system/dev/board/astro/astro-rawnand.c
@@ -0,0 +1,107 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddk/protocol/gpio.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <hw/reg.h>
+#include <soc/aml-s905d2/s905d2-hw.h>
+#include <soc/aml-s905d2/s905d2-gpio.h>
+#include <unistd.h>
+
+#include "astro.h"
+
+static const pbus_mmio_t rawnand_mmios[] = {
+    {   /* nandreg : Registers for NAND controller */
+        /* 
+         * From the Linux devicetree this seems the right
+         * address. The data sheet is WRONG.
+         */
+        .base = 0xffe07800,
+        .length = 0x2000,
+    },
+    {   /* clockreg : Clock Register for NAND controller */
+        /*
+         * From the Linux devicetree. This is the base SD_EMMC_CLOCK
+         * register (for port C)
+         */
+        .base = /* 0xd0070000 */ 0xffe07000,
+        .length = 0x4,  /* Just 4 bytes */
+    },
+};
+
+static const pbus_irq_t rawnand_irqs[] = {
+    {
+        .irq = 66,
+    },
+};
+
+static const pbus_bti_t rawnand_btis[] = {
+    {
+        .iommu_index = 0,
+        .bti_id = BTI_AML_RAWNAND,
+    },
+};
+
+static const pbus_dev_t rawnand_dev = {
+    .name = "aml_rawnand",
+    .vid = PDEV_VID_AMLOGIC,
+    .pid = PDEV_PID_GENERIC,
+    .did = PDEV_DID_AMLOGIC_RAWNAND,
+    .mmios = rawnand_mmios,
+    .mmio_count = countof(rawnand_mmios),
+    .irqs = rawnand_irqs,
+    .irq_count = countof(rawnand_irqs),
+    .btis = rawnand_btis,
+    .bti_count = countof(rawnand_btis),
+};
+
+
+zx_status_t aml_rawnand_init(aml_bus_t* bus) 
+{
+    zx_status_t status;
+
+    // set alternate functions to enable rawnand
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(8),
+                                   2);
+    if (status != ZX_OK)
+        return status;    
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(9),
+                                   2);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(10),
+                                   2);    
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(11),
+                                   2);        
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(12),
+                                   2);            
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(14),
+                                   2);                
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S905D2_GPIOBOOT(15),
+                                   2);                
+    if (status != ZX_OK)
+        return status;
+
+    status = pbus_device_add(&bus->pbus, &rawnand_dev, 0);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: pbus_device_add failed: %d\n",
+               __func__, status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
diff --git a/system/dev/board/astro/astro.h b/system/dev/board/astro/astro.h
index f5439f6..7ef3136 100644
--- a/system/dev/board/astro/astro.h
+++ b/system/dev/board/astro/astro.h
@@ -16,6 +16,7 @@
 enum {
     BTI_BOARD,
     BTI_USB_XHCI,
+    BTI_AML_RAWNAND,
 };
 
 typedef struct {
@@ -35,3 +36,6 @@
 
 // aml-usb.c
 zx_status_t aml_usb_init(aml_bus_t* bus);
+
+// aml-rawnand.c
+zx_status_t aml_rawnand_init(aml_bus_t* bus);
diff --git a/system/dev/board/astro/rules.mk b/system/dev/board/astro/rules.mk
index e34c269..6bf893a 100644
--- a/system/dev/board/astro/rules.mk
+++ b/system/dev/board/astro/rules.mk
@@ -13,6 +13,7 @@
     $(LOCAL_DIR)/astro-gpio.c \
     $(LOCAL_DIR)/astro-i2c.c \
     $(LOCAL_DIR)/astro-usb.c \
+    $(LOCAL_DIR)/astro-rawnand.c \
 
 MODULE_STATIC_LIBS := \
     system/dev/soc/amlogic \
diff --git a/system/dev/board/gauss/gauss-rawnand.c b/system/dev/board/gauss/gauss-rawnand.c
new file mode 100644
index 0000000..7732fb8
--- /dev/null
+++ b/system/dev/board/gauss/gauss-rawnand.c
@@ -0,0 +1,104 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddk/protocol/gpio.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <hw/reg.h>
+#include <soc/aml-a113/a113-hw.h>
+#include <unistd.h>
+
+#include "gauss.h"
+#include "gauss-hw.h"
+
+static const pbus_mmio_t rawnand_mmios[] = {
+    {   /* nandreg : Registers for NAND controller */
+        /* 
+         * From the Linux devicetree this seems the right
+         * address. The data sheet is WRONG.
+         */
+        .base = 0xffe07800,
+        .length = 0x2000,
+    },
+    {   /* clockreg : Clock Register for NAND controller */
+        /*
+         * From the Linux devicetree. This is the base SD_EMMC_CLOCK
+         * register (for port C)
+         */
+        .base = 0xffe07000,
+        .length = 0x4,  /* Just 4 bytes */
+    },
+};
+
+static const pbus_irq_t rawnand_irqs[] = {
+    {
+        .irq = 66,
+    },
+};
+
+static const pbus_bti_t rawnand_btis[] = {
+    {
+        .iommu_index = 0,
+        .bti_id = BTI_AML_RAWNAND,
+    },
+};
+
+static const pbus_dev_t rawnand_dev = {
+    .name = "aml_rawnand",
+    .vid = PDEV_VID_AMLOGIC,
+    .pid = PDEV_PID_GENERIC,
+    .did = PDEV_DID_AMLOGIC_RAWNAND,
+    .mmios = rawnand_mmios,
+    .mmio_count = countof(rawnand_mmios),
+    .irqs = rawnand_irqs,
+    .irq_count = countof(rawnand_irqs),
+    .btis = rawnand_btis,
+    .bti_count = countof(rawnand_btis),
+};
+
+
+zx_status_t gauss_rawnand_init(gauss_bus_t* bus) 
+{
+    zx_status_t status;
+
+    // set alternate functions to enable rawnand
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(8),
+                                   2);
+    if (status != ZX_OK)
+        return status;    
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(9),
+                                   2);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(10),
+                                   2);    
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(11),
+                                   2);        
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(12),
+                                   2);            
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, A113_GPIOBOOT(13),
+                                   2);                
+    if (status != ZX_OK)
+        return status;
+
+    status = pbus_device_add(&bus->pbus, &rawnand_dev, 0);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "gauss_rawnand_init: pbus_device_add failed: %d\n",
+               status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
+
diff --git a/system/dev/board/gauss/gauss.c b/system/dev/board/gauss/gauss.c
index 6fcdc7a..b83b9ac 100644
--- a/system/dev/board/gauss/gauss.c
+++ b/system/dev/board/gauss/gauss.c
@@ -193,6 +193,11 @@
     }
 #endif
 
+    if ((status = gauss_rawnand_init(bus)) != ZX_OK) {
+            zxlogf(ERROR, "gauss_rawnand_init failed: %d\n", status);
+            goto fail;
+    }
+
     if ((status = pbus_device_add(&bus->pbus, &led_dev, 0)) != ZX_OK) {
         zxlogf(ERROR, "a113_i2c_init could not add i2c_led_dev: %d\n", status);
         goto fail;
diff --git a/system/dev/board/gauss/gauss.h b/system/dev/board/gauss/gauss.h
index 2e093a2..18d8182 100644
--- a/system/dev/board/gauss/gauss.h
+++ b/system/dev/board/gauss/gauss.h
@@ -28,6 +28,7 @@
     BTI_AUDIO_IN,
     BTI_AUDIO_OUT,
     BTI_USB_XHCI,
+    BTI_AML_RAWNAND,    
 };
 
 typedef struct {
@@ -63,3 +64,7 @@
 
 // gauss-pcie.c
 zx_status_t gauss_pcie_init(gauss_bus_t* bus);
+
+// gauss-rawnand.c
+zx_status_t gauss_rawnand_init(gauss_bus_t* bus);
+
diff --git a/system/dev/board/gauss/rules.mk b/system/dev/board/gauss/rules.mk
index 4a26688..ade430d 100644
--- a/system/dev/board/gauss/rules.mk
+++ b/system/dev/board/gauss/rules.mk
@@ -16,6 +16,8 @@
     $(LOCAL_DIR)/gauss-i2c.c \
     $(LOCAL_DIR)/gauss-pcie.c \
     $(LOCAL_DIR)/gauss-usb.c \
+    $(LOCAL_DIR)/gauss-rawnand.c \
+
 
 MODULE_STATIC_LIBS := \
     system/dev/soc/amlogic \
diff --git a/system/dev/board/vim/rules.mk b/system/dev/board/vim/rules.mk
index f76e056..00f9e47 100644
--- a/system/dev/board/vim/rules.mk
+++ b/system/dev/board/vim/rules.mk
@@ -18,6 +18,7 @@
     $(LOCAL_DIR)/vim-sd-emmc.c \
     $(LOCAL_DIR)/vim-eth.c \
     $(LOCAL_DIR)/vim-fanctl.c \
+    $(LOCAL_DIR)/vim-rawnand.c \
 
 MODULE_STATIC_LIBS := \
     system/ulib/ddk \
diff --git a/system/dev/board/vim/vim-rawnand.c b/system/dev/board/vim/vim-rawnand.c
new file mode 100644
index 0000000..b276d8f
--- /dev/null
+++ b/system/dev/board/vim/vim-rawnand.c
@@ -0,0 +1,94 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddk/protocol/gpio.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <hw/reg.h>
+#include <soc/aml-s912/s912-gpio.h>
+#include <soc/aml-s912/s912-hw.h>
+#include <unistd.h>
+
+#include "vim.h"
+
+static const pbus_mmio_t rawnand_mmios[] = {
+    {
+        .base = 0xffe07000,
+        .length = 0x2000,
+    }
+};
+
+static const pbus_irq_t rawnand_irqs[] = {
+    {
+        .irq = 34,
+    },
+};
+
+static const pbus_dev_t rawnand_dev = {
+    .name = "aml_rawnand",
+    .vid = PDEV_VID_AMLOGIC,
+    .pid = PDEV_PID_GENERIC,
+    .did = PDEV_DID_AMLOGIC_RAWNAND,
+    .mmios = rawnand_mmios,
+    .mmio_count = countof(rawnand_mmios),
+    .irqs = rawnand_irqs,
+    .irq_count = countof(rawnand_irqs),
+};
+
+
+zx_status_t vim_rawnand_init(vim_bus_t* bus) 
+{
+    zx_status_t status;
+
+    zxlogf(ERROR, "*** MOHAN *** Entered rawnand init\n");
+    return ZX_OK;
+
+    // set alternate functions to enable rawnand
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_CE0,
+                                   S912_RAWNAND_CE0_FN);
+    if (status != ZX_OK)
+        return status;    
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_CE1,
+                                   S912_RAWNAND_CE1_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_RB0,
+                                   S912_RAWNAND_RB0_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_ALE,
+                                   S912_RAWNAND_ALE_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_CLE,
+                                   S912_RAWNAND_CLE_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_WEN_CLK,
+                                   S912_RAWNAND_WEN_CLK_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_REN_WR,
+                                   S912_RAWNAND_REN_WR_FN);
+    if (status != ZX_OK)
+        return status;
+    status = gpio_set_alt_function(&bus->gpio, S912_RAWNAND_NAND_DQS,
+                                   S912_RAWNAND_NAND_DQS_FN);
+    if (status != ZX_OK)
+        return status;    
+
+    status = pbus_device_add(&bus->pbus, &rawnand_dev, 0);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "vim_rawnand_init: pbus_device_add failed: %d\n",
+               status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
+
diff --git a/system/dev/board/vim/vim.c b/system/dev/board/vim/vim.c
index 7be345c..6775ecb 100644
--- a/system/dev/board/vim/vim.c
+++ b/system/dev/board/vim/vim.c
@@ -154,6 +154,14 @@
         goto fail;
     }
 
+    zxlogf(ERROR,
+           "*** MOHAN *** PRINT vim_start_thread: BEFORE BOARD CHECK !!\n");
+    
+    if ((status = vim_rawnand_init(bus)) != ZX_OK) {
+        zxlogf(ERROR, "vim_rawnand_init failed: %d\n", status);
+        goto fail;
+    }
+
     if ((status = pbus_device_add(&bus->pbus, &led2472g_dev, 0)) != ZX_OK) {
       zxlogf(ERROR, "vim_start_thread could not add led2472g_dev: %d\n", status);
       goto fail;
diff --git a/system/dev/board/vim/vim.h b/system/dev/board/vim/vim.h
index c734036..adae2a3 100644
--- a/system/dev/board/vim/vim.h
+++ b/system/dev/board/vim/vim.h
@@ -52,4 +52,7 @@
 zx_status_t vim_eth_init(vim_bus_t* bus);
 
 // vim-fanctl.c
-zx_status_t vim2_fanctl_init(vim_bus_t* bus);
\ No newline at end of file
+zx_status_t vim2_fanctl_init(vim_bus_t* bus);
+
+// vim-rawnand.c
+zx_status_t vim_rawnand_init(vim_bus_t* bus);
diff --git a/system/dev/soc/amlogic/include/soc/aml-common/aml-rawnand.h b/system/dev/soc/amlogic/include/soc/aml-common/aml-rawnand.h
new file mode 100644
index 0000000..c7d9fa3
--- /dev/null
+++ b/system/dev/soc/amlogic/include/soc/aml-common/aml-rawnand.h
@@ -0,0 +1,257 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define AML_NAME "aml-nand"
+
+#define P_NAND_CMD      (0x00)
+#define P_NAND_CFG      (0x04)
+#define P_NAND_DADR     (0x08)
+#define P_NAND_IADR     (0x0c)
+#define P_NAND_BUF      (0x10)
+#define P_NAND_INFO     (0x14)
+#define P_NAND_DC       (0x18)
+#define P_NAND_ADR      (0x1c)
+#define P_NAND_DL       (0x20)
+#define P_NAND_DH       (0x24)
+#define P_NAND_CADR     (0x28)
+#define P_NAND_SADR     (0x2c)
+#define P_NAND_PINS     (0x30)
+#define P_NAND_VER      (0x38)
+
+#define AML_CMD_DRD     (0x8<<14)
+#define AML_CMD_IDLE    (0xc<<14)
+#define AML_CMD_DWR     (0x4<<14)
+#define AML_CMD_CLE     (0x5<<14)
+#define AML_CMD_ALE     (0x6<<14)
+#define AML_CMD_ADL     ((0<<16) | (3<<20))
+#define AML_CMD_ADH     ((1<<16) | (3<<20))
+#define AML_CMD_AIL     ((2<<16) | (3<<20))
+#define AML_CMD_AIH     ((3<<16) | (3<<20))
+#define AML_CMD_SEED    ((8<<16) | (3<<20))
+#define AML_CMD_M2N     ((0<<17) | (2<<20))
+#define AML_CMD_N2M     ((1<<17) | (2<<20))
+#define AML_CMD_RB      (1<<20)
+#define AML_CMD_IO6     ((0xb<<10)|(1<<18))
+
+#define NAND_TWB_TIME_CYCLE     10
+
+#define CMDRWGEN(cmd_dir, ran, bch, short, pagesize, pages) \
+    ((cmd_dir) | (ran) << 19 | (bch) << 14 | \
+     (short) << 13 | ((pagesize)&0x7f) << 6 | ((pages)&0x3f))
+
+#define GENCMDDADDRL(adl, addr) \
+    ((adl) | ((addr) & 0xffff))
+#define GENCMDDADDRH(adh, addr) \
+    ((adh) | (((addr) >> 16) & 0xffff))
+
+#define GENCMDIADDRL(ail, addr) \
+    ((ail) | ((addr) & 0xffff))
+#define GENCMDIADDRH(aih, addr) \
+    ((aih) | (((addr) >> 16) & 0xffff))
+
+#define RB_STA(x) (1<<(26+x))
+
+#define ECC_CHECK_RETURN_FF (-1)
+
+#define DMA_BUSY_TIMEOUT 0x100000
+
+#define MAX_CE_NUM      2
+
+#define RAN_ENABLE      1
+
+#define CLK_ALWAYS_ON   (0x01 << 28)
+#define AML_CLK_CYCLE   6
+
+/* nand flash controller delay 3 ns */
+#define AML_DEFAULT_DELAY 3000
+
+#define MAX_ECC_INDEX   10
+
+typedef enum rawnand_addr_window {
+    NANDREG_WINDOW = 0,
+    CLOCKREG_WINDOW,
+    ADDR_WINDOW_COUNT,  // always last
+} rawnand_addr_window_t;
+
+typedef struct {
+    int ecc_strength;
+    int user_mode;
+    int rand_mode;
+#define NAND_USE_BOUNCE_BUFFER          0x1
+    int options;
+    int bch_mode;
+} aml_controller_t;
+
+typedef struct {
+    platform_device_protocol_t pdev;
+    zx_device_t* zxdev;
+    io_buffer_t mmio[ADDR_WINDOW_COUNT];
+    thrd_t irq_thread;
+    zx_handle_t irq_handle;
+    bool enabled;
+    aml_controller_t  controller_params;
+    uint32_t chip_select;
+    int controller_delay;
+    uint32_t writesize;	/* NAND pagesize - bytes */
+    uint32_t erasesize;	/* size of erase block - bytes */
+    uint32_t erasesize_pages;
+    uint32_t oobsize;	/* oob bytes per NAND page - bytes */
+#define NAND_BUSWIDTH_16        0x00000002    
+    uint32_t bus_width;	/* 16bit or 8bit ? */
+    uint64_t            chipsize; /* MiB */
+    uint32_t page_shift; /* NAND page shift */
+    completion_t req_completion;
+    struct {
+        uint64_t ecc_corrected;
+        uint64_t failed;        
+    } stats;
+    io_buffer_t data_buffer;
+    io_buffer_t info_buffer;
+    zx_handle_t bti_handle;
+    void *info_buf, *data_buf;
+    zx_paddr_t info_buf_paddr, data_buf_paddr;
+} aml_rawnand_t;
+
+static inline void set_bits(uint32_t *_reg, const uint32_t _value,
+                            const uint32_t _start, const uint32_t _len)
+{
+    writel(((readl(_reg) & ~(((1L << (_len))-1) << (_start)))
+            | ((uint32_t)((_value)&((1L<<(_len))-1)) << (_start))), _reg);
+}
+
+static inline void nandctrl_set_cfg(aml_rawnand_t *rawnand,
+                                    uint32_t val)
+{
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    writel(val, reg + P_NAND_CFG);
+}
+
+static inline void nandctrl_set_timing_async(aml_rawnand_t *rawnand,
+                                             int bus_tim,
+                                             int bus_cyc)
+{
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+    
+    set_bits((uint32_t *)(reg + P_NAND_CFG),
+             ((bus_cyc&31)|((bus_tim&31)<<5)|(0<<10)),
+             0, 12);
+}
+
+static inline void nandctrl_send_cmd(aml_rawnand_t *rawnand,
+                                     uint32_t cmd)
+{
+    volatile uint8_t *reg = (volatile uint8_t*)
+        io_buffer_virt(&rawnand->mmio[NANDREG_WINDOW]);
+
+    writel(cmd, reg + P_NAND_CMD);
+}
+
+enum {
+    AML_ECC_NONE    = 0,
+    /* bch8 with ecc page size of 512B */
+    AML_ECC_BCH8,
+    /* bch8 with ecc page size of 1024B */
+    AML_ECC_BCH8_1K,
+    AML_ECC_BCH24_1K,
+    AML_ECC_BCH30_1K,
+    AML_ECC_BCH40_1K,
+    AML_ECC_BCH50_1K,
+    AML_ECC_BCH60_1K,
+
+    /*                                                                      
+     * Short mode is special only for page 0 when inplement booting         
+     * from nand. it means that using a small size(384B/8=48B) of ecc page  
+     * with a fixed ecc mode. rom code use short mode to read page0 for     
+     * getting nand parameter such as ecc, scramber and so on.              
+     * For gxl serial, first page adopt short mode and 60bit ecc; for axg   
+     * serial, adopt short mode and 8bit ecc.                               
+     */
+    AML_ECC_BCH_SHORT,
+};
+
+/*
+ * Controller ECC, OOB, RAND parameters
+ * XXX - This should be controller specific.
+ * Hardcoding for now.
+ */
+struct aml_controller_params {
+    int         ecc_strength;   /* # of ECC bits per ECC page */
+    int         user_mode; /* OOB bytes every ECC page or per block ? */
+    int         rand_mode; /* Randomize ? */
+    int         bch_mode;
+};
+
+/*
+ * In the case where user_mode == 2 (2 OOB bytes per ECC page),
+ * the controller adds one of these structs *per* ECC page in
+ * the info_buf. 
+ */
+struct __attribute__((packed)) aml_info_format {
+    uint16_t info_bytes;
+    uint8_t zero_cnt;    /* bit0~5 is valid */
+    struct ecc_sta {
+        uint8_t eccerr_cnt : 6;
+        uint8_t notused : 1;
+        uint8_t completed : 1;
+    } ecc;
+    uint32_t reserved;
+};
+
+static_assert(sizeof(struct aml_info_format) == 8,
+              "sizeof(struct aml_info_format) must be exactly 8 bytes");
+
+typedef struct nand_setup {
+    union {
+	uint32_t d32;
+	struct {
+	    unsigned cmd:22;
+	    unsigned large_page:1;  // 22
+	    unsigned no_rb:1;	    // 23 from efuse
+	    unsigned a2:1;	    // 24
+	    unsigned reserved25:1;  // 25
+	    unsigned page_list:1;   // 26
+	    unsigned sync_mode:2;   // 27 from efuse
+	    unsigned size:2;        // 29 from efuse
+	    unsigned active:1;	    // 31
+	} b;
+    } cfg;
+    uint16_t id;
+    uint16_t max;    // id:0x100 user, max:0 disable.
+} nand_setup_t;
+
+typedef struct _nand_cmd {
+    u_int8_t type;
+    u_int8_t val;
+} nand_cmd_t;
+
+
+typedef struct _ext_info {
+    uint32_t read_info;   	//nand_read_info;
+    uint32_t new_type;    	//new_nand_type;
+    uint32_t page_per_blk;   	//pages_in_block;
+    uint32_t xlc;		//slc=1, mlc=2, tlc=3.
+    uint32_t ce_mask;
+    /* copact mode: boot means whole uboot
+     * it's easy to understood that copies of
+     * bl2 and fip are the same.
+     * discrete mode, boot means the fip only
+     */
+    uint32_t boot_num;
+    uint32_t each_boot_pages;
+    /* for comptible reason */
+    uint32_t bbt_occupy_pages;
+    uint32_t bbt_start_block;
+} ext_info_t;
+
+typedef struct _nand_page0 {
+    nand_setup_t nand_setup;		//8
+    unsigned char page_list[16];	//16
+    nand_cmd_t retry_usr[32];		//64 (32 cmd max I/F)
+    ext_info_t ext_info;		//64
+} nand_page0_t;				//384 bytes max.
+
+
diff --git a/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h b/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
index 6dd49fd..1676c27 100644
--- a/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
+++ b/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
@@ -268,3 +268,21 @@
 #define S912_EMMC_CMD_FN        1
 #define S912_EMMC_DS            S912_GPIOBOOT(15)
 #define S912_EMMC_DS_FN         1
+
+// rawnand controller 
+#define S912_RAWNAND_CE0            S912_GPIOBOOT(8)
+#define S912_RAWNAND_CE0_FN         2
+#define S912_RAWNAND_CE1            S912_GPIOBOOT(9)
+#define S912_RAWNAND_CE1_FN         2
+#define S912_RAWNAND_RB0            S912_GPIOBOOT(10)
+#define S912_RAWNAND_RB0_FN         2
+#define S912_RAWNAND_ALE            S912_GPIOBOOT(11)
+#define S912_RAWNAND_ALE_FN         2
+#define S912_RAWNAND_CLE            S912_GPIOBOOT(12)
+#define S912_RAWNAND_CLE_FN         2
+#define S912_RAWNAND_WEN_CLK        S912_GPIOBOOT(13)
+#define S912_RAWNAND_WEN_CLK_FN     2
+#define S912_RAWNAND_REN_WR         S912_GPIOBOOT(14)
+#define S912_RAWNAND_REN_WR_FN      2
+#define S912_RAWNAND_NAND_DQS       S912_GPIOBOOT(15)
+#define S912_RAWNAND_NAND_DQS_FN    2
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-defs.h b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
index 20b8c86..cf90382 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-defs.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
@@ -67,6 +67,7 @@
 #define PDEV_DID_AMLOGIC_SD_EMMC    6
 #define PDEV_DID_AMLOGIC_ETH        7
 #define PDEV_DID_AMLOGIC_FANCTL     8
+#define PDEV_DID_AMLOGIC_RAWNAND    9
 
 // Broadcom
 #define PDEV_VID_BROADCOM           6
diff --git a/system/ulib/ddk/include/ddk/protocol/rawnand.h b/system/ulib/ddk/include/ddk/protocol/rawnand.h
new file mode 100644
index 0000000..ce9416b
--- /dev/null
+++ b/system/ulib/ddk/include/ddk/protocol/rawnand.h
@@ -0,0 +1,113 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <assert.h>
+#include <stdint.h>
+
+// nand_info_t is used to retrieve various parameters describing the
+// geometry of the underlying NAND chip(s). This is retrieved using
+// the query api in nand_protocol_ops.
+
+typedef struct nand_info nand_info_t;
+
+struct nand_info {
+    uint32_t    erase_blocksize;            // in bytes
+    uint32_t    pagesize;                   // in bytes
+    uint64_t    device_size;                // in bytes
+    // # of ECC bits per ECC page - in other words, the # of bitflips
+    // that can be corrected per ECC page.
+    uint32_t    ecc_bits;
+};
+
+// nand_op_t's are submitted for processing via the queue() method
+// of the nand_protocol.  Once submitted, the contents of the nand_op_t
+// may be modified while it's being processed
+//
+// The completion_cb() must eventually be called upon success or failure and
+// at that point the cookie field must contain whatever value was in it when
+// the nand_op_t was originally queued.
+
+#define NAND_OP_READ_DATA               0x00000001
+#define NAND_OP_WRITE_DATA              0x00000002
+#define NAND_OP_ERASE                   0x00000003
+// TBD NAND_OP_READ_OOB, NAND_OP_WRITE_OOB
+
+typedef struct nand_op nand_op_t;
+
+struct nand_op {
+    // All Commands
+    uint32_t command;
+    union {
+        // NAND_OP_READ_DATA, NAND_OP_WRITE_DATA
+        struct {
+            uint32_t command;
+            // command
+            zx_handle_t vmo;
+            // vmo of data to read or write
+            uint32_t length;
+            // transfer length in bytes
+            // (0 is invalid).
+            // MUST BE A MULTIPLE OF NAND PAGESIZE
+            uint64_t offset_nand;
+            // offset into nand in bytes.
+            // MUST BE NAND PAGESIZE aligned
+            uint64_t offset_vmo;
+            // vmo offset in bytes
+            uint64_t* pages;
+            // optional physical page list
+            // Return value from READ_DATA, max corrected bit flips in
+            // any underlying ECC page read. The above layer(s) can
+            // compare this returned value against the bits-per-ECC-page
+            // to decide whether the underlying NAND erase block needs
+            // to be moved
+            uint32_t max_bitflips_ecc_page;
+        } rw;
+
+        // NAND_OP_ERASE
+        struct {
+            uint64_t offset_nand;
+            // offset into nand in bytes
+            // MUST BE NAND ERASEBLOCKSIZE aligned
+            uint32_t length;
+            // erase length in bytes(0 is invalid).
+            // MUST BE NAND ERASEBLOCKSIZE multiple
+        } erase;
+    } u;
+
+    // The completion_cb() will be called when the nand operation
+    // succeeds or fails, and cookie will be whatever was set when
+    // the nand_op was initially queue()'d.
+    void (*completion_cb)(nand_op_t *nop, zx_status_t status);
+    void *cookie;
+};
+
+typedef struct nand_protocol_ops {
+    // Obtain the parameters of the nand device (nand_info_t) and
+    // the required size of nand_txn_t.  The nand_txn_t's submitted
+    // via queue() must have nand_op_size_out - sizeof(nand_op_t) bytes
+    // available at the end of the structure for the use of the driver.
+    void (*query)(void *ctx, nand_info_t *info_out, size_t *nand_op_size_out);
+
+    // Submit an IO request for processing.  Success or failure will
+    // be reported via the completion_cb() in the nand_op_t. This
+    // callback may be called before the queue() method returns.
+    void (*queue)(void *ctx, nand_op_t *op);
+
+    // Get list of bad NAND blocks (queried on startup)
+    // Returns the number of bad blocks found (could be 0).
+    // Internally the function allocates a table to hold the addresses of
+    // each bad block found. The returned table needs to be freed by the
+    // caller. The size of the table returned is
+    // (num_bad_blocks * sizeof(uint64_t))
+    void (*get_bad_block_list)(void *ctx, size_t *num_bad_blocks,
+                               uint64_t **blocklist);
+} nand_protocol_ops_t;
+
+
+typedef struct nand_protocol {
+    nand_protocol_ops_t* ops;
+    void* ctx;
+} nand_protocol_t;