[RFC,v2 2/4] mtd: ecc: realize Mediatek HW ECC driver

Xiangsheng Hou xiangsheng.hou at mediatek.com
Sun Sep 26 22:42:03 PDT 2021


The v2 driver export some function for snfi driver in spi
subsystem to use, include nand parameter(page/spare size)
and ecc status(enable/disable).Also need empty page check
function export from snfi driver.

Signed-off-by: Xiangsheng Hou <xiangsheng.hou at mediatek.com>
---
 drivers/mtd/nand/core.c    |   2 +-
 drivers/mtd/nand/ecc.c     |  19 +++
 drivers/mtd/nand/mtk_ecc.c | 315 +++++++++++++++++++++++++++++++++++++
 include/linux/mtd/nand.h   |  12 ++
 4 files changed, 347 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 5e13a03d2b32..3db410de3ba2 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -232,7 +232,7 @@ static int nanddev_get_ecc_engine(struct nand_device *nand)
 		nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
 		break;
 	case NAND_ECC_ENGINE_TYPE_ON_HOST:
-		pr_err("On-host hardware ECC engines not supported yet\n");
+		nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
 		break;
 	default:
 		pr_err("Missing ECC engine type\n");
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 6c43dfda01d4..b334cd88c038 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -380,6 +380,7 @@ static const char * const nand_ecc_algos[] = {
 	[NAND_ECC_ALGO_HAMMING] = "hamming",
 	[NAND_ECC_ALGO_BCH] = "bch",
 	[NAND_ECC_ALGO_RS] = "rs",
+	[NAND_ECC_ALGO_MTK_HWECC] = "ecc-mtk",
 };
 
 static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np)
@@ -611,6 +612,24 @@ struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand)
 }
 EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine);
 
+struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
+{
+	unsigned int algo = nand->ecc.user_conf.algo;
+
+	if (algo == NAND_ECC_ALGO_UNKNOWN)
+		algo = nand->ecc.defaults.algo;
+
+	switch (algo) {
+	case NAND_ECC_ALGO_MTK_HWECC:
+		return mtk_nand_ecc_get_engine();
+	default:
+		break;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine);
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Miquel Raynal <miquel.raynal at bootlin.com>");
 MODULE_DESCRIPTION("Generic ECC engine");
diff --git a/drivers/mtd/nand/mtk_ecc.c b/drivers/mtd/nand/mtk_ecc.c
index ce0f8b491e5d..33c660f094f3 100644
--- a/drivers/mtd/nand/mtk_ecc.c
+++ b/drivers/mtd/nand/mtk_ecc.c
@@ -61,9 +61,36 @@ struct mtk_ecc {
 	struct mutex lock;
 	u32 sectors;
 
+	u32 page_size;
+	u32 spare_size_per_sector;
+	u32 spare_size_idx;
+	u32 fdm_size;
+	u32 sector_size;
+	bool ecc_en;
+
 	u8 *eccdata;
 };
 
+struct mtk_ecc_bad_mark_ctl {
+	void (*bm_swap)(struct nand_device *, u8 *databuf, u8* oobbuf);
+	u32 sec;
+	u32 pos;
+};
+
+struct mtk_ecc_conf {
+	struct nand_ecc_req_tweak_ctx req_ctx;
+	unsigned int code_size;
+	unsigned int nsteps;
+
+	u8 *spare_databuf;
+	u8 *code_buf;
+	u8 *oob_buf;
+
+	struct mtk_ecc *ecc;
+	struct mtk_ecc_config ecc_cfg;
+	struct mtk_ecc_bad_mark_ctl bad_mark;
+};
+
 /* ecc strength that each IP supports */
 static const u8 ecc_strength_mt2701[] = {
 	4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36,
@@ -79,6 +106,11 @@ static const u8 ecc_strength_mt7622[] = {
 	4, 6, 8, 10, 12, 14, 16
 };
 
+/* supported spare size of each IP */
+static const u8 spare_size_mt7622[] = {
+	16, 26, 27, 28
+};
+
 enum mtk_ecc_regs {
 	ECC_ENCPAR00,
 	ECC_ENCIRQ_EN,
@@ -447,6 +479,289 @@ unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc)
 }
 EXPORT_SYMBOL(mtk_ecc_get_parity_bits);
 
+u32 mtk_ecc_get_pagesize(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->page_size;
+}
+EXPORT_SYMBOL(mtk_ecc_get_pagesize);
+
+u32 mtk_ecc_get_sparesize(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->spare_size_per_sector;
+}
+EXPORT_SYMBOL(mtk_ecc_get_sparesize);
+
+u32 mtk_ecc_get_spare_idx(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->spare_size_idx;
+}
+EXPORT_SYMBOL(mtk_ecc_get_spare_idx);
+
+u32 mtk_ecc_get_fdmsize(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->fdm_size;
+}
+EXPORT_SYMBOL(mtk_ecc_get_fdmsize);
+
+u32 mtk_ecc_get_sectorsize(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->sector_size;
+}
+EXPORT_SYMBOL(mtk_ecc_get_sectorsize);
+
+bool mtk_ecc_get_status(struct device_node *dn)
+{
+	struct mtk_ecc *ecc = NULL;
+	struct device_node *child_np;
+
+	child_np = of_get_compatible_child(dn, "spi-nand");
+
+	ecc = of_mtk_ecc_get(child_np);
+
+	return ecc->ecc_en;
+}
+EXPORT_SYMBOL(mtk_ecc_get_status);
+
+int mtk_ecc_prepare_io_req(struct nand_device *nand,
+					  struct nand_page_io_req *req)
+{
+	struct mtk_ecc_conf *engine_conf = nand->ecc.ctx.priv;
+	int ret = 0;
+	u32 val;
+
+	nand_ecc_tweak_req(&engine_conf->req_ctx, req);
+
+	if (req->mode == MTD_OPS_RAW) {
+		/* set ecc status to disable */
+		engine_conf->ecc->ecc_en = false;
+		if (req->type == NAND_PAGE_WRITE) {
+			/*
+			 * format data and oob buf to Mediatek nand flash
+			 * data format
+			 */
+			mtk_ecc_format_page();
+		}
+	} else {
+		/* set ecc status to enable */
+		engine_conf->ecc->ecc_en = true;
+		engine_conf->ecc_cfg.op = ECC_DECODE;
+		if (req->type == NAND_PAGE_WRITE) {
+			/*
+			 * format oob buf
+			 * 1) set data bytes according to mtd ooblayout
+			 * 2) bad mark swap to ensure badmark position
+			 * consistent with nand device spec
+			 */
+			mtd_ooblayout_set_databytes();
+			engine_conf->bad_mark.bm_swap();
+
+			engine_conf->ecc_cfg.op = ECC_ENCODE;
+		}
+
+		ret = mtk_ecc_enable(engine_conf->ecc, &engine_conf->ecc_cfg);
+	}
+
+	return ret;
+}
+
+int mtk_ecc_finish_io_req(struct nand_device *nand,
+					  struct nand_page_io_req *req)
+{
+	struct mtk_ecc_conf *engine_conf = nand->ecc.ctx.priv;
+	struct device_node *dn = nanddev_get_of_node(nand);
+	struct mtd_info *mtd = nanddev_to_mtd(nand);
+	u8 *spare_databuf = engine_conf->spare_databuf;
+	int ret;
+
+	if (req->type == NAND_PAGE_WRITE) {
+		if (req->mode != MTD_OPS_RAW)
+			mtk_ecc_disable(engine_conf->ecc);
+
+		nand_ecc_restore_req(&engine_conf->req_ctx, req);
+
+		return 0;
+	}
+
+	if (req->mode == MTD_OPS_RAW) {
+		/* format data and oob buf from Mediatek nand flash data format */
+		mtk_ecc_format_page();
+		nand_ecc_restore_req(&engine_conf->req_ctx, req);
+		return 0;
+	}
+
+	/* set ecc status to default disable */
+	engine_conf->ecc->ecc_en = false;
+	ret = mtk_ecc_wait_done(engine_conf->ecc, ECC_DECODE);
+	if (ret)
+		return -ETIMEDOUT;
+
+	/* check whether read empty by check nfi export function */
+	ret = mtk_snfi_check_empty(dn);
+	if (ret) {
+		memset(req->databuf.in, 0xff, mtd->writesize);
+		memset(req->oobbuf.in, 0xff, mtd->oobsize);
+		ret = 0;
+	} else {
+		/* check the bitflips or uncorrect error */
+		ret = mtk_ecc_update_status(nand, req);
+
+		/*
+		 * format oob buf
+		 * 1) bad mark swap
+		 * 2) get data bytes according to mtd ooblayout
+		 */
+		engine_conf->bad_mark.bm_swap();
+		mtd_ooblayout_get_databytes();
+	}
+
+	mtk_ecc_disable(engine_conf->ecc);
+	nand_ecc_restore_req(&engine_conf->req_ctx, req);
+
+	return ret;
+}
+
+void mtk_ecc_cleanup_ctx(struct nand_device *nand)
+{
+	struct mtk_ecc_conf *engine_conf = nand->ecc.ctx.priv;
+
+	if (engine_conf) {
+		kfree(engine_conf->ecc);
+		kfree(engine_conf);
+	}
+}
+
+
+static void mtk_ecc_set_bad_mark_ctl(struct mtk_ecc_bad_mark_ctl *bm_ctl,
+				     struct mtd_info *mtd)
+{
+	/* todo */
+}
+
+/* calcute spare size per sector according to nand parameter */
+static int mtk_ecc_set_spare_per_sector(struct nand_device *nand,
+			u32 *sps, u32 *idx)
+{
+	/* todo */
+}
+
+static int mtk_ecc_config_init(struct nand_device *nand)
+{
+	struct mtk_ecc_conf *engine_conf = nand->ecc.ctx.priv;
+	struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
+	struct mtd_info *mtd = nanddev_to_mtd(nand);
+	u32 sps, idx;
+
+	conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
+	conf->algo = NAND_ECC_ALGO_MTK_HWECC;
+	conf->step_size = nand->ecc.user_conf.step_size;
+	conf->strength = nand->ecc.user_conf.strength;
+
+	mtk_ecc_set_spare_per_sector(nand, &sps, &idx);
+
+	/* calculate ecc strength per sector */
+	mtk_ecc_adjust_strength(engine_conf->ecc, &conf->strength);
+
+	/*
+	 * set ecc sector size, spare per sector and spare idx
+	 * in nfi cnfg which will be get by snfi driver
+	 */
+	engine_conf->ecc->sector_size = conf->step_size;
+	engine_conf->ecc->spare_size_per_sector = *sps;
+	engine_conf->ecc->spare_size_idx = idx;
+
+	return 0;
+}
+
+int mtk_ecc_init_ctx(struct nand_device *nand)
+{
+	struct device_node *dn = nanddev_get_of_node(nand);
+	struct mtk_ecc_conf *engine_conf;
+	int ret;
+
+	engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL);
+	engine_conf->ecc = of_mtk_ecc_get(dn);
+
+	mtd_set_ooblayout();
+
+	nand->ecc.ctx.priv = engine_conf;
+	ret = mtk_ecc_config_init(nand);
+	if (ret)
+		goto free_engine_conf;
+
+	/*
+	 * the snfi driver need get nand parameter
+	 * when work in ECC nfi mode
+	 */
+	engine_conf->ecc->page_size = nand->memorg.pagesize;
+	engine_conf->ecc->spare_size = nand->memorg.oobsize;
+
+	/*
+	 * bad mark setting to ensure the badmark position
+	 * in Mediatek nand flash data format consistent
+	 * with nand device spec
+	 */
+	mtk_ecc_set_bad_mark_ctl();
+
+	return 0;
+
+free_engine_conf:
+	kfree(engine_conf);
+
+	return ret;
+}
+
+static struct nand_ecc_engine_ops mtk_nand_ecc_engine_ops = {
+	.init_ctx = mtk_ecc_init_ctx,
+	.cleanup_ctx = mtk_ecc_cleanup_ctx,
+	.prepare_io_req = mtk_ecc_prepare_io_req,
+	.finish_io_req = mtk_ecc_finish_io_req,
+};
+
+static struct nand_ecc_engine mtk_nand_ecc_engine = {
+	.ops = &mtk_nand_ecc_engine_ops,
+};
+
+struct nand_ecc_engine *mtk_nand_ecc_get_engine(void)
+{
+	return &mtk_nand_ecc_engine;
+}
+EXPORT_SYMBOL(mtk_nand_ecc_get_engine);
+
 static const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = {
 	.err_mask = 0x3f,
 	.ecc_strength = ecc_strength_mt2701,
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 32fc7edf65b3..fccca85f34ea 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -167,12 +167,14 @@ enum nand_ecc_placement {
  * @NAND_ECC_ALGO_HAMMING: Hamming algorithm
  * @NAND_ECC_ALGO_BCH: Bose-Chaudhuri-Hocquenghem algorithm
  * @NAND_ECC_ALGO_RS: Reed-Solomon algorithm
+ * @NAND_ECC_ALGO_MTK: Mediatek on-host HW BCH algorithm
  */
 enum nand_ecc_algo {
 	NAND_ECC_ALGO_UNKNOWN,
 	NAND_ECC_ALGO_HAMMING,
 	NAND_ECC_ALGO_BCH,
 	NAND_ECC_ALGO_RS,
+	NAND_ECC_ALGO_MTK_HWECC,
 };
 
 /**
@@ -281,6 +283,7 @@ int nand_ecc_finish_io_req(struct nand_device *nand,
 bool nand_ecc_is_strong_enough(struct nand_device *nand);
 struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand);
 struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand);
+struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand);
 
 #if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING)
 struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void);
@@ -300,6 +303,15 @@ static inline struct nand_ecc_engine *nand_ecc_sw_bch_get_engine(void)
 }
 #endif /* CONFIG_MTD_NAND_ECC_SW_BCH */
 
+#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MTK)
+struct nand_ecc_engine *mtk_nand_ecc_get_engine(void);
+#else
+static inline struct nand_ecc_engine *mtk_nand_ecc_get_engine(void)
+{
+	return NULL;
+}
+#endif /* CONFIG_MTD_NAND_ECC_MTK */
+
 /**
  * struct nand_ecc_req_tweak_ctx - Help for automatically tweaking requests
  * @orig_req: Pointer to the original IO request
-- 
2.25.1




More information about the linux-mtd mailing list