0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074
0075
0076
0077
0078
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096 #include <linux/module.h>
0097 #include <linux/mtd/nand.h>
0098 #include <linux/slab.h>
0099 #include <linux/of.h>
0100 #include <linux/of_device.h>
0101 #include <linux/of_platform.h>
0102
0103 static LIST_HEAD(on_host_hw_engines);
0104 static DEFINE_MUTEX(on_host_hw_engines_mutex);
0105
0106
0107
0108
0109
0110
0111
0112 int nand_ecc_init_ctx(struct nand_device *nand)
0113 {
0114 if (!nand->ecc.engine || !nand->ecc.engine->ops->init_ctx)
0115 return 0;
0116
0117 return nand->ecc.engine->ops->init_ctx(nand);
0118 }
0119 EXPORT_SYMBOL(nand_ecc_init_ctx);
0120
0121
0122
0123
0124
0125 void nand_ecc_cleanup_ctx(struct nand_device *nand)
0126 {
0127 if (nand->ecc.engine && nand->ecc.engine->ops->cleanup_ctx)
0128 nand->ecc.engine->ops->cleanup_ctx(nand);
0129 }
0130 EXPORT_SYMBOL(nand_ecc_cleanup_ctx);
0131
0132
0133
0134
0135
0136
0137 int nand_ecc_prepare_io_req(struct nand_device *nand,
0138 struct nand_page_io_req *req)
0139 {
0140 if (!nand->ecc.engine || !nand->ecc.engine->ops->prepare_io_req)
0141 return 0;
0142
0143 return nand->ecc.engine->ops->prepare_io_req(nand, req);
0144 }
0145 EXPORT_SYMBOL(nand_ecc_prepare_io_req);
0146
0147
0148
0149
0150
0151
0152 int nand_ecc_finish_io_req(struct nand_device *nand,
0153 struct nand_page_io_req *req)
0154 {
0155 if (!nand->ecc.engine || !nand->ecc.engine->ops->finish_io_req)
0156 return 0;
0157
0158 return nand->ecc.engine->ops->finish_io_req(nand, req);
0159 }
0160 EXPORT_SYMBOL(nand_ecc_finish_io_req);
0161
0162
0163 static int nand_ooblayout_ecc_sp(struct mtd_info *mtd, int section,
0164 struct mtd_oob_region *oobregion)
0165 {
0166 struct nand_device *nand = mtd_to_nanddev(mtd);
0167 unsigned int total_ecc_bytes = nand->ecc.ctx.total;
0168
0169 if (section > 1)
0170 return -ERANGE;
0171
0172 if (!section) {
0173 oobregion->offset = 0;
0174 if (mtd->oobsize == 16)
0175 oobregion->length = 4;
0176 else
0177 oobregion->length = 3;
0178 } else {
0179 if (mtd->oobsize == 8)
0180 return -ERANGE;
0181
0182 oobregion->offset = 6;
0183 oobregion->length = total_ecc_bytes - 4;
0184 }
0185
0186 return 0;
0187 }
0188
0189 static int nand_ooblayout_free_sp(struct mtd_info *mtd, int section,
0190 struct mtd_oob_region *oobregion)
0191 {
0192 if (section > 1)
0193 return -ERANGE;
0194
0195 if (mtd->oobsize == 16) {
0196 if (section)
0197 return -ERANGE;
0198
0199 oobregion->length = 8;
0200 oobregion->offset = 8;
0201 } else {
0202 oobregion->length = 2;
0203 if (!section)
0204 oobregion->offset = 3;
0205 else
0206 oobregion->offset = 6;
0207 }
0208
0209 return 0;
0210 }
0211
0212 static const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = {
0213 .ecc = nand_ooblayout_ecc_sp,
0214 .free = nand_ooblayout_free_sp,
0215 };
0216
0217 const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void)
0218 {
0219 return &nand_ooblayout_sp_ops;
0220 }
0221 EXPORT_SYMBOL_GPL(nand_get_small_page_ooblayout);
0222
0223 static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section,
0224 struct mtd_oob_region *oobregion)
0225 {
0226 struct nand_device *nand = mtd_to_nanddev(mtd);
0227 unsigned int total_ecc_bytes = nand->ecc.ctx.total;
0228
0229 if (section || !total_ecc_bytes)
0230 return -ERANGE;
0231
0232 oobregion->length = total_ecc_bytes;
0233 oobregion->offset = mtd->oobsize - oobregion->length;
0234
0235 return 0;
0236 }
0237
0238 static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section,
0239 struct mtd_oob_region *oobregion)
0240 {
0241 struct nand_device *nand = mtd_to_nanddev(mtd);
0242 unsigned int total_ecc_bytes = nand->ecc.ctx.total;
0243
0244 if (section)
0245 return -ERANGE;
0246
0247 oobregion->length = mtd->oobsize - total_ecc_bytes - 2;
0248 oobregion->offset = 2;
0249
0250 return 0;
0251 }
0252
0253 static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = {
0254 .ecc = nand_ooblayout_ecc_lp,
0255 .free = nand_ooblayout_free_lp,
0256 };
0257
0258 const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void)
0259 {
0260 return &nand_ooblayout_lp_ops;
0261 }
0262 EXPORT_SYMBOL_GPL(nand_get_large_page_ooblayout);
0263
0264
0265
0266
0267
0268 static int nand_ooblayout_ecc_lp_hamming(struct mtd_info *mtd, int section,
0269 struct mtd_oob_region *oobregion)
0270 {
0271 struct nand_device *nand = mtd_to_nanddev(mtd);
0272 unsigned int total_ecc_bytes = nand->ecc.ctx.total;
0273
0274 if (section)
0275 return -ERANGE;
0276
0277 switch (mtd->oobsize) {
0278 case 64:
0279 oobregion->offset = 40;
0280 break;
0281 case 128:
0282 oobregion->offset = 80;
0283 break;
0284 default:
0285 return -EINVAL;
0286 }
0287
0288 oobregion->length = total_ecc_bytes;
0289 if (oobregion->offset + oobregion->length > mtd->oobsize)
0290 return -ERANGE;
0291
0292 return 0;
0293 }
0294
0295 static int nand_ooblayout_free_lp_hamming(struct mtd_info *mtd, int section,
0296 struct mtd_oob_region *oobregion)
0297 {
0298 struct nand_device *nand = mtd_to_nanddev(mtd);
0299 unsigned int total_ecc_bytes = nand->ecc.ctx.total;
0300 int ecc_offset = 0;
0301
0302 if (section < 0 || section > 1)
0303 return -ERANGE;
0304
0305 switch (mtd->oobsize) {
0306 case 64:
0307 ecc_offset = 40;
0308 break;
0309 case 128:
0310 ecc_offset = 80;
0311 break;
0312 default:
0313 return -EINVAL;
0314 }
0315
0316 if (section == 0) {
0317 oobregion->offset = 2;
0318 oobregion->length = ecc_offset - 2;
0319 } else {
0320 oobregion->offset = ecc_offset + total_ecc_bytes;
0321 oobregion->length = mtd->oobsize - oobregion->offset;
0322 }
0323
0324 return 0;
0325 }
0326
0327 static const struct mtd_ooblayout_ops nand_ooblayout_lp_hamming_ops = {
0328 .ecc = nand_ooblayout_ecc_lp_hamming,
0329 .free = nand_ooblayout_free_lp_hamming,
0330 };
0331
0332 const struct mtd_ooblayout_ops *nand_get_large_page_hamming_ooblayout(void)
0333 {
0334 return &nand_ooblayout_lp_hamming_ops;
0335 }
0336 EXPORT_SYMBOL_GPL(nand_get_large_page_hamming_ooblayout);
0337
0338 static enum nand_ecc_engine_type
0339 of_get_nand_ecc_engine_type(struct device_node *np)
0340 {
0341 struct device_node *eng_np;
0342
0343 if (of_property_read_bool(np, "nand-no-ecc-engine"))
0344 return NAND_ECC_ENGINE_TYPE_NONE;
0345
0346 if (of_property_read_bool(np, "nand-use-soft-ecc-engine"))
0347 return NAND_ECC_ENGINE_TYPE_SOFT;
0348
0349 eng_np = of_parse_phandle(np, "nand-ecc-engine", 0);
0350 of_node_put(eng_np);
0351
0352 if (eng_np) {
0353 if (eng_np == np)
0354 return NAND_ECC_ENGINE_TYPE_ON_DIE;
0355 else
0356 return NAND_ECC_ENGINE_TYPE_ON_HOST;
0357 }
0358
0359 return NAND_ECC_ENGINE_TYPE_INVALID;
0360 }
0361
0362 static const char * const nand_ecc_placement[] = {
0363 [NAND_ECC_PLACEMENT_OOB] = "oob",
0364 [NAND_ECC_PLACEMENT_INTERLEAVED] = "interleaved",
0365 };
0366
0367 static enum nand_ecc_placement of_get_nand_ecc_placement(struct device_node *np)
0368 {
0369 enum nand_ecc_placement placement;
0370 const char *pm;
0371 int err;
0372
0373 err = of_property_read_string(np, "nand-ecc-placement", &pm);
0374 if (!err) {
0375 for (placement = NAND_ECC_PLACEMENT_OOB;
0376 placement < ARRAY_SIZE(nand_ecc_placement); placement++) {
0377 if (!strcasecmp(pm, nand_ecc_placement[placement]))
0378 return placement;
0379 }
0380 }
0381
0382 return NAND_ECC_PLACEMENT_UNKNOWN;
0383 }
0384
0385 static const char * const nand_ecc_algos[] = {
0386 [NAND_ECC_ALGO_HAMMING] = "hamming",
0387 [NAND_ECC_ALGO_BCH] = "bch",
0388 [NAND_ECC_ALGO_RS] = "rs",
0389 };
0390
0391 static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np)
0392 {
0393 enum nand_ecc_algo ecc_algo;
0394 const char *pm;
0395 int err;
0396
0397 err = of_property_read_string(np, "nand-ecc-algo", &pm);
0398 if (!err) {
0399 for (ecc_algo = NAND_ECC_ALGO_HAMMING;
0400 ecc_algo < ARRAY_SIZE(nand_ecc_algos);
0401 ecc_algo++) {
0402 if (!strcasecmp(pm, nand_ecc_algos[ecc_algo]))
0403 return ecc_algo;
0404 }
0405 }
0406
0407 return NAND_ECC_ALGO_UNKNOWN;
0408 }
0409
0410 static int of_get_nand_ecc_step_size(struct device_node *np)
0411 {
0412 int ret;
0413 u32 val;
0414
0415 ret = of_property_read_u32(np, "nand-ecc-step-size", &val);
0416 return ret ? ret : val;
0417 }
0418
0419 static int of_get_nand_ecc_strength(struct device_node *np)
0420 {
0421 int ret;
0422 u32 val;
0423
0424 ret = of_property_read_u32(np, "nand-ecc-strength", &val);
0425 return ret ? ret : val;
0426 }
0427
0428 void of_get_nand_ecc_user_config(struct nand_device *nand)
0429 {
0430 struct device_node *dn = nanddev_get_of_node(nand);
0431 int strength, size;
0432
0433 nand->ecc.user_conf.engine_type = of_get_nand_ecc_engine_type(dn);
0434 nand->ecc.user_conf.algo = of_get_nand_ecc_algo(dn);
0435 nand->ecc.user_conf.placement = of_get_nand_ecc_placement(dn);
0436
0437 strength = of_get_nand_ecc_strength(dn);
0438 if (strength >= 0)
0439 nand->ecc.user_conf.strength = strength;
0440
0441 size = of_get_nand_ecc_step_size(dn);
0442 if (size >= 0)
0443 nand->ecc.user_conf.step_size = size;
0444
0445 if (of_property_read_bool(dn, "nand-ecc-maximize"))
0446 nand->ecc.user_conf.flags |= NAND_ECC_MAXIMIZE_STRENGTH;
0447 }
0448 EXPORT_SYMBOL(of_get_nand_ecc_user_config);
0449
0450
0451
0452
0453
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463
0464
0465
0466
0467 bool nand_ecc_is_strong_enough(struct nand_device *nand)
0468 {
0469 const struct nand_ecc_props *reqs = nanddev_get_ecc_requirements(nand);
0470 const struct nand_ecc_props *conf = nanddev_get_ecc_conf(nand);
0471 struct mtd_info *mtd = nanddev_to_mtd(nand);
0472 int corr, ds_corr;
0473
0474 if (conf->step_size == 0 || reqs->step_size == 0)
0475
0476 return true;
0477
0478
0479
0480
0481
0482 corr = (mtd->writesize * conf->strength) / conf->step_size;
0483 ds_corr = (mtd->writesize * reqs->strength) / reqs->step_size;
0484
0485 return corr >= ds_corr && conf->strength >= reqs->strength;
0486 }
0487 EXPORT_SYMBOL(nand_ecc_is_strong_enough);
0488
0489
0490 int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx,
0491 struct nand_device *nand)
0492 {
0493 unsigned int total_buffer_size;
0494
0495 ctx->nand = nand;
0496
0497
0498 if (!ctx->page_buffer_size)
0499 ctx->page_buffer_size = nanddev_page_size(nand);
0500 if (!ctx->oob_buffer_size)
0501 ctx->oob_buffer_size = nanddev_per_page_oobsize(nand);
0502
0503 total_buffer_size = ctx->page_buffer_size + ctx->oob_buffer_size;
0504
0505 ctx->spare_databuf = kzalloc(total_buffer_size, GFP_KERNEL);
0506 if (!ctx->spare_databuf)
0507 return -ENOMEM;
0508
0509 ctx->spare_oobbuf = ctx->spare_databuf + ctx->page_buffer_size;
0510
0511 return 0;
0512 }
0513 EXPORT_SYMBOL_GPL(nand_ecc_init_req_tweaking);
0514
0515 void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx)
0516 {
0517 kfree(ctx->spare_databuf);
0518 }
0519 EXPORT_SYMBOL_GPL(nand_ecc_cleanup_req_tweaking);
0520
0521
0522
0523
0524
0525 void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx,
0526 struct nand_page_io_req *req)
0527 {
0528 struct nand_device *nand = ctx->nand;
0529 struct nand_page_io_req *orig, *tweak;
0530
0531
0532 ctx->orig_req = *req;
0533 ctx->bounce_data = false;
0534 ctx->bounce_oob = false;
0535 orig = &ctx->orig_req;
0536 tweak = req;
0537
0538
0539 if (orig->datalen < nanddev_page_size(nand)) {
0540 ctx->bounce_data = true;
0541 tweak->dataoffs = 0;
0542 tweak->datalen = nanddev_page_size(nand);
0543 tweak->databuf.in = ctx->spare_databuf;
0544 memset(tweak->databuf.in, 0xFF, ctx->page_buffer_size);
0545 }
0546
0547 if (orig->ooblen < nanddev_per_page_oobsize(nand)) {
0548 ctx->bounce_oob = true;
0549 tweak->ooboffs = 0;
0550 tweak->ooblen = nanddev_per_page_oobsize(nand);
0551 tweak->oobbuf.in = ctx->spare_oobbuf;
0552 memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size);
0553 }
0554
0555
0556 if (orig->type == NAND_PAGE_WRITE) {
0557 if (ctx->bounce_data)
0558 memcpy((void *)tweak->databuf.out + orig->dataoffs,
0559 orig->databuf.out, orig->datalen);
0560
0561 if (ctx->bounce_oob)
0562 memcpy((void *)tweak->oobbuf.out + orig->ooboffs,
0563 orig->oobbuf.out, orig->ooblen);
0564 }
0565 }
0566 EXPORT_SYMBOL_GPL(nand_ecc_tweak_req);
0567
0568 void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx,
0569 struct nand_page_io_req *req)
0570 {
0571 struct nand_page_io_req *orig, *tweak;
0572
0573 orig = &ctx->orig_req;
0574 tweak = req;
0575
0576
0577 if (orig->type == NAND_PAGE_READ) {
0578 if (ctx->bounce_data)
0579 memcpy(orig->databuf.in,
0580 tweak->databuf.in + orig->dataoffs,
0581 orig->datalen);
0582
0583 if (ctx->bounce_oob)
0584 memcpy(orig->oobbuf.in,
0585 tweak->oobbuf.in + orig->ooboffs,
0586 orig->ooblen);
0587 }
0588
0589
0590 *req = *orig;
0591 }
0592 EXPORT_SYMBOL_GPL(nand_ecc_restore_req);
0593
0594 struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand)
0595 {
0596 unsigned int algo = nand->ecc.user_conf.algo;
0597
0598 if (algo == NAND_ECC_ALGO_UNKNOWN)
0599 algo = nand->ecc.defaults.algo;
0600
0601 switch (algo) {
0602 case NAND_ECC_ALGO_HAMMING:
0603 return nand_ecc_sw_hamming_get_engine();
0604 case NAND_ECC_ALGO_BCH:
0605 return nand_ecc_sw_bch_get_engine();
0606 default:
0607 break;
0608 }
0609
0610 return NULL;
0611 }
0612 EXPORT_SYMBOL(nand_ecc_get_sw_engine);
0613
0614 struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand)
0615 {
0616 return nand->ecc.ondie_engine;
0617 }
0618 EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine);
0619
0620 int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine)
0621 {
0622 struct nand_ecc_engine *item;
0623
0624 if (!engine)
0625 return -EINVAL;
0626
0627
0628 list_for_each_entry(item, &on_host_hw_engines, node)
0629 if (item == engine)
0630 return 0;
0631
0632 mutex_lock(&on_host_hw_engines_mutex);
0633 list_add_tail(&engine->node, &on_host_hw_engines);
0634 mutex_unlock(&on_host_hw_engines_mutex);
0635
0636 return 0;
0637 }
0638 EXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine);
0639
0640 int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine)
0641 {
0642 if (!engine)
0643 return -EINVAL;
0644
0645 mutex_lock(&on_host_hw_engines_mutex);
0646 list_del(&engine->node);
0647 mutex_unlock(&on_host_hw_engines_mutex);
0648
0649 return 0;
0650 }
0651 EXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine);
0652
0653 static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev)
0654 {
0655 struct nand_ecc_engine *item;
0656
0657 list_for_each_entry(item, &on_host_hw_engines, node)
0658 if (item->dev == dev)
0659 return item;
0660
0661 return NULL;
0662 }
0663
0664 struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
0665 {
0666 struct nand_ecc_engine *engine = NULL;
0667 struct device *dev = &nand->mtd.dev;
0668 struct platform_device *pdev;
0669 struct device_node *np;
0670
0671 if (list_empty(&on_host_hw_engines))
0672 return NULL;
0673
0674
0675 np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
0676 if (np) {
0677 pdev = of_find_device_by_node(np);
0678 if (!pdev)
0679 return ERR_PTR(-EPROBE_DEFER);
0680
0681 engine = nand_ecc_match_on_host_hw_engine(&pdev->dev);
0682 platform_device_put(pdev);
0683 of_node_put(np);
0684
0685 if (!engine)
0686 return ERR_PTR(-EPROBE_DEFER);
0687 }
0688
0689 if (engine)
0690 get_device(engine->dev);
0691
0692 return engine;
0693 }
0694 EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine);
0695
0696 void nand_ecc_put_on_host_hw_engine(struct nand_device *nand)
0697 {
0698 put_device(nand->ecc.engine->dev);
0699 }
0700 EXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine);
0701
0702
0703
0704
0705
0706
0707
0708 struct device *nand_ecc_get_engine_dev(struct device *host)
0709 {
0710 struct platform_device *ecc_pdev;
0711 struct device_node *np;
0712
0713
0714
0715
0716
0717 np = of_parse_phandle(host->of_node, "nand-ecc-engine", 0);
0718 if (!np)
0719 return host;
0720
0721 ecc_pdev = of_find_device_by_node(np);
0722 if (!ecc_pdev) {
0723 of_node_put(np);
0724 return NULL;
0725 }
0726
0727 platform_device_put(ecc_pdev);
0728 of_node_put(np);
0729
0730 return &ecc_pdev->dev;
0731 }
0732
0733 MODULE_LICENSE("GPL");
0734 MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
0735 MODULE_DESCRIPTION("Generic ECC engine");