changeset 1728:650ae718ca77

Initial NAND/ECC support
author gthomas
date Tue, 24 Aug 2004 13:16:40 +0000
parents e31d381b2dd1
children ea82d8fd5dd6
files packages/devs/flash/toshiba/tc58xxx/current/ChangeLog packages/devs/flash/toshiba/tc58xxx/current/include/flash_tc58xxx.inl
diffstat 2 files changed, 276 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/packages/devs/flash/toshiba/tc58xxx/current/ChangeLog
+++ b/packages/devs/flash/toshiba/tc58xxx/current/ChangeLog
@@ -1,3 +1,8 @@
+2004-08-24  Gary Thomas  <gary@mlbassoc.com>
+
+	* include/flash_tc58xxx.inl: Initial implementation of NAND ECC 
+	support (used by JFFS2).
+
 2004-03-07  Gary Thomas  <gary@mlbassoc.com>
 
 	* include/flash_tc58xxx.inl: Page addressing was wrong for large (> 32MB) devices.
--- a/packages/devs/flash/toshiba/tc58xxx/current/include/flash_tc58xxx.inl
+++ b/packages/devs/flash/toshiba/tc58xxx/current/include/flash_tc58xxx.inl
@@ -61,6 +61,7 @@
 #include <pkgconf/hal.h>
 #include <cyg/hal/hal_arch.h>
 #include <cyg/hal/hal_cache.h>
+#include <cyg/infra/diag.h>
 #include CYGHWR_MEMORY_LAYOUT_H
 
 #define  _FLASH_PRIVATE_
