mtd: gpmi: set the BCH's geometry with the ecc info
authorHuang Shijie <b32955@freescale.com>
Fri, 17 May 2013 03:17:34 +0000 (11:17 +0800)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Fri, 30 Aug 2013 20:34:48 +0000 (21:34 +0100)
If the nand chip provides us the ECC info, we can use it firstly.
The set_geometry_by_ecc_info() will use the ECC info, and
calculate the parameters we need.

Rename the old code to legacy_set_geometry() which will takes effect
when there is no ECC info from the nand chip or we fails in the ECC info case.

Signed-off-by: Huang Shijie <b32955@freescale.com>
Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
drivers/mtd/nand/gpmi-nand/gpmi-nand.c

index 76d2f4142f2d21689e390f6d8dbd9c1dc9041a6f..2ab6b3fe76ba0fddd0c92a9efeb6565667331e9a 100644 (file)
@@ -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;