Patchwork [REFERENCE,OneNAND] S3C64XX support

login
register
mail settings
Submitter Kyungmin Park
Date Oct. 20, 2008, 9:32 a.m.
Message ID <20081020093220.GA18312@july>
Download mbox | patch
Permalink /patch/5058/
State New
Headers show

Comments

Kyungmin Park - Oct. 20, 2008, 9:32 a.m.
PLEASE DON'T COMMIT IT

Hi,

It's OneNAND support on New Samsung Mobile CPU S3C64XX series 
Now it's only tested with s3c6410 with UBI & UBIFS.
If you want to use it on s3c6400, you should change some values.

There's some ugly hack for support s3c64xx. It will be handled more fancy later.

Any comments are welcome

Thank you,
Kyungmin Park
---
Adrian Hunter - Oct. 20, 2008, 3:07 p.m.
Kyungmin Park wrote:
> It's OneNAND support on New Samsung Mobile CPU S3C64XX series 
> Now it's only tested with s3c6410 with UBI & UBIFS.
> If you want to use it on s3c6400, you should change some values.
> 
> There's some ugly hack for support s3c64xx. It will be handled more fancy later.
> 
> Any comments are welcome

Why not just make it a separate driver and skip using onenand_base etc?
Kyungmin Park - Oct. 20, 2008, 11:01 p.m.
On Tue, Oct 21, 2008 at 12:07 AM, Adrian Hunter
<ext-adrian.hunter@nokia.com> wrote:
> Kyungmin Park wrote:
>>
>> It's OneNAND support on New Samsung Mobile CPU S3C64XX series Now it's
>> only tested with s3c6410 with UBI & UBIFS.
>> If you want to use it on s3c6400, you should change some values.
>>
>> There's some ugly hack for support s3c64xx. It will be handled more fancy
>> later.
>>
>> Any comments are welcome
>
> Why not just make it a separate driver and skip using onenand_base etc?
>

DId you see the s3c64xx OneNAND controller?. It has its own OneNAND
controller and don't use the OneNAND memory access method as OMAP.

Yes it's possible to make an own OneNAND driver for s3c64xx, but maybe
a half of codes are redundant except the access method  so I want to
use default onenand_base and just redefine the OneNAND function for
s3c64xx.

Thank you,
Kyungmin Park
Adrian Hunter - Oct. 21, 2008, 7:04 a.m.
Kyungmin Park wrote:
> On Tue, Oct 21, 2008 at 12:07 AM, Adrian Hunter
> <ext-adrian.hunter@nokia.com> wrote:
>> Kyungmin Park wrote:
>>> It's OneNAND support on New Samsung Mobile CPU S3C64XX series Now it's
>>> only tested with s3c6410 with UBI & UBIFS.
>>> If you want to use it on s3c6400, you should change some values.
>>>
>>> There's some ugly hack for support s3c64xx. It will be handled more fancy
>>> later.
>>>
>>> Any comments are welcome
>> Why not just make it a separate driver and skip using onenand_base etc?
>>
> 
> DId you see the s3c64xx OneNAND controller?. It has its own OneNAND
> controller and don't use the OneNAND memory access method as OMAP.
> 
> Yes it's possible to make an own OneNAND driver for s3c64xx, but maybe
> a half of codes are redundant except the access method  so I want to
> use default onenand_base and just redefine the OneNAND function for
> s3c64xx.


I glanced at the code.  It did not look like there was much code from
onenand_base that was needed, so wouldn't it be cleaner to just copy
those bits?

Patch

diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig
index cb41cbc..1a4b8f7 100644
--- a/drivers/mtd/onenand/Kconfig
+++ b/drivers/mtd/onenand/Kconfig
@@ -27,6 +27,12 @@  config MTD_ONENAND_GENERIC
 	help
 	  Support for OneNAND flash via platform device driver.
 
+config MTD_ONENAND_S3C64XX
+	tristate "S3C64XX OneNAND Controller support"
+	depends on CPU_S3C64XX
+	help
+	  The S3C64XX has own OneNAND controller.
+
 config MTD_ONENAND_OTP
 	bool "OneNAND OTP Support"
 	help
diff --git a/drivers/mtd/onenand/Makefile b/drivers/mtd/onenand/Makefile
index 4d2eacf..cba6eea 100644
--- a/drivers/mtd/onenand/Makefile
+++ b/drivers/mtd/onenand/Makefile
@@ -7,6 +7,7 @@  obj-$(CONFIG_MTD_ONENAND)		+= onenand.o
 
 # Board specific.
 obj-$(CONFIG_MTD_ONENAND_GENERIC)	+= generic.o
+obj-$(CONFIG_MTD_ONENAND_S3C64XX)	+= s3c64xx.o
 
 # Simulator
 obj-$(CONFIG_MTD_ONENAND_SIM)		+= onenand_sim.o
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c
index 926cf3a..5811999 100644
--- a/drivers/mtd/onenand/onenand_base.c
+++ b/drivers/mtd/onenand/onenand_base.c
@@ -636,6 +636,9 @@  static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
 	int blockpage, found = 0;
 	unsigned int i;
 
