/* * 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");