2009-07-10 11:17:27 +04:00
/*
2010-01-01 15:16:47 +03:00
* Copyright © 2009 Nuvoton technology corporation .
2009-07-10 11:17:27 +04:00
*
* 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 )
2010-01-01 15:16:47 +03:00
struct nuc900_nand {
2009-07-10 11:17:27 +04:00
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
}
} ;
2010-01-01 15:16:47 +03:00
static unsigned char nuc900_nand_read_byte ( struct mtd_info * mtd )
2009-07-10 11:17:27 +04:00
{
unsigned char ret ;
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nand ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nand = container_of ( mtd , struct nuc900_nand , mtd ) ;
2009-07-10 11:17:27 +04:00
ret = ( unsigned char ) read_data_reg ( nand ) ;
return ret ;
}
2010-01-01 15:16:47 +03:00
static void nuc900_nand_read_buf ( struct mtd_info * mtd ,
unsigned char * buf , int len )
2009-07-10 11:17:27 +04:00
{
int i ;
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nand ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nand = container_of ( mtd , struct nuc900_nand , mtd ) ;
2009-07-10 11:17:27 +04:00
for ( i = 0 ; i < len ; i + + )
buf [ i ] = ( unsigned char ) read_data_reg ( nand ) ;
}
2010-01-01 15:16:47 +03:00
static void nuc900_nand_write_buf ( struct mtd_info * mtd ,
const unsigned char * buf , int len )
2009-07-10 11:17:27 +04:00
{
int i ;
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nand ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nand = container_of ( mtd , struct nuc900_nand , mtd ) ;
2009-07-10 11:17:27 +04:00
for ( i = 0 ; i < len ; i + + )
write_data_reg ( nand , buf [ i ] ) ;
}
2010-01-01 15:16:47 +03:00
static int nuc900_check_rb ( struct nuc900_nand * nand )
2009-07-10 11:17:27 +04:00
{
unsigned int val ;
spin_lock ( & nand - > lock ) ;
val = __raw_readl ( REG_SMISR ) ;
val & = READYBUSY ;
spin_unlock ( & nand - > lock ) ;
return val ;
}
2010-01-01 15:16:47 +03:00
static int nuc900_nand_devready ( struct mtd_info * mtd )
2009-07-10 11:17:27 +04:00
{
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nand ;
2009-07-10 11:17:27 +04:00
int ready ;
2010-01-01 15:16:47 +03:00
nand = container_of ( mtd , struct nuc900_nand , mtd ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
ready = ( nuc900_check_rb ( nand ) ) ? 1 : 0 ;
2009-07-10 11:17:27 +04:00
return ready ;
}
2010-01-01 15:16:47 +03:00
static void nuc900_nand_command_lp ( struct mtd_info * mtd , unsigned int command ,
int column , int page_addr )
2009-07-10 11:17:27 +04:00
{
register struct nand_chip * chip = mtd - > priv ;
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nand ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nand = container_of ( mtd , struct nuc900_nand , mtd ) ;
2009-07-10 11:17:27 +04:00
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 ) ;
2010-01-01 15:16:47 +03:00
while ( ! nuc900_check_rb ( nand ) )
2009-07-10 11:17:27 +04:00
;
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 ) )
;
}
2010-01-01 15:16:47 +03:00
static void nuc900_nand_enable ( struct nuc900_nand * nand )
2009-07-10 11:17:27 +04:00
{
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 ) ;
}
2012-11-19 22:23:07 +04:00
static int nuc900_nand_probe ( struct platform_device * pdev )
2009-07-10 11:17:27 +04:00
{
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nuc900_nand ;
2009-07-10 11:17:27 +04:00
struct nand_chip * chip ;
int retval ;
struct resource * res ;
retval = 0 ;
2010-01-01 15:16:47 +03:00
nuc900_nand = kzalloc ( sizeof ( struct nuc900_nand ) , GFP_KERNEL ) ;
if ( ! nuc900_nand )
2009-07-10 11:17:27 +04:00
return - ENOMEM ;
2010-01-01 15:16:47 +03:00
chip = & ( nuc900_nand - > chip ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nuc900_nand - > mtd . priv = chip ;
nuc900_nand - > mtd . owner = THIS_MODULE ;
spin_lock_init ( & nuc900_nand - > lock ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nuc900_nand - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( nuc900_nand - > clk ) ) {
2009-07-10 11:17:27 +04:00
retval = - ENOENT ;
goto fail1 ;
}
2010-01-01 15:16:47 +03:00
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 ;
2009-07-10 11:17:27 +04:00
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 ;
}
2010-01-01 15:16:47 +03:00
nuc900_nand - > reg = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! nuc900_nand - > reg ) {
2009-07-10 11:17:27 +04:00
retval = - ENOMEM ;
goto fail2 ;
}
2010-01-01 15:16:47 +03:00
nuc900_nand_enable ( nuc900_nand ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
if ( nand_scan ( & ( nuc900_nand - > mtd ) , 1 ) ) {
2009-07-10 11:17:27 +04:00
retval = - ENXIO ;
goto fail3 ;
}
2011-05-23 13:23:40 +04:00
mtd_device_register ( & ( nuc900_nand - > mtd ) , partitions ,
ARRAY_SIZE ( partitions ) ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
platform_set_drvdata ( pdev , nuc900_nand ) ;
2009-07-10 11:17:27 +04:00
return retval ;
2010-01-01 15:16:47 +03:00
fail3 : iounmap ( nuc900_nand - > reg ) ;
2009-07-10 11:17:27 +04:00
fail2 : release_mem_region ( res - > start , resource_size ( res ) ) ;
2010-01-01 15:16:47 +03:00
fail1 : kfree ( nuc900_nand ) ;
2009-07-10 11:17:27 +04:00
return retval ;
}
2010-01-01 15:16:47 +03:00
static int __devexit nuc900_nand_remove ( struct platform_device * pdev )
2009-07-10 11:17:27 +04:00
{
2010-01-01 15:16:47 +03:00
struct nuc900_nand * nuc900_nand = platform_get_drvdata ( pdev ) ;
2009-07-10 11:17:27 +04:00
struct resource * res ;
2011-06-03 05:51:53 +04:00
nand_release ( & nuc900_nand - > mtd ) ;
2010-01-01 15:16:47 +03:00
iounmap ( nuc900_nand - > reg ) ;
2009-07-10 11:17:27 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
2010-01-01 15:16:47 +03:00
clk_disable ( nuc900_nand - > clk ) ;
clk_put ( nuc900_nand - > clk ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
kfree ( nuc900_nand ) ;
2009-07-10 11:17:27 +04:00
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
2010-01-01 15:16:47 +03:00
static struct platform_driver nuc900_nand_driver = {
. probe = nuc900_nand_probe ,
2012-11-19 22:21:24 +04:00
. remove = nuc900_nand_remove ,
2009-07-10 11:17:27 +04:00
. driver = {
2010-01-01 13:03:47 +03:00
. name = " nuc900-fmi " ,
2009-07-10 11:17:27 +04:00
. owner = THIS_MODULE ,
} ,
} ;
2011-11-27 16:45:03 +04:00
module_platform_driver ( nuc900_nand_driver ) ;
2009-07-10 11:17:27 +04:00
MODULE_AUTHOR ( " Wan ZongShun <mcuos.com@gmail.com> " ) ;
2010-01-01 15:16:47 +03:00
MODULE_DESCRIPTION ( " w90p910/NUC9xx nand driver! " ) ;
2009-07-10 11:17:27 +04:00
MODULE_LICENSE ( " GPL " ) ;
2010-01-01 13:03:47 +03:00
MODULE_ALIAS ( " platform:nuc900-fmi " ) ;