/* * nand_read.c: Simple NAND read functions for booting from NAND * * This is used by cpu/arm920/start.S assembler code, * and the board-specific linker script must make sure this * file is linked within the first 4kB of NAND flash. * * Taken from GPLv2 licensed vivi bootloader, * Copyright (C) 2002 MIZI Research, Inc. * * Author: Hwang, Chideok * Date : $Date: 2004/02/04 10:37:37 $ * * u-boot integration and bad-block skipping (C) 2006 by OpenMoko, Inc. * Author: Harald Welte */ /* NOTE this stuff runs in steppingstone context! */ /* the API refers to 512-byte blocks */ #include #include "nand_read.h" #define NAND_CMD_READ0 0 #define NAND_CMD_READSTART 0x30 #define __REGb(x) (*(volatile unsigned char *)(x)) #define __REGw(x) (*(volatile unsigned short *)(x)) #define __REGi(x) (*(volatile unsigned int *)(x)) #define NF_BASE 0x4e000000 #define NFCONF __REGi(NF_BASE + 0x0) #define NFCONT __REGi(NF_BASE + 0x4) #define NFCMD __REGb(NF_BASE + 0x8) #define NFADDR __REGb(NF_BASE + 0xc) #define NFDATA __REGb(NF_BASE + 0x10) #define NFDATA16 __REGw(NF_BASE + 0x10) #define NFSTAT __REGb(NF_BASE + 0x20) #define NFSTAT_BUSY 1 #define nand_select() (NFCONT &= ~(1 << 1)) #define nand_deselect() (NFCONT |= (1 << 1)) #define nand_clear_RnB() (NFSTAT |= (1 << 2)) static inline void nand_wait(void) { int i; while (!(NFSTAT & NFSTAT_BUSY)) for (i=0; i<10; i++); } /* configuration for 2440 with 2048byte sized flash */ #define NAND_5_ADDR_CYCLE #define NAND_PAGE_SIZE 2048 #define BAD_BLOCK_OFFSET NAND_PAGE_SIZE #define NAND_BLOCK_MASK (NAND_PAGE_SIZE - 1) #define NAND_BLOCK_SIZE (NAND_PAGE_SIZE * 64) int s3c2442_nand_is_bad_block(unsigned long block_index) { unsigned char data; unsigned long page_num; nand_select(); nand_clear_RnB(); page_num = block_index >> 2; /* addr / 2048 */ NFCMD = NAND_CMD_READ0; NFADDR = BAD_BLOCK_OFFSET & 0xff; NFADDR = (BAD_BLOCK_OFFSET >> 8) & 0xff; NFADDR = page_num & 0xff; NFADDR = (page_num >> 8) & 0xff; NFADDR = (page_num >> 16) & 0xff; NFCMD = NAND_CMD_READSTART; nand_wait(); data = (NFDATA & 0xff); if (data != 0xff) return 1; return 0; } static int nand_read_page_ll(unsigned char *buf, unsigned long block512, int blocks512) { unsigned short *ptr16 = (unsigned short *)buf; unsigned int i, page_num; unsigned int block_amount; int blocks_possible = (3 - (block512 & 3)) + 1; if (blocks512 > blocks_possible) blocks512 = blocks_possible; block_amount = (NAND_PAGE_SIZE / 4 / 2) * blocks512; nand_clear_RnB(); NFCMD = NAND_CMD_READ0; page_num = block512 >> 2; /* 512 block -> 2048 block */ /* Write Address */ NFADDR = 0; NFADDR = (block512 & 3) << 1; /* which 512 block in 2048 */ NFADDR = page_num & 0xff; NFADDR = (page_num >> 8) & 0xff; NFADDR = (page_num >> 16) & 0xff; NFCMD = NAND_CMD_READSTART; nand_wait(); for (i = 0; i < block_amount; i++) *ptr16++ = NFDATA16; return blocks512; } /* low level nand read function */ int nand_read_ll(unsigned char *buf, unsigned long start_block512, int blocks512) { int i, j; /* chip Enable */ nand_select(); nand_clear_RnB(); for (i = 0; i < 10; i++) ; while (blocks512 > 0) { if (s3c2442_nand_is_bad_block(start_block512)) { start_block512 += 4; if (start_block512 >> 2 > BAD_BLOCK_OFFSET) /* end of NAND */ return -1; continue; } j = nand_read_page_ll(buf, start_block512, blocks512); start_block512 += j; buf += j << 9; blocks512 -= j; if (this_board->get_ui_keys) if ((this_board->get_ui_keys)() & UI_ACTION_SKIPKERNEL) { puts(" ** skipping \n"); return -3; } } /* chip Disable */ nand_deselect(); return 0; }