|
@@ -29,6 +29,9 @@
|
|
|
#include <linux/delay.h>
|
|
|
#include <linux/mtd/mtd.h>
|
|
|
#include <linux/mtd/partitions.h>
|
|
|
+#include <linux/bitmap.h>
|
|
|
+#include <linux/bitrev.h>
|
|
|
+#include <linux/bch.h>
|
|
|
|
|
|
#include <linux/debugfs.h>
|
|
|
#include <linux/seq_file.h>
|
|
@@ -42,7 +45,6 @@
|
|
|
* As no specification is available from M-Systems/Sandisk, this drivers lacks
|
|
|
* several functions available on the chip, as :
|
|
|
* - IPL write
|
|
|
- * - ECC fixing (lack of BCH algorith understanding)
|
|
|
* - powerdown / powerup
|
|
|
*
|
|
|
* The bus data width (8bits versus 16bits) is not handled (if_cfg flag), and
|
|
@@ -51,8 +53,7 @@
|
|
|
* DocG3 relies on 2 ECC algorithms, which are handled in hardware :
|
|
|
* - a 1 byte Hamming code stored in the OOB for each page
|
|
|
* - a 7 bytes BCH code stored in the OOB for each page
|
|
|
- * The BCH part is only used for check purpose, no correction is available as
|
|
|
- * some information is missing. What is known is that :
|
|
|
+ * The BCH ECC is :
|
|
|
* - BCH is in GF(2^14)
|
|
|
* - BCH is over data of 520 bytes (512 page + 7 page_info bytes
|
|
|
* + 1 hamming byte)
|
|
@@ -75,6 +76,11 @@ static struct nand_ecclayout docg3_oobinfo = {
|
|
|
.oobavail = 8,
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * struct docg3_bch - BCH engine
|
|
|
+ */
|
|
|
+static struct bch_control *docg3_bch;
|
|
|
+
|
|
|
static inline u8 doc_readb(struct docg3 *docg3, u16 reg)
|
|
|
{
|
|
|
u8 val = readb(docg3->base + reg);
|
|
@@ -581,6 +587,54 @@ static void doc_hamming_ecc_init(struct docg3 *docg3, int nb_bytes)
|
|
|
doc_writeb(docg3, ecc_conf1, DOC_ECCCONF1);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * doc_correct_data - Fix if need be read data from flash
|
|
|
+ * @docg3: the device
|
|
|
+ * @buf: the buffer of read data (512 + 7 + 1 bytes)
|
|
|
+ * @hwecc: the hardware calculated ECC.
|
|
|
+ * It's in fact recv_ecc ^ calc_ecc, where recv_ecc was read from OOB
|
|
|
+ * area data, and calc_ecc the ECC calculated by the hardware generator.
|
|
|
+ *
|
|
|
+ * Checks if the received data matches the ECC, and if an error is detected,
|
|
|
+ * tries to fix the bit flips (at most 4) in the buffer buf. As the docg3
|
|
|
+ * understands the (data, ecc, syndroms) in an inverted order in comparison to
|
|
|
+ * the BCH library, the function reverses the order of bits (ie. bit7 and bit0,
|
|
|
+ * bit6 and bit 1, ...) for all ECC data.
|
|
|
+ *
|
|
|
+ * The hardware ecc unit produces oob_ecc ^ calc_ecc. The kernel's bch
|
|
|
+ * algorithm is used to decode this. However the hw operates on page
|
|
|
+ * data in a bit order that is the reverse of that of the bch alg,
|
|
|
+ * requiring that the bits be reversed on the result. Thanks to Ivan
|
|
|
+ * Djelic for his analysis.
|
|
|
+ *
|
|
|
+ * Returns number of fixed bits (0, 1, 2, 3, 4) or -EBADMSG if too many bit
|
|
|
+ * errors were detected and cannot be fixed.
|
|
|
+ */
|
|
|
+static int doc_ecc_bch_fix_data(struct docg3 *docg3, void *buf, u8 *hwecc)
|
|
|
+{
|
|
|
+ u8 ecc[DOC_ECC_BCH_SIZE];
|
|
|
+ int errorpos[DOC_ECC_BCH_T], i, numerrs;
|
|
|
+
|
|
|
+ for (i = 0; i < DOC_ECC_BCH_SIZE; i++)
|
|
|
+ ecc[i] = bitrev8(hwecc[i]);
|
|
|
+ numerrs = decode_bch(docg3_bch, NULL, DOC_ECC_BCH_COVERED_BYTES,
|
|
|
+ NULL, ecc, NULL, errorpos);
|
|
|
+ BUG_ON(numerrs == -EINVAL);
|
|
|
+ if (numerrs < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ for (i = 0; i < numerrs; i++)
|
|
|
+ errorpos[i] = (errorpos[i] & ~7) | (7 - (errorpos[i] & 7));
|
|
|
+ for (i = 0; i < numerrs; i++)
|
|
|
+ if (errorpos[i] < DOC_ECC_BCH_COVERED_BYTES*8)
|
|
|
+ /* error is located in data, correct it */
|
|
|
+ change_bit(errorpos[i], buf);
|
|
|
+out:
|
|
|
+ doc_dbg("doc_ecc_bch_fix_data: flipped %d bits\n", numerrs);
|
|
|
+ return numerrs;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* doc_read_page_prepare - Prepares reading data from a flash page
|
|
|
* @docg3: the device
|
|
@@ -762,7 +816,7 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
u8 *oobbuf = ops->oobbuf;
|
|
|
u8 *buf = ops->datbuf;
|
|
|
size_t len, ooblen, nbdata, nboob;
|
|
|
- u8 calc_ecc[DOC_ECC_BCH_SIZE], eccconf1;
|
|
|
+ u8 hwecc[DOC_ECC_BCH_SIZE], eccconf1;
|
|
|
|
|
|
if (buf)
|
|
|
len = ops->len;
|
|
@@ -797,7 +851,7 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
ret = doc_read_page_prepare(docg3, block0, block1, page, ofs);
|
|
|
if (ret < 0)
|
|
|
goto err;
|
|
|
- ret = doc_read_page_ecc_init(docg3, DOC_ECC_BCH_COVERED_BYTES);
|
|
|
+ ret = doc_read_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES);
|
|
|
if (ret < 0)
|
|
|
goto err_in_read;
|
|
|
ret = doc_read_page_getbytes(docg3, nbdata, buf, 1);
|
|
@@ -811,7 +865,7 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
doc_read_page_getbytes(docg3, DOC_LAYOUT_OOB_SIZE - nboob,
|
|
|
NULL, 0);
|
|
|
|
|
|
- doc_get_hw_bch_syndroms(docg3, calc_ecc);
|
|
|
+ doc_get_hw_bch_syndroms(docg3, hwecc);
|
|
|
eccconf1 = doc_register_readb(docg3, DOC_ECCCONF1);
|
|
|
|
|
|
if (nboob >= DOC_LAYOUT_OOB_SIZE) {
|
|
@@ -825,18 +879,28 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
doc_dbg("OOB - UNUSED: %02x\n", oobbuf[15]);
|
|
|
}
|
|
|
doc_dbg("ECC checks: ECCConf1=%x\n", eccconf1);
|
|
|
- doc_dbg("ECC CALC_ECC: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
|
- calc_ecc[0], calc_ecc[1], calc_ecc[2],
|
|
|
- calc_ecc[3], calc_ecc[4], calc_ecc[5],
|
|
|
- calc_ecc[6]);
|
|
|
-
|
|
|
- ret = -EBADMSG;
|
|
|
- if (block0 >= DOC_LAYOUT_BLOCK_FIRST_DATA) {
|
|
|
- if ((eccconf1 & DOC_ECCCONF1_BCH_SYNDROM_ERR) &&
|
|
|
- (eccconf1 & DOC_ECCCONF1_PAGE_IS_WRITTEN))
|
|
|
- goto err_in_read;
|
|
|
- if (is_prot_seq_error(docg3))
|
|
|
- goto err_in_read;
|
|
|
+ doc_dbg("ECC HW_ECC: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
|
+ hwecc[0], hwecc[1], hwecc[2], hwecc[3], hwecc[4],
|
|
|
+ hwecc[5], hwecc[6]);
|
|
|
+
|
|
|
+ ret = -EIO;
|
|
|
+ if (is_prot_seq_error(docg3))
|
|
|
+ goto err_in_read;
|
|
|
+ ret = 0;
|
|
|
+ if ((block0 >= DOC_LAYOUT_BLOCK_FIRST_DATA) &&
|
|
|
+ (eccconf1 & DOC_ECCCONF1_BCH_SYNDROM_ERR) &&
|
|
|
+ (eccconf1 & DOC_ECCCONF1_PAGE_IS_WRITTEN) &&
|
|
|
+ (ops->mode != MTD_OPS_RAW) &&
|
|
|
+ (nbdata == DOC_LAYOUT_PAGE_SIZE)) {
|
|
|
+ ret = doc_ecc_bch_fix_data(docg3, buf, hwecc);
|
|
|
+ if (ret < 0) {
|
|
|
+ mtd->ecc_stats.failed++;
|
|
|
+ ret = -EBADMSG;
|
|
|
+ }
|
|
|
+ if (ret > 0) {
|
|
|
+ mtd->ecc_stats.corrected += ret;
|
|
|
+ ret = -EUCLEAN;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
doc_read_page_finish(docg3);
|
|
@@ -849,7 +913,7 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
from += DOC_LAYOUT_PAGE_SIZE;
|
|
|
}
|
|
|
|
|
|
- return 0;
|
|
|
+ return ret;
|
|
|
err_in_read:
|
|
|
doc_read_page_finish(docg3);
|
|
|
err:
|
|
@@ -1158,7 +1222,7 @@ static int doc_write_page(struct docg3 *docg3, loff_t to, const u_char *buf,
|
|
|
if (ret)
|
|
|
goto err;
|
|
|
|
|
|
- doc_write_page_ecc_init(docg3, DOC_ECC_BCH_COVERED_BYTES);
|
|
|
+ doc_write_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES);
|
|
|
doc_delay(docg3, 2);
|
|
|
doc_write_page_putbytes(docg3, DOC_LAYOUT_PAGE_SIZE, buf);
|
|
|
|
|
@@ -1721,7 +1785,11 @@ static int __init docg3_probe(struct platform_device *pdev)
|
|
|
docg3_floors = kzalloc(sizeof(*docg3_floors) * DOC_MAX_NBFLOORS,
|
|
|
GFP_KERNEL);
|
|
|
if (!docg3_floors)
|
|
|
- goto nomem;
|
|
|
+ goto nomem1;
|
|
|
+ docg3_bch = init_bch(DOC_ECC_BCH_M, DOC_ECC_BCH_T,
|
|
|
+ DOC_ECC_BCH_PRIMPOLY);
|
|
|
+ if (!docg3_bch)
|
|
|
+ goto nomem2;
|
|
|
|
|
|
ret = 0;
|
|
|
for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) {
|
|
@@ -1751,10 +1819,13 @@ notfound:
|
|
|
ret = -ENODEV;
|
|
|
dev_info(dev, "No supported DiskOnChip found\n");
|
|
|
err_probe:
|
|
|
+ free_bch(docg3_bch);
|
|
|
for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++)
|
|
|
if (docg3_floors[floor])
|
|
|
doc_release_device(docg3_floors[floor]);
|
|
|
-nomem:
|
|
|
+nomem2:
|
|
|
+ kfree(docg3_floors);
|
|
|
+nomem1:
|
|
|
iounmap(base);
|
|
|
noress:
|
|
|
return ret;
|
|
@@ -1779,6 +1850,7 @@ static int __exit docg3_release(struct platform_device *pdev)
|
|
|
doc_release_device(docg3_floors[floor]);
|
|
|
|
|
|
kfree(docg3_floors);
|
|
|
+ free_bch(docg3_bch);
|
|
|
iounmap(base);
|
|
|
return 0;
|
|
|
}
|