@@ -246,7 +247,6 @@ flash_erase_block(void* block, unsigned 
     volatile flash_data_t* ROM;
     volatile flash_data_t* b_p = (volatile flash_data_t*) block;
     int res = FLASH_ERR_OK;
-    int len = 0;
     int cnt = 0;
     flash_data_t stat;
 
@@ -272,6 +272,7 @@ flash_erase_block(void* block, unsigned 
     CYGHWR_FLASH_TC58XXX_CLE(1);
     put_NAND(ROM, FLASH_Start_Erase);
     CYGHWR_FLASH_TC58XXX_CLE(0);        
+    CYGACC_CALL_IF_DELAY_US(10);
     while (!CYGHWR_FLASH_TC58XXX_RDY()) cnt++;  // Wait for operation to complete
     CYGHWR_FLASH_TC58XXX_CLE(1);
     put_NAND(ROM, FLASH_Status);
@@ -284,9 +285,210 @@ flash_erase_block(void* block, unsigned 
 #if FLASH_DEBUG > 0
     diag_printf("block: %x, stat: %x, count = %d\n", b_p, stat, cnt);
 #endif
+    if (stat != 0xC0) {
+        diag_printf("Status after erase: %x\n", stat);
+        if ((stat & 0x80) == 0x00) {
+            res = FLASH_ERR_PROTECT;
+        } else {
+            res = FLASH_ERR_ERASE;
+        }
+    }
     return res;
 }
 
+//
+// ECC support - adapted from Linux:
+//
+//  drivers/mtd/nand_ecc.c
+//
+//  Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+//                     Toshiba America Electronics Components, Inc.
+//
+
+static const unsigned char _nand_ecc_precalc_table[] = {
+    0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
+    0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+    0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+    0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+    0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+    0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+    0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+    0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+    0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+    0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+    0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+    0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+    0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+    0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+    0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+    0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00
+};
+
+static void 
+_nand_trans_result(unsigned char reg2, unsigned char reg3,
+                   unsigned char *ecc0, unsigned char *ecc1)
+{
+    unsigned char a, b, i, tmp1, tmp2;
+	
+    /* Initialize variables */
+    a = b = 0x80;
+    tmp1 = tmp2 = 0;
+	
+    /* Calculate first ECC byte */
+    for (i = 0; i < 4; i++) {
+        if (reg3 & a)		/* LP15,13,11,9 --> ecc_code[0] */
+            tmp1 |= b;
+        b >>= 1;
+        if (reg2 & a)		/* LP14,12,10,8 --> ecc_code[0] */
+            tmp1 |= b;
+        b >>= 1;
+        a >>= 1;
+    }
+	
+    /* Calculate second ECC byte */
+    b = 0x80;
+    for (i = 0; i < 4; i++) {
+        if (reg3 & a)		/* LP7,5,3,1 --> ecc_code[1] */
+            tmp2 |= b;
+        b >>= 1;
+        if (reg2 & a)		/* LP6,4,2,0 --> ecc_code[1] */
+            tmp2 |= b;
+        b >>= 1;
+        a >>= 1;
+    }
+	
+    /* Store two of the ECC bytes */
+    *ecc0 = tmp1;    
+    *ecc1 = tmp2;
+}
+
+//
+// Calculate 3 byte ECC on 256 bytes of data
+//
+static void
+_nand_page_ECC(unsigned char *data, unsigned char *ecc0,
+               unsigned char *ecc1, unsigned char *ecc2)
+{
+    unsigned char idx, reg1, reg2, reg3;
+    int j;
+	
+    /* Initialize variables */
+    reg1 = reg2 = reg3 = 0;
+    *ecc0 = *ecc1 = *ecc2 = 0;
+	
+    /* Build up column parity */ 
+    for(j = 0; j < 256; j++) {
+        /* Get CP0 - CP5 from table */
+        idx = _nand_ecc_precalc_table[*data++];
+        reg1 ^= (idx & 0x3f);
+        /* All bit XOR = 1 ? */
+        if (idx & 0x40) {
+            reg3 ^= (unsigned char) j;
+            reg2 ^= ~((unsigned char) j);
+        }
+    }
+	
+    /* Create non-inverted ECC code from line parity */
+    _nand_trans_result(reg2, reg3, ecc0, ecc1);
+	
+    /* Calculate final ECC code */
+    *ecc0 = ~*ecc0;
+    *ecc1 = ~*ecc1;
+    *ecc2 = ((~reg1) << 2) | 0x03;
+}
+
+//
+// Correct a buffer via ECC (1 bit, 256 byte block)
+//  Return: 0 => No error
+//          1 => Corrected
+//          2 => Not corrected, ECC updated
+//         -1 => Not correctable
+//
+int 
+_nand_correct_data(unsigned char *dat, unsigned char *read_ecc, unsigned char *calc_ecc)
+{
+    unsigned char a, b, c, d1, d2, d3, add, bit, i;
+	
+    /* Do error detection */ 
+    d1 = calc_ecc[0] ^ read_ecc[0];
+    d2 = calc_ecc[1] ^ read_ecc[1];
+    d3 = calc_ecc[2] ^ read_ecc[2];
+	
+    if ((d1 | d2 | d3) == 0) {
+        /* No errors */
+        return 0;
+    } else {
+        a = (d1 ^ (d1 >> 1)) & 0x55;
+        b = (d2 ^ (d2 >> 1)) & 0x55;
+        c = (d3 ^ (d3 >> 1)) & 0x54;
+		
+        /* Found and will correct single bit error in the data */
+        if ((a == 0x55) && (b == 0x55) && (c == 0x54)) {
+            c = 0x80;
+            add = 0;
+            a = 0x80;
+            for (i=0; i<4; i++) {
+                if (d1 & c)
+                    add |= a;
+                c >>= 2;
+                a >>= 1;
+            }
+            c = 0x80;
+            for (i=0; i<4; i++) {
+                if (d2 & c)
+                    add |= a;
+                c >>= 2;
+                a >>= 1;
+            }
+            bit = 0;
+            b = 0x04;
+            c = 0x80;
+            for (i=0; i<3; i++) {
+                if (d3 & c)
+                    bit |= b;
+                c >>= 2;
+                b >>= 1;
+            }
+            b = 0x01;
+            a = dat[add];
+            a ^= (b << bit);
+            dat[add] = a;
+            return 1;
+        } else {
+            i = 0;
+            while (d1) {
+                if (d1 & 0x01)
+                    ++i;
+                d1 >>= 1;
+            }
+            while (d2) {
+                if (d2 & 0x01)
+                    ++i;
+                d2 >>= 1;
+            }
+            while (d3) {
+                if (d3 & 0x01)
+                    ++i;
+                d3 >>= 1;
+            }
+            if (i == 1) {
+                /* ECC Code Error Correction */
+                read_ecc[0] = calc_ecc[0];
+                read_ecc[1] = calc_ecc[1];
+                read_ecc[2] = calc_ecc[2];
+                return 2;
+            } else {
+                /* Uncorrectable Error */
+                return -1;
+            }
+        }
+    }
+	
+    /* Should never happen */
+    return -1;
+}
+
+
 //----------------------------------------------------------------------------
 // Program Buffer
 int
@@ -296,10 +498,9 @@ flash_program_buf(void* addr, void* data
     volatile flash_data_t* addr_ptr = (volatile flash_data_t*) addr;
     volatile flash_data_t* data_ptr = (volatile flash_data_t*) data;
     int res = FLASH_ERR_OK;
-
-    int i;
-    int cnt;
+    int i, cnt;
     flash_data_t stat;
+    unsigned char oob[16];
 
     ROM = (volatile flash_data_t*) CYGNUM_FLASH_BASE;
 #if FLASH_DEBUG > 0
@@ -326,6 +527,13 @@ flash_program_buf(void* addr, void* data
 #if FLASH_DEBUG > 1
         diag_printf(">>\n");
 #endif
+        // Caculate OOB data for page (ECC)
+        for (i = 0;  i < 16;  i++) {
+            oob[i] = 0xFF;
+        }
+        // Calculate ECC for page
+        _nand_page_ECC((unsigned char *)&data_ptr[0], &oob[0], &oob[1], &oob[2]);
+        _nand_page_ECC((unsigned char *)&data_ptr[256], &oob[3], &oob[6], &oob[7]);
         // Move one page of data to buffer
         for (i = 0;  i < 512;  i++) {
             put_NAND(ROM, *data_ptr++);
@@ -335,7 +543,7 @@ flash_program_buf(void* addr, void* data
         }
         // OOB data
         for (i = 0;  i < 16;  i++) {
-            put_NAND(ROM, 0xFF);
+            put_NAND(ROM, oob[i]);
 #if FLASH_DEBUG > 1
             if ((i % 16) == 15) diag_printf("\n");
 #endif
@@ -348,6 +556,7 @@ flash_program_buf(void* addr, void* data
         CYGHWR_FLASH_TC58XXX_CLE(0);
         CYGACC_CALL_IF_DELAY_US(1);  // Actually 200ns
         cnt = 0;
+        CYGACC_CALL_IF_DELAY_US(10);
         while (!CYGHWR_FLASH_TC58XXX_RDY()) cnt++;  // Wait for page data to be ready
         CYGHWR_FLASH_TC58XXX_CLE(1);
         put_NAND(ROM, FLASH_Status);
@@ -361,6 +570,14 @@ flash_program_buf(void* addr, void* data
         diag_printf("program at %x, stat: %x, count = %d\n", addr_ptr, stat, cnt);
 #endif
         addr_ptr += 512;  len -= 512;
+        if (stat != 0xC0) {
+            diag_printf("Status after write: %x\n", stat);
+            if ((stat & 0x80) == 0x00) {
+                res = FLASH_ERR_PROTECT;
+            } else {
+                res = FLASH_ERR_PROGRAM;
+            }
+        }
     }
     return res;
 }
@@ -371,12 +588,13 @@ int
 flash_read_buf(void* addr, void* data, int len)
 {
     volatile flash_data_t* ROM;
-    volatile flash_data_t  dummy;
     volatile flash_data_t* addr_ptr = (volatile flash_data_t*) addr;
     volatile flash_data_t* data_ptr = (volatile flash_data_t*) data;
+    flash_data_t *page;
     int res = FLASH_ERR_OK;
-    int i;
-    int cnt;
+    int i, cnt, offset;
+    flash_data_t stat;
+    unsigned char oob[16], dev_oob[16];
 
     ROM = (volatile flash_data_t*) CYGNUM_FLASH_BASE;
 #if FLASH_DEBUG > 1
@@ -404,25 +622,67 @@ flash_read_buf(void* addr, void* data, i
     diag_printf(">>\n");
 #endif
     cnt = 0;
+    CYGACC_CALL_IF_DELAY_US(10);
     while (!CYGHWR_FLASH_TC58XXX_RDY()) cnt++;  // Wait for page data to be ready 
 #if FLASH_DEBUG > 0
     diag_printf("Read data starting at %p, count = %d\n", data_ptr, cnt);
 #endif
+    offset = 0;
+    page = (unsigned char *)data_ptr;
     while (len-- > 0) {
         *data_ptr++ = *ROM;
-        if (((unsigned long)data_ptr & 0x1FF) == 0) {
-            // Data page has been read, skip over ECC/OOB data
+        if (++offset == 0x200) {
+            // Data page has been read, fetch ECC/OOB data
             for (i = 0;  i < 16;  i++) {
-                dummy = *ROM;
+                dev_oob[i] = *ROM;
+            }
+            // Calculate actual ECC on page
+            _nand_page_ECC(&page[0], &oob[0], &oob[1], &oob[2]);
+            _nand_page_ECC(&page[256], &oob[3], &oob[6], &oob[7]);
+            // Check & repair if possible
+            if ((oob[0] != dev_oob[0]) || (oob[1] != dev_oob[1]) || (oob[2] != dev_oob[2]) || 
+                (oob[3] != dev_oob[3]) || (oob[6] != dev_oob[6]) || (oob[7] != dev_oob[7])) {
+                unsigned char read_ecc[3], calc_ecc[3];
+                int res;
+
+                read_ecc[0] = dev_oob[0];  calc_ecc[0] = oob[0];
+                read_ecc[1] = dev_oob[1];  calc_ecc[1] = oob[1];
+                read_ecc[2] = dev_oob[2];  calc_ecc[2] = oob[2];
+                res = _nand_correct_data(&page[0], read_ecc, calc_ecc);
+                if ((res != 0) && (res != 1)) {
+                    diag_printf("ECC failed\n");
+                    res = FLASH_ERR_HWR;
+                    break;
+                }
+                read_ecc[0] = dev_oob[3];  calc_ecc[0] = oob[3];
+                read_ecc[1] = dev_oob[6];  calc_ecc[1] = oob[6];
+                read_ecc[2] = dev_oob[7];  calc_ecc[2] = oob[7];
+                res = _nand_correct_data(&page[256], read_ecc, calc_ecc);
+                if ((res != 0) && (res != 1)) {
+                    diag_printf("ECC failed\n");
+                    res = FLASH_ERR_HWR;
+                    break;
+                }
             }
             cnt = 0;
+            CYGACC_CALL_IF_DELAY_US(10);
             while (!CYGHWR_FLASH_TC58XXX_RDY()) cnt++;  // Wait for page data to be ready
 #if FLASH_DEBUG > 0
             diag_printf("Read data starting at %p, count = %d\n", data_ptr, cnt);
 #endif
+            offset = 0;
+            page = (unsigned char *)data_ptr;
         }
     }
+    CYGHWR_FLASH_TC58XXX_CLE(1);
+    put_NAND(ROM, FLASH_Status);
+    CYGHWR_FLASH_TC58XXX_CLE(0);        
+    stat = *ROM;
     CYGHWR_FLASH_TC58XXX_CE(0);
+    if (stat != 0xC0) {
+        diag_printf("Status after read: %x\n", stat);
+        res = FLASH_ERR_HWR;
+    }
     return res;
 }