mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	mtd: add nand support for w90p910 (v2)
Add w90p910 NAND driver for w90p910 evaluation board based on w90p910,there is a K8F1G08 NAND on my board. [dwmw2: depend on MTD_PARTITIONS] Signed-off-by: Wan ZongShun <mcuos.com@gmail.com> Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
This commit is contained in:
		
							parent
							
								
									fc28c39f0e
								
							
						
					
					
						commit
						8bff82cbc3
					
				| @ -452,4 +452,11 @@ config MTD_NAND_SOCRATES | |||||||
| 	help | 	help | ||||||
| 	  Enables support for NAND Flash chips wired onto Socrates board. | 	  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 && MTD_PARTITIONS | ||||||
|  | 	help | ||||||
|  | 	  This enables the driver for the NAND Flash on evaluation board based | ||||||
|  | 	  on w90p910. | ||||||
|  | 
 | ||||||
| endif # MTD_NAND | endif # MTD_NAND | ||||||
|  | |||||||
| @ -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_MXC)		+= mxc_nand.o | ||||||
| obj-$(CONFIG_MTD_NAND_SOCRATES)		+= socrates_nand.o | obj-$(CONFIG_MTD_NAND_SOCRATES)		+= socrates_nand.o | ||||||
| obj-$(CONFIG_MTD_NAND_TXX9NDFMC)	+= txx9ndfmc.o | obj-$(CONFIG_MTD_NAND_TXX9NDFMC)	+= txx9ndfmc.o | ||||||
|  | obj-$(CONFIG_MTD_NAND_W90P910)		+= w90p910_nand.o | ||||||
| 
 | 
 | ||||||
| nand-objs := nand_base.o nand_bbt.o | nand-objs := nand_base.o nand_bbt.o | ||||||
|  | |||||||
							
								
								
									
										382
									
								
								drivers/mtd/nand/w90p910_nand.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								drivers/mtd/nand/w90p910_nand.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,382 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 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 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 <mcuos.com@gmail.com>"); | ||||||
|  | MODULE_DESCRIPTION("w90p910 nand driver!"); | ||||||
|  | MODULE_LICENSE("GPL"); | ||||||
|  | MODULE_ALIAS("platform:w90p910-fmi"); | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Wan ZongShun
						Wan ZongShun