9
0
Fork 0
barebox/arch/arm/mach-imx/xload-imx-nand.c

309 lines
6.5 KiB
C

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