CONFIG_HOTPLUG is going away as an option so __devexit is no longer needed. Signed-off-by: Bill Pemberton <wfp5p@virginia.edu> Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
		
			
				
	
	
		
			355 lines
		
	
	
	
		
			7.4 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			355 lines
		
	
	
	
		
			7.4 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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_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 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->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 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		= 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");
 |