fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / mtd / nand / s3c2410.c
index d11fe47..8b32035 100644 (file)
@@ -1,17 +1,26 @@
 /* linux/drivers/mtd/nand/s3c2410.c
  *
- * Copyright (c) 2004 Simtec Electronics
- * Ben Dooks <ben@simtec.co.uk>
+ * Copyright (c) 2004,2005 Simtec Electronics
+ *     http://www.simtec.co.uk/products/SWLINUX/
+ *     Ben Dooks <ben@simtec.co.uk>
  *
- * Samsung S3C2410 NAND driver
+ * Samsung S3C2410/S3C240 NAND driver
  *
  * Changelog:
  *     21-Sep-2004  BJD  Initial version
  *     23-Sep-2004  BJD  Mulitple device support
  *     28-Sep-2004  BJD  Fixed ECC placement for Hardware mode
  *     12-Oct-2004  BJD  Fixed errors in use of platform data
+ *     18-Feb-2005  BJD  Fix sparse errors
+ *     14-Mar-2005  BJD  Applied tglx's code reduction patch
+ *     02-May-2005  BJD  Fixed s3c2440 support
+ *     02-May-2005  BJD  Reduced hwcontrol decode
+ *     20-Jun-2005  BJD  Updated s3c2440 support, fixed timing bug
+ *     08-Jul-2005  BJD  Fix OOPS when no platform data supplied
+ *     20-Oct-2005  BJD  Fix timing calculation bug
+ *     14-Jan-2006  BJD  Allow clock to be stopped when idle
  *
- * $Id: s3c2410.c,v 1.5 2004/10/12 10:10:15 bjd Exp $
+ * $Id: s3c2410.c,v 1.23 2006/04/01 18:06:29 bjd Exp $
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,9 +37,6 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
-#include <config/mtd/nand/s3c2410/hwecc.h>
-#include <config/mtd/nand/s3c2410/debug.h>
-
 #ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
 #define DEBUG
 #endif
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/ioport.h>
-#include <linux/device.h>
+#include <linux/platform_device.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
 
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/nand.h>
 #include <linux/mtd/partitions.h>
 
 #include <asm/io.h>
-#include <asm/mach-types.h>
-#include <asm/hardware/clock.h>
 
 #include <asm/arch/regs-nand.h>
 #include <asm/arch/nand.h>
 
-#define PFX "s3c2410-nand: "
-
 #ifdef CONFIG_MTD_NAND_S3C2410_HWECC
 static int hardware_ecc = 1;
 #else
 static int hardware_ecc = 0;
 #endif
 
+#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
+static int clock_stop = 1;
+#else
+static const int clock_stop = 0;
+#endif
+
+
 /* new oob placement block for use with hardware ecc generation
  */
 
-static struct nand_oobinfo nand_hw_eccoob = {
-       .useecc = MTD_NANDECC_AUTOPLACE,
+static struct nand_ecclayout nand_hw_eccoob = {
        .eccbytes = 3,
-       .eccpos = {0, 1, 2 },
-       .oobfree = { {8, 8} }
+       .eccpos = {0, 1, 2},
+       .oobfree = {{8, 8}}
 };
 
 /* controller and mtd information */
@@ -87,6 +97,12 @@ struct s3c2410_nand_mtd {
        int                             scan_res;
 };
 
+enum s3c_cpu_type {
+       TYPE_S3C2410,
+       TYPE_S3C2412,
+       TYPE_S3C2440,
+};
+
 /* overview of the s3c2410 nand state */
 
 struct s3c2410_nand_info {
@@ -99,8 +115,12 @@ struct s3c2410_nand_info {
        struct device                   *device;
        struct resource                 *area;
        struct clk                      *clk;
-       void                            *regs;
+       void __iomem                    *regs;
+       void __iomem                    *sel_reg;
+       int                             sel_bit;
        int                             mtd_count;
+
+       enum s3c_cpu_type               cpu_type;
 };
 
 /* conversion functions */
@@ -115,32 +135,36 @@ static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd)
        return s3c2410_nand_mtd_toours(mtd)->info;
 }
 
