Skip to contentSkip to conversation

Samsung SoCs: OneNAND support

PATCH New Kyungmin Park 4 participants 7 messages Sep 17, 2009 – Sep 18, 2009

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

S3C64XX, S5PC1XX series OneNAND driver support

S3C6400, S3C6410, and S5PC100 use own OneNAND controller by AHB bus BTW, S5PC110 use generic OneNAND APIs.

This is based on Samsung SoCs cpu detection.

---

arch/arm/plat-s3c/include/plat/regs-onenand.h new file +75 -0
new file mode 100644
index 0000000..943b51c
@@ -0,0 +1,75 @@
+/*
+ * linux/arch/arm/plat-s3c/include/plat/regs-onenand.h
+ *
+ *  Copyright (C) 2008-2009 Samsung Electronics
+ *  Kyungmin Park <[email protected]>
+ *
+ * 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.
+ */
+#ifndef __SAMSUNG_ONENAND_H__
+#define __SAMSUNG_ONENAND_H__
+
+#include <mach/hardware.h>
+
+/*
+ * OneNAND Controller
+ */
+#define S3C64XX_ONENAND0_BASE	0x70100000
+#define S3C64XX_ONENAND1_BASE	0x70200000
+#define S5PC100_ONENAND_BASE	0xE7100000
+#define S5PC110_ONENAND_BASE	0xB0000000
+
+#ifdef CONFIG_ARCH_S3C64XX
+#define SAMSUNG_ONENAND_BASE	S3C64XX_ONENAND0_BASE
+#endif
+#ifdef CONFIG_ARCH_S5PC1XX
+#define SAMSUNG_ONENAND_BASE	S5PC100_ONENAND_BASE
+#endif
+
+#define MEM_CFG_OFFSET		0x0000
+#define BURST_LEN_OFFSET	0x0010
+#define MEM_RESET_OFFSET	0x0020
+#define INT_ERR_STAT_OFFSET	0x0030
+#define INT_ERR_MASK_OFFSET	0x0040
+#define INT_ERR_ACK_OFFSET	0x0050
+#define ECC_ERR_STAT_OFFSET	0x0060
+#define MANUFACT_ID_OFFSET	0x0070
+#define DEVICE_ID_OFFSET	0x0080
+#define DATA_BUF_SIZE_OFFSET	0x0090
+#define BOOT_BUF_SIZE_OFFSET	0x00A0
+#define BUF_AMOUNT_OFFSET	0x00B0
+#define TECH_OFFSET		0x00C0
+#define FBA_WIDTH_OFFSET	0x00D0
+#define FPA_WIDTH_OFFSET	0x00E0
+#define FSA_WIDTH_OFFSET	0x00F0
+#define TRANS_SPARE_OFFSET	0x0140
+#define DBS_DFS_WIDTH_OFFSET	0x0160
+#define INT_PIN_ENABLE_OFFSET	0x01A0
+#define ACC_CLOCK_OFFSET	0x01C0
+#define FLASH_VER_ID_OFFSET	0x01F0
+#define FLASH_AUX_CNTRL_OFFSET	0x0300		/* s3c64xx only */
+
+#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
drivers/mtd/onenand/Kconfig +7 -0
index 79fa79e..74ad1a9 100644
@@ -34,6 +34,13 @@ config MTD_ONENAND_OMAP2
 	  Support for a OneNAND flash device connected to an OMAP2/OMAP3 CPU
 	  via the GPMC memory controller.
 
+config MTD_ONENAND_SAMSUNG
+        tristate "OneNAND on Samsung SOC controller support"
+        depends on MTD_ONENAND && (ARCH_S3C64XX || ARCH_S5PC1XX)
+        help
+          Support for a OneNAND flash device connected to an Samsung SOC
+          S3C64XX/S5PC1XX controller.
+
 config MTD_ONENAND_OTP
 	bool "OneNAND OTP Support"
 	select HAVE_MTD_OTP
drivers/mtd/onenand/Makefile +1 -0
index 64b6cc6..2b7884c 100644
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_ONENAND)		+= onenand.o
 # Board specific.
 obj-$(CONFIG_MTD_ONENAND_GENERIC)	+= generic.o
 obj-$(CONFIG_MTD_ONENAND_OMAP2)		+= omap2.o
+obj-$(CONFIG_MTD_ONENAND_SAMSUNG)       += samsung.o
 
 # Simulator
 obj-$(CONFIG_MTD_ONENAND_SIM)		+= onenand_sim.o
drivers/mtd/onenand/samsung.c new file +835 -0

835 lines not on this page · raw message

include/linux/mtd/onenand.h +10 -0
index 8ed8733..e35909b 100644
@@ -214,4 +214,14 @@ unsigned onenand_block(struct onenand_chip *this, loff_t addr);
 loff_t onenand_addr(struct onenand_chip *this, int block);
 int flexonenand_region(struct mtd_info *mtd, loff_t addr);
 
