]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mtd/nand/s3c2410.c
[PATCH] NAND: fix remaining OOB length calculation
[linux-2.6-omap-h63xx.git] / drivers / mtd / nand / s3c2410.c
index 2df5e47d1f5ce2ec7c16a9ab6018d7e4be6c1eff..2c262fe03d8af60a93bd6cda681246bf4b7d9ce6 100644 (file)
  *     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.14 2005/07/06 20:05:06 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
@@ -35,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
@@ -52,6 +51,7 @@
 #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>
@@ -59,7 +59,6 @@
 #include <linux/mtd/partitions.h>
 
 #include <asm/io.h>
-#include <asm/hardware/clock.h>
 
 #include <asm/arch/regs-nand.h>
 #include <asm/arch/nand.h>
@@ -72,14 +71,20 @@ static int hardware_ecc = 1;
 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,
-       .eccbytes       = 3,
-       .eccpos         = {0, 1, 2 },
-       .oobfree        = { {8, 8} }
+static struct nand_ecclayout nand_hw_eccoob = {
+       .eccbytes = 3,
+       .eccpos = {0, 1, 2},
+       .oobfree = {{8, 8}}
 };
 
 /* controller and mtd information */
@@ -124,32 +129,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 platform_get_drvdata(dev);
+}
+
+static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
 {
-       return dev_get_drvdata(dev);
+       return dev->dev.platform_data;
 }
 
-static struct s3c2410_platform_nand *to_nand_plat(struct device *dev)
+static inline int allow_clk_stop(struct s3c2410_nand_info *info)
 {
-       return dev->platform_data;
+       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)
 {
        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;
        }
 
@@ -159,22 +168,23 @@ 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);
+       int tacls, twrph0, twrph1;
        unsigned long cfg;
 
        /* 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, 4);
+               tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 4);
                twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
                twrph1 = s3c2410_nand_calc_rate(plat->twrph1, clkrate, 8);
        } else {
@@ -183,26 +193,29 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
                twrph0 = 8;
                twrph1 = 8;
        }
-       
+
        if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
                printk(KERN_ERR PFX "cannot get timings suitable for board\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));
+       printk(KERN_INFO PFX "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));
 
        if (!info->is_s3c2440) {
-               cfg  = S3C2410_NFCONF_EN;
-               cfg |= S3C2410_NFCONF_TACLS(tacls-1);
-               cfg |= S3C2410_NFCONF_TWRPH0(twrph0-1);
-               cfg |= S3C2410_NFCONF_TWRPH1(twrph1-1);
+               cfg = S3C2410_NFCONF_EN;
+               cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
+               cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
+               cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
        } else {
-               cfg   = S3C2440_NFCONF_TACLS(tacls-1);
-               cfg  |= S3C2440_NFCONF_TWRPH0(twrph0-1);
-               cfg  |= S3C2440_NFCONF_TWRPH1(twrph1-1);
+               cfg = S3C2440_NFCONF_TACLS(tacls - 1);
+               cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
+               cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
+
+               /* enable the controller and de-assert nFCE */
+
+               writel(S3C2440_NFCONT_ENABLE | S3C2440_NFCONT_ENABLE,
+                      info->regs + S3C2440_NFCONT);
        }
 
        pr_debug(PFX "NF_CONF is 0x%lx\n", cfg);
@@ -216,7 +229,7 @@ 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;
        void __iomem *reg;
        unsigned long cur;
@@ -226,7 +239,10 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
        info = nmtd->info;
 
        bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE;
-       reg = info->regs+((info->is_s3c2440) ? S3C2440_NFCONT:S3C2410_NFCONF);
+       reg = info->regs + ((info->is_s3c2440) ? S3C2440_NFCONT : S3C2410_NFCONF);
+
+       if (chip != -1 && allow_clk_stop(info))
+               clk_enable(info->clk);
 
        cur = readl(reg);
 
@@ -240,77 +256,51 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
 
                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 &= ~bit;
        }
 
        writel(cur, reg);
+
+       if (chip == -1 && allow_clk_stop(info))
+               clk_disable(info->clk);
 }
 
-/* command and control functions 
- *
- * Note, these all use tglx's method of changing the IO_ADDR_W field
- * to make the code simpler, and use the nand layer's code to issue the
- * command and address sequences via the proper IO ports.
+/* s3c2410_nand_hwcontrol
  *
+ * Issue command and address cycles to the chip
 */
 
-static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
+static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+                                  unsigned int ctrl)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       struct nand_chip *chip = mtd->priv;
-
-       switch (cmd) {
-       case NAND_CTL_SETNCE:
-       case NAND_CTL_CLRNCE:
-               printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__);
-               break;
-
-       case NAND_CTL_SETCLE:
-               chip->IO_ADDR_W = info->regs + S3C2410_NFCMD;
-               break;
-
-       case NAND_CTL_SETALE:
-               chip->IO_ADDR_W = info->regs + S3C2410_NFADDR;
-               break;
-
-               /* NAND_CTL_CLRCLE: */
-               /* NAND_CTL_CLRALE: */
-       default:
-               chip->IO_ADDR_W = info->regs + S3C2410_NFDATA;
-               break;
-       }
+       
+       if (cmd == NAND_CMD_NONE)
+               return;
+
+       if (ctrl & NAND_CLE)
+               writeb(cmd, info->regs + S3C2410_NFCMD);
+       else
+               writeb(cmd, info->regs + S3C2410_NFADDR);
 }
 
 /* command and control functions */
 
