/* linux/drivers/mtd/nand/fusiv_bch_decode.c * * Copyright 2010 Ikanos Inc. * * BCH decode algorithm for Fusiv VX185 on-chip NAND flash controller driver * * Changelog: * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #define MATRIX_A(x) pMatrix_a[x] #define MATRIX_B(x) pMatrix_b[x] #define MATRIX_C(x) pMatrix_c[x] #define ELEMENT(x) pElement[x] #define SYNVAL(x) syn_val[x] #define SET_MATRIX_A(x,y) pMatrix_a[x] = (y) #define SET_MATRIX_B(x,y) pMatrix_b[x] = (y) #define SET_MATRIX_C(x,y) pMatrix_c[x] = (y) #define SET_ELEMENT(x,y) pElement[x] = (y) #define SET_SYNVAL(x,y) syn_val[x] = (y) static short *perr_loc_ply; static int errloc_sizex; short *err_loc_ply(int x, int y) { return perr_loc_ply + ((x * errloc_sizex) + y); } /* Read BCH error flags */ static inline unsigned long bch_get_error_flags(void) { return fusiv_nfc_readl(NFC_BCH_STATUS); } /* Read Syndrome values for error location detection */ static void read_syndrome_vals(unsigned short *psynvals, int x) { /* Get the 8 hardware generated syndrome values relating to the * Xth 512 bytes of the page just read. */ unsigned long synregval; int y; for (y = 0; y < 4; y++) { synregval = fusiv_nfc_readl(NFC_SECTx_SYNDROMEy(x, y)); *psynvals++ = (unsigned short)synregval; *psynvals++ = (unsigned short)(synregval >> 16); } } void print_syndrome_registers (void) { uint8_t i; printk("%s BCH_Status: 0x%x, INT_Status: 0x%x, \n",__FUNCTION__, \ fusiv_nfc_readl(NFC_BCH_STATUS),fusiv_nfc_readl(NFC_INT_STATUS)); for (i = 0; i < 16; i++) { printk("Section-%d, Syndrome-reg-%d: 0x%x\n", \ i/4, i%4, fusiv_nfc_readl(NFC_SECT0_SYNDROME12 + i * 4)); } } /* BCH error location detection */ int bch_locate_errors(short *syn_val, short *perrlocations) { static int *pMatrix_a; static int *pMatrix_b; static int *pMatrix_c; static int *pElement; int _Matrix_a[11]; int _Matrix_b[11]; int _Matrix_c[12]; int _Element[7]; short _err_loc_ply[10][24]; const int bch_length = 52; const int data_length = 4096; const int block_length = 8191; int i, j, elp_sum ; int alpha, d_flg; int temp_index; short err_count; pMatrix_a = &_Matrix_a[0]; pMatrix_b = &_Matrix_b[0]; pMatrix_c = &_Matrix_c[0]; pElement = &_Element[0]; perr_loc_ply = &_err_loc_ply[0][0]; errloc_sizex = 10; d_flg = 0; err_count = 0; // initialise table entries for (i = 1; i <= NR_SYNDROMES; i++) { SET_SYNVAL(i, i_to_a[SYNVAL(i)]); } SET_MATRIX_C(0, 0); SET_MATRIX_C(1, SYNVAL(1)); *err_loc_ply(0,0) = 1; *err_loc_ply(1,0) = 1; for (i = 1; i < 8; i++) { *err_loc_ply(0,i) = 0; *err_loc_ply(1,i) = 0; } SET_MATRIX_A(0, 0); SET_MATRIX_A(1, 0); SET_MATRIX_B(0, -1); SET_MATRIX_B(1, 0); alpha = -1; do { // skip even loops alpha += 2; if (MATRIX_C(alpha) != -1) { temp_index = alpha - 2; if (temp_index < 0) { temp_index = 0; } while ((temp_index > 0) && (MATRIX_C(temp_index) == -1)) { temp_index = temp_index - 2; } if (temp_index < 0) { temp_index = 0; } if (temp_index > 0) { j = temp_index; do { j = j - 2; if (j < 0) { j = 0; } if ((MATRIX_C(j) != -1) && (MATRIX_B(temp_index) < MATRIX_B(j))) { temp_index = j; } } while (j > 0); } if (MATRIX_A(alpha) > MATRIX_A(temp_index) + alpha - temp_index) { SET_MATRIX_A(alpha + 2, MATRIX_A(alpha)); } else { SET_MATRIX_A(alpha + 2, MATRIX_A(temp_index) + alpha - temp_index); } for (i = 0; i < 8; i++) { *err_loc_ply(alpha + 2,i) = 0; } for (i = 0; i <= MATRIX_A(temp_index); i++) { if (*err_loc_ply(temp_index,i) != 0) { *err_loc_ply(alpha + 2,i + alpha - temp_index) = a_to_i[(MATRIX_C(alpha) + block_length - MATRIX_C(temp_index) + i_to_a[*err_loc_ply(temp_index,i)]) % block_length]; } } for (i = 0; i <= MATRIX_A(alpha); i++) { *err_loc_ply(alpha + 2,i) ^= *err_loc_ply(alpha,i); } } else { SET_MATRIX_A(alpha + 2, MATRIX_A(alpha)); for (i = 0; i <= MATRIX_A(alpha); i++) { *err_loc_ply(alpha + 2,i) = *err_loc_ply(alpha,i); } } SET_MATRIX_B(alpha + 2, alpha + 1 - MATRIX_A(alpha + 2)); // Form (alpha+2)th discrepancy. if (alpha < 8) { if (SYNVAL(alpha + 2) != -1) { SET_MATRIX_C(alpha + 2, a_to_i[SYNVAL(alpha + 2)]); } else { SET_MATRIX_C(alpha + 2, 0); } for (i = 1; i <= MATRIX_A(alpha + 2); i++) { if ((SYNVAL(alpha + 2 - i) != -1) && (*err_loc_ply(alpha + 2,i) != 0)) { SET_MATRIX_C(alpha + 2, MATRIX_C(alpha + 2) ^ a_to_i[(SYNVAL(alpha + 2 - i) + i_to_a[*err_loc_ply(alpha + 2,i)]) % block_length]); } } SET_MATRIX_C(alpha + 2, i_to_a[MATRIX_C(alpha + 2)]); } } while ((alpha < 7) && (MATRIX_A(alpha + 2) <= 4)); alpha = alpha + 2; SET_MATRIX_A(7, MATRIX_A(alpha)); if (MATRIX_A(7) <= 4) { for (i = 1; i <= MATRIX_A(7); i++) { SET_ELEMENT(i, i_to_a[*err_loc_ply(alpha,i)]); } for (i = 1; i <= block_length; i++) { elp_sum = 1 ; for (j = 1; j <= MATRIX_A(7); j++) { if (ELEMENT(j) != -1) { SET_ELEMENT(j, (ELEMENT(j) + j) % block_length); elp_sum ^= a_to_i[ELEMENT(j)]; } } if (!elp_sum) { if (err_count >= MAX_FIXABLE_ERRS) { /* More errors than caller can handle */ return -1; } perrlocations[err_count] = block_length - i; err_count++; } } if (err_count == MATRIX_A(7)) { d_flg = 1; } } if (d_flg == 0) { /* Page is uncorrectable */ return -1; } for (i = 0; i < err_count; i++) { if (perrlocations[i] >= 52) { /* Return bit index of errors in 512 byte page */ perrlocations[i] = bch_length + data_length - 1 - perrlocations[i]; } else { /* Error in ECC area, these are flagged but ignored */ perrlocations[i] = -1; } } return err_count; } static void fix_errors(int i, short bitindex, void *pmemstart) { unsigned char *perrbyte; if (bitindex == -1) { /* Bit error was outside data area so we ignore it */ return; } /* Access the erroneous byte */ perrbyte = (unsigned char *)pmemstart + (i << 9) + (bitindex >> 3); /* The NAND controller is wired little endian so when the host does * a word copy from the FIFOs to memory we get an endian swap. Normally * this makes no difference as the swaps on write and read cancel each * other out, however we do need to account for it when working out * which byte to correct otherwise we correct byte 3 instead of byte 0, * or byte 2 instead of byte 1. */ perrbyte = (unsigned char *)((unsigned long)perrbyte ^ 3); /* Flip the bit */ *perrbyte ^= (1 << (bitindex & 7)); }