/*
 * HYNIX HY27US08561A
 *
 * 256Mbit (32M x 8bit) NAND Flash
 */
#include <t_services.h>
#include "lpc23xx.h"
#include <string.h>
#include "lcd.h"

#define	NAND_READ0	0x00
#define	NAND_READ1	0x01
#define	NAND_READ2	0x50
#define	NAND_PAGE_PROG1	0x80
#define	NAND_PAGE_PROG2	0x10
#define	NAND_BLOCK_ERASE1 0x60
#define	NAND_BLOCK_ERASE2 0xd0
#define	NAND_READ_ID	0x90
#define	NAND_READSTATUS	0x70

#define	NAND_PAGE_SIZE	512
#define	NAND_PAGE_PER_BLOCK	32
#define	NAND_BLOCK_COUNT	2048
#define	NAND_SPARE_SIZE	16

void nandCheckId()
{
    UH id1, id2, id3;

    Clear_NANDCE;

    nandWriteCmd(NAND_READ_ID);
    asm(" nop");
    asm(" nop");
#ifdef DEBUG
#endif
    nandWriteAddr(0x00);
    asm(" nop");
    asm(" nop");
    asm(" nop");
    asm(" nop");
    id1 = nandRead();
    id2 = nandRead();
    Set_NANDCE;
    syslog(LOG_NOTICE, "id1: %x, id2: %x", id1, id2);
}

UB nandReadStatus(void)
{
    UB status;

    nandWriteCmd(NAND_READSTATUS);
    asm(" nop");
    asm(" nop");
    status = nandRead();
#ifdef DEBUG
    asm(" nop");
    asm(" nop");
#endif
    status = nandRead() & 1;
    return status;
}

UB nandEraseBlock(UH block)
{
    UH blocknumber;
    volatile int i;

    /* 5 bit shift for valid address from A14-A24, address A9:A13 ignored */
    blocknumber = block << 5;

    Clear_NANDCE;

    nandWriteCmd(NAND_BLOCK_ERASE1);
    nandWriteAddr(blocknumber & 0xff);
    nandWriteAddr(blocknumber >> 8);
    for (i = 0; i < 100; i++);

    nandWriteCmd(NAND_BLOCK_ERASE2);
    while (NAND_BUSY)
        ;
    Set_NANDCE;
}

void nandReadPage(UH block, UH page, UH col, UB *buffer)
{
    UH blockpage, i;

    blockpage = (block << 5) + page;
    Clear_NANDCE;

    nandWriteCmd(NAND_READ0);
    nandWriteAddr(col);
    nandWriteAddr(blockpage & 0xff);
    nandWriteAddr((blockpage >> 8) & 0xff);

    while (NAND_BUSY)
        ;

    for (i = 0; i < NAND_PAGE_SIZE + NAND_SPARE_SIZE; i++)
        *buffer++ = nandRead();

    Set_NANDCE;
}

void nandProgramPage(UH block, UH page, UH col, UB *buffer)
{
    UH blockpage, i;

    blockpage = (block << 5) + page;
    Clear_NANDCE;

    nandWriteCmd(NAND_PAGE_PROG1);
    nandWriteAddr(col);
    nandWriteAddr(blockpage & 0xff);
    nandWriteAddr((blockpage >> 8) & 0xff);

    for (i = 0; i < NAND_PAGE_SIZE; i++)
        nandWriteData(*buffer++);
#ifdef DO_ECC
    /* Write ECC code */
    nandWriteData(0xff);
    nandWriteData(0xff);
    nandWriteData(0xff);
    nandWriteData(0xff);
#endif

    nandWriteCmd(NAND_PAGE_PROG2);
    for (i = 0; i < 1000; i++);
    while (NAND_BUSY)
        ;
    Set_NANDCE;
}

UB nand_buffer[NAND_PAGE_SIZE + NAND_SPARE_SIZE] __attribute__((section(".etherram")));