+struct mtd_partition;
+
+struct onenand_platform_data {
+	void		(*mmcontrol)(struct mtd_info *mtd, int sync_read);
+	int		(*read_bufferram)(struct mtd_info *mtd, int area,
+			unsigned char *buffer, int offset, size_t count);
+	struct mtd_partition *parts;
+	unsigned int	nr_parts;
+};
+
 #endif	/* __LINUX_MTD_ONENAND_H */
include/linux/mtd/onenand_regs.h +1 -0
index 86a6bbe..a69d140 100644
@@ -206,6 +206,7 @@
 #define ONENAND_ECC_1BIT_ALL		(0x5555)
 #define ONENAND_ECC_2BIT		(1 << 1)
 #define ONENAND_ECC_2BIT_ALL		(0xAAAA)
+#define ONENAND_ECC_4BIT_UNCORRECTABLE	(0x1010)
 #define FLEXONENAND_UNCORRECTABLE_ERROR	(0x1010)
 
 /*
#1 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

Quoted 37 lines · Kyungmin Park
drivers/mtd/onenand/samsung.c new file +37 -0
+static int s3c_onenand_wait(struct mtd_info *mtd, int state)
+{
+	unsigned int flags = INT_ACT;
+	unsigned int stat, ecc;
+	unsigned long timeout;
+
+	switch (state) {
+	case FL_READING:
+		flags |= BLK_RW_CMP | LOAD_CMP;
+		break;
+	case FL_WRITING:
+		flags |= BLK_RW_CMP | PGM_CMP;
+		break;
+	case FL_ERASING:
+		flags |= BLK_RW_CMP | ERS_CMP;
+		break;
+	case FL_LOCKING:
+		flags |= BLK_RW_CMP;
+		break;
+	default:
+		break;
+	}
+
+	/* The 20 msec is enough */
+	timeout = jiffies + msecs_to_jiffies(20);
+	while (time_before(jiffies, timeout)) {
+		stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+		if (stat&  flags)
+			break;
+
+		if (state != FL_READING)
+			cond_resched();
+	}
+	/* To get correct interrupt status in timeout case */
+	stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+	s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+

drivers/mtd/onenand/samsung.c line 317