+	if (this->options & ONENAND_DONT_USE_BUFFERRAM)
+		return 0;
+
 	if (ONENAND_IS_2PLANE(this))
 		blockpage = onenand_get_2x_blockpage(mtd, addr);
 	else
@@ -832,7 +835,7 @@  static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
 	size_t ooblen = ops->ooblen;
 	u_char *buf = ops->datbuf;
 	u_char *oobbuf = ops->oobbuf;
-	int read = 0, column, thislen;
+	int read = 0, column, thislen, nextlen;
 	int oobread = 0, oobcolumn, thisooblen, oobsize;
 	int ret = 0, boundary = 0;
 	int writesize = this->writesize;
@@ -858,10 +861,16 @@  static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
 
  	/* Read-while-load method */
 
+	thislen = min_t(int, writesize, len - read);
+	column = from & (writesize - 1);
+	if (column + thislen > writesize)
+		thislen = writesize - column;
+
  	/* Do first load to bufferRAM */
  	if (read < len) {
  		if (!onenand_check_bufferram(mtd, from)) {
-			this->command(mtd, ONENAND_CMD_READ, from, writesize);
+			this->main_buf = buf;
+			this->command(mtd, ONENAND_CMD_READ, from, thislen);
  			ret = this->wait(mtd, FL_READING);
  			onenand_update_bufferram(mtd, from, !ret);
 			if (ret == -EBADMSG)
@@ -869,16 +878,13 @@  static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
  		}
  	}
 
