Merge branch 'omap-for-v4.8/legacy' into for-next
[deliverable/linux.git] / drivers / mtd / nand / gpmi-nand / gpmi-nand.c
index 8122c699ccf20895e400b8ae9246b39db00b1f00..6e461560c6a8eff68f7b0698c73e332c8f6bf28a 100644 (file)
@@ -25,7 +25,6 @@
 #include <linux/mtd/partitions.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
-#include <linux/of_mtd.h>
 #include "gpmi-nand.h"
 #include "bch-regs.h"
 
@@ -47,10 +46,44 @@ static struct nand_bbt_descr gpmi_bbt_descr = {
  * We may change the layout if we can get the ECC info from the datasheet,
  * else we will use all the (page + OOB).
  */
-static struct nand_ecclayout gpmi_hw_ecclayout = {
-       .eccbytes = 0,
-       .eccpos = { 0, },
-       .oobfree = { {.offset = 0, .length = 0} }
+static int gpmi_ooblayout_ecc(struct mtd_info *mtd, int section,
+                             struct mtd_oob_region *oobregion)
+{
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct gpmi_nand_data *this = nand_get_controller_data(chip);
+       struct bch_geometry *geo = &this->bch_geometry;
+
+       if (section)
+               return -ERANGE;
+
+       oobregion->offset = 0;
+       oobregion->length = geo->page_size - mtd->writesize;
+
+       return 0;
+}
+
+static int gpmi_ooblayout_free(struct mtd_info *mtd, int section,
+                              struct mtd_oob_region *oobregion)
+{
+       struct nand_chip *chip = mtd_to_nand(mtd);
+       struct gpmi_nand_data *this = nand_get_controller_data(chip);
+       struct bch_geometry *geo = &this->bch_geometry;
+
+       if (section)
+               return -ERANGE;
+
+       /* The available oob size we have. */
+       if (geo->page_size < mtd->writesize + mtd->oobsize) {
+               oobregion->offset = geo->page_size - mtd->writesize;
+               oobregion->length = mtd->oobsize - oobregion->offset;
+       }
+
+       return 0;
+}
+
+static const struct mtd_ooblayout_ops gpmi_ooblayout_ops = {
+       .ecc = gpmi_ooblayout_ecc,
+       .free = gpmi_ooblayout_free,
 };
 
 static const struct gpmi_devdata gpmi_devdata_imx23 = {
@@ -141,7 +174,6 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this)
        struct bch_geometry *geo = &this->bch_geometry;
        struct nand_chip *chip = &this->nand;
        struct mtd_info *mtd = nand_to_mtd(chip);
-       struct nand_oobfree *of = gpmi_hw_ecclayout.oobfree;
        unsigned int block_mark_bit_offset;
 
        if (!(chip->ecc_strength_ds > 0 && chip->ecc_step_ds > 0))
@@ -229,12 +261,6 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this)
        geo->page_size = mtd->writesize + geo->metadata_size +
                (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8;
 
-       /* The available oob size we have. */
-       if (geo->page_size < mtd->writesize + mtd->oobsize) {
-               of->offset = geo->page_size - mtd->writesize;
-               of->length = mtd->oobsize - of->offset;
-       }
-
        geo->payload_size = mtd->writesize;
 
        geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4);
@@ -797,6 +823,7 @@ static void gpmi_free_dma_buffer(struct gpmi_nand_data *this)
 
        this->cmd_buffer        = NULL;
        this->data_buffer_dma   = NULL;
+       this->raw_buffer        = NULL;
        this->page_buffer_virt  = NULL;
        this->page_buffer_size  =  0;
 }
@@ -1037,14 +1064,87 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
        /* Loop over status bytes, accumulating ECC status. */
        status = auxiliary_virt + nfc_geo->auxiliary_status_offset;
 