Quoted 10 lines
drivers/mtd/onenand/samsung.c new file +10 -0
+	/*
+	 * 	
+	 * In the Spec. it checks the controller status first
+	 * However if you get the correct information in case of
+	 * power off recovery (POR) test, it should read ECC status first
+	 */
+	if (stat&  LOAD_CMP) {
+		ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+		if (ecc&  ONENAND_ECC_4BIT_UNCORRECTABLE) {
+			printk(KERN_INFO "onenand_wait: ECC error = 0x%04x\n", ecc);

Copy-paste error? Also see below.

drivers/mtd/onenand/samsung.c line 326

Quoted 9 lines
drivers/mtd/onenand/samsung.c new file +9 -0
+			mtd->ecc_stats.failed++;
+			return -EBADMSG;
+		}
+	}
+
+	if (stat&  (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) {
+		printk(KERN_INFO "s3c_onenand_wait: controller error = 0x%04x\n", stat);
+		if (stat&  LOCKED_BLK)
+			printk(KERN_INFO "s3c_onenand_wait: it's locked error = 0x%04x\n", stat);

Very quick comment. Could you please avoid this style of putting function name to the printk? We have lots of this in onenand_base.c, and they are often wrong, because function names change.

Could we please use dev_printk(), dev_dbg() and the like instead? You'll have to do some more work and make MTD a bit more linux-device-model-friendly, but someone has to do this :-)

But in the worst case, use at least %s + __func__.

And you could also clean things up similarly for onenenand.

Signature · 2 lines
Best Regards,
Artem Bityutskiy (????? ????????)
#2 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

Quoted 44 lines · Artem Bityutskiy
Quoted 43 lines · Kyungmin Park
(quoted diff) +42 -0
+static int s3c_onenand_wait(struct mtd_info *mtd, int state)
+{
+ ? ? ? unsigned int flags = INT_ACT;
+ ? ? ? unsigned int stat, ecc;
+ ? ? ? unsigned long timeout;
+
+ ? ? ? switch (state) {
+ ? ? ? case FL_READING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | LOAD_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_WRITING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | PGM_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_ERASING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | ERS_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_LOCKING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? default:
+ ? ? ? ? ? ? ? break;
+ ? ? ? }
+
+ ? ? ? /* The 20 msec is enough */
+ ? ? ? timeout = jiffies + msecs_to_jiffies(20);
+ ? ? ? while (time_before(jiffies, timeout)) {
+ ? ? ? ? ? ? ? stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ ? ? ? ? ? ? ? if (stat& ?flags)
+ ? ? ? ? ? ? ? ? ? ? ? break;
+
+ ? ? ? ? ? ? ? if (state != FL_READING)
+ ? ? ? ? ? ? ? ? ? ? ? cond_resched();
+ ? ? ? }
+ ? ? ? /* To get correct interrupt status in timeout case */
+ ? ? ? stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ ? ? ? s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ ? ? ? /*
+ ? ? ? ?*
+ ? ? ? ?* In the Spec. it checks the controller status first
+ ? ? ? ?* However if you get the correct information in case of
+ ? ? ? ?* power off recovery (POR) test, it should read ECC status first
Quoted 8 lines
Quoted 6 lines

+ ? ? ? ?*/
+ ? ? ? if (stat& ?LOAD_CMP) {
+ ? ? ? ? ? ? ? ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ ? ? ? ? ? ? ? if (ecc& ?ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ ? ? ? ? ? ? ? ? ? ? ? printk(KERN_INFO "onenand_wait: ECC error = 0x%04x\n", ecc);

Copy-paste error? Also see below.

Nice catch.

Quoted 23 lines
Quoted 12 lines

+ ? ? ? ? ? ? ? ? ? ? ? mtd->ecc_stats.failed++;
+ ? ? ? ? ? ? ? ? ? ? ? return -EBADMSG;
+ ? ? ? ? ? ? ? }
+ ? ? ? }
+
+ ? ? ? if (stat& ?(LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) {
+ ? ? ? ? ? ? ? printk(KERN_INFO "s3c_onenand_wait: controller error = 0x%04x\n", stat);
+ ? ? ? ? ? ? ? if (stat& ?LOCKED_BLK)
+ ? ? ? ? ? ? ? ? ? ? ? printk(KERN_INFO "s3c_onenand_wait: it's locked error = 0x%04x\n", stat);

Very quick comment. Could you please avoid this style of putting function name to the printk? We have lots of this in onenand_base.c, and they are often wrong, because function names change.

Could we please use dev_printk(), dev_dbg() and the like instead? You'll have
to do some more work and make MTD a bit more linux-device-model-friendly, but
someone has to do this :-)

Quoted 3 lines

But in the worst case, use at least %s + __func__.

And you could also clean things up similarly for onenenand.

Okay I will use the your recommended. After more review. I will resend it.

Thank you,
Kyungmin Park

#3 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

Quoted 54 lines · Kyungmin Park
Quoted 44 lines · Artem Bityutskiy
Quoted 43 lines · Kyungmin Park
(quoted diff) +42 -0
+static int s3c_onenand_wait(struct mtd_info *mtd, int state)
+{
+ ? ? ? unsigned int flags = INT_ACT;
+ ? ? ? unsigned int stat, ecc;
+ ? ? ? unsigned long timeout;
+
+ ? ? ? switch (state) {
+ ? ? ? case FL_READING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | LOAD_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_WRITING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | PGM_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_ERASING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP | ERS_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? case FL_LOCKING:
+ ? ? ? ? ? ? ? flags |= BLK_RW_CMP;
+ ? ? ? ? ? ? ? break;
+ ? ? ? default:
+ ? ? ? ? ? ? ? break;
+ ? ? ? }
+
+ ? ? ? /* The 20 msec is enough */
+ ? ? ? timeout = jiffies + msecs_to_jiffies(20);
+ ? ? ? while (time_before(jiffies, timeout)) {
+ ? ? ? ? ? ? ? stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ ? ? ? ? ? ? ? if (stat& ?flags)
+ ? ? ? ? ? ? ? ? ? ? ? break;
+
+ ? ? ? ? ? ? ? if (state != FL_READING)
+ ? ? ? ? ? ? ? ? ? ? ? cond_resched();
+ ? ? ? }
+ ? ? ? /* To get correct interrupt status in timeout case */
+ ? ? ? stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ ? ? ? s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ ? ? ? /*
+ ? ? ? ?*
+ ? ? ? ?* In the Spec. it checks the controller status first
+ ? ? ? ?* However if you get the correct information in case of
+ ? ? ? ?* power off recovery (POR) test, it should read ECC status first
Quoted 8 lines
Quoted 6 lines

+ ? ? ? ?*/
+ ? ? ? if (stat& ?LOAD_CMP) {
+ ? ? ? ? ? ? ? ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ ? ? ? ? ? ? ? if (ecc& ?ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ ? ? ? ? ? ? ? ? ? ? ? printk(KERN_INFO "onenand_wait: ECC error = 0x%04x\n", ecc);

Copy-paste error? Also see below.

Quoted 20 lines

Nice catch.

Quoted 18 lines
Quoted 12 lines

+ ? ? ? ? ? ? ? ? ? ? ? mtd->ecc_stats.failed++;
+ ? ? ? ? ? ? ? ? ? ? ? return -EBADMSG;
+ ? ? ? ? ? ? ? }
+ ? ? ? }
+
+ ? ? ? if (stat& ?(LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) {
+ ? ? ? ? ? ? ? printk(KERN_INFO "s3c_onenand_wait: controller error = 0x%04x\n", stat);
+ ? ? ? ? ? ? ? if (stat& ?LOCKED_BLK)
+ ? ? ? ? ? ? ? ? ? ? ? printk(KERN_INFO "s3c_onenand_wait: it's locked error = 0x%04x\n", stat);

Very quick comment. Could you please avoid this style of putting function name to the printk? We have lots of this in onenand_base.c, and they are often wrong, because function names change.

Could we please use dev_printk(), dev_dbg() and the like instead? You'll

FYI: In case of MTD,
dev_printk(), dev_dbg() not working. I used &mtd->dev,

Do you know how to set the dev instead of &mtd->dev?

Thank you,
Kyungmin Park

#4 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

Quoted 10 lines · Kyungmin Park
Quoted 5 lines
Quoted 5 lines

Very quick comment. Could you please avoid this style of putting function name to the printk? We have lots of this in onenand_base.c, and they are often wrong, because function names change.

Could we please use dev_printk(), dev_dbg() and the like instead? You'll

FYI: In case of MTD,
dev_printk(), dev_dbg() not working. I used &mtd->dev,

Do you know how to set the dev instead of &mtd->dev?

No, but you could investigate this.

Signature · 2 lines
Best Regards,
Artem Bityutskiy (????? ????????)
#5 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

In reply to Kyungmin Park
Quoted 1 line · Kyungmin Park

+#include <asm/io.h>

Please make sure that you use linux/io.h

drivers/mtd/onenand/samsung.c line 638

Quoted 11 lines
drivers/mtd/onenand/samsung.c new file +11 -0
+static int s3c_onenand_probe(struct platform_device *pdev)
+{
+	struct onenand_platform_data *pdata;
+	struct onenand_chip *this;
+	struct mtd_info *mtd;
+	struct resource *r;
+	int size, err;
+	unsigned long ahb_addr = 0, ahb_addr_size;
+
+	if (!(cpu_is_s3c64xx() || cpu_is_s5pc1xx()))
+		return -ENODEV;

Isn't it normal to leave these kind of decisions to the declaration of the devices, rather than encoding it into the driver?

#6 lore raw

PATCH 1/1 Samsung SoCs: OneNAND support

In reply to Kyungmin Park

Hi

I am also facing problem with the NAND Driver. I have a board with Samsung NAND Flash (part number series K9F56). And On this Kernel 2.6.10 was ported. This board takes 110 seconds to boot up and come to the application.

Due to some reason the Samsung NAND Flash was replaced with the ST Micro NAND Flash (part number series NAND256). Now board takes 180 seconds to boot up to the application. The Kernel image remains the same.

To test the ST micro NAND Flash - A stand alone code was written which will continuously read the NAND Pages. Same code was run on both ST Micro and Samsung board. Time taken by both boards was same. This shows that there is no problem with the hardware.
The team is suspecting the kernel code. Looks like, kernel is getting into the loop and is taking time to come out of it.

Is there a way to find out what is causing the 70 seconds delay.

Thanks in advance for the help!
Regards,
Neha

-----Original Message-----
From: [email protected] [mailto:linux-mtd-bounces at lists.infradead.org] On Behalf Of Kyungmin Park
Sent: Thursday, September 17, 2009 1:47 PM
To: linux-arm-kernel at lists.infradead.org
Cc: linux-mtd at lists.infradead.org
Subject: [PATCH] Samsung SoCs: OneNAND support

S3C64XX, S5PC1XX series OneNAND driver support

S3C6400, S3C6410, and S5PC100 use own OneNAND controller by AHB bus BTW, S5PC110 use generic OneNAND APIs.

This is based on Samsung SoCs cpu detection.

---

(quoted diff) +0 -0

b/arch/arm/plat-s3c/include/plat/regs-onenand.h
new file mode 100644
index 0000000..943b51c

arch/arm/plat-s3c/include/plat/regs-onenand.h new file +75 -0

75 lines not on this page · raw message

drivers/mtd/onenand/Kconfig +0 -0
index 79fa79e..74ad1a9 100644
@@ -34,6 +34,13 @@ config MTD_ONENAND_OMAP2
 	  Support for a OneNAND flash device connected to an OMAP2/OMAP3

CPU
via the GPMC memory controller.

+config MTD_ONENAND_SAMSUNG

+        tristate "OneNAND on Samsung SOC controller support"
+        depends on MTD_ONENAND && (ARCH_S3C64XX || ARCH_S5PC1XX)
+        help

+ Support for a OneNAND flash device connected to an Samsung
SOC
+ S3C64XX/S5PC1XX controller.
+
config MTD_ONENAND_OTP
bool "OneNAND OTP Support"
select HAVE_MTD_OTP

drivers/mtd/onenand/Makefile +1 -0
index 64b6cc6..2b7884c 100644
@@ -8,6 +8,7 @@ obj-$(CONFIG_MTD_ONENAND)		+= onenand.o
 # Board specific.
 obj-$(CONFIG_MTD_ONENAND_GENERIC)	+= generic.o
 obj-$(CONFIG_MTD_ONENAND_OMAP2)		+= omap2.o
+obj-$(CONFIG_MTD_ONENAND_SAMSUNG)       += samsung.o
 
 # Simulator
 obj-$(CONFIG_MTD_ONENAND_SIM)		+= onenand_sim.o
(quoted diff) +0 -0

b/drivers/mtd/onenand/samsung.c
new file mode 100644
index 0000000..8c4204d

drivers/mtd/onenand/samsung.c new file +213 -0

216 lines not on this page · raw message

onenand->bootram_command) {
+ if (word_addr == 0)
+ return s3c_read_reg(MANUFACT_ID_OFFSET);
+ if (word_addr == 1)
+ return s3c_read_reg(DEVICE_ID_OFFSET);
+ if (word_addr == 2)
+ return s3c_read_reg(FLASH_VER_ID_OFFSET);
+ }
+
+ value = s3c_read_cmd(CMD_MAP_11(word_addr)) & 0xffff;
+ printk(KERN_INFO "s3c_onenand_readw: Illegal access"
+ " at reg 0x%x, value 0x%x\n", word_addr, value);
+ return value;
+}
+
+static void s3c_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:
+ s3c_write_reg(value, MEM_CFG_OFFSET);
+ return;
+
+ case ONENAND_REG_START_ADDRESS1:
+ case ONENAND_REG_START_ADDRESS2:
+ 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) {
+ s3c_write_reg(ONENAND_MEM_RESET_COLD,
MEM_RESET_OFFSET);
+ onenand->bootram_command = 0;
+ return;
+ }
+ }
+
+ printk(KERN_INFO "s3c_onenand_writew: Illegal access"
+ " at reg 0x%x, value 0x%x\n", word_addr, value);
+
+ s3c_write_cmd(value, CMD_MAP_11(word_addr));
+}
+
+static int s3c_onenand_wait(struct mtd_info *mtd, int state) +{
+ unsigned int flags = INT_ACT;
+ unsigned int stat, ecc;
+ unsigned long timeout;
+
+ switch (state) {
+ case FL_READING:
+ flags |= BLK_RW_CMP | LOAD_CMP;
+ break;
+ case FL_WRITING:
+ flags |= BLK_RW_CMP | PGM_CMP;
+ break;
+ case FL_ERASING:
+ flags |= BLK_RW_CMP | ERS_CMP;
+ break;
+ case FL_LOCKING:
+ flags |= BLK_RW_CMP;
+ break;
+ default:
+ break;
+ }
+
+ /* The 20 msec is enough */
+ timeout = jiffies + msecs_to_jiffies(20);
+ while (time_before(jiffies, timeout)) {
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ if (stat & flags)
+ break;
+
+ if (state != FL_READING)
+ cond_resched();
+ }
+ /* To get correct interrupt status in timeout case */
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ /*
+ *
+ * In the Spec. it checks the controller status first
+ * However if you get the correct information in case of
+ * power off recovery (POR) test, it should read ECC status
first
+ */
+ if (stat & LOAD_CMP) {
+ ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ printk(KERN_INFO "onenand_wait: ECC error =
0x%04x\n", ecc);
+ mtd->ecc_stats.failed++;
+ return -EBADMSG;
+ }
+ }
+
+ if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR))
{
+ printk(KERN_INFO "s3c_onenand_wait: controller error =
0x%04x\n", stat);
+ if (stat & LOCKED_BLK)
+ printk(KERN_INFO "s3c_onenand_wait: it's locked
error = 0x%04x\n", stat);
+
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int s3c_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;
+ unsigned int mem_addr;
+ int i, mcount, scount;
+ int index;
+
+ fba = (int) (addr >> this->erase_shift);
+ fpa = (int) (addr >> this->page_shift);
+ fpa &= this->page_mask;
+
+ mem_addr = onenand->mem_addr(fba, fpa, fsa);
+
+ switch (cmd) {
+ case ONENAND_CMD_READ:
+ case ONENAND_CMD_READOOB:
+ case ONENAND_CMD_BUFFERRAM:
+ ONENAND_SET_NEXT_BUFFERRAM(this);
+ default:
+ break;
+ }
+
+ index = ONENAND_CURRENT_BUFFERRAM(this);
+
+ /*
+ * Emulate Two BufferRAMs and access with 4 bytes pointer
+ */
+ m = (unsigned int *) onenand->page_buf;
+ s = (unsigned int *) onenand->oob_buf;
+
+ if (index) {
+ m += (this->writesize >> 2);
+ s += (mtd->oobsize >> 2);
+ }
+
+ mcount = mtd->writesize >> 2;
+ scount = mtd->oobsize >> 2;
+
+ switch (cmd) {
+ case ONENAND_CMD_READ:
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ *m++ = s3c_read_cmd(CMD_MAP_01(mem_addr));
+ return 0;
+
+ case ONENAND_CMD_READOOB:
+ s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ *m++ = s3c_read_cmd(CMD_MAP_01(mem_addr));
+
+ /* Spare */
+ for (i = 0; i < scount; i++)
+ *s++ = s3c_read_cmd(CMD_MAP_01(mem_addr));
+
+ s3c_write_reg(0, TRANS_SPARE_OFFSET);
+ return 0;
+
+ case ONENAND_CMD_PROG:
+ /* Main */
+ for (i = 0; i < mcount; i++)
+ s3c_write_cmd(*m++, CMD_MAP_01(mem_addr));
+ return 0;
+
+ case ONENAND_CMD_PROGOOB:
+ s3c_write_reg(TSRF, TRANS_SPARE_OFFSET);
+
+ /* Main - dummy write */
+ for (i = 0; i < mcount; i++)
+ s3c_write_cmd(0xffffffff, CMD_MAP_01(mem_addr));
+
+ /* Spare */
+ for (i = 0; i < scount; i++)
+ s3c_write_cmd(*s++, CMD_MAP_01(mem_addr));
+
+ s3c_write_reg(0, TRANS_SPARE_OFFSET);
+ return 0;
+
+ case ONENAND_CMD_UNLOCK_ALL:
+ s3c_write_cmd(ONENAND_UNLOCK_ALL, CMD_MAP_10(mem_addr));
+ return 0;
+
+ case ONENAND_CMD_ERASE:
+ s3c_write_cmd(ONENAND_ERASE_START,
CMD_MAP_10(mem_addr));
+ return 0;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) +{
+ struct onenand_chip *this = mtd->priv;
+ int index = ONENAND_CURRENT_BUFFERRAM(this);
+ unsigned char *p;
+
+ if (area == ONENAND_DATARAM) {
+ p = (unsigned char *) onenand->page_buf;
+ if (index == 1)
+ p += this->writesize;
+ } else {
+ p = (unsigned char *) onenand->oob_buf;
+ if (index == 1)
+ p += mtd->oobsize;
+ }
+
+ return p;
+}
+
+static int onenand_read_bufferram(struct mtd_info *mtd, int area,
+ unsigned char *buffer, int offset,
+ size_t count)
+{
+ unsigned char *p;
+
+ p = s3c_get_bufferram(mtd, area);
+ memcpy(buffer, p + offset, count);
+ return 0;
+}
+
+static int onenand_write_bufferram(struct mtd_info *mtd, int area,
+ const unsigned char *buffer, int
offset,
+ size_t count)
+{
+ unsigned char *p;
+
+ p = s3c_get_bufferram(mtd, area);
+ memcpy(p + offset, buffer, count);
+ return 0;
+}
+
+static int s5pc110_read_bufferram(struct mtd_info *mtd, int area,
+ unsigned char *buffer, int offset, size_t count)
+{

+        struct onenand_chip *this = mtd->priv;
+        void __iomem *bufferram;
+        void __iomem *p;

+
+ p = bufferram = this->base + area;
+ if (ONENAND_CURRENT_BUFFERRAM(this)) {
+ if (area == ONENAND_DATARAM)
+ p += this->writesize;
+ else
+ p += mtd->oobsize;
+ }
+
+ if (count != mtd->writesize) {
+ /* Copy the bufferram to memory to prevent unaligned
access */
+ memcpy(this->page_buf, bufferram, mtd->writesize);
+ p = this->page_buf + offset;
+ }
+
+ memcpy(buffer, p, count);
+
+ return 0;
+}
+
+static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) +{
+ unsigned int flags = INT_ACT | LOAD_CMP;
+ unsigned int stat;
+ unsigned long timeout;
+
+ /* The 20 msec is enough */
+ timeout = jiffies + msecs_to_jiffies(20);
+ while (time_before(jiffies, timeout)) {
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ if (stat & flags)
+ break;
+ }
+ /* To get correct interrupt status in timeout case */
+ stat = s3c_read_reg(INT_ERR_STAT_OFFSET);
+ s3c_write_reg(stat, INT_ERR_ACK_OFFSET);
+
+ if (stat & LD_FAIL_ECC_ERR) {
+ s3c_onenand_reset();
+ return ONENAND_BBT_READ_ERROR;
+ }
+
+ if (stat & LOAD_CMP) {
+ int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET);
+ if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) {
+ s3c_onenand_reset();
+ return ONENAND_BBT_READ_ERROR;
+ }
+ }
+
+ return 0;
+}
+
+static void s3c_onenand_check_lock_status(struct mtd_info *mtd) +{
+ struct onenand_chip *this = mtd->priv;
+ unsigned int block, end;
+ int tmp;
+
+ end = this->chipsize >> this->erase_shift;
+
+ for (block = 0; block < end; block++) {
+ tmp = s3c_read_cmd(CMD_MAP_01(onenand->mem_addr(block,
0, 0)));
+
+ if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) {
+ printk("block %d is write-protected!\n", block);
+ s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET);
+ }
+ }
+}
+
+static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd)
+{
+ struct onenand_chip *this = mtd->priv;
+ int start, end, start_mem_addr, end_mem_addr;
+
+ start = ofs >> this->erase_shift;
+ start_mem_addr = onenand->mem_addr(start, 0, 0);
+ end = start + (len >> this->erase_shift) - 1;
+ end_mem_addr = onenand->mem_addr(end, 0, 0);
+
+ if (cmd == ONENAND_CMD_LOCK) {
+ s3c_write_cmd(ONENAND_LOCK_START,
CMD_MAP_10(start_mem_addr));
+ s3c_write_cmd(ONENAND_LOCK_END,
CMD_MAP_10(end_mem_addr));
+ } else {
+ s3c_write_cmd(ONENAND_UNLOCK_START,
CMD_MAP_10(start_mem_addr));
+ s3c_write_cmd(ONENAND_UNLOCK_END,
CMD_MAP_10(end_mem_addr));
+ }
+
+ this->wait(mtd, FL_LOCKING);
+}
+
+static void s3c_unlock_all(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+ loff_t ofs = 0;
+ size_t len = this->chipsize;
+
+ if (this->options & ONENAND_HAS_UNLOCK_ALL) {
+ /* Write unlock command */
+ this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0);
+
+ /* No need to check return value */
+ this->wait(mtd, FL_LOCKING);
+
+ /* Workaround for all block unlock in DDP */
+ if (!ONENAND_IS_DDP(this)) {
+ s3c_onenand_check_lock_status(mtd);
+ return;
+ }
+
+ /* All blocks on another chip */
+ ofs = this->chipsize >> 1;
+ len = this->chipsize >> 1;
+ }
+
+ s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
+
+ s3c_onenand_check_lock_status(mtd);
+}
+
+static void s3c_onenand_setup(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+
+ onenand->mtd = mtd;
+
+ if (cpu_is_s3c6400())
+ onenand->mem_addr = s3c6400_mem_addr;
+ if (cpu_is_s3c6410())
+ onenand->mem_addr = s3c6410_mem_addr;
+ if (cpu_is_s5pc100())
+ onenand->mem_addr = s5pc100_mem_addr;
+ if (cpu_is_s5pc110()) {
+ /* Use generic onenand functions */
+ this->read_bufferram = s5pc110_read_bufferram;
+ return;
+ }
+
+ this->read_word = s3c_onenand_readw;
+ this->write_word = s3c_onenand_writew;
+
+ this->wait = s3c_onenand_wait;
+ this->bbt_wait = s3c_onenand_bbt_wait;
+ this->unlock_all = s3c_unlock_all;
+ this->command = s3c_onenand_command;
+
+ this->read_bufferram = onenand_read_bufferram;
+ this->write_bufferram = onenand_write_bufferram;
+}
+
+static int s3c_onenand_probe(struct platform_device *pdev) +{
+ struct onenand_platform_data *pdata;
+ struct onenand_chip *this;
+ struct mtd_info *mtd;
+ struct resource *r;
+ int size, err;
+ unsigned long ahb_addr = 0, ahb_addr_size;
+
+ if (!(cpu_is_s3c64xx() || cpu_is_s5pc1xx()))
+ return -ENODEV;
+
+ pdata = pdev->dev.platform_data;
+ /* No need to check pdata. the platform data is optional */
+
+ 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 s3c_onenand), GFP_KERNEL);
+ if (!onenand) {
+ err = -ENOMEM;
+ goto onenand_fail;
+ }
+
+ this = (struct onenand_chip *) &mtd[1];
+ mtd->priv = this;
+ mtd->dev.parent = &pdev->dev;
+ mtd->owner = THIS_MODULE;
+
+ s3c_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, resource_size(r), pdev->name);
+ if (!r) {
+ dev_err(&pdev->dev, "failed to request memory
resource\n");
+ err = -EBUSY;
+ goto request_failed;
+ }
+
+ onenand->base = ioremap(r->start, resource_size(r));
+ if (!onenand->base) {
+ err = -EFAULT;
+ goto ioremap_failed;
+ }
+ /* Set onenand_chip also */
+ this->base = onenand->base;
+
+ if (cpu_is_s3c64xx()) {
+ ahb_addr = S3C64XX_AHB_ADDR;
+ ahb_addr_size = SZ_64M;
+ } else if (cpu_is_s5pc100()) {
+ ahb_addr = S5PC100_AHB_ADDR;
+ ahb_addr_size = SZ_256M - SZ_32M;
+ }
+
+ /*
+ * We only used as followings
+ * S5PC100 S3C64XX
+ * MAP_01 0xB4000000 ~ 0xB4FFFFFF 0x21000000 ~ 0x21FFFFFF
+ * MAP_10 0xB8000000 ~ 0xB8FFFFFF 0x22000000 ~ 0x22FFFFFF
+ * MAP_11 0xBC000000 ~ 0xBCFFFFFF 0x23000000 ~ 0x23FFFFFF
+ */
+ if (ahb_addr) {
+ onenand->ahb_addr = ioremap(ahb_addr, ahb_addr_size);
+ if (!onenand->ahb_addr) {
+ err = -EINVAL;
+ goto ahb_failed;
+ }
+
+ /* Allocate 4KiB BufferRAM */
+ onenand->page_buf = kzalloc(SZ_4K * sizeof(char),
GFP_KERNEL);
+ if (!onenand->page_buf) {
+ err = -ENOMEM;
+ goto page_buf_fail;
+ }
+
+ /* Allocate 128 SpareRAM */
+ onenand->oob_buf = kzalloc(128 * sizeof(char),
GFP_KERNEL);
+ if (!onenand->oob_buf) {
+ err = -ENOMEM;
+ goto oob_buf_fail;
+ }
+ }
+
+ if (onenand_scan(mtd, 1)) {
+ err = -EFAULT;
+ goto scan_failed;
+ }
+
+ if (ahb_addr) {
+ /* S3C don't handle subpage write */
+ mtd->subpage_sft = 0;
+ this->subpagesize = mtd->writesize;
+ }
+
+ if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ)
+ printk(KERN_INFO "OneNAND Sync. Burst Read enabled\n");
+
+#ifdef CONFIG_MTD_PARTITIONS
+ err = parse_mtd_partitions(mtd, part_probes, &onenand->parts,
0);
+ if (err > 0)
+ add_mtd_partitions(mtd, onenand->parts, err);
+ else if (err <= 0 && pdata && pdata->parts)
+ add_mtd_partitions(mtd, pdata->parts, pdata->nr_parts);
+ else
+#endif
+ err = add_mtd_device(mtd);
+
+ platform_set_drvdata(pdev, mtd);
+
+ return 0;
+scan_failed:
+ kfree(onenand->oob_buf);
+oob_buf_fail:
+ kfree(onenand->page_buf);
+page_buf_fail:
+ if (onenand->ahb_addr)
+ iounmap(onenand->ahb_addr);
+ahb_failed:
+ iounmap(onenand->base);
+ioremap_failed:
+ release_mem_region(r->start, resource_size(r));
+request_failed:
+resource_failed:
+ kfree(onenand);
+onenand_fail:
+ kfree(mtd);
+ return err;
+}
+
+static int __devexit s3c_onenand_remove(struct platform_device *pdev) +{
+ struct mtd_info *mtd = platform_get_drvdata(pdev);
+
+ onenand_release(mtd);
+ if (onenand->ahb_addr)
+ iounmap(onenand->ahb_addr);
+ iounmap(onenand->base);
+ platform_set_drvdata(pdev, NULL);
+ kfree(onenand->oob_buf);
+ kfree(onenand->page_buf);
+ kfree(onenand);
+ kfree(mtd);
+ return 0;
+}
+
+static int s3c_pm_ops_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ 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 s3c_pm_ops_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mtd_info *mtd = platform_get_drvdata(pdev);
+ struct onenand_chip *this = mtd->priv;
+
+ mtd->resume(mtd);
+ this->unlock_all(mtd);
+ return 0;
+}
+
+static struct dev_pm_ops s3c_pm_ops = {
+ .suspend = s3c_pm_ops_suspend,
+ .resume = s3c_pm_ops_resume,
+};
+
+static struct platform_driver s3c_onenand_driver = {
+ .driver = {
+ .name = "samsung-onenand",
+ .pm = &s3c_pm_ops,
+ },
+ .probe = s3c_onenand_probe,
+ .remove = __devexit_p(s3c_onenand_remove),
+};
+
+static int __init s3c_onenand_init(void)
+{
+ return platform_driver_register(&s3c_onenand_driver);
+}
+
+static void __exit s3c_onenand_exit(void)
+{
+ platform_driver_unregister(&s3c_onenand_driver);
+}
+
+module_init(s3c_onenand_init); +module_exit(s3c_onenand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <[email protected]>"); +MODULE_DESCRIPTION("Samsung S3C64XX/S5PC1XX OneNAND controller support");
+MODULE_ALIAS("platform:samsung-onenand");

include/linux/mtd/onenand.h +0 -0
index 8ed8733..e35909b 100644
@@ -214,4 +214,14 @@ unsigned onenand_block(struct onenand_chip *this,

loff_t addr);
loff_t onenand_addr(struct onenand_chip *this, int block); int flexonenand_region(struct mtd_info *mtd, loff_t addr);

+struct mtd_partition;
+
+struct onenand_platform_data {
+ void (*mmcontrol)(struct mtd_info *mtd, int
sync_read);
+ int (*read_bufferram)(struct mtd_info *mtd, int
area,
+ unsigned char *buffer, int offset, size_t
count);
+ struct mtd_partition *parts;
+ unsigned int nr_parts;
+};
+
#endif /* __LINUX_MTD_ONENAND_H */

(quoted diff) +0 -0

b/include/linux/mtd/onenand_regs.h
index 86a6bbe..a69d140 100644

include/linux/mtd/onenand_regs.h +1 -0
@@ -206,6 +206,7 @@
 #define ONENAND_ECC_1BIT_ALL		(0x5555)
 #define ONENAND_ECC_2BIT		(1 << 1)
 #define ONENAND_ECC_2BIT_ALL		(0xAAAA)
+#define ONENAND_ECC_4BIT_UNCORRECTABLE	(0x1010)
 #define FLEXONENAND_UNCORRECTABLE_ERROR	(0x1010)
 
 /*

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/