|
@@ -791,6 +791,7 @@ static void gpmi_free_dma_buffer(struct gpmi_nand_data *this)
|
|
|
this->page_buffer_phys);
|
|
|
kfree(this->cmd_buffer);
|
|
|
kfree(this->data_buffer_dma);
|
|
|
+ kfree(this->raw_buffer);
|
|
|
|
|
|
this->cmd_buffer = NULL;
|
|
|
this->data_buffer_dma = NULL;
|
|
@@ -837,6 +838,9 @@ static int gpmi_alloc_dma_buffer(struct gpmi_nand_data *this)
|
|
|
if (!this->page_buffer_virt)
|
|
|
goto error_alloc;
|
|
|
|
|
|
+ this->raw_buffer = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
|
|
|
+ if (!this->raw_buffer)
|
|
|
+ goto error_alloc;
|
|
|
|
|
|
/* Slice up the page buffer. */
|
|
|
this->payload_virt = this->page_buffer_virt;
|
|
@@ -1347,6 +1351,183 @@ gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page)
|
|
|
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * This function reads a NAND page without involving the ECC engine (no HW
|
|
|
+ * ECC correction).
|
|
|
+ * The tricky part in the GPMI/BCH controller is that it stores ECC bits
|
|
|
+ * inline (interleaved with payload DATA), and do not align data chunk on
|
|
|
+ * byte boundaries.
|
|
|
+ * We thus need to take care moving the payload data and ECC bits stored in the
|
|
|
+ * page into the provided buffers, which is why we're using gpmi_copy_bits.
|
|
|
+ *
|
|
|
+ * See set_geometry_by_ecc_info inline comments to have a full description
|
|
|
+ * of the layout used by the GPMI controller.
|
|
|
+ */
|
|
|
+static int gpmi_ecc_read_page_raw(struct mtd_info *mtd,
|
|
|
+ struct nand_chip *chip, uint8_t *buf,
|
|
|
+ int oob_required, int page)
|
|
|
+{
|
|
|
+ struct gpmi_nand_data *this = chip->priv;
|
|
|
+ struct bch_geometry *nfc_geo = &this->bch_geometry;
|
|
|
+ int eccsize = nfc_geo->ecc_chunk_size;
|
|
|
+ int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len;
|
|
|
+ u8 *tmp_buf = this->raw_buffer;
|
|
|
+ size_t src_bit_off;
|
|
|
+ size_t oob_bit_off;
|
|
|
+ size_t oob_byte_off;
|
|
|
+ uint8_t *oob = chip->oob_poi;
|
|
|
+ int step;
|
|
|
+
|
|
|
+ chip->read_buf(mtd, tmp_buf,
|
|
|
+ mtd->writesize + mtd->oobsize);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If required, swap the bad block marker and the data stored in the
|
|
|
+ * metadata section, so that we don't wrongly consider a block as bad.
|
|
|
+ *
|
|
|
+ * See the layout description for a detailed explanation on why this
|
|
|
+ * is needed.
|
|
|
+ */
|
|
|
+ if (this->swap_block_mark) {
|
|
|
+ u8 swap = tmp_buf[0];
|
|
|
+
|
|
|
+ tmp_buf[0] = tmp_buf[mtd->writesize];
|
|
|
+ tmp_buf[mtd->writesize] = swap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Copy the metadata section into the oob buffer (this section is
|
|
|
+ * guaranteed to be aligned on a byte boundary).
|
|
|
+ */
|
|
|
+ if (oob_required)
|
|
|
+ memcpy(oob, tmp_buf, nfc_geo->metadata_size);
|
|
|
+
|
|
|
+ oob_bit_off = nfc_geo->metadata_size * 8;
|
|
|
+ src_bit_off = oob_bit_off;
|
|
|
+
|
|
|
+ /* Extract interleaved payload data and ECC bits */
|
|
|
+ for (step = 0; step < nfc_geo->ecc_chunk_count; step++) {
|
|
|
+ if (buf)
|
|
|
+ gpmi_copy_bits(buf, step * eccsize * 8,
|
|
|
+ tmp_buf, src_bit_off,
|
|
|
+ eccsize * 8);
|
|
|
+ src_bit_off += eccsize * 8;
|
|
|
+
|
|
|
+ /* Align last ECC block to align a byte boundary */
|
|
|
+ if (step == nfc_geo->ecc_chunk_count - 1 &&
|
|
|
+ (oob_bit_off + eccbits) % 8)
|
|
|
+ eccbits += 8 - ((oob_bit_off + eccbits) % 8);
|
|
|
+
|
|
|
+ if (oob_required)
|
|
|
+ gpmi_copy_bits(oob, oob_bit_off,
|
|
|
+ tmp_buf, src_bit_off,
|
|
|
+ eccbits);
|
|
|
+
|
|
|
+ src_bit_off += eccbits;
|
|
|
+ oob_bit_off += eccbits;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (oob_required) {
|
|
|
+ oob_byte_off = oob_bit_off / 8;
|
|
|
+
|
|
|
+ if (oob_byte_off < mtd->oobsize)
|
|
|
+ memcpy(oob + oob_byte_off,
|
|
|
+ tmp_buf + mtd->writesize + oob_byte_off,
|
|
|
+ mtd->oobsize - oob_byte_off);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This function writes a NAND page without involving the ECC engine (no HW
|
|
|
+ * ECC generation).
|
|
|
+ * The tricky part in the GPMI/BCH controller is that it stores ECC bits
|
|
|
+ * inline (interleaved with payload DATA), and do not align data chunk on
|
|
|
+ * byte boundaries.
|
|
|
+ * We thus need to take care moving the OOB area at the right place in the
|
|
|
+ * final page, which is why we're using gpmi_copy_bits.
|
|
|
+ *
|
|
|
+ * See set_geometry_by_ecc_info inline comments to have a full description
|
|
|
+ * of the layout used by the GPMI controller.
|
|
|
+ */
|
|
|
+static int gpmi_ecc_write_page_raw(struct mtd_info *mtd,
|
|
|
+ struct nand_chip *chip,
|
|
|
+ const uint8_t *buf,
|
|
|
+ int oob_required)
|
|
|
+{
|
|
|
+ struct gpmi_nand_data *this = chip->priv;
|
|
|
+ struct bch_geometry *nfc_geo = &this->bch_geometry;
|
|
|
+ int eccsize = nfc_geo->ecc_chunk_size;
|
|
|
+ int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len;
|
|
|
+ u8 *tmp_buf = this->raw_buffer;
|
|
|
+ uint8_t *oob = chip->oob_poi;
|
|
|
+ size_t dst_bit_off;
|
|
|
+ size_t oob_bit_off;
|
|
|
+ size_t oob_byte_off;
|
|
|
+ int step;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Initialize all bits to 1 in case we don't have a buffer for the
|
|
|
+ * payload or oob data in order to leave unspecified bits of data
|
|
|
+ * to their initial state.
|
|
|
+ */
|
|
|
+ if (!buf || !oob_required)
|
|
|
+ memset(tmp_buf, 0xff, mtd->writesize + mtd->oobsize);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * First copy the metadata section (stored in oob buffer) at the
|
|
|
+ * beginning of the page, as imposed by the GPMI layout.
|
|
|
+ */
|
|
|
+ memcpy(tmp_buf, oob, nfc_geo->metadata_size);
|
|
|
+ oob_bit_off = nfc_geo->metadata_size * 8;
|
|
|
+ dst_bit_off = oob_bit_off;
|
|
|
+
|
|
|
+ /* Interleave payload data and ECC bits */
|
|
|
+ for (step = 0; step < nfc_geo->ecc_chunk_count; step++) {
|
|
|
+ if (buf)
|
|
|
+ gpmi_copy_bits(tmp_buf, dst_bit_off,
|
|
|
+ buf, step * eccsize * 8, eccsize * 8);
|
|
|
+ dst_bit_off += eccsize * 8;
|
|
|
+
|
|
|
+ /* Align last ECC block to align a byte boundary */
|
|
|
+ if (step == nfc_geo->ecc_chunk_count - 1 &&
|
|
|
+ (oob_bit_off + eccbits) % 8)
|
|
|
+ eccbits += 8 - ((oob_bit_off + eccbits) % 8);
|
|
|
+
|
|
|
+ if (oob_required)
|
|
|
+ gpmi_copy_bits(tmp_buf, dst_bit_off,
|
|
|
+ oob, oob_bit_off, eccbits);
|
|
|
+
|
|
|
+ dst_bit_off += eccbits;
|
|
|
+ oob_bit_off += eccbits;
|
|
|
+ }
|
|
|
+
|
|
|
+ oob_byte_off = oob_bit_off / 8;
|
|
|
+
|
|
|
+ if (oob_required && oob_byte_off < mtd->oobsize)
|
|
|
+ memcpy(tmp_buf + mtd->writesize + oob_byte_off,
|
|
|
+ oob + oob_byte_off, mtd->oobsize - oob_byte_off);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If required, swap the bad block marker and the first byte of the
|
|
|
+ * metadata section, so that we don't modify the bad block marker.
|
|
|
+ *
|
|
|
+ * See the layout description for a detailed explanation on why this
|
|
|
+ * is needed.
|
|
|
+ */
|
|
|
+ if (this->swap_block_mark) {
|
|
|
+ u8 swap = tmp_buf[0];
|
|
|
+
|
|
|
+ tmp_buf[0] = tmp_buf[mtd->writesize];
|
|
|
+ tmp_buf[mtd->writesize] = swap;
|
|
|
+ }
|
|
|
+
|
|
|
+ chip->write_buf(mtd, tmp_buf, mtd->writesize + mtd->oobsize);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
|
|
{
|
|
|
struct nand_chip *chip = mtd->priv;
|
|
@@ -1664,6 +1845,8 @@ static int gpmi_init_last(struct gpmi_nand_data *this)
|
|
|
ecc->write_page = gpmi_ecc_write_page;
|
|
|
ecc->read_oob = gpmi_ecc_read_oob;
|
|
|
ecc->write_oob = gpmi_ecc_write_oob;
|
|
|
+ ecc->read_page_raw = gpmi_ecc_read_page_raw;
|
|
|
+ ecc->write_page_raw = gpmi_ecc_write_page_raw;
|
|
|
ecc->mode = NAND_ECC_HW;
|
|
|
ecc->size = bch_geo->ecc_chunk_size;
|
|
|
ecc->strength = bch_geo->ecc_strength;
|