1
0
mirror of git://projects.qi-hardware.com/xburst-tools.git synced 2024-12-24 18:12:58 +02:00
xburst-tools/nandprog/jz4730/nandflash_4730.c

607 lines
11 KiB
C
Raw Normal View History

2009-04-05 19:26:33 +03:00
/*
* Common NAND Flash operations for JZ4730.
*
* This software is free.
*/
#include "jz4730.h"
#include "include.h"
unsigned int EMC_BASE;
extern struct nand_oobinfo oob_64[];
/*
* Standard NAND commands.
*/
#define CMD_READA 0x00
#define CMD_READB 0x01
#define CMD_READC 0x50
#define CMD_ERASE_SETUP 0x60
#define CMD_ERASE 0xD0
#define CMD_READ_STATUS 0x70
#define CMD_CONFIRM 0x30
#define CMD_SEQIN 0x80
#define CMD_PGPROG 0x10
#define CMD_READID 0x90
#define CMD_RESET 0xff
#define ECC_BLOCK 256 /* 3-bytes HW ECC per 256-bytes data */
#define ECC_OFFSET 4 /* ECC store location offset to the spare area */
/*
* NAND routines.
*/
#define nand_enable() (REG_EMC_NFCSR |= EMC_NFCSR_NFE | EMC_NFCSR_FCE)
#define nand_disable() (REG_EMC_NFCSR &= ~(EMC_NFCSR_NFE | EMC_NFCSR_FCE))
#define nand_ecc_enable() (REG_EMC_NFCSR |= EMC_NFCSR_ECCE | EMC_NFCSR_ERST)
#define nand_ecc_disable() (REG_EMC_NFCSR &= ~EMC_NFCSR_ECCE)
#define nand_ready() (REG_EMC_NFCSR & EMC_NFCSR_RB)
#define nand_ecc() (REG_EMC_NFECC & 0x00ffffff)
#define nand_cmd(n) (REG8(cmdport+csn) = (n))
#define nand_addr(n) (REG8(addrport+csn) = (n))
#define nand_data8() REG8(dataport+csn)
#define nand_data16() REG16(dataport+csn)
static inline void nand_wait_ready(void)
{
u32 to = 1000;
while (nand_ready() && to--);
while (!nand_ready());
}
static volatile unsigned char *emcbase = 0;
static volatile unsigned char *addrport = 0;
static volatile unsigned char *dataport = 0;
static volatile unsigned char *cmdport = 0;
static u32 bus = 8, row = 2, pagesize = 512, oobsize = 16, ppb = 32;
static u32 bad_block_pos = 0 , bad_block_page =0, csn =0;
static struct nand_oobinfo *oob_pos;
static np_data *np;
/*
* notify(int param)
*
* param value:
* 0 : Ok
* -1: page op fail
* -2: hit bad block, skip it.
*/
static int (*write_proc)(unsigned char *, int) = 0;
static int (*read_proc)(unsigned char *, int) = 0;
static u8 badbuf[2048 + 64] = {0};
static u8 oobbuf[64] = {0};
/*
* I/O read/write interface.
*/
static inline int nand_data_write8(unsigned char *buf, int count)
{
int i;
u8 *p = (u8 *)buf;
for (i = 0; i < count; i++)
nand_data8() = *p++;
return 0;
}
static inline int nand_data_write16(unsigned char *buf, int count)
{
int i;
u16 *p = (u16 *)buf;
for (i = 0; i < count/2; i++)
nand_data16() = *p++;
return 0;
}
static inline int nand_data_read8(unsigned char *buf, int count)
{
int i;
u8 *p = (u8 *)buf;
for (i = 0; i < count; i++)
*p++ = nand_data8();
return 0;
}
static inline int nand_data_read16(unsigned char *buf, int count)
{
int i;
u16 *p = (u16 *)buf;
for (i = 0; i < count/2; i++)
*p++ = nand_data16();
return 0;
}
int chip_select_4730(u8 cs)
{
csn = (u32)cs << 15; //modify this number for your board
return 0;
}
/*
* Init nand parameters and enable nand controller.
*/
int nand_init_4730(np_data *npp)
{
bus = npp->bw;
row = npp->rc;
pagesize = npp->ps;
oobsize = npp->os;
ppb = npp->ppb;
bad_block_pos = npp->bbp;
bad_block_page = npp->bba;
if (bus == 8) {
write_proc = nand_data_write8;
read_proc = nand_data_read8;
} else {
write_proc = nand_data_write16;
read_proc = nand_data_read16;
}
emcbase = (u8 *)npp->base_map;
dataport = (u8 *)npp->port_map;
addrport = (u8 *)((u32)dataport + npp->ap_offset);
cmdport = (u8 *)((u32)dataport + npp->cp_offset);
EMC_BASE = (u32)emcbase;
oob_pos = &oob_64[npp->ep];
np = npp;
nand_enable();
chip_select_4730(npp->cs);
return 0;
}
/*
* Disable nand operation.
*/
int nand_fini_4730(void)
{
nand_disable();
return 0;
}
/*
* Read ID.
*/
unsigned int nand_query_4730(void)
{
u16 vid, did;
nand_cmd(CMD_READID);
nand_addr(0);
vid = nand_data8();
did = nand_data8();
return (vid << 16) | did;
}
/*
* Read oob data for 512B pagesize.
*/
static void read_oob_512(void *buf, u32 oobsize, u32 pg)
{
int i;
u32 rowaddr;
rowaddr = pg;
nand_cmd(0x50);
nand_addr(0);
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
nand_wait_ready();
read_proc(buf, oobsize);
}
/*
* Read oob data for 2KB pagesize.
*/
static void read_oob_2048(u8 *buf, u32 oobsize, u32 pg)
{
u32 i, coladdr, rowaddr;
coladdr = 2048;
rowaddr = pg;
nand_cmd(CMD_READA);
nand_addr(coladdr & 0xff);
nand_addr(coladdr >> 8);
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
nand_cmd(CMD_CONFIRM);
nand_wait_ready();
read_proc(buf, oobsize);
}
/*
* Read oob data.
*/
static void read_oob(u8 *buf, int oobsize, int pg)
{
if (pagesize == 2048)
read_oob_2048(buf, oobsize, pg);
else
read_oob_512(buf, oobsize, pg);
}
/*
* Return 1 if the block is bad block, else return 0.
*/
int nand_check_block(u32 block)
{
u32 pg;
// pg = block * ppb;
pg = block * ppb + bad_block_page;
//bad block ID locate No.bad_block_page
read_oob(oobbuf, oobsize, pg);
if (oobbuf[bad_block_pos] != 0xff)
return -1;
read_oob(oobbuf, oobsize, pg + 1);
if (oobbuf[bad_block_pos] != 0xff)
return -1;
return 0;
}
/*
* Mark a block bad.
*/
void nand_block_markbad(u32 block)
{
u32 i, rowaddr;
for (i = 0; i < pagesize + oobsize; i++)
badbuf[i] = 0xff;
badbuf[pagesize + bad_block_pos] = 0; /* bad block flag */
rowaddr = block * ppb + bad_block_page;
//bad block ID locate No.bad_block_page
nand_cmd(CMD_READA);
nand_cmd(CMD_SEQIN);
nand_addr(0);
if (pagesize == 2048)
nand_addr(0);
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
write_proc((unsigned char *)badbuf, pagesize + oobsize);
nand_cmd(CMD_PGPROG);
nand_wait_ready();
}
/*
* Erase <blk_num> blocks from block <sblk>.
*/
int nand_erase_4730(int blk_num, int sblk, int force)
{
int i, cnt;
u32 cur_blk, rowaddr;
force = 0;
/* Send reset command to nand */
nand_cmd(CMD_RESET);
nand_wait_ready();
cur_blk = sblk;
cnt = 0;
while (cnt < blk_num) {
/*
* if force flag was not set, check for bad block.
* if force flag was set, erase anything.
*/
#if 1
//we do force erase for ever??
if (!force) {
if (nand_check_block(cur_blk)) {
cur_blk ++; /* Bad block, set to next block */
continue;
}
}
#endif
nand_cmd(CMD_ERASE_SETUP);
rowaddr = cur_blk * ppb;
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
nand_cmd(CMD_ERASE);
nand_wait_ready();
nand_cmd(CMD_READ_STATUS);
nand_wait_ready();
if (nand_data8() & 0x01) {
/* Erase Error, mark it as bad block */
nand_block_markbad(cur_blk);
} else {
/* Erase OK */
cnt++;
}
cur_blk++;
}
return 0;
}
/*
* Do nand hw ecc correction.
*/
int nand_hw_ecc_correct(u8 *buf, u8 *stored_ecc, u8 *calc_ecc, int eccblock)
{
u32 i, j, ecc_bit,a,b,c,tmp;
int res = 0;
for (i = 0; i < eccblock; i++) {
a=stored_ecc[oob_pos->eccpos[i*3+0]] ^ calc_ecc[i*4+0];
b=stored_ecc[oob_pos->eccpos[i*3+1]] ^ calc_ecc[i*4+1];
c=stored_ecc[oob_pos->eccpos[i*3+2]] ^ calc_ecc[i*4+2];
tmp = (c<<16) + (b<<8) +a;
#if 0
printf("AAAAAAAA %x %x %x : %x %x %x %x\n",
stored_ecc[oob_pos->eccpos[i*3+0]],
stored_ecc[oob_pos->eccpos[i*3+1]],
stored_ecc[oob_pos->eccpos[i*3+2]],
calc_ecc[i*4+0],
calc_ecc[i*4+1],
calc_ecc[i*4+2],
tmp);
#endif
if (tmp) { /* ECC error */
ecc_bit = 0;
for (j = 0; j < 24; j++)
if ((tmp >> j) & 0x01)
ecc_bit ++;
if (ecc_bit == 11) { /* Correctable error */
u8 idx;
ecc_bit = 0;
for (j = 12; j >= 1; j--) {
ecc_bit <<= 1;
ecc_bit |= ((tmp >> (j*2-1)) & 0x01);
}
idx = ecc_bit & 0x07;
buf[i * ECC_BLOCK + (ecc_bit >> 3)] ^= (1 << idx);
}
else { /* Fatal error */
//if ecc all ff means this page no data!
if (stored_ecc[oob_pos->eccpos[i*3+0]]==0xff
&&stored_ecc[oob_pos->eccpos[i*3+1]]==0xff
&&stored_ecc[oob_pos->eccpos[i*3+2]]==0xff )
return res;
// printf("Uncorrectable ecc error!\n");
res = -1;
}
}
}
return res;
}
/*
* Read data <pagenum> pages from <startpage> page.
* Skip bad block if detected.
* HW ECC is used.
*/
int nand_read_4730(u8 *buf, u32 startpage, u32 pagenum)
{
u32 cnt, i;
u32 cur_page, rowaddr, eccblock;
u32 calc_ecc[8];
u8 *tmpbuf,*stored_ecc;
eccblock = pagesize / ECC_BLOCK;
cur_page = startpage;
cnt = 0;
while (cnt < pagenum) {
//we do not check bad block here!
#if 0
if ((cur_page % ppb) == 0) {
cur_blk = cur_page / ppb;
if (nand_check_block(cur_blk)) {
cur_page += ppb; /* Bad block, set to next block */
continue;
}
}
#endif
nand_cmd(CMD_READA);
nand_addr(0);
if (pagesize == 2048)
nand_addr(0);
rowaddr = cur_page;
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
if (pagesize == 2048)
nand_cmd(CMD_CONFIRM);
nand_wait_ready();
tmpbuf = (u8 *)((u32)buf + cnt * (pagesize + oobsize));
for (i = 0; i < eccblock; i++) {
nand_ecc_enable();
read_proc(tmpbuf, ECC_BLOCK);
nand_ecc_disable();
calc_ecc[i] = nand_ecc();
if (oob_pos->eccname == LINUXHM)
calc_ecc[i] = ~(calc_ecc[i]) | 0x00030000;
tmpbuf += ECC_BLOCK;
}
read_proc((u8 *)tmpbuf, oobsize);
tmpbuf = (u8 *)((u32)buf + cnt * (pagesize + oobsize));
//read ecc from oob
stored_ecc = (u8 *)(((u32)tmpbuf) + pagesize );
/* Check ECC */
nand_hw_ecc_correct(tmpbuf, stored_ecc, (u8 *)calc_ecc, eccblock);
cur_page++;
cnt++;
}
return 0;
}
/*
* Read data <pagenum> pages from <startpage> page.
* Don't skip bad block.
* Don't use HW ECC.
*/
int nand_read_raw_4730(u8 *buf, u32 startpage, u32 pagenum)
{
u32 cnt, i;
u32 cur_page, rowaddr;
u8 *tmpbuf;
tmpbuf = (u8 *)buf;
cur_page = startpage;
cnt = 0;
while (cnt < pagenum) {
nand_cmd(CMD_READA);
nand_addr(0);
if (pagesize == 2048)
nand_addr(0);
rowaddr = cur_page;
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
if (pagesize == 2048)
nand_cmd(CMD_CONFIRM);
nand_wait_ready();
read_proc(tmpbuf, pagesize);
tmpbuf += pagesize;
cur_page++;
cnt++;
}
return 0;
}
/*
* Read oob <pagenum> pages from <startpage> page.
* Don't skip bad block.
* Don't use HW ECC.
*/
int nand_read_oob_4730(u8 *buf, u32 startpage, u32 pagenum)
{
u32 cnt, cur_page;
u8 *tmpbuf;
tmpbuf = (u8 *)buf;
cur_page = startpage;
cnt = 0;
while (cnt < pagenum) {
read_oob((void *)tmpbuf, oobsize, cur_page);
tmpbuf += oobsize;
cur_page++;
cnt++;
}
return 0;
}
/*
* Write <pagenum> pages from <startpage> page.
* Skip bad block if detected.
*/
int nand_program_4730(u8 *buf, int startpage, int pagenum)
{
u32 cnt, i,j;
u32 cur_page, rowaddr, eccblock;
u8 *tmpbuf;
tmpbuf = (u8 *)buf;
eccblock = pagesize / ECC_BLOCK;
cur_page = startpage;
cnt = 0;
while (cnt < pagenum) {
//do not check bad block here! check uplayer!
for (j=0;j<np->os;j++)
{
if (tmpbuf[j+np->ps]!=0xff)
break;
}
if (j==np->os)
{
tmpbuf += np->ps+np->os;
cur_page ++;
cnt ++;
continue;
}
// nand_wait_ready();
nand_cmd(CMD_READA);
nand_cmd(CMD_SEQIN);
nand_addr(0);
if (pagesize == 2048)
nand_addr(0);
rowaddr = cur_page;
for (i = 0; i < row; i++) {
nand_addr(rowaddr & 0xff);
rowaddr >>= 8;
}
/* Write out data and oob*/
// we don't need work out ecc
//because it already exist in image file
write_proc(tmpbuf, np->ps+np->os);
tmpbuf += np->ps+np->os;
nand_cmd(CMD_PGPROG);
nand_wait_ready();
nand_cmd(CMD_READ_STATUS);
// nand_wait_ready();
if (nand_data8() & 0x01) {
/* Page program error.
* Note: we should mark this block bad, and copy data of this
* block to a new block.
*/
;
} else {
;
}
cur_page ++;
cnt ++;
}
return cnt;
}