[ACPI] merge acpi-2.6.12 branch into latest Linux 2.6.13-rc...
[linux-2.6] / drivers / mtd / nand / s3c2410.c
1 /* linux/drivers/mtd/nand/s3c2410.c
2  *
3  * Copyright (c) 2004,2005 Simtec Electronics
4  *      http://www.simtec.co.uk/products/SWLINUX/
5  *      Ben Dooks <ben@simtec.co.uk>
6  *
7  * Samsung S3C2410/S3C240 NAND driver
8  *
9  * Changelog:
10  *      21-Sep-2004  BJD  Initial version
11  *      23-Sep-2004  BJD  Mulitple device support
12  *      28-Sep-2004  BJD  Fixed ECC placement for Hardware mode
13  *      12-Oct-2004  BJD  Fixed errors in use of platform data
14  *      18-Feb-2005  BJD  Fix sparse errors
15  *      14-Mar-2005  BJD  Applied tglx's code reduction patch
16  *      02-May-2005  BJD  Fixed s3c2440 support
17  *      02-May-2005  BJD  Reduced hwcontrol decode
18  *      20-Jun-2005  BJD  Updated s3c2440 support, fixed timing bug
19  *      08-Jul-2005  BJD  Fix OOPS when no platform data supplied
20  *
21  * $Id: s3c2410.c,v 1.14 2005/07/06 20:05:06 bjd Exp $
22  *
23  * This program is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation; either version 2 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  * GNU General Public License for more details.
32  *
33  * You should have received a copy of the GNU General Public License
34  * along with this program; if not, write to the Free Software
35  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
36 */
37
38 #include <config/mtd/nand/s3c2410/hwecc.h>
39 #include <config/mtd/nand/s3c2410/debug.h>
40
41 #ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
42 #define DEBUG
43 #endif
44
45 #include <linux/module.h>
46 #include <linux/types.h>
47 #include <linux/init.h>
48 #include <linux/kernel.h>
49 #include <linux/string.h>
50 #include <linux/ioport.h>
51 #include <linux/device.h>
52 #include <linux/delay.h>
53 #include <linux/err.h>
54
55 #include <linux/mtd/mtd.h>
56 #include <linux/mtd/nand.h>
57 #include <linux/mtd/nand_ecc.h>
58 #include <linux/mtd/partitions.h>
59
60 #include <asm/io.h>
61 #include <asm/mach-types.h>
62 #include <asm/hardware/clock.h>
63
64 #include <asm/arch/regs-nand.h>
65 #include <asm/arch/nand.h>
66
67 #define PFX "s3c2410-nand: "
68
69 #ifdef CONFIG_MTD_NAND_S3C2410_HWECC
70 static int hardware_ecc = 1;
71 #else
72 static int hardware_ecc = 0;
73 #endif
74
75 /* new oob placement block for use with hardware ecc generation
76  */
77
78 static struct nand_oobinfo nand_hw_eccoob = {
79         .useecc         = MTD_NANDECC_AUTOPLACE,
80         .eccbytes       = 3,
81         .eccpos         = {0, 1, 2 },
82         .oobfree        = { {8, 8} }
83 };
84
85 /* controller and mtd information */
86
87 struct s3c2410_nand_info;
88
89 struct s3c2410_nand_mtd {
90         struct mtd_info                 mtd;
91         struct nand_chip                chip;
92         struct s3c2410_nand_set         *set;
93         struct s3c2410_nand_info        *info;
94         int                             scan_res;
95 };
96
97 /* overview of the s3c2410 nand state */
98
99 struct s3c2410_nand_info {
100         /* mtd info */
101         struct nand_hw_control          controller;
102         struct s3c2410_nand_mtd         *mtds;
103         struct s3c2410_platform_nand    *platform;
104
105         /* device info */
106         struct device                   *device;
107         struct resource                 *area;
108         struct clk                      *clk;
109         void __iomem                    *regs;
110         int                             mtd_count;
111
112         unsigned char                   is_s3c2440;
113 };
114
115 /* conversion functions */
116
117 static struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd)
118 {
119         return container_of(mtd, struct s3c2410_nand_mtd, mtd);
120 }
121
122 static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd)
123 {
124         return s3c2410_nand_mtd_toours(mtd)->info;
125 }
126
127 static struct s3c2410_nand_info *to_nand_info(struct device *dev)
128 {
129         return dev_get_drvdata(dev);
130 }
131
132 static struct s3c2410_platform_nand *to_nand_plat(struct device *dev)
133 {
134         return dev->platform_data;
135 }
136
137 /* timing calculations */
138
139 #define NS_IN_KHZ 10000000
140
141 static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max)
142 {
143         int result;
144
145         result = (wanted * NS_IN_KHZ) / clk;
146         result++;
147
148         pr_debug("result %d from %ld, %d\n", result, clk, wanted);
149
150         if (result > max) {
151                 printk("%d ns is too big for current clock rate %ld\n",
152                        wanted, clk);
153                 return -1;
154         }
155
156         if (result < 1)
157                 result = 1;
158
159         return result;
160 }
161
162 #define to_ns(ticks,clk) (((clk) * (ticks)) / NS_IN_KHZ)
163
164 /* controller setup */
165
166 static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, 
167                                struct device *dev)
168 {
169         struct s3c2410_platform_nand *plat = to_nand_plat(dev);
170         unsigned int tacls, twrph0, twrph1;
171         unsigned long clkrate = clk_get_rate(info->clk);
172         unsigned long cfg;
173
174         /* calculate the timing information for the controller */
175
176         if (plat != NULL) {
177                 tacls  = s3c2410_nand_calc_rate(plat->tacls, clkrate, 4);
178                 twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
179                 twrph1 = s3c2410_nand_calc_rate(plat->twrph1, clkrate, 8);
180         } else {
181                 /* default timings */
182                 tacls = 4;
183                 twrph0 = 8;
184                 twrph1 = 8;
185         }
186         
187         if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
188                 printk(KERN_ERR PFX "cannot get timings suitable for board\n");
189                 return -EINVAL;
190         }
191
192         printk(KERN_INFO PFX "timing: Tacls %ldns, Twrph0 %ldns, Twrph1 %ldns\n",
193                to_ns(tacls, clkrate),
194                to_ns(twrph0, clkrate),
195                to_ns(twrph1, clkrate));
196
197         if (!info->is_s3c2440) {
198                 cfg  = S3C2410_NFCONF_EN;
199                 cfg |= S3C2410_NFCONF_TACLS(tacls-1);
200                 cfg |= S3C2410_NFCONF_TWRPH0(twrph0-1);
201                 cfg |= S3C2410_NFCONF_TWRPH1(twrph1-1);
202         } else {
203                 cfg   = S3C2440_NFCONF_TACLS(tacls-1);
204                 cfg  |= S3C2440_NFCONF_TWRPH0(twrph0-1);
205                 cfg  |= S3C2440_NFCONF_TWRPH1(twrph1-1);
206         }
207
208         pr_debug(PFX "NF_CONF is 0x%lx\n", cfg);
209
210         writel(cfg, info->regs + S3C2410_NFCONF);
211         return 0;
212 }
213
214 /* select chip */
215
216 static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
217 {
218         struct s3c2410_nand_info *info;
219         struct s3c2410_nand_mtd *nmtd; 
220         struct nand_chip *this = mtd->priv;
221         void __iomem *reg;
222         unsigned long cur;
223         unsigned long bit;
224
225         nmtd = this->priv;
226         info = nmtd->info;
227
228         bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE;
229         reg = info->regs+((info->is_s3c2440) ? S3C2440_NFCONT:S3C2410_NFCONF);
230
231         cur = readl(reg);
232
233         if (chip == -1) {
234                 cur |= bit;
235         } else {
236                 if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
237                         printk(KERN_ERR PFX "chip %d out of range\n", chip);
238                         return;
239                 }
240
241                 if (info->platform != NULL) {
242                         if (info->platform->select_chip != NULL)
243                                 (info->platform->select_chip)(nmtd->set, chip);
244                 }
245
246                 cur &= ~bit;
247         }
248
249         writel(cur, reg);
250 }
251
252 /* command and control functions 
253  *
254  * Note, these all use tglx's method of changing the IO_ADDR_W field
255  * to make the code simpler, and use the nand layer's code to issue the
256  * command and address sequences via the proper IO ports.
257  *
258 */
259
260 static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
261 {
262         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
263         struct nand_chip *chip = mtd->priv;
264
265         switch (cmd) {
266         case NAND_CTL_SETNCE:
267         case NAND_CTL_CLRNCE:
268                 printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__);
269                 break;
270
271         case NAND_CTL_SETCLE:
272                 chip->IO_ADDR_W = info->regs + S3C2410_NFCMD;
273                 break;
274
275         case NAND_CTL_SETALE:
276                 chip->IO_ADDR_W = info->regs + S3C2410_NFADDR;
277                 break;
278
279                 /* NAND_CTL_CLRCLE: */
280                 /* NAND_CTL_CLRALE: */
281         default:
282                 chip->IO_ADDR_W = info->regs + S3C2410_NFDATA;
283                 break;
284         }
285 }
286
287 /* command and control functions */
288
289 static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
290 {
291         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
292         struct nand_chip *chip = mtd->priv;
293
294         switch (cmd) {
295         case NAND_CTL_SETNCE:
296         case NAND_CTL_CLRNCE:
297                 printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__);
298                 break;
299
300         case NAND_CTL_SETCLE:
301                 chip->IO_ADDR_W = info->regs + S3C2440_NFCMD;
302                 break;
303
304         case NAND_CTL_SETALE:
305                 chip->IO_ADDR_W = info->regs + S3C2440_NFADDR;
306                 break;
307
308                 /* NAND_CTL_CLRCLE: */
309                 /* NAND_CTL_CLRALE: */
310         default:
311                 chip->IO_ADDR_W = info->regs + S3C2440_NFDATA;
312                 break;
313         }
314 }
315
316 /* s3c2410_nand_devready()
317  *
318  * returns 0 if the nand is busy, 1 if it is ready
319 */
320
321 static int s3c2410_nand_devready(struct mtd_info *mtd)
322 {
323         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
324         
325         if (info->is_s3c2440)
326                 return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
327         return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
328 }
329
330
331 /* ECC handling functions */
332
333 static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
334                                      u_char *read_ecc, u_char *calc_ecc)
335 {
336         pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n",
337                  mtd, dat, read_ecc, calc_ecc);
338
339         pr_debug("eccs: read %02x,%02x,%02x vs calc %02x,%02x,%02x\n",
340                  read_ecc[0], read_ecc[1], read_ecc[2],
341                  calc_ecc[0], calc_ecc[1], calc_ecc[2]);
342
343         if (read_ecc[0] == calc_ecc[0] &&
344             read_ecc[1] == calc_ecc[1] &&
345             read_ecc[2] == calc_ecc[2]) 
346                 return 0;
347
348         /* we curently have no method for correcting the error */
349
350         return -1;
351 }
352
353 /* ECC functions
354  *
355  * These allow the s3c2410 and s3c2440 to use the controller's ECC
356  * generator block to ECC the data as it passes through]
357 */
358
359 static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
360 {
361         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
362         unsigned long ctrl;
363
364         ctrl = readl(info->regs + S3C2410_NFCONF);
365         ctrl |= S3C2410_NFCONF_INITECC;
366         writel(ctrl, info->regs + S3C2410_NFCONF);
367 }
368
369 static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
370 {
371         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
372         unsigned long ctrl;
373
374         ctrl = readl(info->regs + S3C2440_NFCONT);
375         writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
376 }
377
378 static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd,
379                                       const u_char *dat, u_char *ecc_code)
380 {
381         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
382
383         ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0);
384         ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
385         ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
386
387         pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n",
388                  ecc_code[0], ecc_code[1], ecc_code[2]);
389
390         return 0;
391 }
392
393
394 static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd,
395                                       const u_char *dat, u_char *ecc_code)
396 {
397         struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
398         unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
399
400         ecc_code[0] = ecc;
401         ecc_code[1] = ecc >> 8;
402         ecc_code[2] = ecc >> 16;
403
404         pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n",
405                  ecc_code[0], ecc_code[1], ecc_code[2]);
406
407         return 0;
408 }
409
410
411 /* over-ride the standard functions for a little more speed. We can
412  * use read/write block to move the data buffers to/from the controller
413 */
414
415 static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
416 {
417         struct nand_chip *this = mtd->priv;
418         readsb(this->IO_ADDR_R, buf, len);
419 }
420
421 static void s3c2410_nand_write_buf(struct mtd_info *mtd,
422                                    const u_char *buf, int len)
423 {
424         struct nand_chip *this = mtd->priv;
425         writesb(this->IO_ADDR_W, buf, len);
426 }
427
428 /* device management functions */
429
430 static int s3c2410_nand_remove(struct device *dev)
431 {
432         struct s3c2410_nand_info *info = to_nand_info(dev);
433
434         dev_set_drvdata(dev, NULL);
435
436         if (info == NULL) 
437                 return 0;
438
439         /* first thing we need to do is release all our mtds
440          * and their partitions, then go through freeing the
441          * resources used 
442          */
443         
444         if (info->mtds != NULL) {
445                 struct s3c2410_nand_mtd *ptr = info->mtds;
446                 int mtdno;
447
448                 for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) {
449                         pr_debug("releasing mtd %d (%p)\n", mtdno, ptr);
450                         nand_release(&ptr->mtd);
451                 }
452
453                 kfree(info->mtds);
454         }
455
456         /* free the common resources */
457
458         if (info->clk != NULL && !IS_ERR(info->clk)) {
459                 clk_disable(info->clk);
460                 clk_unuse(info->clk);
461                 clk_put(info->clk);
462         }
463
464         if (info->regs != NULL) {
465                 iounmap(info->regs);
466                 info->regs = NULL;
467         }
468
469         if (info->area != NULL) {
470                 release_resource(info->area);
471                 kfree(info->area);
472                 info->area = NULL;
473         }
474
475         kfree(info);
476
477         return 0;
478 }
479
480 #ifdef CONFIG_MTD_PARTITIONS
481 static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
482                                       struct s3c2410_nand_mtd *mtd,
483                                       struct s3c2410_nand_set *set)
484 {
485         if (set == NULL)
486                 return add_mtd_device(&mtd->mtd);
487
488         if (set->nr_partitions > 0 && set->partitions != NULL) {
489                 return add_mtd_partitions(&mtd->mtd,
490                                           set->partitions,
491                                           set->nr_partitions);
492         }
493
494         return add_mtd_device(&mtd->mtd);
495 }
496 #else
497 static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
498                                       struct s3c2410_nand_mtd *mtd,
499                                       struct s3c2410_nand_set *set)
500 {
501         return add_mtd_device(&mtd->mtd);
502 }
503 #endif
504
505 /* s3c2410_nand_init_chip
506  *
507  * init a single instance of an chip 
508 */
509
510 static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
511                                    struct s3c2410_nand_mtd *nmtd,
512                                    struct s3c2410_nand_set *set)
513 {
514         struct nand_chip *chip = &nmtd->chip;
515
516         chip->IO_ADDR_R    = info->regs + S3C2410_NFDATA;
517         chip->IO_ADDR_W    = info->regs + S3C2410_NFDATA;
518         chip->hwcontrol    = s3c2410_nand_hwcontrol;
519         chip->dev_ready    = s3c2410_nand_devready;
520         chip->write_buf    = s3c2410_nand_write_buf;
521         chip->read_buf     = s3c2410_nand_read_buf;
522         chip->select_chip  = s3c2410_nand_select_chip;
523         chip->chip_delay   = 50;
524         chip->priv         = nmtd;
525         chip->options      = 0;
526         chip->controller   = &info->controller;
527
528         if (info->is_s3c2440) {
529                 chip->IO_ADDR_R  = info->regs + S3C2440_NFDATA;
530                 chip->IO_ADDR_W  = info->regs + S3C2440_NFDATA;
531                 chip->hwcontrol  = s3c2440_nand_hwcontrol;
532         }
533
534         nmtd->info         = info;
535         nmtd->mtd.priv     = chip;
536         nmtd->set          = set;
537
538         if (hardware_ecc) {
539                 chip->correct_data  = s3c2410_nand_correct_data;
540                 chip->enable_hwecc  = s3c2410_nand_enable_hwecc;
541                 chip->calculate_ecc = s3c2410_nand_calculate_ecc;
542                 chip->eccmode       = NAND_ECC_HW3_512;
543                 chip->autooob       = &nand_hw_eccoob;
544
545                 if (info->is_s3c2440) {
546                         chip->enable_hwecc  = s3c2440_nand_enable_hwecc;
547                         chip->calculate_ecc = s3c2440_nand_calculate_ecc;
548                 }
549         } else {
550                 chip->eccmode       = NAND_ECC_SOFT;
551         }
552 }
553
554 /* s3c2410_nand_probe
555  *
556  * called by device layer when it finds a device matching
557  * one our driver can handled. This code checks to see if
558  * it can allocate all necessary resources then calls the
559  * nand layer to look for devices
560 */
561
562 static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
563 {
564         struct platform_device *pdev = to_platform_device(dev);
565         struct s3c2410_platform_nand *plat = to_nand_plat(dev);
566         struct s3c2410_nand_info *info;
567         struct s3c2410_nand_mtd *nmtd;
568         struct s3c2410_nand_set *sets;
569         struct resource *res;
570         int err = 0;
571         int size;
572         int nr_sets;
573         int setno;
574
575         pr_debug("s3c2410_nand_probe(%p)\n", dev);
576
577         info = kmalloc(sizeof(*info), GFP_KERNEL);
578         if (info == NULL) {
579                 printk(KERN_ERR PFX "no memory for flash info\n");
580                 err = -ENOMEM;
581                 goto exit_error;
582         }
583
584         memzero(info, sizeof(*info));
585         dev_set_drvdata(dev, info);
586
587         spin_lock_init(&info->controller.lock);
588         init_waitqueue_head(&info->controller.wq);
589
590         /* get the clock source and enable it */
591
592         info->clk = clk_get(dev, "nand");
593         if (IS_ERR(info->clk)) {
594                 printk(KERN_ERR PFX "failed to get clock");
595                 err = -ENOENT;
596                 goto exit_error;
597         }
598
599         clk_use(info->clk);
600         clk_enable(info->clk);
601
602         /* allocate and map the resource */
603
604         /* currently we assume we have the one resource */
605         res  = pdev->resource;
606         size = res->end - res->start + 1;
607
608         info->area = request_mem_region(res->start, size, pdev->name);
609
610         if (info->area == NULL) {
611                 printk(KERN_ERR PFX "cannot reserve register region\n");
612                 err = -ENOENT;
613                 goto exit_error;
614         }
615
616         info->device     = dev;
617         info->platform   = plat;
618         info->regs       = ioremap(res->start, size);
619         info->is_s3c2440 = is_s3c2440;
620
621         if (info->regs == NULL) {
622                 printk(KERN_ERR PFX "cannot reserve register region\n");
623                 err = -EIO;
624                 goto exit_error;
625         }               
626
627         printk(KERN_INFO PFX "mapped registers at %p\n", info->regs);
628
629         /* initialise the hardware */
630
631         err = s3c2410_nand_inithw(info, dev);
632         if (err != 0)
633                 goto exit_error;
634
635         sets = (plat != NULL) ? plat->sets : NULL;
636         nr_sets = (plat != NULL) ? plat->nr_sets : 1;
637
638         info->mtd_count = nr_sets;
639
640         /* allocate our information */
641
642         size = nr_sets * sizeof(*info->mtds);
643         info->mtds = kmalloc(size, GFP_KERNEL);
644         if (info->mtds == NULL) {
645                 printk(KERN_ERR PFX "failed to allocate mtd storage\n");
646                 err = -ENOMEM;
647                 goto exit_error;
648         }
649
650         memzero(info->mtds, size);
651
652         /* initialise all possible chips */
653
654         nmtd = info->mtds;
655
656         for (setno = 0; setno < nr_sets; setno++, nmtd++) {
657                 pr_debug("initialising set %d (%p, info %p)\n",
658                          setno, nmtd, info);
659                 
660                 s3c2410_nand_init_chip(info, nmtd, sets);
661
662                 nmtd->scan_res = nand_scan(&nmtd->mtd,
663                                            (sets) ? sets->nr_chips : 1);
664
665                 if (nmtd->scan_res == 0) {
666                         s3c2410_nand_add_partition(info, nmtd, sets);
667                 }
668
669                 if (sets != NULL)
670                         sets++;
671         }
672         
673         pr_debug("initialised ok\n");
674         return 0;
675
676  exit_error:
677         s3c2410_nand_remove(dev);
678
679         if (err == 0)
680                 err = -EINVAL;
681         return err;
682 }
683
684 /* driver device registration */
685
686 static int s3c2410_nand_probe(struct device *dev)
687 {
688         return s3c24xx_nand_probe(dev, 0);
689 }
690
691 static int s3c2440_nand_probe(struct device *dev)
692 {
693         return s3c24xx_nand_probe(dev, 1);
694 }
695
696 static struct device_driver s3c2410_nand_driver = {
697         .name           = "s3c2410-nand",
698         .bus            = &platform_bus_type,
699         .probe          = s3c2410_nand_probe,
700         .remove         = s3c2410_nand_remove,
701 };
702
703 static struct device_driver s3c2440_nand_driver = {
704         .name           = "s3c2440-nand",
705         .bus            = &platform_bus_type,
706         .probe          = s3c2440_nand_probe,
707         .remove         = s3c2410_nand_remove,
708 };
709
710 static int __init s3c2410_nand_init(void)
711 {
712         printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
713
714         driver_register(&s3c2440_nand_driver);
715         return driver_register(&s3c2410_nand_driver);
716 }
717
718 static void __exit s3c2410_nand_exit(void)
719 {
720         driver_unregister(&s3c2440_nand_driver);
721         driver_unregister(&s3c2410_nand_driver);
722 }
723
724 module_init(s3c2410_nand_init);
725 module_exit(s3c2410_nand_exit);
726
727 MODULE_LICENSE("GPL");
728 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
729 MODULE_DESCRIPTION("S3C24XX MTD NAND driver");