diff options
| -rw-r--r-- | drivers/mtd/nand/Kconfig | 40 | ||||
| -rw-r--r-- | drivers/mtd/nand/omap2.c | 245 |
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 | ||
| 118 | config 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 | |||
| 127 | choice | ||
| 128 | prompt "BCH error correction capability" | ||
| 129 | depends on MTD_NAND_OMAP_BCH | ||
| 130 | |||
| 131 | config 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 | |||
| 139 | config 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 | |||
| 148 | endchoice | ||
| 149 | |||
| 150 | if MTD_NAND_OMAP_BCH | ||
| 151 | config BCH_CONST_M | ||
| 152 | default 13 | ||
| 153 | config BCH_CONST_T | ||
| 154 | default 4 if MTD_NAND_OMAP_BCH4 | ||
| 155 | default 8 if MTD_NAND_OMAP_BCH8 | ||
| 156 | endif | ||
| 157 | |||
| 118 | config MTD_NAND_IDS | 158 | config 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 | */ | ||
| 948 | static 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 | */ | ||
| 972 | static 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 | */ | ||
| 986 | static 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 | */ | ||
| 1001 | static 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 | */ | ||
| 1030 | static 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 | */ | ||
| 1045 | static 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; | ||
| 1097 | fail: | ||
| 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 | */ | ||
| 1106 | static 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; | ||
| 1141 | fail: | ||
| 1142 | omap3_free_bch(mtd); | ||
| 1143 | return -1; | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | #else | ||
| 1147 | static 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 | } | ||
| 1152 | static int omap3_init_bch_tail(struct mtd_info *mtd) | ||
| 1153 | { | ||
| 1154 | return -1; | ||
| 1155 | } | ||
| 1156 | static void omap3_free_bch(struct mtd_info *mtd) | ||
| 1157 | { | ||
| 1158 | } | ||
| 1159 | #endif /* CONFIG_MTD_NAND_OMAP_BCH */ | ||
| 1160 | |||
| 932 | static int __devinit omap_nand_probe(struct platform_device *pdev) | 1161 | static 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) |
