From patchwork Fri Jul 10 07:17:27 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Wan ZongShun X-Patchwork-Id: 29667 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [18.85.46.34]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by bilbo.ozlabs.org (Postfix) with ESMTPS id 5BF9AB7067 for ; Fri, 10 Jul 2009 17:19:43 +1000 (EST) Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.69 #1 (Red Hat Linux)) id 1MPAMb-0004U2-KQ; Fri, 10 Jul 2009 07:17:45 +0000 Received: from rv-out-0708.google.com ([209.85.198.240]) by bombadil.infradead.org with esmtp (Exim 4.69 #1 (Red Hat Linux)) id 1MPAMS-0003yR-0T for linux-mtd@lists.infradead.org; Fri, 10 Jul 2009 07:17:42 +0000 Received: by rv-out-0708.google.com with SMTP id c5so203910rvf.42 for ; Fri, 10 Jul 2009 00:17:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:message-id:date:from :user-agent:mime-version:to:subject:content-type :content-transfer-encoding; bh=GqmQEFGsadLM4BOystR720X30JlHVRjSFWZCBZPVOrw=; b=FNnUUqsUi+xSyvIIz6Egff54yMX7tLG1UMI0cxf/84FuZoGmW6V1zdWavSVIbE7FBt T5OPW8GBGo1pUKZvJiWOCOrpKwG7mkjPTTRUm+dUo5nithKdaU7SEyw6TmWbo3eSTiPK Eg0d3pbFh/Tk2ycTLZidC7l+eQxFd9kUTRENE= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=message-id:date:from:user-agent:mime-version:to:subject :content-type:content-transfer-encoding; b=pTDzoXqVTVpvP9PCNUKpiLipVJe+4iB4/qJ1lOYI44T4LjFWO0T2ts4lliIrTxoOgw rLTDY4TE2I6l6NxzLdgPRR6TAM3oIEoctbtdFYMcKRKnUQgevZQUiJuT4HT+OMbXQsf1 GSuQ7QEkNUAI+lpakmwxx+BKDY6L5lIPFcswQ= Received: by 10.140.139.16 with SMTP id m16mr1088201rvd.182.1247210253470; Fri, 10 Jul 2009 00:17:33 -0700 (PDT) Received: from ?192.169.0.164? ([116.226.99.116]) by mx.google.com with ESMTPS id k37sm3160348rvb.18.2009.07.10.00.17.30 (version=TLSv1/SSLv3 cipher=RC4-MD5); Fri, 10 Jul 2009 00:17:33 -0700 (PDT) Message-ID: <4A56EB07.20606@gmail.com> Date: Fri, 10 Jul 2009 15:17:27 +0800 From: Wan ZongShun User-Agent: Thunderbird 2.0.0.21 (X11/20090318) MIME-Version: 1.0 To: linux-arm-kernel , linux-mtd , David Woodhouse Subject: [PATCH v2] mtd: Add nand support for w90p910 X-Spam-Score: 0.0 (/) X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.11 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-mtd-bounces@lists.infradead.org Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org Dear David, I have modified some clock api issue,and resubmitted it, marking it as V2.Thanks! Add w90p910 NAND driver for w90p910 evaluation board based on w90p910,there is a K8F1G08 NAND on my board. Signed-off-by: Wan ZongShun --- drivers/mtd/nand/Kconfig | 7 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/w90p910_nand.c | 382 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/w90p910_nand.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index ce96c09..14201c8 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -452,4 +452,11 @@ config MTD_NAND_SOCRATES help Enables support for NAND Flash chips wired onto Socrates board. +config MTD_NAND_W90P910 + tristate "Support for NAND on w90p910 evaluation board." + depends on ARCH_W90X900 + help + This enables the driver for the NAND Flash on evaluation board based + on w90p910. + endif # MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index f3a786b..4f7b189 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -40,5 +40,6 @@ obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o +obj-$(CONFIG_MTD_NAND_W90P910) += w90p910_nand.o nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/w90p910_nand.c b/drivers/mtd/nand/w90p910_nand.c new file mode 100644 index 0000000..7680e73 --- /dev/null +++ b/drivers/mtd/nand/w90p910_nand.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2009 Nuvoton technology corporation. + * + * Wan ZongShun + * + * 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 + * the Free Software Foundation;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define REG_FMICSR 0x00 +#define REG_SMCSR 0xa0 +#define REG_SMISR 0xac +#define REG_SMCMD 0xb0 +#define REG_SMADDR 0xb4 +#define REG_SMDATA 0xb8 + +#define RESET_FMI 0x01 +#define NAND_EN 0x08 +#define READYBUSY (0x01 << 18) + +#define SWRST 0x01 +#define PSIZE (0x01 << 3) +#define DMARWEN (0x03 << 1) +#define BUSWID (0x01 << 4) +#define ECC4EN (0x01 << 5) +#define WP (0x01 << 24) +#define NANDCS (0x01 << 25) +#define ENDADDR (0x01 << 31) + +#define read_data_reg(dev) \ + __raw_readl((dev)->reg + REG_SMDATA) + +#define write_data_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMDATA) + +#define write_cmd_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMCMD) + +#define write_addr_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMADDR) + +struct w90p910_nand { + struct mtd_info mtd; + struct nand_chip chip; + void __iomem *reg; + struct clk *clk; + spinlock_t lock; +}; + +static const struct mtd_partition partitions[] = { + { + .name = "NAND FS 0", + .offset = 0, + .size = 8 * 1024 * 1024 + }, + { + .name = "NAND FS 1", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL + } +}; + +static unsigned char w90p910_nand_read_byte(struct mtd_info *mtd) +{ + unsigned char ret; + struct w90p910_nand *nand; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + ret = (unsigned char)read_data_reg(nand); + + return ret; +} + +static void w90p910_nand_read_buf(struct mtd_info *mtd, + unsigned char *buf, int len) +{ + int i; + struct w90p910_nand *nand; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + for (i = 0; i < len; i++) + buf[i] = (unsigned char)read_data_reg(nand); +} + +static void w90p910_nand_write_buf(struct mtd_info *mtd, + const unsigned char *buf, int len) +{ + int i; + struct w90p910_nand *nand; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + for (i = 0; i < len; i++) + write_data_reg(nand, buf[i]); +} + +static int w90p910_verify_buf(struct mtd_info *mtd, + const unsigned char *buf, int len) +{ + int i; + struct w90p910_nand *nand; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + for (i = 0; i < len; i++) { + if (buf[i] != (unsigned char)read_data_reg(nand)) + return -EFAULT; + } + + return 0; +} + +static int w90p910_check_rb(struct w90p910_nand *nand) +{ + unsigned int val; + spin_lock(&nand->lock); + val = __raw_readl(REG_SMISR); + val &= READYBUSY; + spin_unlock(&nand->lock); + + return val; +} + +static int w90p910_nand_devready(struct mtd_info *mtd) +{ + struct w90p910_nand *nand; + int ready; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + ready = (w90p910_check_rb(nand)) ? 1 : 0; + return ready; +} + +static void w90p910_nand_command_lp(struct mtd_info *mtd, + unsigned int command, int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + struct w90p910_nand *nand; + + nand = container_of(mtd, struct w90p910_nand, mtd); + + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + write_cmd_reg(nand, command & 0xff); + + if (column != -1 || page_addr != -1) { + + if (column != -1) { + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + write_addr_reg(nand, column); + write_addr_reg(nand, column >> 8 | ENDADDR); + } + if (page_addr != -1) { + write_addr_reg(nand, page_addr); + + if (chip->chipsize > (128 << 20)) { + write_addr_reg(nand, page_addr >> 8); + write_addr_reg(nand, page_addr >> 16 | ENDADDR); + } else { + write_addr_reg(nand, page_addr >> 8 | ENDADDR); + } + } + } + + switch (command) { + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_DEPLETE1: + return; + + case NAND_CMD_STATUS_ERROR: + case NAND_CMD_STATUS_ERROR0: + case NAND_CMD_STATUS_ERROR1: + case NAND_CMD_STATUS_ERROR2: + case NAND_CMD_STATUS_ERROR3: + udelay(chip->chip_delay); + return; + + case NAND_CMD_RESET: + if (chip->dev_ready) + break; + udelay(chip->chip_delay); + + write_cmd_reg(nand, NAND_CMD_STATUS); + write_cmd_reg(nand, command); + + while (!w90p910_check_rb(nand)) + ; + + return; + + case NAND_CMD_RNDOUT: + write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); + return; + + case NAND_CMD_READ0: + + write_cmd_reg(nand, NAND_CMD_READSTART); + default: + + if (!chip->dev_ready) { + udelay(chip->chip_delay); + return; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay(100); + + while (!chip->dev_ready(mtd)) + ; +} + + +static void w90p910_nand_enable(struct w90p910_nand *nand) +{ + unsigned int val; + spin_lock(&nand->lock); + __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); + + val = __raw_readl(nand->reg + REG_FMICSR); + + if (!(val & NAND_EN)) + __raw_writel(val | NAND_EN, REG_FMICSR); + + val = __raw_readl(nand->reg + REG_SMCSR); + + val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); + val |= WP; + + __raw_writel(val, nand->reg + REG_SMCSR); + + spin_unlock(&nand->lock); +} + +static int __devinit w90p910_nand_probe(struct platform_device *pdev) +{ + struct w90p910_nand *w90p910_nand; + struct nand_chip *chip; + int retval; + struct resource *res; + + retval = 0; + + w90p910_nand = kzalloc(sizeof(struct w90p910_nand), GFP_KERNEL); + if (!w90p910_nand) + return -ENOMEM; + chip = &(w90p910_nand->chip); + + w90p910_nand->mtd.priv = chip; + w90p910_nand->mtd.owner = THIS_MODULE; + spin_lock_init(&w90p910_nand->lock); + + w90p910_nand->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(w90p910_nand->clk)) { + retval = -ENOENT; + goto fail1; + } + clk_enable(w90p910_nand->clk); + + chip->cmdfunc = w90p910_nand_command_lp; + chip->dev_ready = w90p910_nand_devready; + chip->read_byte = w90p910_nand_read_byte; + chip->write_buf = w90p910_nand_write_buf; + chip->read_buf = w90p910_nand_read_buf; + chip->verify_buf = w90p910_verify_buf; + chip->chip_delay = 50; + chip->options = 0; + chip->ecc.mode = NAND_ECC_SOFT; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -ENXIO; + goto fail1; + } + + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + retval = -EBUSY; + goto fail1; + } + + w90p910_nand->reg = ioremap(res->start, resource_size(res)); + if (!w90p910_nand->reg) { + retval = -ENOMEM; + goto fail2; + } + + w90p910_nand_enable(w90p910_nand); + + if (nand_scan(&(w90p910_nand->mtd), 1)) { + retval = -ENXIO; + goto fail3; + } + + add_mtd_partitions(&(w90p910_nand->mtd), partitions, + ARRAY_SIZE(partitions)); + + platform_set_drvdata(pdev, w90p910_nand); + + return retval; + +fail3: iounmap(w90p910_nand->reg); +fail2: release_mem_region(res->start, resource_size(res)); +fail1: kfree(w90p910_nand); + return retval; +} + +static int __devexit w90p910_nand_remove(struct platform_device *pdev) +{ + struct w90p910_nand *w90p910_nand = platform_get_drvdata(pdev); + struct resource *res; + + iounmap(w90p910_nand->reg); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + clk_disable(w90p910_nand->clk); + clk_put(w90p910_nand->clk); + + kfree(w90p910_nand); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver w90p910_nand_driver = { + .probe = w90p910_nand_probe, + .remove = __devexit_p(w90p910_nand_remove), + .driver = { + .name = "w90p910-fmi", + .owner = THIS_MODULE, + }, +}; + +static int __init w90p910_nand_init(void) +{ + return platform_driver_register(&w90p910_nand_driver); +} + +static void __exit w90p910_nand_exit(void) +{ + platform_driver_unregister(&w90p910_nand_driver); +} + +module_init(w90p910_nand_init); +module_exit(w90p910_nand_exit); + +MODULE_AUTHOR("Wan ZongShun "); +MODULE_DESCRIPTION("w90p910 nand driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:w90p910-fmi");