www.pudn.com > ucosii_core.rar > loader.c, change:2007-07-26,size:5919b


/*
 * loader.c
 *
 * nandboot loader of the Linux kernel.
 *
 * Copyright (c) 2005-2007  Ingenic Semiconductor Inc.
 * Author: <jlwei@ingenic.cn>
 *
 * 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.
 */

#include "config.h"
#include "nand.h"
#include "jz4730.h"

/* Load image parameters */
#define LOAD_IMAGE_SIZE		0x200000   /* max data size */
#define LOAD_ADDR		0x80100000 /* load to address */
#define LOAD_START_BLOCK	1          /* load from nand block */

/* kernel parameters */
#define KERNEL_ENTRY		0x80100000
#define PARAM_BASE		0x80040000
#define TMPBUF_BASE		0x80050000

#define ECC_BLOCK		256 /* 3-bytes HW ECC per 256-bytes data */
#define ECC_POS			4   /* ECC offset to spare area */

static u32 *load_addr = 0, *param_addr = 0, *databuf32 = 0;
static u32 *stored_ecc = 0, *calc_ecc = 0;
static u8 *oobbuf = 0, *databuf = 0, *tmpbuf = 0;

const u8 cmdline[256] = CFG_CMDLINE;

typedef void (*linux_entry)(int, char **, char *);
static linux_entry entry;

static inline void nand_read_buf16(void *buf, int count)
{
	int i;
	u16 *p = (u16 *)buf;

	for (i = 0; i < count; i += 2)
		*p++ = __nand_data16();
}

static inline void nand_read_buf8(void *buf, int count)
{
	int i;
	u8 *p = (u8 *)buf;

	for (i = 0; i < count; i++)
		*p++ = __nand_data8();
}

static inline void nand_read_buf(void *buf, int count, int bw)
{
	if (bw == 8)
		nand_read_buf8(buf, count);
	else
		nand_read_buf16(buf, count);
}

/*
 * Main entry.
 */
