diff --git a/common.h b/common.h index 74ba4b6..ac10849 100644 --- a/common.h +++ b/common.h @@ -42,7 +42,7 @@ /* #define NAND_DEBUG_WRITE_RAMP 1 */ #if defined(board_sysmobts_v2) -#define UBL_VERSION_STR "sysmobts_v2 Loader v1.1.0 (" __DATE__ "-" __TIME__ ")" +#define UBL_VERSION_STR "sysmobts_v2 Loader v1.1.0a (" __DATE__ "-" __TIME__ ")" #else #define UBL_VERSION_STR "HV-UBL v0.2.11" #endif @@ -50,7 +50,7 @@ /* Define this for bypassing the ECC check when reading from the NAND. * This is useful for debugging or during development. */ -#define NAND_BYPASS_READ_PAGE_ECC_CHECK 1 +//#define NAND_BYPASS_READ_PAGE_ECC_CHECK 1 #define MAGIC_NUMBER_MASK 0xFFFFFF00 #define MAGIC_NUMBER_VALID 0xA1ACED00 diff --git a/nand.c b/nand.c index f708a5f..926f9be 100644 --- a/nand.c +++ b/nand.c @@ -596,13 +596,77 @@ nand_read_spare(void) return spare_ecc_temp; } +/* + * GPLv2 code taken from the kernel to correct the bit error. + * The content of the ECC register is in the sprue20c.pdf. For + * the odd/even there are four bits spare each. The kernel is + * shifting this around and it is the easiest just to do the + * same. + */ +static uint32_t squeeze_ecc_bits(uint32_t tmp) +{ + /* Squeeze 4 bytes ECC into 3 bytes by removing RESERVED bits + * and shifting. RESERVED bits are 31 to 28 and 15 to 12. */ + tmp = (tmp & 0x00000fff) | ((tmp & 0x0fff0000) >> 4); + + /* Invert so that erased block ECC is correct */ + return ~tmp; +} + +static int nand_check_fix( + const uint32_t _calc_ecc, + const uint32_t _ecc_nand, uint8_t *dat, int size) +{ + uint32_t ecc_nand, ecc_calc; + + ecc_calc = squeeze_ecc_bits(_calc_ecc); + ecc_nand = squeeze_ecc_bits(_ecc_nand); + + uint32_t diff = ecc_calc ^ ecc_nand; + + if (diff) { + if ((((diff >> 12) ^ diff) & 0xfff) == 0xfff) { + /* Correctable error */ + if ((diff >> (12 + 3)) < size) { + uint8_t find_bit = 1 << ((diff >> 12) & 7); + uint32_t find_byte = diff >> (12 + 3); + + dat[find_byte] ^= find_bit; + log_info("Correcting single bit error."); + uart_send_str("BYTE="); + uart_send_hexnum(find_byte, 8); + uart_send_str(" BIT="); + uart_send_hexnum(find_bit, 8); + uart_send_lf(); + return 1; + } else { + log_info("Too many bit errors:"); + uart_send_hexnum(diff >> (12+3), 8); + uart_send_lf(); + return -1; + } + } else if (!(diff & (diff - 1))) { + /* Single bit ECC error in the ECC itself, + nothing to fix */ + log_info("Bit error in the spare data. Ignoring"); + return 1; + } else { + /* Uncorrectable error */ + log_info("Uncorrectable error"); + return -1; + } + } + return 0; +} + /* Read a page from NAND */ int -nand_read_page(uint32_t block, uint32_t page, uint8_t *dest) +nand_read_page(uint32_t block, uint32_t page, uint8_t *orig_dest) { uint32_t hw_ecc[4]; uint32_t spare_ecc[4]; uint8_t numReads, i; + uint8_t *dest = orig_dest; numReads = (nand_info.bytes_per_page >> 9); /* Divide by 512 */ if (numReads == 0) @@ -651,7 +715,7 @@ nand_read_page(uint32_t block, uint32_t page, uint8_t *dest) #ifndef NAND_BYPASS_READ_PAGE_ECC_CHECK for (i = 0; i < numReads; i++) { /* Verify ECC values */ - if (hw_ecc[i] != spare_ecc[i]) { + if (nand_check_fix(hw_ecc[i], spare_ecc[i], &orig_dest[i * nand_info.chunk_size], nand_info.chunk_size) < 0) { log_info("NAND ECC failure:"); uart_send_str("HW = "); uart_send_hexnum(hw_ecc[i], 8);