diff options
author | Ben Dooks <ben@simtec.co.uk> | 2005-06-20 12:48:25 +0100 |
---|---|---|
committer | Thomas Gleixner <tglx@mtd.linutronix.de> | 2005-06-29 14:30:47 +0200 |
commit | a4f957f16d41b9ff944dddd84c4892496a129f68 (patch) | |
tree | 16b2984a1c648cd2bc5e5cd4ba85bc24ae8fa8b3 /drivers/mtd/nand/s3c2410.c | |
parent | d7e78d4f2173298c34e88f496c3acea247feec61 (diff) |
[MTD] NAND: s3c24xx updates
Fix error in timing generation, Tacls is only in the range 0..3
Add proper support for the s3c2440 NAND controller, which has now
been tested on several s3c2440 implementations.
Signed-off-by: Ben Dooks <ben@simtec.co.uk>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'drivers/mtd/nand/s3c2410.c')
-rw-r--r-- | drivers/mtd/nand/s3c2410.c | 180 |
1 files changed, 148 insertions, 32 deletions
diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index 64b1d95e3e3..630a9c0edf3 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -1,10 +1,10 @@ /* linux/drivers/mtd/nand/s3c2410.c * - * Copyright (c) 2004 Simtec Electronics + * 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 @@ -13,8 +13,11 @@ * 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 * - * $Id: s3c2410.c,v 1.12 2005/03/17 11:31:26 bjd Exp $ + * $Id: s3c2410.c,v 1.13 2005/06/20 11:48:21 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 @@ -104,6 +107,8 @@ struct s3c2410_nand_info { struct clk *clk; void __iomem *regs; int mtd_count; + + unsigned char is_s3c2440; }; /* conversion functions */ @@ -168,12 +173,12 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, /* calculate the timing information for the controller */ if (plat != NULL) { - tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 8); + 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 { /* default timings */ - tacls = 8; + tacls = 4; twrph0 = 8; twrph1 = 8; } @@ -188,10 +193,16 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, to_ns(twrph0, clkrate), 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); + 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); + } else { + 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); @@ -206,15 +217,20 @@ 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; - cur = readl(info->regs + S3C2410_NFCONF); + bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE; + reg = info->regs+((info->is_s3c2440) ? S3C2440_NFCONT:S3C2410_NFCONF); + + cur = readl(reg); if (chip == -1) { - cur |= S3C2410_NFCONF_nFCE; + cur |= bit; } else { if (chip > nmtd->set->nr_chips) { printk(KERN_ERR PFX "chip %d out of range\n", chip); @@ -226,45 +242,72 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) (info->platform->select_chip)(nmtd->set, chip); } - cur &= ~S3C2410_NFCONF_nFCE; + cur &= ~bit; } - writel(cur, info->regs + S3C2410_NFCONF); + writel(cur, reg); } -/* command and control functions */ +/* 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. + * +*/ static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); struct nand_chip *chip = mtd->priv; - 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); + 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; + } +} + +/* command and control functions */ + +static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd) +{ + 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: - cur = readl(info->regs + S3C2410_NFCONF); - cur |= S3C2410_NFCONF_nFCE; - writel(cur, info->regs + S3C2410_NFCONF); + printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__); break; case NAND_CTL_SETCLE: - chip->IO_ADDR_W = info->regs + S3C2410_NFCMD; + chip->IO_ADDR_W = info->regs + S3C2440_NFCMD; break; case NAND_CTL_SETALE: - chip->IO_ADDR_W = info->regs + S3C2410_NFADDR; + chip->IO_ADDR_W = info->regs + S3C2440_NFADDR; break; /* NAND_CTL_CLRCLE: */ /* NAND_CTL_CLRALE: */ default: - chip->IO_ADDR_W = info->regs + S3C2410_NFDATA; + chip->IO_ADDR_W = info->regs + S3C2440_NFDATA; break; } } @@ -278,9 +321,12 @@ 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, @@ -303,6 +349,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); @@ -313,6 +365,15 @@ static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode) writel(ctrl, info->regs + S3C2410_NFCONF); } +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) { @@ -329,7 +390,26 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, } -/* over-ride the standard functions for a little more speed? */ +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]); + + 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) { @@ -444,6 +524,12 @@ 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->hwcontrol = s3c2440_nand_hwcontrol; + } + nmtd->info = info; nmtd->mtd.priv = chip; nmtd->set = set; @@ -454,6 +540,11 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, chip->calculate_ecc = s3c2410_nand_calculate_ecc; chip->eccmode = NAND_ECC_HW3_512; chip->autooob = &nand_hw_eccoob; + + if (info->is_s3c2440) { + chip->enable_hwecc = s3c2440_nand_enable_hwecc; + chip->calculate_ecc = s3c2440_nand_calculate_ecc; + } } else { chip->eccmode = NAND_ECC_SOFT; } @@ -467,7 +558,7 @@ 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 device *dev, int is_s3c2440) { struct platform_device *pdev = to_platform_device(dev); struct s3c2410_platform_nand *plat = to_nand_plat(dev); @@ -493,6 +584,7 @@ static int s3c2410_nand_probe(struct device *dev) dev_set_drvdata(dev, info); spin_lock_init(&info->controller.lock); + init_waitqueue_head(&info->controller.wq); /* get the clock source and enable it */ @@ -508,7 +600,8 @@ static int s3c2410_nand_probe(struct device *dev) /* 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); @@ -519,9 +612,10 @@ static int s3c2410_nand_probe(struct device *dev) goto exit_error; } - info->device = dev; - info->platform = plat; - info->regs = ioremap(res->start, size); + info->device = 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"); @@ -586,6 +680,18 @@ static int s3c2410_nand_probe(struct device *dev) return err; } +/* driver device registration */ + +static int s3c2410_nand_probe(struct device *dev) +{ + return s3c24xx_nand_probe(dev, 0); +} + +static int s3c2440_nand_probe(struct device *dev) +{ + return s3c24xx_nand_probe(dev, 1); +} + static struct device_driver s3c2410_nand_driver = { .name = "s3c2410-nand", .bus = &platform_bus_type, @@ -593,14 +699,24 @@ static struct device_driver s3c2410_nand_driver = { .remove = s3c2410_nand_remove, }; +static struct device_driver s3c2440_nand_driver = { + .name = "s3c2440-nand", + .bus = &platform_bus_type, + .probe = s3c2440_nand_probe, + .remove = s3c2410_nand_remove, +}; + static int __init s3c2410_nand_init(void) { - printk("S3C2410 NAND Driver, (c) 2004 Simtec Electronics\n"); + printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n"); + + driver_register(&s3c2440_nand_driver); return driver_register(&s3c2410_nand_driver); } static void __exit s3c2410_nand_exit(void) { + driver_unregister(&s3c2440_nand_driver); driver_unregister(&s3c2410_nand_driver); } @@ -609,4 +725,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"); |