Merge branch 'for-linus' of master.kernel.org:/pub/scm/linux/kernel/git/dtor/input
[pandora-kernel.git] / drivers / mtd / nand / s3c2410.c
index 2c262fe..5fac4c4 100644 (file)
@@ -63,8 +63,6 @@
 #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
@@ -99,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 {
@@ -112,9 +116,11 @@ struct s3c2410_nand_info {
        struct resource                 *area;
        struct clk                      *clk;
        void __iomem                    *regs;
+       void __iomem                    *sel_reg;
+       int                             sel_bit;
        int                             mtd_count;
 
-       unsigned char                   is_s3c2440;
+       enum s3c_cpu_type               cpu_type;
 };
 
 /* conversion functions */
@@ -148,7 +154,7 @@ static inline int allow_clk_stop(struct s3c2410_nand_info *info)
 
 #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;
 
@@ -172,53 +178,58 @@ static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max)
 
 /* controller setup */
 
-static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, struct platform_device *pdev)
+static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
+                              struct platform_device *pdev)
 {
        struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
        unsigned long clkrate = clk_get_rate(info->clk);
+       int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
        int tacls, twrph0, twrph1;
-       unsigned long cfg;
+       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, 4);
-               twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
-               twrph1 = s3c2410_nand_calc_rate(plat->twrph1, 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 = 4;
+               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 "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
+       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));
 
-       if (!info->is_s3c2440) {
+       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);
-       } else {
+               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);
 
                /* enable the controller and de-assert nFCE */
 
-               writel(S3C2440_NFCONT_ENABLE | S3C2440_NFCONT_ENABLE,
-                      info->regs + S3C2440_NFCONT);
+               writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
        }
 
-       pr_debug(PFX "NF_CONF is 0x%lx\n", cfg);
+       dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);
 
        writel(cfg, info->regs + S3C2410_NFCONF);
        return 0;
@@ -231,26 +242,21 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
        struct s3c2410_nand_info *info;
        struct s3c2410_nand_mtd *nmtd;
        struct nand_chip *this = mtd->priv;
-       void __iomem *reg;
        unsigned long cur;
-       unsigned long bit;
 
        nmtd = this->priv;
        info = nmtd->info;
 
-       bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE;
-       reg = info->regs + ((info->is_s3c2440) ? S3C2440_NFCONT : S3C2410_NFCONF);
-
        if (chip != -1 && allow_clk_stop(info))
                clk_enable(info->clk);
 
-       cur = readl(reg);
+       cur = readl(info->sel_reg);
 
        if (chip == -1) {
-               cur |= bit;
+               cur |= info->sel_bit;
        } else {
                if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
-                       printk(KERN_ERR PFX "chip %d out of range\n", chip);
+                       dev_err(info->device, "invalid chip %d\n", chip);
                        return;
                }
 
@@ -259,10 +265,10 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
                                (info->platform->select_chip) (nmtd->set, chip);
                }
 
-               cur &= ~bit;
+               cur &= ~info->sel_bit;
        }
 
-       writel(cur, reg);
+       writel(cur, info->sel_reg);
 
        if (chip == -1 && allow_clk_stop(info))
                clk_disable(info->clk);
@@ -277,7 +283,7 @@ static void s3c2410_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;
 
@@ -311,27 +317,89 @@ 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;
 }
 
+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)
+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);
+       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+       unsigned int diff0, diff1, diff2;
+       unsigned int bit, byte;
 
-       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]);
+       pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc);
 
-       if (read_ecc[0] == calc_ecc[0] && read_ecc[1] == calc_ecc[1] && read_ecc[2] == calc_ecc[2])
-               return 0;
+       diff0 = read_ecc[0] ^ calc_ecc[0];
+       diff1 = read_ecc[1] ^ calc_ecc[1];
+       diff2 = read_ecc[2] ^ calc_ecc[2];
 
-       /* we curently have no method for correcting the error */
+       pr_debug("%s: rd %02x%02x%02x calc %02x%02x%02x diff %02x%02x%02x\n",
+                __func__,
+                read_ecc[0], read_ecc[1], read_ecc[2],
+                calc_ecc[0], calc_ecc[1], calc_ecc[2],
+                diff0, diff1, diff2);
 
-       return -1;
+       if (diff0 == 0 && diff1 == 0 && diff2 == 0)
+               return 0;               /* ECC is ok */
+
+       /* Can we correct this ECC (ie, one row and column change).
+        * Note, this is similar to the 256 error code on smartmedia */
+
+       if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 &&
+           ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 &&
+           ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) {
+               /* calculate the bit position of the error */
+
+               bit  = (diff2 >> 2) & 1;
+               bit |= (diff2 >> 3) & 2;
+               bit |= (diff2 >> 4) & 4;
+
+               /* calculate the byte position of the error */
+
+               byte  = (diff1 << 1) & 0x80;
+               byte |= (diff1 << 2) & 0x40;
+               byte |= (diff1 << 3) & 0x20;
+               byte |= (diff1 << 4) & 0x10;
+
+               byte |= (diff0 >> 3) & 0x08;
+               byte |= (diff0 >> 2) & 0x04;
+               byte |= (diff0 >> 1) & 0x02;
+               byte |= (diff0 >> 0) & 0x01;
+
+               byte |= (diff2 << 8) & 0x100;
+
+               dev_dbg(info->device, "correcting error bit %d, byte %d\n",
+                       bit, byte);
+
+               dat[byte] ^= (1 << bit);
+               return 1;
+       }
+
+       /* if there is only one bit difference in the ECC, then
+        * one of only a row or column parity has changed, which
+        * means the error is most probably in the ECC itself */
+
+       diff0 |= (diff1 << 8);
+       diff0 |= (diff2 << 16);
+
+       if ((diff0 & ~(1<<fls(diff0))) == 0)
+               return 1;
+
+       return 0;
 }
 
 /* ECC functions
@@ -350,6 +418,15 @@ static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
        writel(ctrl, info->regs + S3C2410_NFCONF);
 }
 
+static void s3c2412_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 | S3C2412_NFCONT_INIT_MAIN_ECC, info->regs + S3C2440_NFCONT);
+}
+
 static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
 {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
@@ -367,6 +444,21 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u
        ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
        ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
 
+       pr_debug("%s: returning ecc %02x%02x%02x\n", __func__,
+                ecc_code[0], ecc_code[1], ecc_code[2]);
+
+       return 0;
+}
+
+static int s3c2412_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 + S3C2412_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]);
 
        return 0;
@@ -381,7 +473,7 @@ static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u
        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("%s: returning ecc %06lx\n", __func__, ecc);
 
        return 0;
 }
@@ -487,11 +579,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    = info->regs + S3C2410_NFDATA;
-       chip->IO_ADDR_W    = info->regs + S3C2410_NFDATA;
-       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;
        chip->select_chip  = s3c2410_nand_select_chip;
@@ -500,11 +589,37 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
        chip->options      = 0;
        chip->controller   = &info->controller;
 
-       if (info->is_s3c2440) {
-               chip->IO_ADDR_R  = info->regs + S3C2440_NFDATA;
-               chip->IO_ADDR_W  = info->regs + S3C2440_NFDATA;
-               chip->cmd_ctrl   = s3c2440_nand_hwcontrol;
-       }
+       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;
@@ -512,17 +627,29 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
        nmtd->set          = set;
 
        if (hardware_ecc) {
-               chip->ecc.correct   = s3c2410_nand_correct_data;
-               chip->ecc.hwctl     = s3c2410_nand_enable_hwecc;
                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;
 
-               if (info->is_s3c2440) {
-                       chip->ecc.hwctl     = s3c2440_nand_enable_hwecc;
-                       chip->ecc.calculate = s3c2440_nand_calculate_ecc;
+               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:
+                       chip->ecc.hwctl     = s3c2412_nand_enable_hwecc;
+                       chip->ecc.calculate = s3c2412_nand_calculate_ecc;
+                       break;
+
+               case TYPE_S3C2440:
+                       chip->ecc.hwctl     = s3c2440_nand_enable_hwecc;
+                       chip->ecc.calculate = s3c2440_nand_calculate_ecc;
+                       break;
+
                }
        } else {
                chip->ecc.mode      = NAND_ECC_SOFT;
@@ -537,7 +664,8 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
  * nand layer to look for devices
 */
 
-static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440)
+static int s3c24xx_nand_probe(struct platform_device *pdev,
+                             enum s3c_cpu_type cpu_type)
 {
        struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
        struct s3c2410_nand_info *info;
@@ -592,7 +720,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440)
        info->device     = &pdev->dev;
        info->platform   = plat;
        info->regs       = ioremap(res->start, size);
-       info->is_s3c2440 = is_s3c2440;
+       info->cpu_type   = cpu_type;
 
        if (info->regs == NULL) {
                dev_err(&pdev->dev, "cannot reserve register region\n");
@@ -699,12 +827,17 @@ static int s3c24xx_nand_resume(struct platform_device *dev)
 
 static int s3c2410_nand_probe(struct platform_device *dev)
 {
-       return s3c24xx_nand_probe(dev, 0);
+       return s3c24xx_nand_probe(dev, TYPE_S3C2410);
 }
 
 static int s3c2440_nand_probe(struct platform_device *dev)
 {
-       return s3c24xx_nand_probe(dev, 1);
+       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 = {
@@ -729,16 +862,29 @@ static struct platform_driver s3c2440_nand_driver = {
        },
 };
 
+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("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)
 {
+       platform_driver_unregister(&s3c2412_nand_driver);
        platform_driver_unregister(&s3c2440_nand_driver);
        platform_driver_unregister(&s3c2410_nand_driver);
 }