-static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
+static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+                                  unsigned int ctrl)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       struct nand_chip *chip = mtd->priv;
-
-       switch (cmd) {
-       case NAND_CTL_SETNCE:
-       case NAND_CTL_CLRNCE:
-               printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__);
-               break;
-
-       case NAND_CTL_SETCLE:
-               chip->IO_ADDR_W = info->regs + S3C2440_NFCMD;
-               break;
-
-       case NAND_CTL_SETALE:
-               chip->IO_ADDR_W = info->regs + S3C2440_NFADDR;
-               break;
-
-               /* NAND_CTL_CLRCLE: */
-               /* NAND_CTL_CLRALE: */
-       default:
-               chip->IO_ADDR_W = info->regs + S3C2440_NFDATA;
-               break;
-       }
+
+       if (cmd == NAND_CMD_NONE)
+               return;
+
+       if (ctrl & NAND_CLE)
+               writeb(cmd, info->regs + S3C2440_NFCMD);
+       else
+               writeb(cmd, info->regs + S3C2440_NFADDR);
 }
 
 /* s3c2410_nand_devready()
@@ -321,28 +311,22 @@ static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
 static int s3c2410_nand_devready(struct mtd_info *mtd)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-       
+
        if (info->is_s3c2440)
                return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
        return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
 }
 
-
 /* ECC handling functions */
 
-static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
-                                    u_char *read_ecc, u_char *calc_ecc)
+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 */
@@ -375,8 +359,7 @@ static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
        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)
+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);
 
@@ -384,15 +367,12 @@ 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)
+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);
@@ -401,13 +381,11 @@ static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd,
        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]);
+       pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]);
 
        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
 */
@@ -418,8 +396,7 @@ static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
        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 = mtd->priv;
        writesb(this->IO_ADDR_W, buf, len);
@@ -427,20 +404,20 @@ static void s3c2410_nand_write_buf(struct mtd_info *mtd,
 
 /* 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;
@@ -456,8 +433,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);
        }
 
@@ -486,9 +463,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);
@@ -504,7 +479,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,
@@ -515,7 +490,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
 
        chip->IO_ADDR_R    = info->regs + S3C2410_NFDATA;
        chip->IO_ADDR_W    = info->regs + S3C2410_NFDATA;
-       chip->hwcontrol    = s3c2410_nand_hwcontrol;
+       chip->cmd_ctrl     = s3c2410_nand_hwcontrol;
        chip->dev_ready    = s3c2410_nand_devready;
        chip->write_buf    = s3c2410_nand_write_buf;
        chip->read_buf     = s3c2410_nand_read_buf;
@@ -528,26 +503,29 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
        if (info->is_s3c2440) {
                chip->IO_ADDR_R  = info->regs + S3C2440_NFDATA;
                chip->IO_ADDR_W  = info->regs + S3C2440_NFDATA;
-               chip->hwcontrol  = s3c2440_nand_hwcontrol;
+               chip->cmd_ctrl   = s3c2440_nand_hwcontrol;
        }
 
        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.correct   = s3c2410_nand_correct_data;
+               chip->ecc.hwctl     = s3c2410_nand_enable_hwecc;
+               chip->ecc.calculate = s3c2410_nand_calculate_ecc;
+               chip->ecc.mode      = NAND_ECC_HW;
+               chip->ecc.size      = 512;
+               chip->ecc.bytes     = 3;
+               chip->ecc.layout    = &nand_hw_eccoob;
 
                if (info->is_s3c2440) {
-                       chip->enable_hwecc  = s3c2440_nand_enable_hwecc;
-                       chip->calculate_ecc = s3c2440_nand_calculate_ecc;
+                       chip->ecc.hwctl     = s3c2440_nand_enable_hwecc;
+                       chip->ecc.calculate = s3c2440_nand_calculate_ecc;
                }
        } else {
-               chip->eccmode       = NAND_ECC_SOFT;
+               chip->ecc.mode      = NAND_ECC_SOFT;
        }
 }
 
@@ -559,10 +537,9 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
  * nand layer to look for devices
 */
 
-static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
+static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440)
 {
-       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,31 +549,30 @@ static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
        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 */
@@ -608,27 +584,27 @@ static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
        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->device     = &pdev->dev;
        info->platform   = plat;
        info->regs       = ioremap(res->start, size);
        info->is_s3c2440 = is_s3c2440;
 
        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;
 
@@ -642,7 +618,7 @@ static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
        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;
        }
@@ -654,13 +630,11 @@ static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
        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);
@@ -669,56 +643,104 @@ static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
                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;
 }
 
+/* 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 device *dev)
+static int s3c2410_nand_probe(struct platform_device *dev)
 {
        return s3c24xx_nand_probe(dev, 0);
 }
 
-static int s3c2440_nand_probe(struct device *dev)
+static int s3c2440_nand_probe(struct platform_device *dev)
 {
        return s3c24xx_nand_probe(dev, 1);
 }
 
-static struct device_driver s3c2410_nand_driver = {
-       .name           = "s3c2410-nand",
-       .bus            = &platform_bus_type,
+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 device_driver s3c2440_nand_driver = {
-       .name           = "s3c2440-nand",
-       .bus            = &platform_bus_type,
+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 int __init s3c2410_nand_init(void)
 {
        printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
 
-       driver_register(&s3c2440_nand_driver);
-       return driver_register(&s3c2410_nand_driver);
+       platform_driver_register(&s3c2440_nand_driver);
+       return platform_driver_register(&s3c2410_nand_driver);
 }
 
 static void __exit s3c2410_nand_exit(void)
 {
-       driver_unregister(&s3c2440_nand_driver);
-       driver_unregister(&s3c2410_nand_driver);
+       platform_driver_unregister(&s3c2440_nand_driver);
+       platform_driver_unregister(&s3c2410_nand_driver);
 }
 
 module_init(s3c2410_nand_init);