diff options
Diffstat (limited to 'drivers/mtd/nand/atmel_nand.c')
-rw-r--r-- | drivers/mtd/nand/atmel_nand.c | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c new file mode 100644 index 00000000000..99aec46e214 --- /dev/null +++ b/drivers/mtd/nand/atmel_nand.c | |||
@@ -0,0 +1,650 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2003 Rick Bronson | ||
3 | * | ||
4 | * Derived from drivers/mtd/nand/autcpu12.c | ||
5 | * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de) | ||
6 | * | ||
7 | * Derived from drivers/mtd/spia.c | ||
8 | * Copyright (C) 2000 Steven J. Hill (sjhill@cotw.com) | ||
9 | * | ||
10 | * | ||
11 | * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 | ||
12 | * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright (C) 2007 | ||
13 | * | ||
14 | * Derived from Das U-Boot source code | ||
15 | * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) | ||
16 | * (C) Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas | ||
17 | * | ||
18 | * | ||
19 | * This program is free software; you can redistribute it and/or modify | ||
20 | * it under the terms of the GNU General Public License version 2 as | ||
21 | * published by the Free Software Foundation. | ||
22 | * | ||
23 | */ | ||
24 | |||
25 | #include <linux/slab.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/mtd/mtd.h> | ||
29 | #include <linux/mtd/nand.h> | ||
30 | #include <linux/mtd/partitions.h> | ||
31 | |||
32 | #include <linux/gpio.h> | ||
33 | #include <linux/io.h> | ||
34 | |||
35 | #include <asm/arch/board.h> | ||
36 | #include <asm/arch/cpu.h> | ||
37 | |||
38 | #ifdef CONFIG_MTD_NAND_ATMEL_ECC_HW | ||
39 | #define hard_ecc 1 | ||
40 | #else | ||
41 | #define hard_ecc 0 | ||
42 | #endif | ||
43 | |||
44 | #ifdef CONFIG_MTD_NAND_ATMEL_ECC_NONE | ||
45 | #define no_ecc 1 | ||
46 | #else | ||
47 | #define no_ecc 0 | ||
48 | #endif | ||
49 | |||
50 | /* Register access macros */ | ||
51 | #define ecc_readl(add, reg) \ | ||
52 | __raw_readl(add + ATMEL_ECC_##reg) | ||
53 | #define ecc_writel(add, reg, value) \ | ||
54 | __raw_writel((value), add + ATMEL_ECC_##reg) | ||
55 | |||
56 | #include "atmel_nand_ecc.h" /* Hardware ECC registers */ | ||
57 | |||
58 | /* oob layout for large page size | ||
59 | * bad block info is on bytes 0 and 1 | ||
60 | * the bytes have to be consecutives to avoid | ||
61 | * several NAND_CMD_RNDOUT during read | ||
62 | */ | ||
63 | static struct nand_ecclayout atmel_oobinfo_large = { | ||
64 | .eccbytes = 4, | ||
65 | .eccpos = {60, 61, 62, 63}, | ||
66 | .oobfree = { | ||
67 | {2, 58} | ||
68 | }, | ||
69 | }; | ||
70 | |||
71 | /* oob layout for small page size | ||
72 | * bad block info is on bytes 4 and 5 | ||
73 | * the bytes have to be consecutives to avoid | ||
74 | * several NAND_CMD_RNDOUT during read | ||
75 | */ | ||
76 | static struct nand_ecclayout atmel_oobinfo_small = { | ||
77 | .eccbytes = 4, | ||
78 | .eccpos = {0, 1, 2, 3}, | ||
79 | .oobfree = { | ||
80 | {6, 10} | ||
81 | }, | ||
82 | }; | ||
83 | |||
84 | struct atmel_nand_host { | ||
85 | struct nand_chip nand_chip; | ||
86 | struct mtd_info mtd; | ||
87 | void __iomem *io_base; | ||
88 | struct atmel_nand_data *board; | ||
89 | struct device *dev; | ||
90 | void __iomem *ecc; | ||
91 | }; | ||
92 | |||
93 | /* | ||
94 | * Enable NAND. | ||
95 | */ | ||
96 | static void atmel_nand_enable(struct atmel_nand_host *host) | ||
97 | { | ||
98 | if (host->board->enable_pin) | ||
99 | gpio_set_value(host->board->enable_pin, 0); | ||
100 | } | ||
101 | |||
102 | /* | ||
103 | * Disable NAND. | ||
104 | */ | ||
105 | static void atmel_nand_disable(struct atmel_nand_host *host) | ||
106 | { | ||
107 | if (host->board->enable_pin) | ||
108 | gpio_set_value(host->board->enable_pin, 1); | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * Hardware specific access to control-lines | ||
113 | */ | ||
114 | static void atmel_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) | ||
115 | { | ||
116 | struct nand_chip *nand_chip = mtd->priv; | ||
117 | struct atmel_nand_host *host = nand_chip->priv; | ||
118 | |||
119 | if (ctrl & NAND_CTRL_CHANGE) { | ||
120 | if (ctrl & NAND_NCE) | ||
121 | atmel_nand_enable(host); | ||
122 | else | ||
123 | atmel_nand_disable(host); | ||
124 | } | ||
125 | if (cmd == NAND_CMD_NONE) | ||
126 | return; | ||
127 | |||
128 | if (ctrl & NAND_CLE) | ||
129 | writeb(cmd, host->io_base + (1 << host->board->cle)); | ||
130 | else | ||
131 | writeb(cmd, host->io_base + (1 << host->board->ale)); | ||
132 | } | ||
133 | |||
134 | /* | ||
135 | * Read the Device Ready pin. | ||
136 | */ | ||
137 | static int atmel_nand_device_ready(struct mtd_info *mtd) | ||
138 | { | ||
139 | struct nand_chip *nand_chip = mtd->priv; | ||
140 | struct atmel_nand_host *host = nand_chip->priv; | ||
141 | |||
142 | return gpio_get_value(host->board->rdy_pin); | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * Minimal-overhead PIO for data access. | ||
147 | */ | ||
148 | static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len) | ||
149 | { | ||
150 | struct nand_chip *nand_chip = mtd->priv; | ||
151 | |||
152 | __raw_readsb(nand_chip->IO_ADDR_R, buf, len); | ||
153 | } | ||
154 | |||
155 | static void atmel_read_buf16(struct mtd_info *mtd, u8 *buf, int len) | ||
156 | { | ||
157 | struct nand_chip *nand_chip = mtd->priv; | ||
158 | |||
159 | __raw_readsw(nand_chip->IO_ADDR_R, buf, len / 2); | ||
160 | } | ||
161 | |||
162 | static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len) | ||
163 | { | ||
164 | struct nand_chip *nand_chip = mtd->priv; | ||
165 | |||
166 | __raw_writesb(nand_chip->IO_ADDR_W, buf, len); | ||
167 | } | ||
168 | |||
169 | static void atmel_write_buf16(struct mtd_info *mtd, const u8 *buf, int len) | ||
170 | { | ||
171 | struct nand_chip *nand_chip = mtd->priv; | ||
172 | |||
173 | __raw_writesw(nand_chip->IO_ADDR_W, buf, len / 2); | ||
174 | } | ||
175 | |||
176 | /* | ||
177 | * write oob for small pages | ||
178 | */ | ||
179 | static int atmel_nand_write_oob_512(struct mtd_info *mtd, | ||
180 | struct nand_chip *chip, int page) | ||
181 | { | ||
182 | int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad; | ||
183 | int eccsize = chip->ecc.size, length = mtd->oobsize; | ||
184 | int len, pos, status = 0; | ||
185 | const uint8_t *bufpoi = chip->oob_poi; | ||
186 | |||
187 | pos = eccsize + chunk; | ||
188 | |||
189 | chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page); | ||
190 | len = min_t(int, length, chunk); | ||
191 | chip->write_buf(mtd, bufpoi, len); | ||
192 | bufpoi += len; | ||
193 | length -= len; | ||
194 | if (length > 0) | ||
195 | chip->write_buf(mtd, bufpoi, length); | ||
196 | |||
197 | chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); | ||
198 | status = chip->waitfunc(mtd, chip); | ||
199 | |||
200 | return status & NAND_STATUS_FAIL ? -EIO : 0; | ||
201 | |||
202 | } | ||
203 | |||
204 | /* | ||
205 | * read oob for small pages | ||
206 | */ | ||
207 | static int atmel_nand_read_oob_512(struct mtd_info *mtd, | ||
208 | struct nand_chip *chip, int page, int sndcmd) | ||
209 | { | ||
210 | if (sndcmd) { | ||
211 | chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); | ||
212 | sndcmd = 0; | ||
213 | } | ||
214 | chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); | ||
215 | return sndcmd; | ||
216 | } | ||
217 | |||
218 | /* | ||
219 | * Calculate HW ECC | ||
220 | * | ||
221 | * function called after a write | ||
222 | * | ||
223 | * mtd: MTD block structure | ||
224 | * dat: raw data (unused) | ||
225 | * ecc_code: buffer for ECC | ||
226 | */ | ||
227 | static int atmel_nand_calculate(struct mtd_info *mtd, | ||
228 | const u_char *dat, unsigned char *ecc_code) | ||
229 | { | ||
230 | struct nand_chip *nand_chip = mtd->priv; | ||
231 | struct atmel_nand_host *host = nand_chip->priv; | ||
232 | uint32_t *eccpos = nand_chip->ecc.layout->eccpos; | ||
233 | unsigned int ecc_value; | ||
234 | |||
235 | /* get the first 2 ECC bytes */ | ||
236 | ecc_value = ecc_readl(host->ecc, PR); | ||
237 | |||
238 | ecc_code[eccpos[0]] = ecc_value & 0xFF; | ||
239 | ecc_code[eccpos[1]] = (ecc_value >> 8) & 0xFF; | ||
240 | |||
241 | /* get the last 2 ECC bytes */ | ||
242 | ecc_value = ecc_readl(host->ecc, NPR) & ATMEL_ECC_NPARITY; | ||
243 | |||
244 | ecc_code[eccpos[2]] = ecc_value & 0xFF; | ||
245 | ecc_code[eccpos[3]] = (ecc_value >> 8) & 0xFF; | ||
246 | |||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | /* | ||
251 | * HW ECC read page function | ||
252 | * | ||
253 | * mtd: mtd info structure | ||
254 | * chip: nand chip info structure | ||
255 | * buf: buffer to store read data | ||
256 | */ | ||
257 | static int atmel_nand_read_page(struct mtd_info *mtd, | ||
258 | struct nand_chip *chip, uint8_t *buf) | ||
259 | { | ||
260 | int eccsize = chip->ecc.size; | ||
261 | int eccbytes = chip->ecc.bytes; | ||
262 | uint32_t *eccpos = chip->ecc.layout->eccpos; | ||
263 | uint8_t *p = buf; | ||
264 | uint8_t *oob = chip->oob_poi; | ||
265 | uint8_t *ecc_pos; | ||
266 | int stat; | ||
267 | |||
268 | /* | ||
269 | * Errata: ALE is incorrectly wired up to the ECC controller | ||
270 | * on the AP7000, so it will include the address cycles in the | ||
271 | * ECC calculation. | ||
272 | * | ||
273 | * Workaround: Reset the parity registers before reading the | ||
274 | * actual data. | ||
275 | */ | ||
276 | if (cpu_is_at32ap7000()) { | ||
277 | struct atmel_nand_host *host = chip->priv; | ||
278 | ecc_writel(host->ecc, CR, ATMEL_ECC_RST); | ||
279 | } | ||
280 | |||
281 | /* read the page */ | ||
282 | chip->read_buf(mtd, p, eccsize); | ||
283 | |||
284 | /* move to ECC position if needed */ | ||
285 | if (eccpos[0] != 0) { | ||
286 | /* This only works on large pages | ||
287 | * because the ECC controller waits for | ||
288 | * NAND_CMD_RNDOUTSTART after the | ||
289 | * NAND_CMD_RNDOUT. | ||
290 | * anyway, for small pages, the eccpos[0] == 0 | ||
291 | */ | ||
292 | chip->cmdfunc(mtd, NAND_CMD_RNDOUT, | ||
293 | mtd->writesize + eccpos[0], -1); | ||
294 | } | ||
295 | |||
296 | /* the ECC controller needs to read the ECC just after the data */ | ||
297 | ecc_pos = oob + eccpos[0]; | ||
298 | chip->read_buf(mtd, ecc_pos, eccbytes); | ||
299 | |||
300 | /* check if there's an error */ | ||
301 | stat = chip->ecc.correct(mtd, p, oob, NULL); | ||
302 | |||
303 | if (stat < 0) | ||
304 | mtd->ecc_stats.failed++; | ||
305 | else | ||
306 | mtd->ecc_stats.corrected += stat; | ||
307 | |||
308 | /* get back to oob start (end of page) */ | ||
309 | chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); | ||
310 | |||
311 | /* read the oob */ | ||
312 | chip->read_buf(mtd, oob, mtd->oobsize); | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | /* | ||
318 | * HW ECC Correction | ||
319 | * | ||
320 | * function called after a read | ||
321 | * | ||
322 | * mtd: MTD block structure | ||
323 | * dat: raw data read from the chip | ||
324 | * read_ecc: ECC from the chip (unused) | ||
325 | * isnull: unused | ||
326 | * | ||
327 | * Detect and correct a 1 bit error for a page | ||
328 | */ | ||
329 | static int atmel_nand_correct(struct mtd_info *mtd, u_char *dat, | ||
330 | u_char *read_ecc, u_char *isnull) | ||
331 | { | ||
332 | struct nand_chip *nand_chip = mtd->priv; | ||
333 | struct atmel_nand_host *host = nand_chip->priv; | ||
334 | unsigned int ecc_status; | ||
335 | unsigned int ecc_word, ecc_bit; | ||
336 | |||
337 | /* get the status from the Status Register */ | ||
338 | ecc_status = ecc_readl(host->ecc, SR); | ||
339 | |||
340 | /* if there's no error */ | ||
341 | if (likely(!(ecc_status & ATMEL_ECC_RECERR))) | ||
342 | return 0; | ||
343 | |||
344 | /* get error bit offset (4 bits) */ | ||
345 | ecc_bit = ecc_readl(host->ecc, PR) & ATMEL_ECC_BITADDR; | ||
346 | /* get word address (12 bits) */ | ||
347 | ecc_word = ecc_readl(host->ecc, PR) & ATMEL_ECC_WORDADDR; | ||
348 | ecc_word >>= 4; | ||
349 | |||
350 | /* if there are multiple errors */ | ||
351 | if (ecc_status & ATMEL_ECC_MULERR) { | ||
352 | /* check if it is a freshly erased block | ||
353 | * (filled with 0xff) */ | ||
354 | if ((ecc_bit == ATMEL_ECC_BITADDR) | ||
355 | && (ecc_word == (ATMEL_ECC_WORDADDR >> 4))) { | ||
356 | /* the block has just been erased, return OK */ | ||
357 | return 0; | ||
358 | } | ||
359 | /* it doesn't seems to be a freshly | ||
360 | * erased block. | ||
361 | * We can't correct so many errors */ | ||
362 | dev_dbg(host->dev, "atmel_nand : multiple errors detected." | ||
363 | " Unable to correct.\n"); | ||
364 | return -EIO; | ||
365 | } | ||
366 | |||
367 | /* if there's a single bit error : we can correct it */ | ||
368 | if (ecc_status & ATMEL_ECC_ECCERR) { | ||
369 | /* there's nothing much to do here. | ||
370 | * the bit error is on the ECC itself. | ||
371 | */ | ||
372 | dev_dbg(host->dev, "atmel_nand : one bit error on ECC code." | ||
373 | " Nothing to correct\n"); | ||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | dev_dbg(host->dev, "atmel_nand : one bit error on data." | ||
378 | " (word offset in the page :" | ||
379 | " 0x%x bit offset : 0x%x)\n", | ||
380 | ecc_word, ecc_bit); | ||
381 | /* correct the error */ | ||
382 | if (nand_chip->options & NAND_BUSWIDTH_16) { | ||
383 | /* 16 bits words */ | ||
384 | ((unsigned short *) dat)[ecc_word] ^= (1 << ecc_bit); | ||
385 | } else { | ||
386 | /* 8 bits words */ | ||
387 | dat[ecc_word] ^= (1 << ecc_bit); | ||
388 | } | ||
389 | dev_dbg(host->dev, "atmel_nand : error corrected\n"); | ||
390 | return 1; | ||
391 | } | ||
392 | |||
393 | /* | ||
394 | * Enable HW ECC : unused on most chips | ||
395 | */ | ||
396 | static void atmel_nand_hwctl(struct mtd_info *mtd, int mode) | ||
397 | { | ||
398 | if (cpu_is_at32ap7000()) { | ||
399 | struct nand_chip *nand_chip = mtd->priv; | ||
400 | struct atmel_nand_host *host = nand_chip->priv; | ||
401 | ecc_writel(host->ecc, CR, ATMEL_ECC_RST); | ||
402 | } | ||
403 | } | ||
404 | |||
405 | #ifdef CONFIG_MTD_PARTITIONS | ||
406 | static const char *part_probes[] = { "cmdlinepart", NULL }; | ||
407 | #endif | ||
408 | |||
409 | /* | ||
410 | * Probe for the NAND device. | ||
411 | */ | ||
412 | static int __init atmel_nand_probe(struct platform_device *pdev) | ||
413 | { | ||
414 | struct atmel_nand_host *host; | ||
415 | struct mtd_info *mtd; | ||
416 | struct nand_chip *nand_chip; | ||
417 | struct resource *regs; | ||
418 | struct resource *mem; | ||
419 | int res; | ||
420 | |||
421 | #ifdef CONFIG_MTD_PARTITIONS | ||
422 | struct mtd_partition *partitions = NULL; | ||
423 | int num_partitions = 0; | ||
424 | #endif | ||
425 | |||
426 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
427 | if (!mem) { | ||
428 | printk(KERN_ERR "atmel_nand: can't get I/O resource mem\n"); | ||
429 | return -ENXIO; | ||
430 | } | ||
431 | |||
432 | /* Allocate memory for the device structure (and zero it) */ | ||
433 | host = kzalloc(sizeof(struct atmel_nand_host), GFP_KERNEL); | ||
434 | if (!host) { | ||
435 | printk(KERN_ERR "atmel_nand: failed to allocate device structure.\n"); | ||
436 | return -ENOMEM; | ||
437 | } | ||
438 | |||
439 | host->io_base = ioremap(mem->start, mem->end - mem->start + 1); | ||
440 | if (host->io_base == NULL) { | ||
441 | printk(KERN_ERR "atmel_nand: ioremap failed\n"); | ||
442 | res = -EIO; | ||
443 | goto err_nand_ioremap; | ||
444 | } | ||
445 | |||
446 | mtd = &host->mtd; | ||
447 | nand_chip = &host->nand_chip; | ||
448 | host->board = pdev->dev.platform_data; | ||
449 | host->dev = &pdev->dev; | ||
450 | |||
451 | nand_chip->priv = host; /* link the private data structures */ | ||
452 | mtd->priv = nand_chip; | ||
453 | mtd->owner = THIS_MODULE; | ||
454 | |||
455 | /* Set address of NAND IO lines */ | ||
456 | nand_chip->IO_ADDR_R = host->io_base; | ||
457 | nand_chip->IO_ADDR_W = host->io_base; | ||
458 | nand_chip->cmd_ctrl = atmel_nand_cmd_ctrl; | ||
459 | |||
460 | if (host->board->rdy_pin) | ||
461 | nand_chip->dev_ready = atmel_nand_device_ready; | ||
462 | |||
463 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); | ||
464 | if (!regs && hard_ecc) { | ||
465 | printk(KERN_ERR "atmel_nand: can't get I/O resource " | ||
466 | "regs\nFalling back on software ECC\n"); | ||
467 | } | ||
468 | |||
469 | nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */ | ||
470 | if (no_ecc) | ||
471 | nand_chip->ecc.mode = NAND_ECC_NONE; | ||
472 | if (hard_ecc && regs) { | ||
473 | host->ecc = ioremap(regs->start, regs->end - regs->start + 1); | ||
474 | if (host->ecc == NULL) { | ||
475 | printk(KERN_ERR "atmel_nand: ioremap failed\n"); | ||
476 | res = -EIO; | ||
477 | goto err_ecc_ioremap; | ||
478 | } | ||
479 | nand_chip->ecc.mode = NAND_ECC_HW_SYNDROME; | ||
480 | nand_chip->ecc.calculate = atmel_nand_calculate; | ||
481 | nand_chip->ecc.correct = atmel_nand_correct; | ||
482 | nand_chip->ecc.hwctl = atmel_nand_hwctl; | ||
483 | nand_chip->ecc.read_page = atmel_nand_read_page; | ||
484 | nand_chip->ecc.bytes = 4; | ||
485 | nand_chip->ecc.prepad = 0; | ||
486 | nand_chip->ecc.postpad = 0; | ||
487 | } | ||
488 | |||
489 | nand_chip->chip_delay = 20; /* 20us command delay time */ | ||
490 | |||
491 | if (host->board->bus_width_16) { /* 16-bit bus width */ | ||
492 | nand_chip->options |= NAND_BUSWIDTH_16; | ||
493 | nand_chip->read_buf = atmel_read_buf16; | ||
494 | nand_chip->write_buf = atmel_write_buf16; | ||
495 | } else { | ||
496 | nand_chip->read_buf = atmel_read_buf; | ||
497 | nand_chip->write_buf = atmel_write_buf; | ||
498 | } | ||
499 | |||
500 | platform_set_drvdata(pdev, host); | ||
501 | atmel_nand_enable(host); | ||
502 | |||
503 | if (host->board->det_pin) { | ||
504 | if (gpio_get_value(host->board->det_pin)) { | ||
505 | printk("No SmartMedia card inserted.\n"); | ||
506 | res = ENXIO; | ||
507 | goto err_no_card; | ||
508 | } | ||
509 | } | ||
510 | |||
511 | /* first scan to find the device and get the page size */ | ||
512 | if (nand_scan_ident(mtd, 1)) { | ||
513 | res = -ENXIO; | ||
514 | goto err_scan_ident; | ||
515 | } | ||
516 | |||
517 | if (nand_chip->ecc.mode == NAND_ECC_HW_SYNDROME) { | ||
518 | /* ECC is calculated for the whole page (1 step) */ | ||
519 | nand_chip->ecc.size = mtd->writesize; | ||
520 | |||
521 | /* set ECC page size and oob layout */ | ||
522 | switch (mtd->writesize) { | ||
523 | case 512: | ||
524 | nand_chip->ecc.layout = &atmel_oobinfo_small; | ||
525 | nand_chip->ecc.read_oob = atmel_nand_read_oob_512; | ||
526 | nand_chip->ecc.write_oob = atmel_nand_write_oob_512; | ||
527 | ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_528); | ||
528 | break; | ||
529 | case 1024: | ||
530 | nand_chip->ecc.layout = &atmel_oobinfo_large; | ||
531 | ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_1056); | ||
532 | break; | ||
533 | case 2048: | ||
534 | nand_chip->ecc.layout = &atmel_oobinfo_large; | ||
535 | ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_2112); | ||
536 | break; | ||
537 | case 4096: | ||
538 | nand_chip->ecc.layout = &atmel_oobinfo_large; | ||
539 | ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_4224); | ||
540 | break; | ||
541 | default: | ||
542 | /* page size not handled by HW ECC */ | ||
543 | /* switching back to soft ECC */ | ||
544 | nand_chip->ecc.mode = NAND_ECC_SOFT; | ||
545 | nand_chip->ecc.calculate = NULL; | ||
546 | nand_chip->ecc.correct = NULL; | ||
547 | nand_chip->ecc.hwctl = NULL; | ||
548 | nand_chip->ecc.read_page = NULL; | ||
549 | nand_chip->ecc.postpad = 0; | ||
550 | nand_chip->ecc.prepad = 0; | ||
551 | nand_chip->ecc.bytes = 0; | ||
552 | break; | ||
553 | } | ||
554 | } | ||
555 | |||
556 | /* second phase scan */ | ||
557 | if (nand_scan_tail(mtd)) { | ||
558 | res = -ENXIO; | ||
559 | goto err_scan_tail; | ||
560 | } | ||
561 | |||
562 | #ifdef CONFIG_MTD_PARTITIONS | ||
563 | #ifdef CONFIG_MTD_CMDLINE_PARTS | ||
564 | mtd->name = "atmel_nand"; | ||
565 | num_partitions = parse_mtd_partitions(mtd, part_probes, | ||
566 | &partitions, 0); | ||
567 | #endif | ||
568 | if (num_partitions <= 0 && host->board->partition_info) | ||
569 | partitions = host->board->partition_info(mtd->size, | ||
570 | &num_partitions); | ||
571 | |||
572 | if ((!partitions) || (num_partitions == 0)) { | ||
573 | printk(KERN_ERR "atmel_nand: No parititions defined, or unsupported device.\n"); | ||
574 | res = ENXIO; | ||
575 | goto err_no_partitions; | ||
576 | } | ||
577 | |||
578 | res = add_mtd_partitions(mtd, partitions, num_partitions); | ||
579 | #else | ||
580 | res = add_mtd_device(mtd); | ||
581 | #endif | ||
582 | |||
583 | if (!res) | ||
584 | return res; | ||
585 | |||
586 | #ifdef CONFIG_MTD_PARTITIONS | ||
587 | err_no_partitions: | ||
588 | #endif | ||
589 | nand_release(mtd); | ||
590 | err_scan_tail: | ||
591 | err_scan_ident: | ||
592 | err_no_card: | ||
593 | atmel_nand_disable(host); | ||
594 | platform_set_drvdata(pdev, NULL); | ||
595 | if (host->ecc) | ||
596 | iounmap(host->ecc); | ||
597 | err_ecc_ioremap: | ||
598 | iounmap(host->io_base); | ||
599 | err_nand_ioremap: | ||
600 | kfree(host); | ||
601 | return res; | ||
602 | } | ||
603 | |||
604 | /* | ||
605 | * Remove a NAND device. | ||
606 | */ | ||
607 | static int __exit atmel_nand_remove(struct platform_device *pdev) | ||
608 | { | ||
609 | struct atmel_nand_host *host = platform_get_drvdata(pdev); | ||
610 | struct mtd_info *mtd = &host->mtd; | ||
611 | |||
612 | nand_release(mtd); | ||
613 | |||
614 | atmel_nand_disable(host); | ||
615 | |||
616 | if (host->ecc) | ||
617 | iounmap(host->ecc); | ||
618 | iounmap(host->io_base); | ||
619 | kfree(host); | ||
620 | |||
621 | return 0; | ||
622 | } | ||
623 | |||
624 | static struct platform_driver atmel_nand_driver = { | ||
625 | .remove = __exit_p(atmel_nand_remove), | ||
626 | .driver = { | ||
627 | .name = "atmel_nand", | ||
628 | .owner = THIS_MODULE, | ||
629 | }, | ||
630 | }; | ||
631 | |||
632 | static int __init atmel_nand_init(void) | ||
633 | { | ||
634 | return platform_driver_probe(&atmel_nand_driver, atmel_nand_probe); | ||
635 | } | ||
636 | |||
637 | |||
638 | static void __exit atmel_nand_exit(void) | ||
639 | { | ||
640 | platform_driver_unregister(&atmel_nand_driver); | ||
641 | } | ||
642 | |||
643 | |||
644 | module_init(atmel_nand_init); | ||
645 | module_exit(atmel_nand_exit); | ||
646 | |||
647 | MODULE_LICENSE("GPL"); | ||
648 | MODULE_AUTHOR("Rick Bronson"); | ||
649 | MODULE_DESCRIPTION("NAND/SmartMedia driver for AT91 / AVR32"); | ||
650 | MODULE_ALIAS("platform:atmel_nand"); | ||