-static struct s3c2410_nand_info *to_nand_info(struct device *dev)
+static struct s3c2410_nand_info *to_nand_info(struct platform_device *dev)
 {
-       return (struct s3c2410_nand_info *)dev_get_drvdata(dev);
+       return platform_get_drvdata(dev);
 }
 
-static struct s3c2410_platform_nand *to_nand_plat(struct device *dev)
+static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
 {
-       return (struct s3c2410_platform_nand *)dev->platform_data;
+       return dev->dev.platform_data;
+}
+
+static inline int allow_clk_stop(struct s3c2410_nand_info *info)
+{
+       return clock_stop;
 }
 
 /* timing calculations */
 
-#define NS_IN_KHZ 10000000
+#define NS_IN_KHZ 1000000
 
-static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max)
+static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
 {
        int result;
 
-       result = (wanted * NS_IN_KHZ) / clk;
+       result = (wanted * clk) / NS_IN_KHZ;
        result++;
 
        pr_debug("result %d from %ld, %d\n", result, clk, wanted);
 
        if (result > max) {
-               printk("%d ns is too big for current clock rate %ld\n",
-                      wanted, clk);
+               printk("%d ns is too big for current clock rate %ld\n", wanted, clk);
                return -1;
        }
 
@@ -150,47 +174,62 @@ static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max)
        return result;
 }
 
-#define to_ns(ticks,clk) (((clk) * (ticks)) / NS_IN_KHZ)
+#define to_ns(ticks,clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
 
 /* controller setup */
 
-static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, 
-                              struct device *dev)
+static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
+                              struct platform_device *pdev)
 {
-       struct s3c2410_platform_nand *plat = to_nand_plat(dev);
-       unsigned int tacls, twrph0, twrph1;
+       struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
        unsigned long clkrate = clk_get_rate(info->clk);
-       unsigned long cfg;
+       int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
+       int tacls, twrph0, twrph1;
+       unsigned long cfg = 0;
 
        /* calculate the timing information for the controller */
 
+       clkrate /= 1000;        /* turn clock into kHz for ease of use */
+
        if (plat != NULL) {
-               tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 8);
-               twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
-               twrph1 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
+               tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
+               twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
+               twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
        } else {
                /* default timings */
-               tacls = 8;
+               tacls = tacls_max;
                twrph0 = 8;
                twrph1 = 8;
        }
-       
+
        if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
-               printk(KERN_ERR PFX "cannot get timings suitable for board\n");
+               dev_err(info->device, "cannot get suitable timings\n");
                return -EINVAL;
        }
 
-       printk(KERN_INFO PFX "timing: Tacls %ldns, Twrph0 %ldns, Twrph1 %ldns\n",
-              to_ns(tacls, clkrate),
-              to_ns(twrph0, clkrate),
-              to_ns(twrph1, clkrate));
+       dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
+              tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));
 
-       cfg  = S3C2410_NFCONF_EN;
-       cfg |= S3C2410_NFCONF_TACLS(tacls-1);
-       cfg |= S3C2410_NFCONF_TWRPH0(twrph0-1);
-       cfg |= S3C2410_NFCONF_TWRPH1(twrph1-1);
+       switch (info->cpu_type) {
+       case TYPE_S3C2410:
+               cfg = S3C2410_NFCONF_EN;
+               cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
+               cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
+               cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
+               break;
+
+       case TYPE_S3C2440:
+       case TYPE_S3C2412:
+               cfg = S3C2440_NFCONF_TACLS(tacls - 1);
+               cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
+               cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
 
-       pr_debug(PFX "NF_CONF is 0x%lx\n", cfg);
+               /* enable the controller and de-assert nFCE */
+
+               writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
+       }
+
+       dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);
 
        writel(cfg, info->regs + S3C2410_NFCONF);
        return 0;
@@ -201,165 +240,75 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
 static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
 {
        struct s3c2410_nand_info *info;
-       struct s3c2410_nand_mtd *nmtd; 
+       struct s3c2410_nand_mtd *nmtd;
        struct nand_chip *this = mtd->priv;
        unsigned long cur;
 
-       nmtd = (struct s3c2410_nand_mtd *)this->priv;
+       nmtd = this->priv;
        info = nmtd->info;
 
-       cur = readl(info->regs + S3C2410_NFCONF);
+       if (chip != -1 && allow_clk_stop(info))
+               clk_enable(info->clk);
+
+       cur = readl(info->sel_reg);
 
        if (chip == -1) {
-               cur |= S3C2410_NFCONF_nFCE;
+               cur |= info->sel_bit;
        } else {
-               if (chip > nmtd->set->nr_chips) {
-                       printk(KERN_ERR PFX "chip %d out of range\n", chip);
+               if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
+                       dev_err(info->device, "invalid chip %d\n", chip);
                        return;
                }
 
                if (info->platform != NULL) {
                        if (info->platform->select_chip != NULL)
-                               (info->platform->select_chip)(nmtd->set, chip);
+                               (info->platform->select_chip) (nmtd->set, chip);
                }
 
-               cur &= ~S3C2410_NFCONF_nFCE;
+               cur &= ~info->sel_bit;
        }
 
-       writel(cur, info->regs + S3C2410_NFCONF);
-}
+       writel(cur, info->sel_reg);
 
-/* command and control functions */
-
-static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
-{
-       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       unsigned long cur;
-
-       switch (cmd) {
-       case NAND_CTL_SETNCE:
-               cur = readl(info->regs + S3C2410_NFCONF);
-               cur &= ~S3C2410_NFCONF_nFCE;
-               writel(cur, info->regs + S3C2410_NFCONF);
-               break;
-
-       case NAND_CTL_CLRNCE:
-               cur = readl(info->regs + S3C2410_NFCONF);
-               cur |= S3C2410_NFCONF_nFCE;
-               writel(cur, info->regs + S3C2410_NFCONF);
-               break;
-
-               /* we don't need to implement these */
-       case NAND_CTL_SETCLE:
-       case NAND_CTL_CLRCLE:
-       case NAND_CTL_SETALE:
-       case NAND_CTL_CLRALE:
-               pr_debug(PFX "s3c2410_nand_hwcontrol(%d) unusedn", cmd);
-               break;
-       }
+       if (chip == -1 && allow_clk_stop(info))
+               clk_disable(info->clk);
 }
 
-/* s3c2410_nand_command
+/* s3c2410_nand_hwcontrol
  *
- * This function implements sending commands and the relevant address
- * information to the chip, via the hardware controller. Since the
- * S3C2410 generates the correct ALE/CLE signaling automatically, we
- * do not need to use hwcontrol.
+ * Issue command and address cycles to the chip
 */
 
-static void s3c2410_nand_command (struct mtd_info *mtd, unsigned command,
-                                 int column, int page_addr)
+static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+                                  unsigned int ctrl)
 {
-       register struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       register struct nand_chip *this = mtd->priv;
-
-       /*
-        * Write out the command to the device.
-        */
-       if (command == NAND_CMD_SEQIN) {
-               int readcmd;
-
-               if (column >= mtd->oobblock) {
-                       /* OOB area */
-                       column -= mtd->oobblock;
-                       readcmd = NAND_CMD_READOOB;
-               } else if (column < 256) {
-                       /* First 256 bytes --> READ0 */
-                       readcmd = NAND_CMD_READ0;
-               } else {
-                       column -= 256;
-                       readcmd = NAND_CMD_READ1;
-               }
-               
-               writeb(readcmd, info->regs + S3C2410_NFCMD);
-       }
-       writeb(command, info->regs + S3C2410_NFCMD);
-
-       /* Set ALE and clear CLE to start address cycle */
-
-       if (column != -1 || page_addr != -1) {
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
 
-               /* Serially input address */
-               if (column != -1) {
-                       /* Adjust columns for 16 bit buswidth */
-                       if (this->options & NAND_BUSWIDTH_16)
-                               column >>= 1;
-                       writeb(column, info->regs + S3C2410_NFADDR);
-               }
-               if (page_addr != -1) {
-                       writeb((unsigned char) (page_addr), info->regs + S3C2410_NFADDR);
-                       writeb((unsigned char) (page_addr >> 8), info->regs + S3C2410_NFADDR);
-                       /* One more address cycle for higher density devices */
-                       if (this->chipsize & 0x0c000000) 
-                               writeb((unsigned char) ((page_addr >> 16) & 0x0f),
-                                      info->regs + S3C2410_NFADDR);
-               }
-               /* Latch in address */
-       }
-       
-       /* 
-        * program and erase have their own busy handlers 
-        * status and sequential in needs no delay
-       */
-       switch (command) {
-                       
-       case NAND_CMD_PAGEPROG:
-       case NAND_CMD_ERASE1:
-       case NAND_CMD_ERASE2:
-       case NAND_CMD_SEQIN:
-       case NAND_CMD_STATUS:
+       if (cmd == NAND_CMD_NONE)
                return;
 
-       case NAND_CMD_RESET:
-               if (this->dev_ready)    
-                       break;
+       if (ctrl & NAND_CLE)
+               writeb(cmd, info->regs + S3C2410_NFCMD);
+       else
+               writeb(cmd, info->regs + S3C2410_NFADDR);
+}
 
-               udelay(this->chip_delay);
-               writeb(NAND_CMD_STATUS, info->regs + S3C2410_NFCMD);
+/* command and control functions */
 
-               while ( !(this->read_byte(mtd) & 0x40));
+static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+                                  unsigned int ctrl)
+{
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+
+       if (cmd == NAND_CMD_NONE)
                return;
 
-       /* This applies to read commands */     
-       default:
-               /* 
-                * If we don't have access to the busy pin, we apply the given
-                * command delay
-               */
-               if (!this->dev_ready) {
-                       udelay (this->chip_delay);
-                       return;
-               }       
-       }
-       
-       /* Apply this short delay always to ensure that we do wait tWB in
-        * any case on any machine. */
-       ndelay (100);
-       /* wait until command is processed */
-       while (!this->dev_ready(mtd));
+       if (ctrl & NAND_CLE)
+               writeb(cmd, info->regs + S3C2440_NFCMD);
+       else
+               writeb(cmd, info->regs + S3C2440_NFADDR);
 }
 
-
 /* s3c2410_nand_devready()
  *
  * returns 0 if the nand is busy, 1 if it is ready
@@ -368,25 +317,32 @@ static void s3c2410_nand_command (struct mtd_info *mtd, unsigned command,
 static int s3c2410_nand_devready(struct mtd_info *mtd)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       
        return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
 }
 
+static int s3c2440_nand_devready(struct mtd_info *mtd)
+{
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+       return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
+}
+
+static int s3c2412_nand_devready(struct mtd_info *mtd)
+{
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+       return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY;
+}
+
 /* ECC handling functions */
 
 static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
                                     u_char *read_ecc, u_char *calc_ecc)
 {
-       pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n",
-                mtd, dat, read_ecc, calc_ecc);
+       pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n", mtd, dat, read_ecc, calc_ecc);
 
        pr_debug("eccs: read %02x,%02x,%02x vs calc %02x,%02x,%02x\n",
-                read_ecc[0], read_ecc[1], read_ecc[2],
-                calc_ecc[0], calc_ecc[1], calc_ecc[2]);
+                read_ecc[0], read_ecc[1], read_ecc[2], calc_ecc[0], calc_ecc[1], calc_ecc[2]);
 
-       if (read_ecc[0] == calc_ecc[0] &&
-           read_ecc[1] == calc_ecc[1] &&
-           read_ecc[2] == calc_ecc[2]) 
+       if (read_ecc[0] == calc_ecc[0] && read_ecc[1] == calc_ecc[1] && read_ecc[2] == calc_ecc[2])
                return 0;
 
        /* we curently have no method for correcting the error */
@@ -394,6 +350,12 @@ static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
        return -1;
 }
 
+/* ECC functions
+ *
+ * These allow the s3c2410 and s3c2440 to use the controller's ECC
+ * generator block to ECC the data as it passes through]
+*/
+
 static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
@@ -404,8 +366,16 @@ static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
        writel(ctrl, info->regs + S3C2410_NFCONF);
 }
 
-static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd,
-                                     const u_char *dat, u_char *ecc_code)
+static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+       unsigned long ctrl;
+
+       ctrl = readl(info->regs + S3C2440_NFCONT);
+       writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
+}
+
+static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
 
@@ -413,44 +383,57 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd,
        ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
        ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
 
-       pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n",
-                ecc_code[0], ecc_code[1], ecc_code[2]);
+       pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]);
 
        return 0;
 }
 
+static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
+{
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+       unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
+
+       ecc_code[0] = ecc;
+       ecc_code[1] = ecc >> 8;
+       ecc_code[2] = ecc >> 16;
+
+       pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]);
 
-/* over-ride the standard functions for a little more speed? */
+       return 0;
+}
+
+/* over-ride the standard functions for a little more speed. We can
+ * use read/write block to move the data buffers to/from the controller
+*/
 
 static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
 {
-       struct nand_chip *this = (struct nand_chip *)mtd->priv;
+       struct nand_chip *this = mtd->priv;
        readsb(this->IO_ADDR_R, buf, len);
 }
 
-static void s3c2410_nand_write_buf(struct mtd_info *mtd,
-                                  const u_char *buf, int len)
+static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
 {
-       struct nand_chip *this = (struct nand_chip *)mtd->priv;
+       struct nand_chip *this = mtd->priv;
        writesb(this->IO_ADDR_W, buf, len);
 }
 
 /* device management functions */
 
