Commit a4f957f1 authored by Ben Dooks's avatar Ben Dooks Committed by Thomas Gleixner

[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: default avatarBen Dooks <ben@simtec.co.uk>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent d7e78d4f
# drivers/mtd/nand/Kconfig
# $Id: Kconfig,v 1.26 2005/01/05 12:42:24 dwmw2 Exp $
# $Id: Kconfig,v 1.31 2005/06/20 12:03:21 bjd Exp $
menu "NAND Flash Device Drivers"
depends on MTD!=n
......@@ -95,10 +95,11 @@ config MTD_NAND_PPCHAMELEONEVB
This enables the NAND flash driver on the PPChameleon EVB Board.
config MTD_NAND_S3C2410
tristate "NAND Flash support for S3C2410 SoC"
tristate "NAND Flash support for S3C2410/S3C2440 SoC"
depends on ARCH_S3C2410 && MTD_NAND
help
This enables the NAND flash controller on the S3C2410.
This enables the NAND flash controller on the S3C2410 and S3C2440
SoCs
No board specfic support is done by this driver, each board
must advertise a platform_device for the driver to attach.
......
/* 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));
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,31 +242,29 @@ 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);
break;
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:
......@@ -269,6 +283,35 @@ static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
}
}
/* 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:
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;
}
}
/* s3c2410_nand_devready()
*
* returns 0 if the nand is busy, 1 if it is ready
......@@ -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);
......@@ -522,6 +615,7 @@ static int s3c2410_nand_probe(struct device *dev)
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");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment