From 2187592c6617a88bce1d304649cc5f4f86027ca5 Mon Sep 17 00:00:00 2001 From: rob Date: Sat, 29 Mar 2008 17:18:53 +0000 Subject: [PATCH] Updated TCC780x NAND driver. Still work-in-progress but lots better than the previous version. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@16878 a1c6a512-1295-4272-9138-f99709370657 --- firmware/target/arm/tcc780x/ata-nand-tcc780x.c | 554 +++++++++++++++++-------- 1 file changed, 372 insertions(+), 182 deletions(-) diff --git a/firmware/target/arm/tcc780x/ata-nand-tcc780x.c b/firmware/target/arm/tcc780x/ata-nand-tcc780x.c index 906635c51..f6d1df96c 100644 --- a/firmware/target/arm/tcc780x/ata-nand-tcc780x.c +++ b/firmware/target/arm/tcc780x/ata-nand-tcc780x.c @@ -18,14 +18,18 @@ ****************************************************************************/ #include "ata.h" #include "ata-target.h" -#include "ata_idle_notify.h" #include "system.h" #include -#include "thread.h" #include "led.h" -#include "disk.h" #include "panic.h" -#include "usb.h" + +/* The NAND driver is currently work-in-progress and as such contains + some dead code and debug stuff, such as the next few lines. */ + +#if defined(BOOTLOADER) +#include "../../../../bootloader/common.h" /* for printf */ +extern int line; +#endif /* for compatibility */ int ata_spinup_time = 0; @@ -58,6 +62,21 @@ static struct mutex ata_mtx NOCACHEBSS_ATTR; #define NFC_CS1 (1<<22) #define NFC_READY (1<<20) +#define ECC_CTRL (*(volatile unsigned long *)0xF005B000) +#define ECC_BASE (*(volatile unsigned long *)0xF005B004) +#define ECC_CLR (*(volatile unsigned long *)0xF005B00C) +#define ECC_MLC0W (*(volatile unsigned long *)0xF005B030) +#define ECC_MLC1W (*(volatile unsigned long *)0xF005B034) +#define ECC_MLC2W (*(volatile unsigned long *)0xF005B038) +#define ECC_ERR (*(volatile unsigned long *)0xF005B070) +#define ECC_ERRADDR (*(volatile unsigned long *)0xF005B050) +#define ECC_ERRDATA (*(volatile unsigned long *)0xF005B060) + +/* ECC_CTRL flags */ +#define ECC_M4EN (1<<6) +#define ECC_ENC (1<<27) +#define ECC_READY (1<<26) + /* Chip characteristics, initialised by nand_get_chip_info() */ static int page_size = 0; @@ -71,20 +90,79 @@ static int total_banks = 0; static int sectors_per_page = 0; static int bytes_per_segment = 0; static int sectors_per_segment = 0; +static int segments_per_bank = 0; /* Maximum values for static buffers */ #define MAX_PAGE_SIZE 4096 #define MAX_SPARE_SIZE 128 #define MAX_BLOCKS_PER_BANK 8192 -#define MAX_BANKS 4 +#define MAX_PAGES_PER_BLOCK 128 + +/* In theory we can support 4 banks, but only 2 have been seen on 2/4/8Gb D2s. */ +#ifdef COWON_D2 +#define MAX_BANKS 2 +#else +#define MAX_BANKS 4 +#endif + +#define MAX_SEGMENTS (MAX_BLOCKS_PER_BANK * MAX_BANKS / 4) + +/* Logical/Physical translation table */ + +struct lpt_entry +{ + short chip; + short phys_segment; + //short segment_flag; +}; +static struct lpt_entry lpt_lookup[MAX_SEGMENTS]; + +/* Write Caches */ + +#define MAX_WRITE_CACHES 8 + +struct write_cache +{ + short chip; + short phys_segment; + short log_segment; + short page_map[MAX_PAGES_PER_BLOCK * 4]; +}; +static struct write_cache write_caches[MAX_WRITE_CACHES]; + +static int write_caches_in_use = 0; + +/* Read buffer */ + +unsigned int page_buf[(MAX_PAGE_SIZE + MAX_SPARE_SIZE) / 4]; + + +/* Conversion functions */ + +static inline int phys_segment_to_page_addr(int phys_segment, int page_in_seg) +{ + int page_addr = phys_segment * pages_per_block * 2; + + if (page_in_seg & 1) + { + /* Data is located in block+1 */ + page_addr += pages_per_block; + } -/* - Block translation table - maps logical Segment Number to physical page address - Format: 0xBTPPPPPP (B = Bank; T = Block Type flag; P = Page Address) - */ -static int segment_location[MAX_BLOCKS_PER_BANK * MAX_BANKS / 4]; + if (page_in_seg & 2) + { + /* Data is located in second plane */ + page_addr += (blocks_per_bank/2) * pages_per_block; + } + page_addr += page_in_seg/4; + + return page_addr; +} + + +/* NAND physical access functions */ static void nand_chip_select(int chip) { @@ -123,6 +201,8 @@ static void nand_chip_select(int chip) static void nand_read_id(int chip, unsigned char* id_buf) { + int i; + /* Enable NFC bus clock */ BCLKCTR |= DEV_NAND; @@ -147,12 +227,11 @@ static void nand_read_id(int chip, unsigned char* id_buf) NFC_CMD = 0x90; NFC_SADDR = 0x00; - /* Read the 5 single bytes */ - id_buf[0] = NFC_SDATA & 0xFF; - id_buf[1] = NFC_SDATA & 0xFF; - id_buf[2] = NFC_SDATA & 0xFF; - id_buf[3] = NFC_SDATA & 0xFF; - id_buf[4] = NFC_SDATA & 0xFF; + /* Read the 5 chip ID bytes */ + for (i = 0; i < 5; i++) + { + id_buf[i] = NFC_SDATA & 0xFF; + } nand_chip_select(-1); @@ -213,14 +292,9 @@ static void nand_read_uid(int chip, unsigned int* uid_buf) } -/* NB: size must be divisible by 4 due to 32-bit read */ static void nand_read_raw(int chip, int row, int column, int size, void* buf) { int i; - - /* Currently this relies on a word-aligned input buffer */ - unsigned int* int_buf = (unsigned int*)buf; - if ((unsigned int)buf & 3) panicf("nand_read_raw() non-aligned input buffer"); /* Enable NFC bus clock */ BCLKCTR |= DEV_NAND; @@ -260,57 +334,31 @@ static void nand_read_raw(int chip, int row, int column, int size, void* buf) while (!(NFC_CTRL & NFC_READY)) {}; /* Read data into page buffer */ - for (i = 0; i < (size/4); i++) + if (((unsigned int)buf & 3) || (size & 3)) + { + /* Use byte copy since either the buffer or size are not word-aligned */ + /* TODO: Byte copy only where necessary (use words for mid-section) */ + for (i = 0; i < size; i++) + { + ((unsigned char*)buf)[i] = NFC_SDATA; + } + } + else { - int_buf[i] = NFC_WDATA; + /* Use 4-byte copy as buffer and size are both word-aligned */ + for (i = 0; i < (size/4); i++) + { + ((unsigned int*)buf)[i] = NFC_WDATA; + } } nand_chip_select(-1); - + /* Disable NFC bus clock */ BCLKCTR &= ~DEV_NAND; } -/* NB: Output buffer must currently be word-aligned */ -static bool nand_read_sector(int segment, int sector, void* buf) -{ - int physaddr = segment_location[segment]; - int bank = physaddr >> 28; - int page = physaddr & 0xffffff; - - int page_in_seg = sector / sectors_per_page; - int sec_in_page = sector % sectors_per_page; - - /* TODO: Check if there are any 0x15 pages referring to this segment/sector - combination. If present we need to read that data instead. */ - - if (physaddr == -1) return false; - - if (page_in_seg & 1) - { - /* Data is located in block+1 */ - page += pages_per_block; - } - - if (page_in_seg & 2) - { - /* Data is located in second plane */ - page += (blocks_per_bank/2) * pages_per_block; - } - - page += page_in_seg/4; - - nand_read_raw(bank, page, - sec_in_page * (SECTOR_SIZE+16), - SECTOR_SIZE, buf); - - /* TODO: Read the 16 spare bytes, perform ECC correction */ - - return true; -} - - static void nand_get_chip_info(void) { bool found = false; @@ -362,6 +410,7 @@ static void nand_get_chip_info(void) } pages_per_bank = blocks_per_bank * pages_per_block; + segments_per_bank = blocks_per_bank / 4; bytes_per_segment = page_size * pages_per_block * 4; sectors_per_page = page_size / SECTOR_SIZE; sectors_per_segment = bytes_per_segment / SECTOR_SIZE; @@ -405,97 +454,238 @@ static void nand_get_chip_info(void) /* Bank 1 returned differing id - assume it is junk */ total_banks = 1; } + + /* + Sanity checks: - /* Check block 0, page 0 for "BMPM" string & total_banks byte. If this is - confirmed for all D2s we can remove the above code & nand_read_uid(). */ - + 1. "BMP" tag at block 0, page 0, offset [always present] + 2. Byte at +4 contains number of banks [or 0xff if 1 bank] + + If this is confirmed for all D2s we can simplify the above code and + also remove the icky nand_read_uid() function. + */ + nand_read_raw(0, /* bank */ 0, /* page */ page_size, /* offset */ 8, id_buf); + + if (strncmp(id_buf, "BMP", 3)) panicf("BMP tag not present"); - if (strncmp(id_buf, "BMPM", 4)) panicf("BMPM tag not present"); - if (id_buf[4] != total_banks) panicf("BMPM total_banks mismatch"); + if (total_banks > 1) + { + if (id_buf[4] != total_banks) panicf("BMPM total_banks mismatch"); + } } -/* TEMP testing function */ +static bool nand_read_sector_of_phys_page(int chip, int page, + int sector, void* buf) +{ + nand_read_raw(chip, page, + sector * (SECTOR_SIZE+16), + SECTOR_SIZE, buf); -#ifdef BOOTLOADER -#include "lcd.h" + /* TODO: Read the 16 spare bytes, perform ECC correction */ -extern int line; -unsigned int buf[(MAX_PAGE_SIZE + MAX_SPARE_SIZE) / 4]; + return true; +} -static void nand_test(void) + +static bool nand_read_sector_of_phys_segment(int chip, int phys_segment, + int page_in_seg, int sector, + void* buf) { - int i; - unsigned int seq_segments = 0; -#if 0 - int chip,page; -#endif + int page_addr = phys_segment_to_page_addr(phys_segment, + page_in_seg); - printf("%d banks", total_banks); - printf("* %d pages", pages_per_bank); - printf("* %d bytes per page", page_size); + return nand_read_sector_of_phys_page(chip, page_addr, sector, buf); +} - i = 0; - while (segment_location[i] != -1 - && i++ < (blocks_per_bank * total_banks / 4)) + +static bool nand_read_sector_of_logical_segment(int log_segment, int sector, + void* buf) +{ + int page_in_segment = sector / sectors_per_page; + int sector_in_page = sector % sectors_per_page; + + int chip = lpt_lookup[log_segment].chip; + int phys_segment = lpt_lookup[log_segment].phys_segment; + + /* Check if any of the write caches refer to this segment/page. + If present we need to read the cached page instead. */ + + int cache_num = 0; + bool found = false; + + while (!found && cache_num < write_caches_in_use) { - seq_segments++; + if (write_caches[cache_num].log_segment == log_segment + && write_caches[cache_num].page_map[page_in_segment] != -1) + { + found = true; + chip = write_caches[cache_num].chip; + phys_segment = write_caches[cache_num].phys_segment; + page_in_segment = write_caches[cache_num].page_map[page_in_segment]; + } + else + { + cache_num++; + } } - printf("%d sequential segments found (%dMb)", seq_segments, - (seq_segments*bytes_per_segment)>>20); - while (!button_read_device()) {}; - while (button_read_device()) {}; + return nand_read_sector_of_phys_segment(chip, phys_segment, + page_in_segment, + sector_in_page, buf); +} -#if 0 - /* Read & display sequential pages */ - for (chip = 0; chip < total_banks; chip++) +#if 0 // LPT table is work-in-progress + +static void read_lpt_block(int chip, int phys_segment) +{ + int page = 1; /* table starts at page 1 of segment */ + bool cont = true; + + struct lpt_entry* lpt_ptr = NULL; + + while (cont && page < pages_per_block) { - for (page = 0x0; page < 0x100; page++) + int i = 0; + + nand_read_sector_of_phys_segment(chip, phys_segment, + page, 0, /* only sector 0 is used */ + page_buf); + + /* Find out which chunk of the LPT table this section contains. + Do this by reading the logical segment number of entry 0 */ + if (lpt_ptr == NULL) { - nand_read_raw(chip, page, 0, page_size+spare_size, buf); + int first_chip = page_buf[0] / segments_per_bank; + int first_phys_segment = page_buf[0] % segments_per_bank; + + unsigned char spare_buf[16]; - for (i = 0; i < (page_size+spare_size)/4; i += 132) + nand_read_raw(first_chip, + phys_segment_to_page_addr(first_phys_segment, 0), + SECTOR_SIZE, /* offset */ + 16, spare_buf); + + int first_log_segment = (spare_buf[6] << 8) | spare_buf[7]; + + lpt_ptr = &lpt_lookup[first_log_segment]; + +#if defined(BOOTLOADER) && 1 + printf("lpt @ %lx:%lx (ls:%lx)", + first_chip, first_phys_segment, first_log_segment); +#endif + } + + while (cont && (i < SECTOR_SIZE/4)) + { + if (page_buf[i] != 0xFFFFFFFF) { - int j,interesting = 0; - line = 0; - printf("c:%d p:%lx i:%d", chip, page, i); + lpt_ptr->chip = page_buf[i] / segments_per_bank; + lpt_ptr->phys_segment = page_buf[i] % segments_per_bank; - for (j=i; j<(i+131); j++) - { - if (buf[j] != 0xffffffff) interesting = 1; - } + lpt_ptr++; + i++; + } + else cont = false; + } + page++; + } +} - if (interesting) - { - for (j=i; j<(i+63); j+=4) - { - printf("%lx %lx %lx %lx", - buf[j], buf[j+1], buf[j+2], buf[j+3]); - } - printf("--->"); - while (!button_read_device()) {}; - while (button_read_device()) {}; +#endif - line = 1; - printf("<---"); - for (j=j; j<(i+131); j+=4) - { - printf("%lx %lx %lx %lx", - buf[j], buf[j+1], buf[j+2], buf[j+3]); - } - while (!button_read_device()) {}; - while (button_read_device()) {}; - reset_screen(); - } + +static void read_write_cache_segment(int chip, int phys_segment) +{ + int page; + unsigned char spare_buf[16]; + + if (write_caches_in_use == MAX_WRITE_CACHES) + panicf("Max NAND write caches reached"); + + write_caches[write_caches_in_use].chip = chip; + write_caches[write_caches_in_use].phys_segment = phys_segment; + + /* Loop over each page in the phys segment (from page 1 onwards). + Read spare for 1st sector, store location of page in array. */ + for (page = 1; page < pages_per_block * 4; page++) + { + unsigned short cached_page; + unsigned short log_segment; + + nand_read_raw(chip, phys_segment_to_page_addr(phys_segment, page), + SECTOR_SIZE, /* offset to first sector's spare */ + 16, spare_buf); + + cached_page = (spare_buf[3] << 8) | spare_buf[2]; /* why does endian */ + log_segment = (spare_buf[6] << 8) | spare_buf[7]; /* -ness differ? */ + + if (cached_page != 0xFFFF) + { + write_caches[write_caches_in_use].log_segment = log_segment; + write_caches[write_caches_in_use].page_map[cached_page] = page; + } + } + write_caches_in_use++; +} + + +/* TEMP testing functions */ + +#ifdef BOOTLOADER + +#if 0 +static void display_page(int chip, int page) +{ + int i; + nand_read_raw(chip, page, 0, page_size+spare_size, page_buf); + + for (i = 0; i < (page_size+spare_size)/4; i += 132) + { + int j,interesting = 0; + line = 1; + printf("c:%d p:%lx s:%d", chip, page, i/128); + + for (j=i; j<(i+131); j++) + { + if (page_buf[j] != 0xffffffff) interesting = 1; + } + + if (interesting) + { + for (j=i; j<(i+131); j+=8) + { + printf("%lx %lx %lx %lx %lx %lx %lx %lx", + page_buf[j],page_buf[j+1],page_buf[j+2],page_buf[j+3], + page_buf[j+4],page_buf[j+5],page_buf[j+6],page_buf[j+7]); } + while (!button_read_device()) {}; + while (button_read_device()) {}; + reset_screen(); } } +} #endif + +static void nand_test(void) +{ + int segment = 0; + + printf("%d banks", total_banks); + printf("* %d pages", pages_per_bank); + printf("* %d bytes per page", page_size); + + while (lpt_lookup[segment].chip != -1 + && segment < segments_per_bank * total_banks) + { + segment++; + } + printf("%d sequential segments found (%dMb)", + segment, (unsigned)(segment*bytes_per_segment)>>20); } #endif @@ -523,7 +713,7 @@ int ata_read_sectors(IF_MV2(int drive,) unsigned long start, int incount, while (incount > 0 && secmod < sectors_per_segment) { - if (!nand_read_sector(segment, secmod, inbuf)) + if (!nand_read_sector_of_logical_segment(segment, secmod, inbuf)) { mutex_unlock(&ata_mtx); return -1; @@ -554,7 +744,7 @@ int ata_write_sectors(IF_MV2(int drive,) unsigned long start, int count, (void)start; (void)count; (void)outbuf; - return 0; + return -1; } void ata_spindown(int seconds) @@ -600,81 +790,81 @@ void ata_enable(bool on) int ata_init(void) { - int i, bank, page; - unsigned int spare_buf[4]; - - if (initialized) return 0; + int i, bank, phys_segment; + unsigned char spare_buf[16]; + if (initialized) return 0; + /* Get chip characteristics and number of banks */ nand_get_chip_info(); - - for (i = 0; i < (MAX_BLOCKS_PER_BANK * MAX_BANKS / 4); i++) + + for (i = 0; i < MAX_SEGMENTS; i++) { - segment_location[i] = -1; + lpt_lookup[i].chip = -1; + lpt_lookup[i].phys_segment = -1; + //lpt_lookup[i].segment_flag = -1; } + write_caches_in_use = 0; + + for (i = 0; i < MAX_WRITE_CACHES; i++) + { + int page; + + write_caches[i].log_segment = -1; + write_caches[i].chip = -1; + write_caches[i].phys_segment = -1; + + for (page = 0; page < MAX_PAGES_PER_BLOCK * 4; page++) + { + write_caches[i].page_map[page] = -1; + } + } + /* Scan banks to build up block translation table */ for (bank = 0; bank < total_banks; bank++) { - for (page = 0; page < pages_per_bank/2; page += pages_per_block*2) + for (phys_segment = 0; phys_segment < segments_per_bank; phys_segment++) { - unsigned char segment_flag; - unsigned char stored_flag; - unsigned short segment_id; - - unsigned char* buf_ptr = (unsigned char*)spare_buf; - /* Read spare bytes from first sector of each segment */ - nand_read_raw(bank, page, + nand_read_raw(bank, phys_segment_to_page_addr(phys_segment, 0), SECTOR_SIZE, /* offset */ 16, spare_buf); - segment_id = (buf_ptr[6] << 8) | buf_ptr[7]; - segment_flag = buf_ptr[4]; - - stored_flag = (segment_location[segment_id] >> 24) & 0xf; - -#if defined(BOOTLOADER) && 0 - if (segment_flag == 0x15) - { - printf("Segment %lx: c:%lx p:%lx, type:%lx, stored:x%lx", - segment_id, bank, page, segment_flag, stored_flag); - while (!button_read_device()) {}; - while (button_read_device()) {}; - } -#endif - - if (segment_flag == 0x13 || segment_flag == 0x17) + switch (spare_buf[4]) /* block type */ { - if (segment_id < (blocks_per_bank * total_banks / 4)) - { -#if defined(BOOTLOADER) && 0 - if (segment_location[segment_id] != -1 && stored_flag != 0x3) + case 0x12: + { + /* Log->Phys Translation table (for Main data area) */ + //read_lpt_block(bank, phys_segment); + break; + } + + case 0x13: + case 0x17: + { + /* Main data area segment */ + int segment = (spare_buf[6] << 8) | spare_buf[7]; + + if (segment < MAX_SEGMENTS) { - int orig_bank = segment_location[segment_id] >> 28; - int orig_page = segment_location[segment_id] & 0xFFFFFF; - - printf("Segment %d already set! (stored flag:x%lx)", - segment_id, stored_flag); - - printf("0x%08x 0x%08x 0x%08x 0x%08x", - spare_buf[0],spare_buf[1],spare_buf[2],spare_buf[3]); - - nand_read_raw(orig_bank, orig_page, - SECTOR_SIZE, - 16, spare_buf); - - printf("0x%08x 0x%08x 0x%08x 0x%08x", - spare_buf[0],spare_buf[1],spare_buf[2],spare_buf[3]); + /* Store in LPT if not present or 0x17 overrides 0x13 */ + //if (lpt_lookup[segment].segment_flag == -1 || + // lpt_lookup[segment].segment_flag == 0x13) + { + lpt_lookup[segment].chip = bank; + lpt_lookup[segment].phys_segment = phys_segment; + //lpt_lookup[segment].segment_flag = spare_buf[4]; + } } -#endif - /* Write bank, block type & physical address into table */ - segment_location[segment_id] - = page | (bank << 28) | ((segment_flag & 0xf) << 24); + break; } - else + + case 0x15: { - panicf("Invalid segment id:%d found", segment_id); + /* Recently-written page data (for Main data area) */ + read_write_cache_segment(bank, phys_segment); + break; } } } -- 2.11.4.GIT