-static int s3c2410_nand_remove(struct device *dev)
+static int s3c2410_nand_remove(struct platform_device *pdev)
 {
-       struct s3c2410_nand_info *info = to_nand_info(dev);
+       struct s3c2410_nand_info *info = to_nand_info(pdev);
 
-       dev_set_drvdata(dev, NULL);
+       platform_set_drvdata(pdev, NULL);
 
-       if (info == NULL) 
+       if (info == NULL)
                return 0;
 
        /* first thing we need to do is release all our mtds
         * and their partitions, then go through freeing the
-        * resources used 
+        * resources used
         */
-       
+
        if (info->mtds != NULL) {
                struct s3c2410_nand_mtd *ptr = info->mtds;
                int mtdno;
@@ -466,8 +449,8 @@ static int s3c2410_nand_remove(struct device *dev)
        /* free the common resources */
 
        if (info->clk != NULL && !IS_ERR(info->clk)) {
-               clk_disable(info->clk);
-               clk_unuse(info->clk);
+               if (!allow_clk_stop(info))
+                       clk_disable(info->clk);
                clk_put(info->clk);
        }
 
@@ -496,9 +479,7 @@ static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
                return add_mtd_device(&mtd->mtd);
 
        if (set->nr_partitions > 0 && set->partitions != NULL) {
-               return add_mtd_partitions(&mtd->mtd,
-                                         set->partitions,
-                                         set->nr_partitions);
+               return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions);
        }
 
        return add_mtd_device(&mtd->mtd);
@@ -514,7 +495,7 @@ static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
 
 /* s3c2410_nand_init_chip
  *
- * init a single instance of an chip 
+ * init a single instance of an chip
 */
 
 static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
@@ -522,12 +503,8 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
                                   struct s3c2410_nand_set *set)
 {
        struct nand_chip *chip = &nmtd->chip;
+       void __iomem *regs = info->regs;
 
-       chip->IO_ADDR_R    = (char *)info->regs + S3C2410_NFDATA;
-       chip->IO_ADDR_W    = (char *)info->regs + S3C2410_NFDATA;
-       chip->hwcontrol    = s3c2410_nand_hwcontrol;
-       chip->dev_ready    = s3c2410_nand_devready;
-       chip->cmdfunc      = s3c2410_nand_command;
        chip->write_buf    = s3c2410_nand_write_buf;
        chip->read_buf     = s3c2410_nand_read_buf;
        chip->select_chip  = s3c2410_nand_select_chip;
@@ -536,18 +513,66 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
        chip->options      = 0;
        chip->controller   = &info->controller;
 
+       switch (info->cpu_type) {
+       case TYPE_S3C2410:
+               chip->IO_ADDR_W = regs + S3C2410_NFDATA;
+               info->sel_reg   = regs + S3C2410_NFCONF;
+               info->sel_bit   = S3C2410_NFCONF_nFCE;
+               chip->cmd_ctrl  = s3c2410_nand_hwcontrol;
+               chip->dev_ready = s3c2410_nand_devready;
+               break;
+
+       case TYPE_S3C2440:
+               chip->IO_ADDR_W = regs + S3C2440_NFDATA;
+               info->sel_reg   = regs + S3C2440_NFCONT;
+               info->sel_bit   = S3C2440_NFCONT_nFCE;
+               chip->cmd_ctrl  = s3c2440_nand_hwcontrol;
+               chip->dev_ready = s3c2440_nand_devready;
+               break;
+
+       case TYPE_S3C2412:
+               chip->IO_ADDR_W = regs + S3C2440_NFDATA;
+               info->sel_reg   = regs + S3C2440_NFCONT;
+               info->sel_bit   = S3C2412_NFCONT_nFCE0;
+               chip->cmd_ctrl  = s3c2440_nand_hwcontrol;
+               chip->dev_ready = s3c2412_nand_devready;
+
+               if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
+                       dev_info(info->device, "System booted from NAND\n");
+
+               break;
+       }
+
+       chip->IO_ADDR_R = chip->IO_ADDR_W;
+
        nmtd->info         = info;
        nmtd->mtd.priv     = chip;
+       nmtd->mtd.owner    = THIS_MODULE;
        nmtd->set          = set;
 
        if (hardware_ecc) {
-               chip->correct_data  = s3c2410_nand_correct_data;
-               chip->enable_hwecc  = s3c2410_nand_enable_hwecc;
-               chip->calculate_ecc = s3c2410_nand_calculate_ecc;
-               chip->eccmode       = NAND_ECC_HW3_512;
-               chip->autooob       = &nand_hw_eccoob;
+               chip->ecc.calculate = s3c2410_nand_calculate_ecc;
+               chip->ecc.correct   = s3c2410_nand_correct_data;
+               chip->ecc.mode      = NAND_ECC_HW;
+               chip->ecc.size      = 512;
+               chip->ecc.bytes     = 3;
+               chip->ecc.layout    = &nand_hw_eccoob;
+
+               switch (info->cpu_type) {
+               case TYPE_S3C2410:
+                       chip->ecc.hwctl     = s3c2410_nand_enable_hwecc;
+                       chip->ecc.calculate = s3c2410_nand_calculate_ecc;
+                       break;
+
+               case TYPE_S3C2412:
+               case TYPE_S3C2440:
+                       chip->ecc.hwctl     = s3c2440_nand_enable_hwecc;
+                       chip->ecc.calculate = s3c2440_nand_calculate_ecc;
+                       break;
+
+               }
        } else {
-               chip->eccmode       = NAND_ECC_SOFT;
+               chip->ecc.mode      = NAND_ECC_SOFT;
        }
 }
 
@@ -559,10 +584,10 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
  * nand layer to look for devices
 */
 
