summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-imx/xload-imx-nand.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-imx/xload-imx-nand.c')
-rw-r--r--arch/arm/mach-imx/xload-imx-nand.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/xload-imx-nand.c b/arch/arm/mach-imx/xload-imx-nand.c
new file mode 100644
index 0000000000..22e41fac77
--- /dev/null
+++ b/arch/arm/mach-imx/xload-imx-nand.c
@@ -0,0 +1,308 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#define pr_fmt(fmt) "imx-nand-boot: " fmt
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <linux/mtd/nand.h>
+#include <mach/imx-nand.h>
+#include <mach/generic.h>
+#include <mach/imx53-regs.h>
+#include <mach/xload.h>
+
+struct imx_nand {
+ void __iomem *base;
+ void __iomem *main_area0;
+ void __iomem *regs_ip;
+ void __iomem *regs_axi;
+ void *spare0;
+ int pagesize;
+ int v1;
+ int pages_per_block;
+};
+
+static void wait_op_done(struct imx_nand *host)
+{
+ u32 r;
+
+ while (1) {
+ r = readl(NFC_V3_IPC);
+ if (r & NFC_V3_IPC_INT)
+ break;
+ };
+
+ r &= ~NFC_V3_IPC_INT;
+
+ writel(r, NFC_V3_IPC);
+}
+
+/*
+ * This function issues the specified command to the NAND device and
+ * waits for completion.
+ *
+ * @param cmd command for NAND Flash
+ */
+static void imx_nandboot_send_cmd(struct imx_nand *host, u16 cmd)
+{
+ /* fill command */
+ writel(cmd, NFC_V3_FLASH_CMD);
+
+ /* send out command */
+ writel(NFC_CMD, NFC_V3_LAUNCH);
+
+ /* Wait for operation to complete */
+ wait_op_done(host);
+}
+
+/*
+ * This function sends an address (or partial address) to the
+ * NAND device. The address is used to select the source/destination for
+ * a NAND command.
+ *
+ * @param addr address to be written to NFC.
+ * @param islast True if this is the last address cycle for command
+ */
+static void imx_nandboot_send_addr(struct imx_nand *host, u16 addr)
+{
+ /* fill address */
+ writel(addr, NFC_V3_FLASH_ADDR0);
+
+ /* send out address */
+ writel(NFC_ADDR, NFC_V3_LAUNCH);
+
+ wait_op_done(host);
+}
+
+static void imx_nandboot_nfc_addr(struct imx_nand *host, int page)
+{
+ imx_nandboot_send_addr(host, 0);
+
+ if (host->pagesize == 2048)
+ imx_nandboot_send_addr(host, 0);
+
+ imx_nandboot_send_addr(host, page & 0xff);
+ imx_nandboot_send_addr(host, (page >> 8) & 0xff);
+ imx_nandboot_send_addr(host, (page >> 16) & 0xff);
+
+ if (host->pagesize == 2048)
+ imx_nandboot_send_cmd(host, NAND_CMD_READSTART);
+}
+
+static void imx_nandboot_send_page(struct imx_nand *host, unsigned int ops)
+{
+ uint32_t tmp;
+
+ tmp = readl(NFC_V3_CONFIG1);
+ tmp &= ~(7 << 4);
+ writel(tmp, NFC_V3_CONFIG1);
+
+ /* transfer data from NFC ram to nand */
+ writel(ops, NFC_V3_LAUNCH);
+
+ wait_op_done(host);
+}
+
+static void __memcpy32(void *trg, const void *src, int size)
+{
+ int i;
+ unsigned int *t = trg;
+ unsigned const int *s = src;
+
+ for (i = 0; i < (size >> 2); i++)
+ *t++ = *s++;
+}
+
+static void imx_nandboot_get_page(struct imx_nand *host, unsigned int page)
+{
+ imx_nandboot_send_cmd(host, NAND_CMD_READ0);
+ imx_nandboot_nfc_addr(host, page);
+ imx_nandboot_send_page(host, NFC_OUTPUT);
+}
+
+static int imx_nandboot_read_page(struct imx_nand *host, unsigned int page,
+ void *buf)
+{
+ int nsubpages;
+ u32 eccstat, err;
+
+ imx_nandboot_get_page(host, page);
+
+ __memcpy32(buf, host->main_area0, host->pagesize);
+
+ eccstat = readl(NFC_V3_ECC_STATUS_RESULT);
+ nsubpages = host->pagesize / 512;
+
+ do {
+ err = eccstat & 0xf;
+ if (err == 0xf)
+ return -EBADMSG;
+ eccstat >>= 4;
+ } while (--nsubpages);
+
+ return 0;
+}
+
+static int dbbt_block_is_bad(void *_dbbt, int block)
+{
+ int i;
+ u32 *dbbt = _dbbt;
+ int num_bad_blocks;
+
+ if (!_dbbt)
+ return false;
+
+ dbbt++; /* reserved */
+
+ num_bad_blocks = *dbbt++;
+
+ for (i = 0; i < num_bad_blocks; i++) {
+ if (*dbbt == block)
+ return true;
+ dbbt++;
+ }
+
+ return false;
+}
+
+static int read_firmware(struct imx_nand *host, void *dbbt, int page, void *buf,
+ int npages)
+{
+ int ret;
+
+ if (dbbt_block_is_bad(dbbt, page / host->pages_per_block))
+ page = ALIGN(page, host->pages_per_block);
+
+ while (npages) {
+ if (!(page % host->pages_per_block)) {
+ if (dbbt_block_is_bad(NULL, page / host->pages_per_block)) {
+ page += host->pages_per_block;
+ continue;
+ }
+ }
+
+ ret = imx_nandboot_read_page(host, page, buf);
+ if (ret)
+ return ret;
+
+ buf += host->pagesize;
+ page++;
+ npages--;
+ }
+
+ return 0;
+}
+
+int imx53_nand_start_image(void)
+{
+ struct imx_nand host;
+ void *buf = IOMEM(MX53_CSD0_BASE_ADDR);
+ void *dbbt = NULL;
+ int page_firmware1, page_firmware2, page_dbbt, image_size, npages;
+ void (*firmware)(void);
+ int ret;
+ u32 cfg1 = readl(IOMEM(MX53_SRC_BASE_ADDR) + 0x4);
+
+ host.base = IOMEM(MX53_NFC_AXI_BASE_ADDR);
+ host.main_area0 = host.base;
+ host.regs_ip = IOMEM(MX53_NFC_BASE_ADDR);
+ host.regs_axi = host.base + 0x1e00;
+ host.spare0 = host.base + 0x1000;
+
+ switch ((cfg1 >> 14) & 0x3) {
+ case 0:
+ host.pagesize = 512;
+ break;
+ case 1:
+ host.pagesize = 2048;
+ break;
+ case 2:
+ case 3:
+ host.pagesize = 4096;
+ break;
+ }
+
+ switch ((cfg1 >> 17) & 0x3) {
+ case 0:
+ host.pages_per_block = 32;
+ break;
+ case 1:
+ host.pages_per_block = 64;
+ break;
+ case 2:
+ host.pages_per_block = 128;
+ break;
+ case 3:
+ host.pages_per_block = 256;
+ break;
+ }
+
+ pr_debug("Using pagesize %d, %d pages per block\n",
+ host.pagesize, host.pages_per_block);
+
+ ret = imx_nandboot_read_page(&host, 0, buf);
+ if (ret)
+ return ret;
+
+ if (*(u32 *)(buf + 0x4) != 0x20424346) {
+ pr_err("No FCB Found on flash\n");
+ return -EINVAL;
+ }
+
+ page_firmware1 = *(u32 *)(buf + 0x68);
+ page_firmware2 = *(u32 *)(buf + 0x6c);
+ page_dbbt = *(u32 *)(buf + 0x78);
+
+ image_size = ALIGN(imx_image_size(), host.pagesize);
+ npages = image_size / host.pagesize;
+
+ if (page_dbbt) {
+ ret = imx_nandboot_read_page(&host, page_dbbt, buf);
+ if (!ret && *(u32 *)(buf + 0x4) == 0x44424254) {
+ ret = imx_nandboot_read_page(&host, page_dbbt + 4, buf);
+ if (!ret) {
+ pr_debug("Using DBBT from page %d\n", page_dbbt + 4);
+ dbbt = buf;
+ buf += host.pagesize;
+ }
+ }
+ }
+
+ pr_debug("Reading firmware from page %d, size %d\n",
+ page_firmware1, image_size);
+
+ ret = read_firmware(&host, dbbt, page_firmware1, buf, npages);
+ if (ret) {
+ pr_debug("Reading primary firmware failed\n");
+ if (page_firmware2) {
+ pr_debug("Reading firmware from page %d, size %d\n",
+ page_firmware2, image_size);
+ ret = read_firmware(&host, dbbt, page_firmware2, buf, npages);
+ if (ret) {
+ pr_err("Could not read firmware\n");
+ return -EINVAL;
+ }
+ } else {
+ pr_err("Reading primary firmware failed, no secondary firmware found\n");
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("Firmware read, starting it\n");
+
+ firmware = buf;
+
+ firmware();
+
+ return 0;
+}