Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 1 | /* |
| 2 | * (C) Copyright 2007-2008 |
Stelian Pop | c9e798d | 2011-11-01 00:00:39 +0100 | [diff] [blame] | 3 | * Stelian Pop <stelian@popies.net> |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 4 | * Lead Tech Design <www.leadtechdesign.com> |
| 5 | * |
| 6 | * (C) Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas |
| 7 | * |
| 8 | * See file CREDITS for list of people who contributed to this |
| 9 | * project. |
| 10 | * |
| 11 | * This program is free software; you can redistribute it and/or |
| 12 | * modify it under the terms of the GNU General Public License as |
| 13 | * published by the Free Software Foundation; either version 2 of |
| 14 | * the License, or (at your option) any later version. |
| 15 | * |
| 16 | * This program is distributed in the hope that it will be useful, |
| 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | * GNU General Public License for more details. |
| 20 | * |
| 21 | * You should have received a copy of the GNU General Public License |
| 22 | * along with this program; if not, write to the Free Software |
| 23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| 24 | * MA 02111-1307 USA |
| 25 | */ |
| 26 | |
| 27 | #include <common.h> |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 28 | #include <asm/arch/hardware.h> |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 29 | #include <asm/arch/gpio.h> |
| 30 | #include <asm/arch/at91_pio.h> |
| 31 | |
| 32 | #include <nand.h> |
| 33 | |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 34 | #ifdef CONFIG_ATMEL_NAND_HWECC |
| 35 | |
| 36 | /* Register access macros */ |
| 37 | #define ecc_readl(add, reg) \ |
| 38 | readl(AT91_BASE_SYS + add + ATMEL_ECC_##reg) |
| 39 | #define ecc_writel(add, reg, value) \ |
| 40 | writel((value), AT91_BASE_SYS + add + ATMEL_ECC_##reg) |
| 41 | |
| 42 | #include "atmel_nand_ecc.h" /* Hardware ECC registers */ |
| 43 | |
| 44 | /* oob layout for large page size |
| 45 | * bad block info is on bytes 0 and 1 |
| 46 | * the bytes have to be consecutives to avoid |
| 47 | * several NAND_CMD_RNDOUT during read |
| 48 | */ |
| 49 | static struct nand_ecclayout atmel_oobinfo_large = { |
| 50 | .eccbytes = 4, |
| 51 | .eccpos = {60, 61, 62, 63}, |
| 52 | .oobfree = { |
| 53 | {2, 58} |
| 54 | }, |
| 55 | }; |
| 56 | |
| 57 | /* oob layout for small page size |
| 58 | * bad block info is on bytes 4 and 5 |
| 59 | * the bytes have to be consecutives to avoid |
| 60 | * several NAND_CMD_RNDOUT during read |
| 61 | */ |
| 62 | static struct nand_ecclayout atmel_oobinfo_small = { |
| 63 | .eccbytes = 4, |
| 64 | .eccpos = {0, 1, 2, 3}, |
| 65 | .oobfree = { |
| 66 | {6, 10} |
| 67 | }, |
| 68 | }; |
| 69 | |
| 70 | /* |
| 71 | * Calculate HW ECC |
| 72 | * |
| 73 | * function called after a write |
| 74 | * |
| 75 | * mtd: MTD block structure |
| 76 | * dat: raw data (unused) |
| 77 | * ecc_code: buffer for ECC |
| 78 | */ |
| 79 | static int atmel_nand_calculate(struct mtd_info *mtd, |
| 80 | const u_char *dat, unsigned char *ecc_code) |
| 81 | { |
| 82 | struct nand_chip *nand_chip = mtd->priv; |
| 83 | unsigned int ecc_value; |
| 84 | |
| 85 | /* get the first 2 ECC bytes */ |
| 86 | ecc_value = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR); |
| 87 | |
| 88 | ecc_code[0] = ecc_value & 0xFF; |
| 89 | ecc_code[1] = (ecc_value >> 8) & 0xFF; |
| 90 | |
| 91 | /* get the last 2 ECC bytes */ |
| 92 | ecc_value = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, NPR) & ATMEL_ECC_NPARITY; |
| 93 | |
| 94 | ecc_code[2] = ecc_value & 0xFF; |
| 95 | ecc_code[3] = (ecc_value >> 8) & 0xFF; |
| 96 | |
| 97 | return 0; |
| 98 | } |
| 99 | |
| 100 | /* |
| 101 | * HW ECC read page function |
| 102 | * |
| 103 | * mtd: mtd info structure |
| 104 | * chip: nand chip info structure |
| 105 | * buf: buffer to store read data |
| 106 | */ |
| 107 | static int atmel_nand_read_page(struct mtd_info *mtd, |
| 108 | struct nand_chip *chip, uint8_t *buf, int page) |
| 109 | { |
| 110 | int eccsize = chip->ecc.size; |
| 111 | int eccbytes = chip->ecc.bytes; |
| 112 | uint32_t *eccpos = chip->ecc.layout->eccpos; |
| 113 | uint8_t *p = buf; |
| 114 | uint8_t *oob = chip->oob_poi; |
| 115 | uint8_t *ecc_pos; |
| 116 | int stat; |
| 117 | |
| 118 | /* read the page */ |
| 119 | chip->read_buf(mtd, p, eccsize); |
| 120 | |
| 121 | /* move to ECC position if needed */ |
| 122 | if (eccpos[0] != 0) { |
| 123 | /* This only works on large pages |
| 124 | * because the ECC controller waits for |
| 125 | * NAND_CMD_RNDOUTSTART after the |
| 126 | * NAND_CMD_RNDOUT. |
| 127 | * anyway, for small pages, the eccpos[0] == 0 |
| 128 | */ |
| 129 | chip->cmdfunc(mtd, NAND_CMD_RNDOUT, |
| 130 | mtd->writesize + eccpos[0], -1); |
| 131 | } |
| 132 | |
| 133 | /* the ECC controller needs to read the ECC just after the data */ |
| 134 | ecc_pos = oob + eccpos[0]; |
| 135 | chip->read_buf(mtd, ecc_pos, eccbytes); |
| 136 | |
| 137 | /* check if there's an error */ |
| 138 | stat = chip->ecc.correct(mtd, p, oob, NULL); |
| 139 | |
| 140 | if (stat < 0) |
| 141 | mtd->ecc_stats.failed++; |
| 142 | else |
| 143 | mtd->ecc_stats.corrected += stat; |
| 144 | |
| 145 | /* get back to oob start (end of page) */ |
| 146 | chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); |
| 147 | |
| 148 | /* read the oob */ |
| 149 | chip->read_buf(mtd, oob, mtd->oobsize); |
| 150 | |
| 151 | return 0; |
| 152 | } |
| 153 | |
| 154 | /* |
| 155 | * HW ECC Correction |
| 156 | * |
| 157 | * function called after a read |
| 158 | * |
| 159 | * mtd: MTD block structure |
| 160 | * dat: raw data read from the chip |
| 161 | * read_ecc: ECC from the chip (unused) |
| 162 | * isnull: unused |
| 163 | * |
| 164 | * Detect and correct a 1 bit error for a page |
| 165 | */ |
| 166 | static int atmel_nand_correct(struct mtd_info *mtd, u_char *dat, |
| 167 | u_char *read_ecc, u_char *isnull) |
| 168 | { |
| 169 | struct nand_chip *nand_chip = mtd->priv; |
| 170 | unsigned int ecc_status, ecc_parity, ecc_mode; |
| 171 | unsigned int ecc_word, ecc_bit; |
| 172 | |
| 173 | /* get the status from the Status Register */ |
| 174 | ecc_status = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, SR); |
| 175 | |
| 176 | /* if there's no error */ |
| 177 | if (likely(!(ecc_status & ATMEL_ECC_RECERR))) |
| 178 | return 0; |
| 179 | |
| 180 | /* get error bit offset (4 bits) */ |
| 181 | ecc_bit = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR) & ATMEL_ECC_BITADDR; |
| 182 | /* get word address (12 bits) */ |
| 183 | ecc_word = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR) & ATMEL_ECC_WORDADDR; |
| 184 | ecc_word >>= 4; |
| 185 | |
| 186 | /* if there are multiple errors */ |
| 187 | if (ecc_status & ATMEL_ECC_MULERR) { |
| 188 | /* check if it is a freshly erased block |
| 189 | * (filled with 0xff) */ |
| 190 | if ((ecc_bit == ATMEL_ECC_BITADDR) |
| 191 | && (ecc_word == (ATMEL_ECC_WORDADDR >> 4))) { |
| 192 | /* the block has just been erased, return OK */ |
| 193 | return 0; |
| 194 | } |
| 195 | /* it doesn't seems to be a freshly |
| 196 | * erased block. |
| 197 | * We can't correct so many errors */ |
| 198 | printk(KERN_WARNING "atmel_nand : multiple errors detected." |
| 199 | " Unable to correct.\n"); |
| 200 | return -EIO; |
| 201 | } |
| 202 | |
| 203 | /* if there's a single bit error : we can correct it */ |
| 204 | if (ecc_status & ATMEL_ECC_ECCERR) { |
| 205 | /* there's nothing much to do here. |
| 206 | * the bit error is on the ECC itself. |
| 207 | */ |
| 208 | printk(KERN_WARNING "atmel_nand : one bit error on ECC code." |
| 209 | " Nothing to correct\n"); |
| 210 | return 0; |
| 211 | } |
| 212 | |
| 213 | printk(KERN_WARNING "atmel_nand : one bit error on data." |
| 214 | " (word offset in the page :" |
| 215 | " 0x%x bit offset : 0x%x)\n", |
| 216 | ecc_word, ecc_bit); |
| 217 | /* correct the error */ |
| 218 | if (nand_chip->options & NAND_BUSWIDTH_16) { |
| 219 | /* 16 bits words */ |
| 220 | ((unsigned short *) dat)[ecc_word] ^= (1 << ecc_bit); |
| 221 | } else { |
| 222 | /* 8 bits words */ |
| 223 | dat[ecc_word] ^= (1 << ecc_bit); |
| 224 | } |
| 225 | printk(KERN_WARNING "atmel_nand : error corrected\n"); |
| 226 | return 1; |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | * Enable HW ECC : unused on most chips |
| 231 | */ |
| 232 | static void atmel_nand_hwctl(struct mtd_info *mtd, int mode) |
| 233 | { |
| 234 | } |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 235 | |
| 236 | int atmel_hwecc_nand_init_param(struct nand_chip *nand, struct mtd_info *mtd) |
| 237 | { |
| 238 | nand->ecc.mode = NAND_ECC_HW; |
| 239 | nand->ecc.calculate = atmel_nand_calculate; |
| 240 | nand->ecc.correct = atmel_nand_correct; |
| 241 | nand->ecc.hwctl = atmel_nand_hwctl; |
| 242 | nand->ecc.read_page = atmel_nand_read_page; |
| 243 | nand->ecc.bytes = 4; |
| 244 | |
| 245 | if (nand->ecc.mode == NAND_ECC_HW) { |
| 246 | /* ECC is calculated for the whole page (1 step) */ |
| 247 | nand->ecc.size = mtd->writesize; |
| 248 | |
| 249 | /* set ECC page size and oob layout */ |
| 250 | switch (mtd->writesize) { |
| 251 | case 512: |
| 252 | nand->ecc.layout = &atmel_oobinfo_small; |
| 253 | ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, |
| 254 | ATMEL_ECC_PAGESIZE_528); |
| 255 | break; |
| 256 | case 1024: |
| 257 | nand->ecc.layout = &atmel_oobinfo_large; |
| 258 | ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, |
| 259 | ATMEL_ECC_PAGESIZE_1056); |
| 260 | break; |
| 261 | case 2048: |
| 262 | nand->ecc.layout = &atmel_oobinfo_large; |
| 263 | ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, |
| 264 | ATMEL_ECC_PAGESIZE_2112); |
| 265 | break; |
| 266 | case 4096: |
| 267 | nand->ecc.layout = &atmel_oobinfo_large; |
| 268 | ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, |
| 269 | ATMEL_ECC_PAGESIZE_4224); |
| 270 | break; |
| 271 | default: |
| 272 | /* page size not handled by HW ECC */ |
| 273 | /* switching back to soft ECC */ |
| 274 | nand->ecc.mode = NAND_ECC_SOFT; |
| 275 | nand->ecc.calculate = NULL; |
| 276 | nand->ecc.correct = NULL; |
| 277 | nand->ecc.hwctl = NULL; |
| 278 | nand->ecc.read_page = NULL; |
| 279 | nand->ecc.postpad = 0; |
| 280 | nand->ecc.prepad = 0; |
| 281 | nand->ecc.bytes = 0; |
| 282 | break; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | return 0; |
| 287 | } |
| 288 | |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 289 | #endif |
| 290 | |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 291 | static void at91_nand_hwcontrol(struct mtd_info *mtd, |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 292 | int cmd, unsigned int ctrl) |
| 293 | { |
| 294 | struct nand_chip *this = mtd->priv; |
| 295 | |
| 296 | if (ctrl & NAND_CTRL_CHANGE) { |
| 297 | ulong IO_ADDR_W = (ulong) this->IO_ADDR_W; |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 298 | IO_ADDR_W &= ~(CONFIG_SYS_NAND_MASK_ALE |
| 299 | | CONFIG_SYS_NAND_MASK_CLE); |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 300 | |
| 301 | if (ctrl & NAND_CLE) |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 302 | IO_ADDR_W |= CONFIG_SYS_NAND_MASK_CLE; |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 303 | if (ctrl & NAND_ALE) |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 304 | IO_ADDR_W |= CONFIG_SYS_NAND_MASK_ALE; |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 305 | |
michael | 67a490d | 2011-03-14 21:16:38 +0000 | [diff] [blame] | 306 | #ifdef CONFIG_SYS_NAND_ENABLE_PIN |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 307 | at91_set_gpio_value(CONFIG_SYS_NAND_ENABLE_PIN, |
| 308 | !(ctrl & NAND_NCE)); |
michael | 67a490d | 2011-03-14 21:16:38 +0000 | [diff] [blame] | 309 | #endif |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 310 | this->IO_ADDR_W = (void *) IO_ADDR_W; |
| 311 | } |
| 312 | |
| 313 | if (cmd != NAND_CMD_NONE) |
| 314 | writeb(cmd, this->IO_ADDR_W); |
| 315 | } |
| 316 | |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 317 | #ifdef CONFIG_SYS_NAND_READY_PIN |
| 318 | static int at91_nand_ready(struct mtd_info *mtd) |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 319 | { |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 320 | return at91_get_gpio_value(CONFIG_SYS_NAND_READY_PIN); |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 321 | } |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 322 | #endif |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 323 | |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 324 | #ifndef CONFIG_SYS_NAND_BASE_LIST |
| 325 | #define CONFIG_SYS_NAND_BASE_LIST { CONFIG_SYS_NAND_BASE } |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 326 | #endif |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 327 | static struct nand_chip nand_chip[CONFIG_SYS_MAX_NAND_DEVICE]; |
| 328 | static ulong base_addr[CONFIG_SYS_MAX_NAND_DEVICE] = CONFIG_SYS_NAND_BASE_LIST; |
| 329 | |
| 330 | int atmel_nand_chip_init(int devnum, ulong base_addr) |
| 331 | { |
| 332 | int ret; |
| 333 | struct mtd_info *mtd = &nand_info[devnum]; |
| 334 | struct nand_chip *nand = &nand_chip[devnum]; |
| 335 | |
| 336 | mtd->priv = nand; |
| 337 | nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr; |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 338 | |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 339 | nand->ecc.mode = NAND_ECC_SOFT; |
| 340 | #ifdef CONFIG_SYS_NAND_DBW_16 |
| 341 | nand->options = NAND_BUSWIDTH_16; |
| 342 | #endif |
Jean-Christophe PLAGNIOL-VILLARD | 74c076d | 2009-03-22 10:22:34 +0100 | [diff] [blame] | 343 | nand->cmd_ctrl = at91_nand_hwcontrol; |
| 344 | #ifdef CONFIG_SYS_NAND_READY_PIN |
| 345 | nand->dev_ready = at91_nand_ready; |
| 346 | #endif |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 347 | nand->chip_delay = 20; |
| 348 | |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 349 | ret = nand_scan_ident(mtd, CONFIG_SYS_NAND_MAX_CHIPS, NULL); |
| 350 | if (ret) |
| 351 | return ret; |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 352 | |
| 353 | #ifdef CONFIG_ATMEL_NAND_HWECC |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 354 | ret = atmel_hwecc_nand_init_param(nand, mtd); |
| 355 | if (ret) |
| 356 | return ret; |
Nikolay Petukhov | 7c27b7b | 2010-03-19 10:49:27 +0500 | [diff] [blame] | 357 | #endif |
| 358 | |
Wu, Josh | fe2185e | 2012-08-23 00:05:34 +0000 | [diff] [blame^] | 359 | ret = nand_scan_tail(mtd); |
| 360 | if (!ret) |
| 361 | nand_register(devnum); |
| 362 | |
| 363 | return ret; |
| 364 | } |
| 365 | |
| 366 | void board_nand_init(void) |
| 367 | { |
| 368 | int i; |
| 369 | for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) |
| 370 | if (atmel_nand_chip_init(i, base_addr[i])) |
| 371 | printk(KERN_ERR "atmel_nand: Fail to initialize #%d chip", |
| 372 | i); |
Sergey Lapin | 1079432 | 2008-10-31 12:28:43 +0100 | [diff] [blame] | 373 | } |