-static int s3c2410_nand_probe(struct device *dev)
+static int s3c24xx_nand_probe(struct platform_device *pdev,
+                             enum s3c_cpu_type cpu_type)
 {
-       struct platform_device *pdev = to_platform_device(dev);
-       struct s3c2410_platform_nand *plat = to_nand_plat(dev);
+       struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
        struct s3c2410_nand_info *info;
        struct s3c2410_nand_mtd *nmtd;
        struct s3c2410_nand_set *sets;
@@ -572,60 +597,62 @@ static int s3c2410_nand_probe(struct device *dev)
        int nr_sets;
        int setno;
 
-       pr_debug("s3c2410_nand_probe(%p)\n", dev);
+       pr_debug("s3c2410_nand_probe(%p)\n", pdev);
 
        info = kmalloc(sizeof(*info), GFP_KERNEL);
        if (info == NULL) {
-               printk(KERN_ERR PFX "no memory for flash info\n");
+               dev_err(&pdev->dev, "no memory for flash info\n");
                err = -ENOMEM;
                goto exit_error;
        }
 
        memzero(info, sizeof(*info));
-       dev_set_drvdata(dev, info);
+       platform_set_drvdata(pdev, info);
 
        spin_lock_init(&info->controller.lock);
+       init_waitqueue_head(&info->controller.wq);
 
        /* get the clock source and enable it */
 
-       info->clk = clk_get(dev, "nand");
+       info->clk = clk_get(&pdev->dev, "nand");
        if (IS_ERR(info->clk)) {
-               printk(KERN_ERR PFX "failed to get clock");
+               dev_err(&pdev->dev, "failed to get clock");
                err = -ENOENT;
                goto exit_error;
        }
 
-       clk_use(info->clk);
        clk_enable(info->clk);
 
        /* allocate and map the resource */
 
-       res = pdev->resource;  /* assume that the flash has one resource */
+       /* currently we assume we have the one resource */
+       res  = pdev->resource;
        size = res->end - res->start + 1;
 
        info->area = request_mem_region(res->start, size, pdev->name);
 
        if (info->area == NULL) {
-               printk(KERN_ERR PFX "cannot reserve register region\n");
+               dev_err(&pdev->dev, "cannot reserve register region\n");
                err = -ENOENT;
                goto exit_error;
        }
 
-       info->device = dev;
-       info->platform = plat;
-       info->regs = ioremap(res->start, size);
+       info->device     = &pdev->dev;
+       info->platform   = plat;
+       info->regs       = ioremap(res->start, size);
+       info->cpu_type   = cpu_type;
 
        if (info->regs == NULL) {
-               printk(KERN_ERR PFX "cannot reserve register region\n");
+               dev_err(&pdev->dev, "cannot reserve register region\n");
                err = -EIO;
                goto exit_error;
-       }               
+       }
 
-       printk(KERN_INFO PFX "mapped registers at %p\n", info->regs);
+       dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
 
        /* initialise the hardware */
 
-       err = s3c2410_nand_inithw(info, dev);
+       err = s3c2410_nand_inithw(info, pdev);
        if (err != 0)
                goto exit_error;
 
@@ -639,7 +666,7 @@ static int s3c2410_nand_probe(struct device *dev)
        size = nr_sets * sizeof(*info->mtds);
        info->mtds = kmalloc(size, GFP_KERNEL);
        if (info->mtds == NULL) {
-               printk(KERN_ERR PFX "failed to allocate mtd storage\n");
+               dev_err(&pdev->dev, "failed to allocate mtd storage\n");
                err = -ENOMEM;
                goto exit_error;
        }
@@ -651,13 +678,11 @@ static int s3c2410_nand_probe(struct device *dev)
        nmtd = info->mtds;
 
        for (setno = 0; setno < nr_sets; setno++, nmtd++) {
-               pr_debug("initialising set %d (%p, info %p)\n",
-                        setno, nmtd, info);
-               
+               pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
+
                s3c2410_nand_init_chip(info, nmtd, sets);
 
-               nmtd->scan_res = nand_scan(&nmtd->mtd,
-                                          (sets) ? sets->nr_chips : 1);
+               nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1);
 
                if (nmtd->scan_res == 0) {
                        s3c2410_nand_add_partition(info, nmtd, sets);
@@ -666,34 +691,122 @@ static int s3c2410_nand_probe(struct device *dev)
                if (sets != NULL)
                        sets++;
        }