-	thislen = min_t(int, writesize, len - read);
-	column = from & (writesize - 1);
-	if (column + thislen > writesize)
-		thislen = writesize - column;
-
  	while (!ret) {
  		/* If there is more to load then start next load */
  		from += thislen;
  		if (read + thislen < len) {
-			this->command(mtd, ONENAND_CMD_READ, from, writesize);
+			this->main_buf = buf + thislen;
+			nextlen = min_t(int, writesize, len - thislen - read);
+			this->command(mtd, ONENAND_CMD_READ, from, nextlen);
  			/*
  			 * Chip boundary handling in DDP
  			 * Now we issued chip 1 read and pointed chip 1
@@ -999,6 +1005,7 @@  static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
 		thislen = oobsize - column;
 		thislen = min_t(int, thislen, len);
 
+		this->spare_buf = buf;
 		this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
 
 		onenand_update_bufferram(mtd, from, 0);
@@ -1129,11 +1136,8 @@  static int onenand_bbt_wait(struct mtd_info *mtd, int state)
 
 	if (interrupt & ONENAND_INT_READ) {
 		int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
-		if (ecc & ONENAND_ECC_2BIT_ALL) {
-			printk(KERN_INFO "onenand_bbt_wait: ecc error = 0x%04x"
-				", controller error 0x%04x\n", ecc, ctrl);
+		if (ecc & ONENAND_ECC_2BIT_ALL)
 			return ONENAND_BBT_READ_ERROR;
-		}
 	} else {
 		printk(KERN_ERR "onenand_bbt_wait: read timeout!"
 			"ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt);
@@ -1189,11 +1193,12 @@  int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from,
 		thislen = mtd->oobsize - column;
 		thislen = min_t(int, thislen, len);
 
+		this->spare_buf = buf;
 		this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
 
 		onenand_update_bufferram(mtd, from, 0);
 
-		ret = onenand_bbt_wait(mtd, FL_READING);
+		ret = this->bbt_wait(mtd, FL_READING);
 		if (ret)
 			break;
 
@@ -2116,6 +2121,9 @@  static void onenand_unlock_all(struct mtd_info *mtd)
 		    & ONENAND_CTRL_ONGO)
 			continue;
 
+		if (this->options & ONENAND_SKIP_UNLOCK_CHECK)
+			return;
+
 		/* Check lock status */
 		if (onenand_check_lock_status(this))
 			return;
@@ -2699,6 +2707,10 @@  int onenand_scan(struct mtd_info *mtd, int maxchips)
 		this->command = onenand_command;
 	if (!this->wait)
 		onenand_setup_wait(mtd);
+	if (!this->bbt_wait)
+		this->bbt_wait = onenand_bbt_wait;
+	if (!this->unlock_all)
+		this->unlock_all = onenand_unlock_all;
 
 	if (!this->read_bufferram)
 		this->read_bufferram = onenand_read_bufferram;
@@ -2812,7 +2824,7 @@  int onenand_scan(struct mtd_info *mtd, int maxchips)
 	mtd->owner = THIS_MODULE;
 
 	/* Unlock whole block */
-	onenand_unlock_all(mtd);
+	this->unlock_all(mtd);
 
 	return this->scan_bbt(mtd);
 }
diff --git a/drivers/mtd/onenand/s3c64xx.c b/drivers/mtd/onenand/s3c64xx.c
new file mode 100644
index 0000000..1983ad5
--- /dev/null
+++ b/drivers/mtd/onenand/s3c64xx.c
@@ -0,0 +1,652 @@ 
+/*
+ *  linux/drivers/mtd/onenand/s3c64xx.c
+ *
+ *  Copyright (C) 2008 Samsung Electronics
+ *  Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/onenand.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/mach/flash.h>
+#include <asm/plat-s3c/s3c64xx-onenand.h>
+
+#include <asm/io.h>
+
+#if 0
+#define DPRINTK(format, args...)					\
+do {									\
+	printk("%s[%d]: " format "\n", __func__, __LINE__, ##args);	\
+} while (0)
+#else
+#define DPRINTK(...)			do { } while (0)
+#endif
+
+/* b0010000 << 26 */
+#define AHB_ADDR			0x20000000
+
+#define ONENAND_ERASE_STATUS		0x00
+#define ONENAND_MULTI_ERASE_SET		0x01
+#define ONENAND_ERASE_START		0x03
+
+#define ONENAND_UNLOCK_START		0x08
+#define ONENAND_UNLOCK_END		0x09
+#define ONENAND_LOCK_START		0x0A
+#define ONENAND_LOCK_END		0x0B
+#define ONENAND_LOCK_TIGHT_START	0x0C
+#define ONENAND_LOCK_TIGHT_END		0x0D
+#define ONENAND_UNLOCK_ALL		0x0E
+
+#define MAP_00				(0x0 << 24)
+#define MAP_01				(0x1 << 24)
+#define MAP_10				(0x2 << 24)
+#define MAP_11				(0x3 << 24)
+
+#define MEM_ADDR(fba, fpa, fsa)		(((fba) << 12 | (fpa) << 6 | (fsa) << 4) & 0xffffff)
+
+/* The 'addr' is byte address. It makes a 16-bit word */
+#define CMD_MAP_00(addr)		(AHB_ADDR | MAP_00 | ((addr) << 1))
+#define CMD_MAP_01(mem_addr)	 	(AHB_ADDR | MAP_01 | (mem_addr))
+#define CMD_MAP_10(mem_addr)		(AHB_ADDR | MAP_10 | (mem_addr))
+#define CMD_MAP_11(addr)		(AHB_ADDR | MAP_11 | ((addr) << 2))
+
+struct s3c64xx_onenand {
+	struct mtd_info	*mtd;
+
+	int		sync_mode;
+
+	void __iomem	*base;
+	void __iomem	*ahb_addr;
+
+	int		command_mask;
+	int		bootram_command;
+
+	int		mem_addr;
+	unsigned char	*page_buf;
+	unsigned char	oobbuf[64];
+};
+
+static struct s3c64xx_onenand *onenand;
+
+#ifdef CONFIG_MTD_PARTITIONS
+static const char *part_probes[] = { "cmdlinepart", NULL, };
+#endif
+
+static inline int s3c64xx_read_reg(int offset)
+{
+	return readl(onenand->base + offset);
+}
+
+static inline void s3c64xx_write_reg(int value, int offset)
+{
+	writel(value, onenand->base + offset);
+}
+
+static inline int s3c64xx_read_cmd(unsigned int cmd)
+{
+	return readl(onenand->ahb_addr + ((cmd) & onenand->command_mask));
+}
+
+static inline void s3c64xx_write_cmd(int value, unsigned int cmd)
+{
+	writel(value, onenand->ahb_addr + ((cmd) & onenand->command_mask));
+}
+
+static void s3c64xx_onenand_reset(void)
+{
+	int stat;
+
+	s3c64xx_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET0_OFFSET);
+	while (1) {
+		stat = s3c64xx_read_reg(INT_ERR_STAT0_OFFSET);
+		if (stat & INT_ACT)
+			break;
+	}
+	s3c64xx_write_reg(stat, INT_ERR_ACK0_OFFSET);
+
+	/* Clear interrupt */
+	s3c64xx_write_reg(0x0, INT_ERR_ACK0_OFFSET);
+	/* Clear the ECC status */
+	s3c64xx_write_reg(0x0, ECC_ERR_STAT0_OFFSET);
+}
+
+static unsigned short s3c64xx_onenand_readw(void __iomem *addr)
+{
+	struct onenand_chip *this = onenand->mtd->priv;
+	int reg = addr - this->base;
+	int word_addr = reg >> 1;
+
+	/* It's used for probing time */
+	switch (reg) {
+	case ONENAND_REG_MANUFACTURER_ID:
+		return s3c64xx_read_reg(MANUFACT_ID0_OFFSET);
+	case ONENAND_REG_DEVICE_ID:
+		return s3c64xx_read_reg(DEVICE_ID0_OFFSET);
+	case ONENAND_REG_VERSION_ID:
+		return s3c64xx_read_reg(FLASH_VER_ID0_OFFSET);
+	case ONENAND_REG_DATA_BUFFER_SIZE:
+		return s3c64xx_read_reg(DATA_BUF_SIZE0_OFFSET);
+	case ONENAND_REG_SYS_CFG1:
+		return s3c64xx_read_reg(MEM_CFG0_OFFSET);
+
+	default:
+		break;
+	}
+
+	/* BootRAM access control */
+	if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) {
+		if (word_addr == 0)
+			return s3c64xx_read_reg(MANUFACT_ID0_OFFSET);
+		if (word_addr == 1)
+			return s3c64xx_read_reg(DEVICE_ID0_OFFSET);
+		if (word_addr == 2)
+			return s3c64xx_read_reg(FLASH_VER_ID0_OFFSET);
+	}
+
+	DPRINTK("illegal reg 0x%x, -> 0x%x 0x%x", word_addr, s3c64xx_read_cmd(CMD_MAP_11(word_addr)), s3c64xx_read_reg(INT_ERR_STAT0_OFFSET));
+	return s3c64xx_read_cmd(CMD_MAP_11(word_addr)) & 0xffff;
+}
+
+static void s3c64xx_onenand_writew(unsigned short value, void __iomem *addr)
+{
+	struct onenand_chip *this = onenand->mtd->priv;
+	int reg = addr - this->base;
+	int word_addr = reg >> 1;
+
+	/* It's used for probing time */
+	switch (reg) {
+	case ONENAND_REG_SYS_CFG1:
+		s3c64xx_write_reg(value, MEM_CFG0_OFFSET);
+		return;
+
+	/* Lock/lock-tight/unlock/unlock_all */
+	case ONENAND_REG_START_BLOCK_ADDRESS:
+		return;
+
+	default:
+		break;
+	}
+
+	/* BootRAM access control */
+	if ((unsigned int) addr < ONENAND_DATARAM) {
+		if (value == ONENAND_CMD_READID) {
+			onenand->bootram_command = 1;
+			return;
+		}
+		if (value == ONENAND_CMD_RESET) {
+			s3c64xx_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET0_OFFSET);
+			onenand->bootram_command = 0;
+			return;
+		}
+	}
+
+	DPRINTK("illegal reg 0x%x, value 0x%x", word_addr, value);
+	s3c64xx_write_cmd(value, CMD_MAP_11(word_addr));
+}
+
+static int s3c64xx_onenand_wait(struct mtd_info *mtd, int state)
+{
+	unsigned long timeout;
+	unsigned int flags = INT_ACT;
+	unsigned int stat, ecc;
+
+	/* The 20 msec is enough */
+	timeout = jiffies + msecs_to_jiffies(20);
+	while (time_before(jiffies, timeout)) {
+		stat = s3c64xx_read_reg(INT_ERR_STAT0_OFFSET);
+		if (stat & flags)
+			break;
+
+		if (state != FL_READING)
+			cond_resched();
+	}
+	/* To get correct interrupt status in timeout case */
+	stat = s3c64xx_read_reg(INT_ERR_STAT0_OFFSET);
+
+	s3c64xx_write_reg(stat, INT_ERR_ACK0_OFFSET);
+	if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | INT_TO | LD_FAIL_ECC_ERR)) {
+		printk(KERN_INFO "s3c64xx_onenand_wait: controller error = 0x%04x\n", stat);
+		if (stat & LOCKED_BLK)
+			printk(KERN_INFO "s3c64xx_onenand_wait: it's locked error\n");
+
+		return -EIO;
+	}
+
+	if (stat & LOAD_CMP) {
+		ecc = s3c64xx_read_reg(ECC_ERR_STAT0_OFFSET);
+		if (ecc & ONENAND_ECC_2BIT_ALL) {
+			printk(KERN_INFO "onenand_wait: ECC error = 0x%04x\n", ecc);
+			return -EBADMSG;
+		}
+	}
+
+	return 0;
+}
+
+static int s3c64xx_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr,
+                           size_t len)
+{
+	struct onenand_chip *this = mtd->priv;
+	unsigned int *m, *s;
+	int fba, fpa, fsa = 0;
+	int mem_addr;
+	int i, count;
+	int writesize = this->writesize;
+	int column;
+	int dummy, copy = 0;
+
+	fba = (int) (addr >> this->erase_shift);
+	fpa = (int) (addr >> this->page_shift);
+	fpa &= this->page_mask;
+
+	mem_addr = MEM_ADDR(fba, fpa, fsa);
+
+#if 0
+	if (cmd != ONENAND_CMD_READOOB) // && (addr & 0x1ff || len & 0x1ff))
+		printk("cmd 0x%x, addr 0x%x, fba %d, fpa %d, len 0x%x\n", cmd, (unsigned int) addr, fba, fpa, len);
+#endif
+
+	switch (cmd) {
+	case ONENAND_CMD_READ:
+		column = addr & (writesize - 1);
+
+		/* Check it's already read */
+#if 0
+		if (onenand->mem_addr == mem_addr) {
+			memcpy(this->main_buf, onenand->page_buf + column, len);
+			return 0;
+		}
+#endif
+		if (len == mtd->writesize)
+			m = (unsigned int *) this->main_buf;
+		else {
+			m = (unsigned int *) onenand->page_buf;
+			copy = 1;
+		}
+
+		count = mtd->writesize >> 2;
+		for (i = 0; i < count; i++)
+			*m++ = s3c64xx_read_cmd(CMD_MAP_01(mem_addr));
+
+		if (copy) {
+			memcpy(this->main_buf, onenand->page_buf + column, len);
+			onenand->mem_addr = mem_addr;
+		} else
+			onenand->mem_addr = -1;
+		return 0;
+
+	case ONENAND_CMD_READOOB:
+		s3c64xx_write_reg(TSRF, TRANS_SPARE0_OFFSET);
+
+		/* Main */
+		count = mtd->writesize >> 2;
+		for (i = 0; i < count; i++)
+			dummy = s3c64xx_read_cmd(CMD_MAP_01(mem_addr));
+
+		/* Spare */
+		memset(onenand->oobbuf, 0xff, mtd->oobsize);
+		s = (unsigned int *) onenand->oobbuf;
+		count = mtd->oobsize >> 2;
+		for (i = 0; i < count; i++)
+			*s++ = s3c64xx_read_cmd(CMD_MAP_01(mem_addr));
+
+		m = (unsigned int *) this->spare_buf;
+		s = (unsigned int *) onenand->oobbuf;
+		count = len >> 2;
+		for (i = 0; i < count; i++)
+			*m++ = *s++;
+
+		s3c64xx_write_reg(~TSRF, TRANS_SPARE0_OFFSET);
+		return 0;
+
+	case ONENAND_CMD_PROG:
+//		s3c64xx_write_reg(TSRF, TRANS_SPARE0_OFFSET);
+		m = (unsigned int *) this->main_buf;
+		if (len != mtd->writesize)
+			printk(KERN_ERR "length error %d", len);
+		DPRINTK("write buffer 0x%x\n", (unsigned int) this->main_buf);
+		count = len >> 2;
+		for (i = 0; i < count; i++)
+			s3c64xx_write_cmd(*m++, CMD_MAP_01(mem_addr));
+		/* FIXME how to write oob together */
+#if 0
+		s = (unsigned int *) this->spare_buf;
+		count = mtd->oobsize >> 2;
+		for (i = 0; i < count; i++)
+			s3c64xx_write_cmd(*s++, CMD_MAP_01(mem_addr));
+#endif
+//		s3c64xx_write_reg(~TSRF, TRANS_SPARE0_OFFSET);
+		return 0;
+
+	case ONENAND_CMD_PROGOOB:
+		s3c64xx_write_reg(TSRF, TRANS_SPARE0_OFFSET);
+
+		/* Main */
+		count = mtd->writesize >> 2;
+		for (i = 0; i < count; i++)
+			s3c64xx_write_cmd(0xffffffff, CMD_MAP_01(mem_addr));
+
+		/* Copy spare buffer to oob buffer */
+		memset(onenand->oobbuf, 0xff, mtd->oobsize);
+		memcpy(onenand->oobbuf, this->spare_buf, len);
+
+		/* Spare */
+		s = (unsigned int *) onenand->oobbuf;
+		count = mtd->oobsize >> 2;
+		for (i = 0; i < count; i++)
+			s3c64xx_write_cmd(*s++, CMD_MAP_01(mem_addr));
+
+		s3c64xx_write_reg(~TSRF, TRANS_SPARE0_OFFSET);
+		return 0;
+
+	case ONENAND_CMD_UNLOCK_ALL:
+		s3c64xx_write_cmd(ONENAND_UNLOCK_ALL, CMD_MAP_10(mem_addr));
+		return 0;
+
+	case ONENAND_CMD_ERASE:
+		s3c64xx_write_cmd(ONENAND_ERASE_START, CMD_MAP_10(mem_addr));
+		return 0;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int s3c64xx_read_bufferram(struct mtd_info *mtd, int area,
+				  unsigned char *buffer, int offset,
+				  size_t count)
+{
+#if 0
+	if (area == ONENAND_DATARAM && (offset & 0x1ff || count & 0x1ff))
+		printk("0x%x, 0x%x, 0x%x, 0x%x\n", area, (unsigned int) buffer, offset, count);
+#endif
+	return 0;
+}
+
+static int s3c64xx_write_bufferram(struct mtd_info *mtd, int area,
+				   const unsigned char *buffer, int offset,
+				   size_t count)
+{
+	struct onenand_chip *this = mtd->priv;
+
+	DPRINTK("0x%x, 0x%x, 0x%x, 0x%x", area, (unsigned int) buffer, offset, (unsigned int) count);
+	if (area == ONENAND_DATARAM)
+		this->main_buf = (unsigned char *) buffer;
+	else 
+		this->spare_buf = (unsigned char *) buffer;
+
+	return 0;
+}
+
+static int s3c64xx_onenand_bbt_wait(struct mtd_info *mtd, int state)
+{
+	unsigned long timeout;
+	unsigned int flags = INT_ACT;
+	unsigned int stat;
+
+	/* The 20 msec is enough */
+	timeout = jiffies + msecs_to_jiffies(20);
+	while (time_before(jiffies, timeout)) {
+		stat = s3c64xx_read_reg(INT_ERR_STAT0_OFFSET);
+		if (stat & flags)
+			break;
+
+		if (state != FL_READING)
+			cond_resched();
+	}
+	/* To get correct interrupt status in timeout case */
+	stat = s3c64xx_read_reg(INT_ERR_STAT0_OFFSET);
+
+	s3c64xx_write_reg(stat, INT_ERR_ACK0_OFFSET);
+	if (stat & LD_FAIL_ECC_ERR) {
+		s3c64xx_onenand_reset();
+		return ONENAND_BBT_READ_ERROR;
+	}
+
+	if (stat & LOAD_CMP) {
+		int ecc = s3c64xx_read_reg(ECC_ERR_STAT0_OFFSET);
+		if (ecc & ONENAND_ECC_2BIT_ALL) {
+			s3c64xx_onenand_reset();
+			return ONENAND_BBT_READ_ERROR;
+		}
+	} else
+		return ONENAND_BBT_READ_FATAL_ERROR;
+
+	return 0;
+}
+
+void s3c64xx_set_width_regs(struct onenand_chip *this)
+{
+	int dev_id, ddp, density;
+	int dbs_dfs, fba, fpa, fsa;
+
+	dev_id = s3c64xx_read_reg(DEVICE_ID0_OFFSET);
+
+	ddp = dev_id & ONENAND_DEVICE_IS_DDP;
+	density = (dev_id >> ONENAND_DEVICE_DENSITY_SHIFT) & 0xf;
+
+	dbs_dfs = 0;
+	fba = density + 7;
+	fpa = 6;
+	fsa = 2;
+
+	if (ddp) {
+		dbs_dfs = 1;
+		fba--;
+	}
+
+	s3c64xx_write_reg(fba, FBA_WIDTH0_OFFSET); 
+	s3c64xx_write_reg(fpa, FPA_WIDTH0_OFFSET); 
+	s3c64xx_write_reg(fsa, FSA_WIDTH0_OFFSET); 
+	s3c64xx_write_reg(dbs_dfs, DBS_DFS_WIDTH0_OFFSET); 
+}
+
+void s3c64xx_onenand_setup(struct mtd_info *mtd)
+{
+	struct onenand_chip *this = mtd->priv;
+
+	onenand->mtd = mtd;
+
+	/* XXX use cpu_is_s3c6400 style function if provided */
+	/* S3C6400 */
+	onenand->command_mask = 0x0ffffff;
+	/* S3C6410 */
+	onenand->command_mask = 0x3ffffff;
+
+	this->read_word = s3c64xx_onenand_readw;
+	this->write_word = s3c64xx_onenand_writew;
+
+	this->wait = s3c64xx_onenand_wait;
+	this->bbt_wait = s3c64xx_onenand_bbt_wait;
+	this->command = s3c64xx_onenand_command;
+
+	this->read_bufferram = s3c64xx_read_bufferram;
+	this->write_bufferram = s3c64xx_write_bufferram;
+
+	this->options |= ONENAND_SKIP_UNLOCK_CHECK;
+	this->options |= ONENAND_DONT_USE_BUFFERRAM;
+}
+
+static int s3c64xx_onenand_probe(struct platform_device *pdev)
+{
+	struct flash_platform_data *pdata;
+	struct onenand_chip *this;
+	struct mtd_info *mtd;
+	struct resource *r;
+	int size, err;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data defined\n");
+		return -ENODEV;
+	}
+
+	size = sizeof(struct mtd_info) + sizeof(struct onenand_chip);
+	mtd = kzalloc(size, GFP_KERNEL);
+	if (!mtd) {
+		dev_err(&pdev->dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	onenand = kzalloc(sizeof(struct s3c64xx_onenand), GFP_KERNEL);
+	if (!onenand) {
+		err = -ENOMEM;
+		goto onenand_fail;
+	}
+
+	this = (struct onenand_chip *) &mtd[1];
+	mtd->priv = this;
+
+	s3c64xx_onenand_setup(mtd);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		dev_err(&pdev->dev, "no resource defined\n");
+		return -ENXIO;
+		goto resource_failed;
+	}
+
+	r = request_mem_region(r->start, r->end - r->start + 1, pdev->name);
+	if (!r) {
+		dev_err(&pdev->dev, "failed to request memory resource\n");
+		err = -EBUSY;
+		goto request_failed;
+	}
+
+	onenand->base = ioremap(r->start, r->end - r->start + 1);
+	if (!onenand->base) {
+		err = -EFAULT;
+		goto ioremap_failed;
+	}
+
+	onenand->ahb_addr = ioremap(AHB_ADDR, SZ_256M);
+	if (!onenand->ahb_addr) {
+		err = -EINVAL;
+		goto ahb_failed;
+	}
+
+	platform_set_drvdata(pdev, mtd);
+
+	if (onenand_scan(mtd, 1)) {
+		err = -EFAULT;
+		goto scan_failed;
+	}
+	
+	onenand->page_buf = kzalloc(mtd->writesize, GFP_KERNEL);
+	if (!onenand->page_buf) {
+		err = -ENOMEM;
+		goto page_buf_fail;
+	}
+	onenand->mem_addr = -1;
+
+	/* S3C64XX don't handle subpage write */
+	mtd->subpage_sft = 0;
+	this->subpagesize = mtd->writesize;
+
+	if (s3c64xx_read_reg(MEM_CFG0_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ) {
+		printk(KERN_INFO "OneNAND Sync. Burst Read enabled\n");
+		onenand->sync_mode = 1;
+	}
+
+	s3c64xx_set_width_regs(this);
+
+#ifdef CONFIG_MTD_PARTITIONS
+	err = parse_mtd_partitions(mtd, part_probes, &pdata->parts, 0);
+	if (err > 0)
+		add_mtd_partitions(mtd, pdata->parts, err);
+	else if (pdata->parts)
+		add_mtd_partitions(mtd, pdata->parts, pdata->nr_parts);
+	else
+#endif
+		err = add_mtd_device(mtd);
+
+	return 0;
+page_buf_fail:
+	onenand_release(mtd);
+scan_failed:
+	iounmap(onenand->ahb_addr);
+ahb_failed:
+	iounmap(onenand->base);
+ioremap_failed:
+	release_mem_region(r->start, r->end - r->start + 1);
+request_failed:
+resource_failed:
+	kfree(onenand);
+onenand_fail:
+	kfree(mtd);
+	return err;
+}
+
+static int s3c64xx_onenand_remove(struct platform_device *pdev)
+{
+	struct mtd_info *mtd = platform_get_drvdata(pdev);
+
+	onenand_release(mtd);
+	iounmap(onenand->base);
+	platform_set_drvdata(pdev, NULL);
+	kfree(onenand->page_buf);
+	kfree(onenand);
+	kfree(mtd);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c64xx_onenand_suspend(struct platform_device *pdev, pm_message_t pm)
+{
+	struct mtd_info *mtd = platform_get_drvdata(pdev);
+	struct onenand_chip *this = mtd->priv;
+
+	this->wait(mtd, FL_PM_SUSPENDED);
+	return mtd->suspend(mtd);
+}
+
+static int s3c64xx_onenand_resume(struct platform_device *pdev)
+{
+	struct mtd_info *mtd = platform_get_drvdata(pdev);
+	struct onenand_chip *this = mtd->priv;
+
+	mtd->resume(mtd);
+	this->unlock_all(mtd);
+	return 0;
+}
+#else
+#define s3c64xx_onenand_suspend		NULL
+#define s3c64xx_onenand_resume		NULL
+#endif
+
+static struct platform_driver s3c64xx_onenand_driver = {
+	.driver		= {
+		.name		= "s3c64xx-onenand",
+	},
+	.probe		= s3c64xx_onenand_probe,
+	.remove		= s3c64xx_onenand_remove,
+	.suspend	= s3c64xx_onenand_suspend,
+	.resume		= s3c64xx_onenand_resume,
+};
+
+static int __init s3c64xx_onenand_init(void)
+{
+	return platform_driver_register(&s3c64xx_onenand_driver);
+}
+
+static void __exit s3c64xx_onenand_exit(void)
+{
+	platform_driver_unregister(&s3c64xx_onenand_driver);
+}
+
+module_init(s3c64xx_onenand_init);
+module_exit(s3c64xx_onenand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("Samsung S3C64XX OneNAND controller support");
diff --git a/include/asm-arm/plat-s3c/s3c64xx-onenand.h b/include/asm-arm/plat-s3c/s3c64xx-onenand.h
new file mode 100644
index 0000000..d478019
--- /dev/null
+++ b/include/asm-arm/plat-s3c/s3c64xx-onenand.h
@@ -0,0 +1,77 @@ 
+#ifndef __S3C64XX_ONENAND_H__
+#define __S3C64XX_ONENAND_H__
+
+#include <mach/hardware.h>
+
+/*
+ * OneNAND Controller
+ */
+#define S3C64XX_ONENAND_BASE	0x70100000
+
+#define MEM_CFG0_OFFSET		0x0000
+#define BURST_LEN0_OFFSET	0x0010
+#define MEM_RESET0_OFFSET	0x0020
+#define INT_ERR_STAT0_OFFSET	0x0030
+#define INT_ERR_MASK0_OFFSET	0x0040
+#define INT_ERR_ACK0_OFFSET	0x0050
+#define ECC_ERR_STAT0_OFFSET	0x0060
+#define MANUFACT_ID0_OFFSET	0x0070
+#define DEVICE_ID0_OFFSET	0x0080
+#define DATA_BUF_SIZE0_OFFSET	0x0090
+#define BOOT_BUF_SIZE0_OFFSET	0x00A0
+#define BUF_AMOUNT0_OFFSET	0x00B0
+#define TECH0_OFFSET		0x00C0
+#define FBA_WIDTH0_OFFSET	0x00D0
+#define FPA_WIDTH0_OFFSET	0x00E0
+#define FSA_WIDTH0_OFFSET	0x00F0
+#define TRANS_SPARE0_OFFSET	0x0140
+#define DBS_DFS_WIDTH0_OFFSET	0x0160
+#define INT_PIN_ENABLE0_OFFSET	0x01A0
+#define ACC_CLOCK0_OFFSET	0x01C0
+#define FLASH_VER_ID0_OFFSET	0x01F0
+#define FLASH_AUX_CNTRL0_OFFSET	0x0300
+
+#ifndef __KERNEL__
+#define MEM_CFG0_REG		__REG(S3C64XX_ONENAND_BASE + MEM_CFG0_OFFSET)
+#define BURST_LEN0_REG		__REG(S3C64XX_ONENAND_BASE + BURST_LEN0_OFFSET)
+#define MEM_RESET0_REG		__REG(S3C64XX_ONENAND_BASE + MEM_RESET0_OFFSET)
+#define INT_ERR_STAT0_REG	__REG(S3C64XX_ONENAND_BASE + INT_ERR_STAT0_OFFSET)
+#define INT_ERR_MASK0_REG	__REG(S3C64XX_ONENAND_BASE + INT_ERR_MASK0_OFFSET)
+#define INT_ERR_ACK0_REG	__REG(S3C64XX_ONENAND_BASE + INT_ERR_ACK0_OFFSET)
+#define ECC_ERR_STAT0_REG	__REG(S3C64XX_ONENAND_BASE + ECC_ERR_STAT0_OFFSET)
+#define MANUFACT_ID0_REG	__REG(S3C64XX_ONENAND_BASE + MANUFACT_ID0_OFFSET)
+#define DEVICE_ID0_REG		__REG(S3C64XX_ONENAND_BASE + DEVICE_ID0_OFFSET)
+#define DATA_BUF_SIZE0_REG	__REG(S3C64XX_ONENAND_BASE + DATA_BUF_SIZE0_OFFSET)
+#define FBA_WIDTH0_REG		__REG(S3C64XX_ONENAND_BASE + FBA_WIDTH0_OFFSET)
+#define FPA_WIDTH0_REG		__REG(S3C64XX_ONENAND_BASE + FPA_WIDTH0_OFFSET)
+#define FSA_WIDTH0_REG		__REG(S3C64XX_ONENAND_BASE + FSA_WIDTH0_OFFSET)
+#define TRANS_SPARE0_REG	__REG(S3C64XX_ONENAND_BASE + TRANS_SPARE0_OFFSET)
+#define DBS_DFS_WIDTH0_REG	__REG(S3C64XX_ONENAND_BASE + DBS_DFS_WIDTH0_OFFSET)
+#define INT_PIN_ENABLE0_REG	__REG(S3C64XX_ONENAND_BASE + INT_PIN_ENABLE0_OFFSET)
+#define ACC_CLOCK0_REG		__REG(S3C64XX_ONENAND_BASE + ACC_CLOCK0_OFFSET)
+#define FLASH_VER_ID0_REG	__REG(S3C64XX_ONENAND_BASE + FLASH_VER_ID0_OFFSET)
+#define FLASH_AUX_CNTRL0_REG	__REG(S3C64XX_ONENAND_BASE + FLASH_AUX_CNTRL0_OFFSET)
+#endif
+
+#define ONENAND_MEM_RESET_HOT	0x3
+#define ONENAND_MEM_RESET_COLD	0x2
+#define ONENAND_MEM_RESET_WARM	0x1
+
+#define CACHE_OP_ERR    (1 << 13)
+#define RST_CMP         (1 << 12)
+#define RDY_ACT         (1 << 11)
+#define INT_ACT         (1 << 10)
+#define UNSUP_CMD       (1 << 9)
+#define LOCKED_BLK      (1 << 8)
+#define BLK_RW_CMP      (1 << 7)
+#define ERS_CMP         (1 << 6)
+#define PGM_CMP         (1 << 5)
+#define LOAD_CMP        (1 << 4)
+#define ERS_FAIL        (1 << 3)
+#define PGM_FAIL        (1 << 2)
+#define INT_TO          (1 << 1)
+#define LD_FAIL_ECC_ERR (1 << 0)
+
+#define TSRF		(1 << 0)
+
+#endif
diff --git a/include/linux/mtd/onenand.h b/include/linux/mtd/onenand.h
index 9aa2a91..64f173a 100644
--- a/include/linux/mtd/onenand.h
+++ b/include/linux/mtd/onenand.h
@@ -108,6 +108,8 @@  struct onenand_chip {
 
 	int (*command)(struct mtd_info *mtd, int cmd, loff_t address, size_t len);
 	int (*wait)(struct mtd_info *mtd, int state);
+	int (*bbt_wait)(struct mtd_info *mtd, int state);
+	void (*unlock_all)(struct mtd_info *mtd);
 	int (*read_bufferram)(struct mtd_info *mtd, int area,
 			unsigned char *buffer, int offset, size_t count);
 	int (*write_bufferram)(struct mtd_info *mtd, int area,
@@ -121,6 +123,9 @@  struct onenand_chip {
 	struct completion	complete;
 	int			irq;
 
+	unsigned char		*main_buf;
+	unsigned char		*spare_buf;
+
 	spinlock_t		chip_lock;
 	wait_queue_head_t	wq;
 	onenand_state_t		state;
@@ -169,6 +174,8 @@  struct onenand_chip {
 #define ONENAND_HAS_CONT_LOCK		(0x0001)
 #define ONENAND_HAS_UNLOCK_ALL		(0x0002)
 #define ONENAND_HAS_2PLANE		(0x0004)
+#define ONENAND_SKIP_UNLOCK_CHECK	(0x0010)
+#define ONENAND_DONT_USE_BUFFERRAM	(0x0020)
 #define ONENAND_PAGEBUF_ALLOC		(0x1000)
 #define ONENAND_OOBBUF_ALLOC		(0x2000)