/* * 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 #include #include #include #include #include #include #include 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; }