diff options
Diffstat (limited to 'drivers/mtd/nand/gpmi-nand/gpmi-nand.c')
-rw-r--r-- | drivers/mtd/nand/gpmi-nand/gpmi-nand.c | 161 |
1 files changed, 129 insertions, 32 deletions
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c index 8122c699ccf2..6e461560c6a8 100644 --- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c @@ -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; |