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