-       
+
+       if (allow_clk_stop(info)) {
+               dev_info(&pdev->dev, "clock idle support enabled\n");
+               clk_disable(info->clk);
+       }
+
        pr_debug("initialised ok\n");
        return 0;
 
  exit_error:
-       s3c2410_nand_remove(dev);
+       s3c2410_nand_remove(pdev);
 
        if (err == 0)
                err = -EINVAL;
        return err;
 }
 
-static struct device_driver s3c2410_nand_driver = {
-       .name           = "s3c2410-nand",
-       .bus            = &platform_bus_type,
+/* PM Support */
+#ifdef CONFIG_PM
+
+static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
+{
+       struct s3c2410_nand_info *info = platform_get_drvdata(dev);
+
+       if (info) {
+               if (!allow_clk_stop(info))
+                       clk_disable(info->clk);
+       }
+
+       return 0;
+}
+
+static int s3c24xx_nand_resume(struct platform_device *dev)
+{
+       struct s3c2410_nand_info *info = platform_get_drvdata(dev);
+
+       if (info) {
+               clk_enable(info->clk);
+               s3c2410_nand_inithw(info, dev);
+
+               if (allow_clk_stop(info))
+                       clk_disable(info->clk);
+       }
+
+       return 0;
+}
+
+#else
+#define s3c24xx_nand_suspend NULL
+#define s3c24xx_nand_resume NULL
+#endif
+
+/* driver device registration */
+
+static int s3c2410_nand_probe(struct platform_device *dev)
+{
+       return s3c24xx_nand_probe(dev, TYPE_S3C2410);
+}
+
+static int s3c2440_nand_probe(struct platform_device *dev)
+{
+       return s3c24xx_nand_probe(dev, TYPE_S3C2440);
+}
+
+static int s3c2412_nand_probe(struct platform_device *dev)
+{
+       return s3c24xx_nand_probe(dev, TYPE_S3C2412);
+}
+
+static struct platform_driver s3c2410_nand_driver = {
        .probe          = s3c2410_nand_probe,
        .remove         = s3c2410_nand_remove,
+       .suspend        = s3c24xx_nand_suspend,
+       .resume         = s3c24xx_nand_resume,
+       .driver         = {
+               .name   = "s3c2410-nand",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static struct platform_driver s3c2440_nand_driver = {
+       .probe          = s3c2440_nand_probe,
+       .remove         = s3c2410_nand_remove,
+       .suspend        = s3c24xx_nand_suspend,
+       .resume         = s3c24xx_nand_resume,
+       .driver         = {
+               .name   = "s3c2440-nand",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static struct platform_driver s3c2412_nand_driver = {
+       .probe          = s3c2412_nand_probe,
+       .remove         = s3c2410_nand_remove,
+       .suspend        = s3c24xx_nand_suspend,
+       .resume         = s3c24xx_nand_resume,
+       .driver         = {
+               .name   = "s3c2412-nand",
+               .owner  = THIS_MODULE,
+       },
 };
 
 static int __init s3c2410_nand_init(void)
 {
-       printk("S3C2410 NAND Driver, (c) 2004 Simtec Electronics\n");
-       return driver_register(&s3c2410_nand_driver);
+       printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
+
+       platform_driver_register(&s3c2412_nand_driver);
+       platform_driver_register(&s3c2440_nand_driver);
+       return platform_driver_register(&s3c2410_nand_driver);
 }
 
 static void __exit s3c2410_nand_exit(void)
 {
-       driver_unregister(&s3c2410_nand_driver);
+       platform_driver_unregister(&s3c2412_nand_driver);
+       platform_driver_unregister(&s3c2440_nand_driver);
+       platform_driver_unregister(&s3c2410_nand_driver);
 }
 
 module_init(s3c2410_nand_init);
@@ -701,4 +814,4 @@ module_exit(s3c2410_nand_exit);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
-MODULE_DESCRIPTION("S3C2410 MTD NAND driver");
+MODULE_DESCRIPTION("S3C24XX MTD NAND driver");