|
@@ -111,7 +111,132 @@ static inline bool gpmi_check_ecc(struct gpmi_nand_data *this)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-int common_nfc_set_geometry(struct gpmi_nand_data *this)
|
|
|
+/*
|
|
|
+ * If we can get the ECC information from the nand chip, we do not
|
|
|
+ * need to calculate them ourselves.
|
|
|
+ *
|
|
|
+ * We may have available oob space in this case.
|
|
|
+ */
|
|
|
+static bool set_geometry_by_ecc_info(struct gpmi_nand_data *this)
|
|
|
+{
|
|
|
+ struct bch_geometry *geo = &this->bch_geometry;
|
|
|
+ struct mtd_info *mtd = &this->mtd;
|
|
|
+ struct nand_chip *chip = mtd->priv;
|
|
|
+ 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))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ switch (chip->ecc_step_ds) {
|
|
|
+ case SZ_512:
|
|
|
+ geo->gf_len = 13;
|
|
|
+ break;
|
|
|
+ case SZ_1K:
|
|
|
+ geo->gf_len = 14;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ dev_err(this->dev,
|
|
|
+ "unsupported nand chip. ecc bits : %d, ecc size : %d\n",
|
|
|
+ chip->ecc_strength_ds, chip->ecc_step_ds);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ geo->ecc_chunk_size = chip->ecc_step_ds;
|
|
|
+ geo->ecc_strength = round_up(chip->ecc_strength_ds, 2);
|
|
|
+ if (!gpmi_check_ecc(this))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ /* Keep the C >= O */
|
|
|
+ if (geo->ecc_chunk_size < mtd->oobsize) {
|
|
|
+ dev_err(this->dev,
|
|
|
+ "unsupported nand chip. ecc size: %d, oob size : %d\n",
|
|
|
+ chip->ecc_step_ds, mtd->oobsize);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The default value, see comment in the legacy_set_geometry(). */
|
|
|
+ geo->metadata_size = 10;
|
|
|
+
|
|
|
+ geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now, the NAND chip with 2K page(data chunk is 512byte) shows below:
|
|
|
+ *
|
|
|
+ * | P |
|
|
|
+ * |<----------------------------------------------------->|
|
|
|
+ * | |
|
|
|
+ * | (Block Mark) |
|
|
|
+ * | P' | | | |
|
|
|
+ * |<-------------------------------------------->| D | | O' |
|
|
|
+ * | |<---->| |<--->|
|
|
|
+ * V V V V V
|
|
|
+ * +---+----------+-+----------+-+----------+-+----------+-+-----+
|
|
|
+ * | M | data |E| data |E| data |E| data |E| |
|
|
|
+ * +---+----------+-+----------+-+----------+-+----------+-+-----+
|
|
|
+ * ^ ^
|
|
|
+ * | O |
|
|
|
+ * |<------------>|
|
|
|
+ * | |
|
|
|
+ *
|
|
|
+ * P : the page size for BCH module.
|
|
|
+ * E : The ECC strength.
|
|
|
+ * G : the length of Galois Field.
|
|
|
+ * N : The chunk count of per page.
|
|
|
+ * M : the metasize of per page.
|
|
|
+ * C : the ecc chunk size, aka the "data" above.
|
|
|
+ * P': the nand chip's page size.
|
|
|
+ * O : the nand chip's oob size.
|
|
|
+ * O': the free oob.
|
|
|
+ *
|
|
|
+ * The formula for P is :
|
|
|
+ *
|
|
|
+ * E * G * N
|
|
|
+ * P = ------------ + P' + M
|
|
|
+ * 8
|
|
|
+ *
|
|
|
+ * The position of block mark moves forward in the ECC-based view
|
|
|
+ * of page, and the delta is:
|
|
|
+ *
|
|
|
+ * E * G * (N - 1)
|
|
|
+ * D = (---------------- + M)
|
|
|
+ * 8
|
|
|
+ *
|
|
|
+ * Please see the comment in legacy_set_geometry().
|
|
|
+ * With the condition C >= O , we still can get same result.
|
|
|
+ * So the bit position of the physical block mark within the ECC-based
|
|
|
+ * view of the page is :
|
|
|
+ * (P' - D) * 8
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ mtd->oobavail = gpmi_hw_ecclayout.oobavail = of->length;
|
|
|
+ }
|
|
|
+
|
|
|
+ geo->payload_size = mtd->writesize;
|
|
|
+
|
|
|
+ geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4);
|
|
|
+ geo->auxiliary_size = ALIGN(geo->metadata_size, 4)
|
|
|
+ + ALIGN(geo->ecc_chunk_count, 4);
|
|
|
+
|
|
|
+ if (!this->swap_block_mark)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ /* For bit swap. */
|
|
|
+ block_mark_bit_offset = mtd->writesize * 8 -
|
|
|
+ (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - 1)
|
|
|
+ + geo->metadata_size * 8);
|
|
|
+
|
|
|
+ geo->block_mark_byte_offset = block_mark_bit_offset / 8;
|
|
|
+ geo->block_mark_bit_offset = block_mark_bit_offset % 8;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static int legacy_set_geometry(struct gpmi_nand_data *this)
|
|
|
{
|
|
|
struct bch_geometry *geo = &this->bch_geometry;
|
|
|
struct mtd_info *mtd = &this->mtd;
|
|
@@ -223,6 +348,11 @@ int common_nfc_set_geometry(struct gpmi_nand_data *this)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+int common_nfc_set_geometry(struct gpmi_nand_data *this)
|
|
|
+{
|
|
|
+ return set_geometry_by_ecc_info(this) ? 0 : legacy_set_geometry(this);
|
|
|
+}
|
|
|
+
|
|
|
struct dma_chan *get_dma_chan(struct gpmi_nand_data *this)
|
|
|
{
|
|
|
int chipnr = this->current_chip;
|