aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Djelic <ivan.djelic@parrot.com>2012-04-30 06:17:18 -0400
committerDavid Woodhouse <David.Woodhouse@intel.com>2012-05-14 00:25:51 -0400
commit0e618ef0a6a33cf7ef96c2c824402088dd8ef48c (patch)
tree308d1554fe7139f6df1577a7387703cde7cf342a
parent8d602cf50d3bba864bc1438f486b626df69c87b3 (diff)
mtd: nand: omap: add support for hardware BCH ecc
Two modes are supported: 4-bit and 8-bit error correction. Note that 4-bit mode is only confirmed to work on OMAP3630 ES 1.x, x >= 1. The OMAP3 GPMC hardware BCH engine computes remainder polynomials, it does not provide automatic error location and correction: this step is implemented using the BCH library. This implementation only protects page data, there is no support for protecting user-defined spare area bytes (this could be added with few modifications); therefore, it cannot be used with YAFFS2 or other similar filesystems that depend on oob storage. Before being stored to nand flash, hardware BCH ecc is adjusted so that an erased page has a valid ecc; thus allowing correction of bitflips in blank pages (also common on 4-bit devices). BCH correction mode is selected at runtime by setting platform data parameter 'ecc_opt' to value OMAP_ECC_BCH4_CODE_HW or OMAP_ECC_BCH8_CODE_HW. This code has been tested with mtd test modules, UBI and UBIFS on a BeagleBoard revC3 (OMAP3530 ES3.0 + Micron NAND 256MiB 1,8V 16-bit). Signed-off-by: Ivan Djelic <ivan.djelic@parrot.com> Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com> Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
-rw-r--r--drivers/mtd/nand/Kconfig40
-rw-r--r--drivers/mtd/nand/omap2.c245
2 files changed, 285 insertions, 0 deletions
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index bf0a28d513d4..31bb7e5b504a 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -115,6 +115,46 @@ config MTD_NAND_OMAP2
115 Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4 115 Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4
116 platforms. 116 platforms.
117 117
118config MTD_NAND_OMAP_BCH
119 depends on MTD_NAND && MTD_NAND_OMAP2 && ARCH_OMAP3
120 bool "Enable support for hardware BCH error correction"
121 default n
122 select BCH
123 select BCH_CONST_PARAMS
124 help
125 Support for hardware BCH error correction.
126
127choice
128 prompt "BCH error correction capability"
129 depends on MTD_NAND_OMAP_BCH
130
131config MTD_NAND_OMAP_BCH8
132 bool "8 bits / 512 bytes (recommended)"
133 help
134 Support correcting up to 8 bitflips per 512-byte block.
135 This will use 13 bytes of spare area per 512 bytes of page data.
136 This is the recommended mode, as 4-bit mode does not work
137 on some OMAP3 revisions, due to a hardware bug.
138
139config MTD_NAND_OMAP_BCH4
140 bool "4 bits / 512 bytes"
141 help
142 Support correcting up to 4 bitflips per 512-byte block.
143 This will use 7 bytes of spare area per 512 bytes of page data.
144 Note that this mode does not work on some OMAP3 revisions, due to a
145 hardware bug. Please check your OMAP datasheet before selecting this
146 mode.
147
148endchoice
149
150if MTD_NAND_OMAP_BCH
151config BCH_CONST_M
152 default 13
153config BCH_CONST_T
154 default 4 if MTD_NAND_OMAP_BCH4
155 default 8 if MTD_NAND_OMAP_BCH8
156endif
157
118config MTD_NAND_IDS 158config MTD_NAND_IDS
119 tristate 159 tristate
120 160
diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c
index 05d3562ec748..d7f681d0c9b9 100644
--- a/drivers/mtd/nand/omap2.c
+++ b/drivers/mtd/nand/omap2.c
@@ -21,6 +21,10 @@
21#include <linux/io.h> 21#include <linux/io.h>
22#include <linux/slab.h> 22#include <linux/slab.h>
23 23
24#ifdef CONFIG_MTD_NAND_OMAP_BCH
25#include <linux/bch.h>
26#endif
27
24#include <plat/dma.h> 28#include <plat/dma.h>
25#include <plat/gpmc.h> 29#include <plat/gpmc.h>
26#include <plat/nand.h> 30#include <plat/nand.h>
@@ -127,6 +131,11 @@ struct omap_nand_info {
127 } iomode; 131 } iomode;
128 u_char *buf; 132 u_char *buf;
129 int buf_len; 133 int buf_len;
134
135#ifdef CONFIG_MTD_NAND_OMAP_BCH
136 struct bch_control *bch;
137 struct nand_ecclayout ecclayout;
138#endif
130}; 139};
131 140
132/** 141/**
@@ -929,6 +938,226 @@ static int omap_dev_ready(struct mtd_info *mtd)
929 return 1; 938 return 1;
930} 939}
931 940
941#ifdef CONFIG_MTD_NAND_OMAP_BCH
942
943/**
944 * omap3_enable_hwecc_bch - Program OMAP3 GPMC to perform BCH ECC correction
945 * @mtd: MTD device structure
946 * @mode: Read/Write mode
947 */
948static void omap3_enable_hwecc_bch(struct mtd_info *mtd, int mode)
949{
950 int nerrors;
951 unsigned int dev_width;
952 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
953 mtd);
954 struct nand_chip *chip = mtd->priv;
955
956 nerrors = (info->nand.ecc.bytes == 13) ? 8 : 4;
957 dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0;
958 /*
959 * Program GPMC to perform correction on one 512-byte sector at a time.
960 * Using 4 sectors at a time (i.e. ecc.size = 2048) is also possible and
961 * gives a slight (5%) performance gain (but requires additional code).
962 */
963 (void)gpmc_enable_hwecc_bch(info->gpmc_cs, mode, dev_width, 1, nerrors);
964}
965
966/**
967 * omap3_calculate_ecc_bch4 - Generate 7 bytes of ECC bytes
968 * @mtd: MTD device structure
969 * @dat: The pointer to data on which ecc is computed
970 * @ecc_code: The ecc_code buffer
971 */
972static int omap3_calculate_ecc_bch4(struct mtd_info *mtd, const u_char *dat,
973 u_char *ecc_code)
974{
975 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
976 mtd);
977 return gpmc_calculate_ecc_bch4(info->gpmc_cs, dat, ecc_code);
978}
979
980/**
981 * omap3_calculate_ecc_bch8 - Generate 13 bytes of ECC bytes
982 * @mtd: MTD device structure
983 * @dat: The pointer to data on which ecc is computed
984 * @ecc_code: The ecc_code buffer
985 */
986static int omap3_calculate_ecc_bch8(struct mtd_info *mtd, const u_char *dat,
987 u_char *ecc_code)
988{
989 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
990 mtd);
991 return gpmc_calculate_ecc_bch8(info->gpmc_cs, dat, ecc_code);
992}
993
994/**
995 * omap3_correct_data_bch - Decode received data and correct errors
996 * @mtd: MTD device structure
997 * @data: page data
998 * @read_ecc: ecc read from nand flash
999 * @calc_ecc: ecc read from HW ECC registers
1000 */
1001static int omap3_correct_data_bch(struct mtd_info *mtd, u_char *data,
1002 u_char *read_ecc, u_char *calc_ecc)
1003{
1004 int i, count;
1005 /* cannot correct more than 8 errors */
1006 unsigned int errloc[8];
1007 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
1008 mtd);
1009
1010 count = decode_bch(info->bch, NULL, 512, read_ecc, calc_ecc, NULL,
1011 errloc);
1012 if (count > 0) {
1013 /* correct errors */
1014 for (i = 0; i < count; i++) {
1015 /* correct data only, not ecc bytes */
1016 if (errloc[i] < 8*512)
1017 data[errloc[i]/8] ^= 1 << (errloc[i] & 7);
1018 pr_debug("corrected bitflip %u\n", errloc[i]);
1019 }
1020 } else if (count < 0) {
1021 pr_err("ecc unrecoverable error\n");
1022 }
1023 return count;
1024}
1025
1026/**
1027 * omap3_free_bch - Release BCH ecc resources
1028 * @mtd: MTD device structure
1029 */
1030static void omap3_free_bch(struct mtd_info *mtd)
1031{
1032 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
1033 mtd);
1034 if (info->bch) {
1035 free_bch(info->bch);
1036 info->bch = NULL;
1037 }
1038}
1039
1040/**
1041 * omap3_init_bch - Initialize BCH ECC
1042 * @mtd: MTD device structure
1043 * @ecc_opt: OMAP ECC mode (OMAP_ECC_BCH4_CODE_HW or OMAP_ECC_BCH8_CODE_HW)
1044 */
1045static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
1046{
1047 int ret, max_errors;
1048 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
1049 mtd);
1050#ifdef CONFIG_MTD_NAND_OMAP_BCH8
1051 const int hw_errors = 8;
1052#else
1053 const int hw_errors = 4;
1054#endif
1055 info->bch = NULL;
1056
1057 max_errors = (ecc_opt == OMAP_ECC_BCH8_CODE_HW) ? 8 : 4;
1058 if (max_errors != hw_errors) {
1059 pr_err("cannot configure %d-bit BCH ecc, only %d-bit supported",
1060 max_errors, hw_errors);
1061 goto fail;
1062 }
1063
1064 /* initialize GPMC BCH engine */
1065 ret = gpmc_init_hwecc_bch(info->gpmc_cs, 1, max_errors);
1066 if (ret)
1067 goto fail;
1068
1069 /* software bch library is only used to detect and locate errors */
1070 info->bch = init_bch(13, max_errors, 0x201b /* hw polynomial */);
1071 if (!info->bch)
1072 goto fail;
1073
1074 info->nand.ecc.size = 512;
1075 info->nand.ecc.hwctl = omap3_enable_hwecc_bch;
1076 info->nand.ecc.correct = omap3_correct_data_bch;
1077 info->nand.ecc.mode = NAND_ECC_HW;
1078
1079 /*
1080 * The number of corrected errors in an ecc block that will trigger
1081 * block scrubbing defaults to the ecc strength (4 or 8).
1082 * Set mtd->bitflip_threshold here to define a custom threshold.
1083 */
1084
1085 if (max_errors == 8) {
1086 info->nand.ecc.strength = 8;
1087 info->nand.ecc.bytes = 13;
1088 info->nand.ecc.calculate = omap3_calculate_ecc_bch8;
1089 } else {
1090 info->nand.ecc.strength = 4;
1091 info->nand.ecc.bytes = 7;
1092 info->nand.ecc.calculate = omap3_calculate_ecc_bch4;
1093 }
1094
1095 pr_info("enabling NAND BCH ecc with %d-bit correction\n", max_errors);
1096 return 0;
1097fail:
1098 omap3_free_bch(mtd);
1099 return -1;
1100}
1101
1102/**
1103 * omap3_init_bch_tail - Build an oob layout for BCH ECC correction.
1104 * @mtd: MTD device structure
1105 */
1106static int omap3_init_bch_tail(struct mtd_info *mtd)
1107{
1108 int i, steps;
1109 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
1110 mtd);
1111 struct nand_ecclayout *layout = &info->ecclayout;
1112
1113 /* build oob layout */
1114 steps = mtd->writesize/info->nand.ecc.size;
1115 layout->eccbytes = steps*info->nand.ecc.bytes;
1116
1117 /* do not bother creating special oob layouts for small page devices */
1118 if (mtd->oobsize < 64) {
1119 pr_err("BCH ecc is not supported on small page devices\n");
1120 goto fail;
1121 }
1122
1123 /* reserve 2 bytes for bad block marker */
1124 if (layout->eccbytes+2 > mtd->oobsize) {
1125 pr_err("no oob layout available for oobsize %d eccbytes %u\n",
1126 mtd->oobsize, layout->eccbytes);
1127 goto fail;
1128 }
1129
1130 /* put ecc bytes at oob tail */
1131 for (i = 0; i < layout->eccbytes; i++)
1132 layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i;
1133
1134 layout->oobfree[0].offset = 2;
1135 layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes;
1136 info->nand.ecc.layout = layout;
1137
1138 if (!(info->nand.options & NAND_BUSWIDTH_16))
1139 info->nand.badblock_pattern = &bb_descrip_flashbased;
1140 return 0;
1141fail:
1142 omap3_free_bch(mtd);
1143 return -1;
1144}
1145
1146#else
1147static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
1148{
1149 pr_err("CONFIG_MTD_NAND_OMAP_BCH is not enabled\n");
1150 return -1;
1151}
1152static int omap3_init_bch_tail(struct mtd_info *mtd)
1153{
1154 return -1;
1155}
1156static void omap3_free_bch(struct mtd_info *mtd)
1157{
1158}
1159#endif /* CONFIG_MTD_NAND_OMAP_BCH */
1160
932static int __devinit omap_nand_probe(struct platform_device *pdev) 1161static int __devinit omap_nand_probe(struct platform_device *pdev)
933{ 1162{
934 struct omap_nand_info *info; 1163 struct omap_nand_info *info;
@@ -1067,6 +1296,13 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
1067 info->nand.ecc.hwctl = omap_enable_hwecc; 1296 info->nand.ecc.hwctl = omap_enable_hwecc;
1068 info->nand.ecc.correct = omap_correct_data; 1297 info->nand.ecc.correct = omap_correct_data;
1069 info->nand.ecc.mode = NAND_ECC_HW; 1298 info->nand.ecc.mode = NAND_ECC_HW;
1299 } else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
1300 (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
1301 err = omap3_init_bch(&info->mtd, pdata->ecc_opt);
1302 if (err) {
1303 err = -EINVAL;
1304 goto out_release_mem_region;
1305 }
1070 } 1306 }
1071 1307
1072 /* DIP switches on some boards change between 8 and 16 bit 1308 /* DIP switches on some boards change between 8 and 16 bit
@@ -1098,6 +1334,14 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
1098 (offset + omap_oobinfo.eccbytes); 1334 (offset + omap_oobinfo.eccbytes);
1099 1335
1100 info->nand.ecc.layout = &omap_oobinfo; 1336 info->nand.ecc.layout = &omap_oobinfo;
1337 } else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
1338 (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
1339 /* build OOB layout for BCH ECC correction */
1340 err = omap3_init_bch_tail(&info->mtd);
1341 if (err) {
1342 err = -EINVAL;
1343 goto out_release_mem_region;
1344 }
1101 } 1345 }
1102 1346
1103 /* second phase scan */ 1347 /* second phase scan */
@@ -1126,6 +1370,7 @@ static int omap_nand_remove(struct platform_device *pdev)
1126 struct mtd_info *mtd = platform_get_drvdata(pdev); 1370 struct mtd_info *mtd = platform_get_drvdata(pdev);
1127 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, 1371 struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
1128 mtd); 1372 mtd);
1373 omap3_free_bch(&info->mtd);
1129 1374
1130 platform_set_drvdata(pdev, NULL); 1375 platform_set_drvdata(pdev, NULL);
1131 if (info->dma_ch != -1) 1376 if (info->dma_ch != -1)