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;