void nandloader(void)
{
	register u32 i, j, k;
	register u32 cnt, num_blk, blkaddr, rowaddr, addr;
	register u32 boot_sel, tmp, ecc_bit, idx;
	register u32 buswidth, pagesize, rowaddrcycle, ecccount, oobsize, ppb;

	/*
	 * Init hardware
	 */
	gpio_init();
	serial_init();

	serial_puts("\n\nJZ4730 NANDBoot version 1.0\n\n");

	serial_puts("PLL init ... ");
	pll_init();
	serial_puts("done\n");

	serial_puts("SDRAM init ... ");
#if  (CFG_SDRAM_ROW == 12)
	sdram_init0();      /* init sdram as 64M */
#endif
	sdram_init();
	serial_puts("done\n");

	/*
	 * Decode the boot select
	 */
	boot_sel = (REG_EMC_NFCSR & 0x70) >> 4;
	buswidth = (boot_sel & 0x1) ? 16 : 8;           /* bus width */
	pagesize = (boot_sel & 0x2) ? 2048 : 512; /* page size */
	rowaddrcycle = (boot_sel & 0x4) ? 3 : 2;  /* row address cycles */

	ppb = (pagesize == 2048) ? 64 : 32;         /* pages per block */

	ecccount = pagesize / ECC_BLOCK;
	oobsize = pagesize / 32;

	/* Init buffers */
	load_addr = (u32 *)LOAD_ADDR;
	databuf32 = (u32 *)TMPBUF_BASE;
	databuf = (u8 *)databuf32;
	oobbuf = (u8 *)(TMPBUF_BASE + 0x1000);
	calc_ecc = (u32 *)(TMPBUF_BASE + 0x2000);
	entry = (linux_entry)KERNEL_ENTRY;

	/* Calc block number */
	num_blk = LOAD_IMAGE_SIZE / (pagesize * ppb);
	if ((num_blk * pagesize * ppb) < LOAD_IMAGE_SIZE)
		num_blk++;

	/*
	 * Start to load image
	 */
	serial_puts("NAND data load ... ");

	__nand_enable();

	cnt = 0;
	blkaddr = LOAD_START_BLOCK;

	while (cnt < num_blk) {
nand_load_block:
		/* read one block */
		rowaddr = blkaddr * ppb;
		for (i = 0; i < ppb; i++) {
			/* send READ0 command */
			__nand_cmd(NAND_CMD_READ0);

			/* col addr cycle */
			__nand_addr(0);
			if (pagesize == 2048)
				__nand_addr(0);

			/* row addr cycle */
			addr = rowaddr + i;
			__nand_addr(addr & 0xff);
			__nand_addr((addr >> 8) & 0xff);
			if (rowaddrcycle == 3)
				__nand_addr((addr >> 16) & 0xff);

			/* send READSTART command for 2048 ps NAND */
			if (pagesize == 2048)
				__nand_cmd(NAND_CMD_READSTART);

			/* wait ready */
			__nand_sync();

			/* read page and oob data */
			tmpbuf = databuf;
			for (j = 0; j < ecccount; j++) {
				__nand_ecc_enable();
				nand_read_buf((void *)tmpbuf, ECC_BLOCK, buswidth);
				__nand_ecc_disable();
				calc_ecc[j] = __nand_ecc();
				tmpbuf += ECC_BLOCK;
			}
			nand_read_buf((void *)oobbuf, oobsize, buswidth);

			/* check for bad block */
			if (i == 0) {
				if (oobbuf[0] != 0xff) {
					/* bad block, jump to next block */
					blkaddr ++;
					goto nand_load_block;
				}
			}

			/* check for ECC */
			stored_ecc = (u32 *)(((u32)oobbuf) + ECC_POS);
			tmpbuf = (u8 *)databuf;

			for (j = 0; j < ecccount; j++) {
				tmp = stored_ecc[j] ^ calc_ecc[j];
				if (tmp) { /* ECC error occurred */
					if (stored_ecc[j] == 0xffffffff) {
						/* block containing invalid data,
						 * finish loading.
						 */
						goto nand_load_finish;
					}
					ecc_bit = 0;
					for (k = 0; k < 24; k++)
						if ((tmp >> k) & 0x01)
							ecc_bit ++;
					if (ecc_bit != 11) {
						/* Fatal error */
						serial_puts("ECC error.\n");
						blkaddr ++;
						goto nand_load_block;
					} else {
						/* Correctable error */
						ecc_bit = 0;
						for (k = 12; k >= 1; k--) {
							ecc_bit <<= 1;
							ecc_bit |= ((tmp>>(k*2-1)) & 0x01);
						}
						idx = ecc_bit & 0x07;
						tmpbuf[j * ECC_BLOCK + (ecc_bit >> 3)] ^= (1 << idx);
					}
				}
			}

			/* Data is OK, transfer to target buffer */
			for (j = 0; j < ((pagesize == 2048) ? 512:128); j++)
				*load_addr++ = databuf32[j];
		}

		cnt ++;
		blkaddr ++;

	} /* end of while */

 nand_load_finish:

	serial_puts("done\n\n");

	/* prepare execute parameters and environment */
	param_addr = (u32 *)PARAM_BASE;
	param_addr[0] = 0;	/* might be address of ascii-z string: "memsize" */
	param_addr[1] = 0;	/* might be address of ascii-z string: "0x01000000" */
	param_addr[2] = 0;
	param_addr[3] = 0;
	param_addr[4] = 0;
	param_addr[5] = PARAM_BASE + 32;
	param_addr[6] = KERNEL_ENTRY;
	tmpbuf = (u8 *)(PARAM_BASE + 32);
	for (i = 0; i < 256; i++)
		tmpbuf[i] = cmdline[i];  /* linux command line */

	serial_puts("Starting kernel ...\n\n");

	/* flush dcache and icache */
	flush_cache_all();

	/* jump to kernel */
	entry(2, (char **)(PARAM_BASE+16), (char *)PARAM_BASE);
}