void nand_write_page(int page)
{
    int i;
    UB *bp;

    bp = nand_buffer;
    for (i = 0; i < NAND_PAGE_SIZE/16; i++) {
        *bp++ = i & 0xff;
    }
    nandProgramPage(page >> 5, page & 31, 0, nand_buffer);
}

void nand_load(int start_page, int offset, int length, UB *buffer)
{
    int cpage, clen, nb;

    cpage = start_page + offset / NAND_PAGE_SIZE;
    offset %= NAND_PAGE_SIZE;

    while (length > 0) {
        nandReadPage(cpage >> 5, cpage & 31, 0, nand_buffer);
        clen = NAND_PAGE_SIZE - offset;

        if (length <= clen)         /* data is available */
            nb = length;
        else
            nb = clen;
        memcpy(buffer, nand_buffer + offset, nb);
        offset += nb;
        buffer += nb;
        length -= nb;
        if (offset >= NAND_PAGE_SIZE) {
            cpage++;
            offset -= NAND_PAGE_SIZE;
        }
    }
}

void nand_dump_page(int page)
{
    int i;
    UB *bp;

    nandReadPage(page >> 5, page & 31, 0, nand_buffer);
    bp = nand_buffer;
    for (i = 0; i < NAND_PAGE_SIZE/16; i++) {
        cons_printf("%03x: %02x %02x %02x %02x", i * 16, bp[0], bp[1], bp[2], bp[3]);
        cons_printf(" %02x %02x %02x %02x", bp[4], bp[5], bp[6], bp[7]);
        cons_printf(" %02x %02x %02x %02x", bp[8], bp[9], bp[10], bp[11]);
        cons_printf(" %02x %02x %02x %02x\n", bp[12], bp[13], bp[14], bp[15]);
        bp += 16;
    }
    cons_printf("**** %02x %02x %02x %02x", bp[0], bp[1], bp[2], bp[3]);
    cons_printf(" %02x %02x %02x %02x", bp[4], bp[5], bp[6], bp[7]);
    cons_printf(" %02x %02x %02x %02x", bp[8], bp[9], bp[10], bp[11]);
    cons_printf(" %02x %02x %02x %02x\n", bp[12], bp[13], bp[14], bp[15]);
}

void nand_bad_check()
{
    int block, page;
    int addr;
    UB *mp;
    int i;

    addr = 0;
    for (block = 0; block < NAND_BLOCK_COUNT/2; block++) {
        for (page = 0; page < NAND_PAGE_PER_BLOCK; page++) {
            nandReadPage(addr >> 5, addr & 31, 0, nand_buffer);
            mp = nand_buffer + NAND_PAGE_SIZE;
            for (i = 0; i < NAND_SPARE_SIZE; i++) {
                if (*mp != 0xff) {
                    cons_printf("block: %d, page: %d, byte: %x\n",
                        block, page, *mp);
                    break;
                }
                mp++;
            }
            addr++;
        }
    }
}

void nand_command(int argc, char **argv)
{
    if (argc > 1) {
        char *cp;
        int page;

        cp = argv[1];
        if (strcmp(cp, "write") == 0) {
            if (argc > 2) {
                page = atoi(argv[2]);
                nand_write_page(page);
            }
            return;
        }
        if (strcmp(cp, "erase") == 0) {
            if (argc > 2) {
                page = atoi(argv[2]);
                if (page > 0) {
                    nandEraseBlock(page);
                } else {
                    for (page = 1; page < NAND_BLOCK_COUNT; page) {
                        nandEraseBlock(page);
                        dly_tsk(10);
                        page++;
                    }
                }
            }
            return;
        }
        if (strcmp(cp, "read") == 0) {
            if (argc > 2) {
                page = atoi(argv[2]);
                nand_dump_page(page);
            }
            return;
        }
        if (strcmp(cp, "bad") == 0) {
            nand_bad_check();
            return;
        }
    }
    nandCheckId();
}
