@@ -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");
@@ -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@bootlin.com>");
MODULE_DESCRIPTION("Generic ECC engine");
@@ -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,
@@ -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
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@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(-)