+       read_page_swap_end(this, buf, nfc_geo->payload_size,
+                          this->payload_virt, this->payload_phys,
+                          nfc_geo->payload_size,
+                          payload_virt, payload_phys);
+
        for (i = 0; i < nfc_geo->ecc_chunk_count; i++, status++) {
                if ((*status == STATUS_GOOD) || (*status == STATUS_ERASED))
                        continue;
 
                if (*status == STATUS_UNCORRECTABLE) {
+                       int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len;
+                       u8 *eccbuf = this->raw_buffer;
+                       int offset, bitoffset;
+                       int eccbytes;
+                       int flips;
+
+                       /* Read ECC bytes into our internal raw_buffer */
+                       offset = nfc_geo->metadata_size * 8;
+                       offset += ((8 * nfc_geo->ecc_chunk_size) + eccbits) * (i + 1);
+                       offset -= eccbits;
+                       bitoffset = offset % 8;
+                       eccbytes = DIV_ROUND_UP(offset + eccbits, 8);
+                       offset /= 8;
+                       eccbytes -= offset;
+                       chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+                       chip->read_buf(mtd, eccbuf, eccbytes);
+
+                       /*
+                        * ECC data are not byte aligned and we may have
+                        * in-band data in the first and last byte of
+                        * eccbuf. Set non-eccbits to one so that
+                        * nand_check_erased_ecc_chunk() does not count them
+                        * as bitflips.
+                        */
+                       if (bitoffset)
+                               eccbuf[0] |= GENMASK(bitoffset - 1, 0);
+
+                       bitoffset = (bitoffset + eccbits) % 8;
+                       if (bitoffset)
+                               eccbuf[eccbytes - 1] |= GENMASK(7, bitoffset);
+
+                       /*
+                        * The ECC hardware has an uncorrectable ECC status
+                        * code in case we have bitflips in an erased page. As
+                        * nothing was written into this subpage the ECC is
+                        * obviously wrong and we can not trust it. We assume
+                        * at this point that we are reading an erased page and
+                        * try to correct the bitflips in buffer up to
+                        * ecc_strength bitflips. If this is a page with random
+                        * data, we exceed this number of bitflips and have a
+                        * ECC failure. Otherwise we use the corrected buffer.
+                        */
+                       if (i == 0) {
+                               /* The first block includes metadata */
+                               flips = nand_check_erased_ecc_chunk(
+                                               buf + i * nfc_geo->ecc_chunk_size,
+                                               nfc_geo->ecc_chunk_size,
+                                               eccbuf, eccbytes,
+                                               auxiliary_virt,
+                                               nfc_geo->metadata_size,
+                                               nfc_geo->ecc_strength);
+                       } else {
+                               flips = nand_check_erased_ecc_chunk(
+                                               buf + i * nfc_geo->ecc_chunk_size,
+                                               nfc_geo->ecc_chunk_size,
+                                               eccbuf, eccbytes,
+                                               NULL, 0,
+                                               nfc_geo->ecc_strength);
+                       }
+
+                       if (flips > 0) {
+                               max_bitflips = max_t(unsigned int, max_bitflips,
+                                                    flips);
+                               mtd->ecc_stats.corrected += flips;
+                               continue;
+                       }
+
                        mtd->ecc_stats.failed++;
                        continue;
                }
+
                mtd->ecc_stats.corrected += *status;
                max_bitflips = max_t(unsigned int, max_bitflips, *status);
        }
@@ -1064,11 +1164,6 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
                chip->oob_poi[0] = ((uint8_t *) auxiliary_virt)[0];
        }
 
-       read_page_swap_end(this, buf, nfc_geo->payload_size,
-                       this->payload_virt, this->payload_phys,
-                       nfc_geo->payload_size,
-                       payload_virt, payload_phys);
-
        return max_bitflips;
 }
 
@@ -1327,18 +1422,19 @@ static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
 static int
 gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
 {
-       struct nand_oobfree *of = mtd->ecclayout->oobfree;
+       struct mtd_oob_region of = { };
        int status = 0;
 
        /* Do we have available oob area? */
-       if (!of->length)
+       mtd_ooblayout_free(mtd, 0, &of);
+       if (!of.length)
                return -EPERM;
 
        if (!nand_is_slc(chip))
                return -EPERM;
 
-       chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of->offset, page);
-       chip->write_buf(mtd, chip->oob_poi + of->offset, of->length);
+       chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of.offset, page);
+       chip->write_buf(mtd, chip->oob_poi + of.offset, of.length);
        chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
 
        status = chip->waitfunc(mtd, chip);
@@ -1840,6 +1936,7 @@ static void gpmi_nand_exit(struct gpmi_nand_data *this)
 static int gpmi_init_last(struct gpmi_nand_data *this)
 {
        struct nand_chip *chip = &this->nand;
+       struct mtd_info *mtd = nand_to_mtd(chip);
        struct nand_ecc_ctrl *ecc = &chip->ecc;
        struct bch_geometry *bch_geo = &this->bch_geometry;
        int ret;
@@ -1861,7 +1958,7 @@ static int gpmi_init_last(struct gpmi_nand_data *this)
        ecc->mode       = NAND_ECC_HW;
        ecc->size       = bch_geo->ecc_chunk_size;
        ecc->strength   = bch_geo->ecc_strength;
-       ecc->layout     = &gpmi_hw_ecclayout;
+       mtd_set_ooblayout(mtd, &gpmi_ooblayout_ops);
 
        /*
         * We only enable the subpage read when:
@@ -1914,16 +2011,6 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
        /* Set up swap_block_mark, must be set before the gpmi_set_geometry() */
        this->swap_block_mark = !GPMI_IS_MX23(this);
 
-       if (of_get_nand_on_flash_bbt(this->dev->of_node)) {
-               chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
-
-               if (of_property_read_bool(this->dev->of_node,
-                                               "fsl,no-blockmark-swap"))
-                       this->swap_block_mark = false;
-       }
-       dev_dbg(this->dev, "Blockmark swapping %sabled\n",
-               this->swap_block_mark ? "en" : "dis");
-
        /*
         * Allocate a temporary DMA buffer for reading ID in the
         * nand_scan_ident().
@@ -1938,6 +2025,16 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
        if (ret)
                goto err_out;
 
+       if (chip->bbt_options & NAND_BBT_USE_FLASH) {
+               chip->bbt_options |= NAND_BBT_NO_OOB;
+
+               if (of_property_read_bool(this->dev->of_node,
+                                               "fsl,no-blockmark-swap"))
+                       this->swap_block_mark = false;
+       }
+       dev_dbg(this->dev, "Blockmark swapping %sabled\n",
+               this->swap_block_mark ? "en" : "dis");
+
        ret = gpmi_init_last(this);
        if (ret)
                goto err_out;
This page took 0.028447 seconds and 5 git commands to generate.