| /* |
| * Copyright © 2009 Nuvoton technology corporation. |
| * |
| * Wan ZongShun <mcuos.com@gmail.com> |
| * |
| * 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 <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/clk.h> |
| #include <linux/err.h> |
| |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/nand.h> |
| #include <linux/mtd/partitions.h> |
| |
| #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 nuc900_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 nuc900_nand_read_byte(struct mtd_info *mtd) |
| { |
| unsigned char ret; |
| struct nuc900_nand *nand; |
| |
| nand = container_of(mtd, struct nuc900_nand, mtd); |
| |
| ret = (unsigned char)read_data_reg(nand); |
| |
| return ret; |
| } |
| |
| static void nuc900_nand_read_buf(struct mtd_info *mtd, |
| unsigned char *buf, int len) |
| { |
| int i; |
| struct nuc900_nand *nand; |
| |
| nand = container_of(mtd, struct nuc900_nand, mtd); |
| |
| for (i = 0; i < len; i++) |
| buf[i] = (unsigned char)read_data_reg(nand); |
| } |
| |
| static void nuc900_nand_write_buf(struct mtd_info *mtd, |
| const unsigned char *buf, int len) |
| { |
| int i; |
| struct nuc900_nand *nand; |
| |
| nand = container_of(mtd, struct nuc900_nand, mtd); |
| |
| for (i = 0; i < len; i++) |
| write_data_reg(nand, buf[i]); |
| } |
| |
| static int nuc900_verify_buf(struct mtd_info *mtd, |
| const unsigned char *buf, int len) |
| { |
| int i; |
| struct nuc900_nand *nand; |
| |
| nand = container_of(mtd, struct nuc900_nand, mtd); |
| |
| for (i = 0; i < len; i++) { |
| if (buf[i] != (unsigned char)read_data_reg(nand)) |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int nuc900_check_rb(struct nuc900_nand *nand) |
| { |
| unsigned int val; |
| spin_lock(&nand->lock); |
| val = __raw_readl(REG_SMISR); |
| val &= READYBUSY; |
| spin_unlock(&nand->lock); |
| |
| return val; |
| } |
| |
| static int nuc900_nand_devready(struct mtd_info *mtd) |
| { |
| struct nuc900_nand *nand; |
| int ready; |
| |
| nand = container_of(mtd, struct nuc900_nand, mtd); |
| |
| ready = (nuc900_check_rb(nand)) ? 1 : 0; |
| return ready; |
| } |
| |
| static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command, |
| int column, int page_addr) |
| { |
| register struct nand_chip *chip = mtd->priv; |
| struct nuc900_nand *nand; |
| |
| nand = container_of(mtd, struct nuc900_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 (!nuc900_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 nuc900_nand_enable(struct nuc900_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 nuc900_nand_probe(struct platform_device *pdev) |
| { |
| struct nuc900_nand *nuc900_nand; |
| struct nand_chip *chip; |
| int retval; |
| struct resource *res; |
| |
| retval = 0; |
| |
| nuc900_nand = kzalloc(sizeof(struct nuc900_nand), GFP_KERNEL); |
| if (!nuc900_nand) |
| return -ENOMEM; |
| chip = &(nuc900_nand->chip); |
| |
| nuc900_nand->mtd.priv = chip; |
| nuc900_nand->mtd.owner = THIS_MODULE; |
| spin_lock_init(&nuc900_nand->lock); |
| |
| nuc900_nand->clk = clk_get(&pdev->dev, NULL); |
| if (IS_ERR(nuc900_nand->clk)) { |
| retval = -ENOENT; |
| goto fail1; |
| } |
| clk_enable(nuc900_nand->clk); |
| |
| chip->cmdfunc = nuc900_nand_command_lp; |
| chip->dev_ready = nuc900_nand_devready; |
| chip->read_byte = nuc900_nand_read_byte; |
| chip->write_buf = nuc900_nand_write_buf; |
| chip->read_buf = nuc900_nand_read_buf; |
| chip->verify_buf = nuc900_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; |
| } |
| |
| nuc900_nand->reg = ioremap(res->start, resource_size(res)); |
| if (!nuc900_nand->reg) { |
| retval = -ENOMEM; |
| goto fail2; |
| } |
| |
| nuc900_nand_enable(nuc900_nand); |
| |
| if (nand_scan(&(nuc900_nand->mtd), 1)) { |
| retval = -ENXIO; |
| goto fail3; |
| } |
| |
| mtd_device_register(&(nuc900_nand->mtd), partitions, |
| ARRAY_SIZE(partitions)); |
| |
| platform_set_drvdata(pdev, nuc900_nand); |
| |
| return retval; |
| |
| fail3: iounmap(nuc900_nand->reg); |
| fail2: release_mem_region(res->start, resource_size(res)); |
| fail1: kfree(nuc900_nand); |
| return retval; |
| } |
| |
| static int __devexit nuc900_nand_remove(struct platform_device *pdev) |
| { |
| struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); |
| struct resource *res; |
| |
| nand_release(&nuc900_nand->mtd); |
| iounmap(nuc900_nand->reg); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| release_mem_region(res->start, resource_size(res)); |
| |
| clk_disable(nuc900_nand->clk); |
| clk_put(nuc900_nand->clk); |
| |
| kfree(nuc900_nand); |
| |
| platform_set_drvdata(pdev, NULL); |
| |
| return 0; |
| } |
| |
| static struct platform_driver nuc900_nand_driver = { |
| .probe = nuc900_nand_probe, |
| .remove = __devexit_p(nuc900_nand_remove), |
| .driver = { |
| .name = "nuc900-fmi", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| module_platform_driver(nuc900_nand_driver); |
| |
| MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); |
| MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:nuc900-fmi"); |