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/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 nand_chip chip ;
void __iomem * reg ;
struct clk * clk ;
spinlock_t lock ;
} ;
2015-12-10 10:59:47 +03:00
static inline struct nuc900_nand * mtd_to_nuc900 ( struct mtd_info * mtd )
{
2015-12-10 11:00:15 +03:00
return container_of ( mtd_to_nand ( mtd ) , struct nuc900_nand , chip ) ;
2015-12-10 10:59:47 +03:00
}
2009-07-10 11:17:27 +04:00
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 ;
2015-12-10 10:59:47 +03:00
struct nuc900_nand * nand = mtd_to_nuc900 ( 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 ;
2015-12-10 10:59:47 +03:00
struct nuc900_nand * nand = mtd_to_nuc900 ( 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 ;
2015-12-10 10:59:47 +03:00
struct nuc900_nand * nand = mtd_to_nuc900 ( 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 ) ;
2016-01-14 00:38:08 +03:00
val = __raw_readl ( nand - > reg + REG_SMISR ) ;
2009-07-10 11:17:27 +04:00
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
{
2015-12-10 10:59:47 +03:00
struct nuc900_nand * nand = mtd_to_nuc900 ( mtd ) ;
2009-07-10 11:17:27 +04:00
int ready ;
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
{
2015-12-01 14:03:04 +03:00
register struct nand_chip * chip = mtd_to_nand ( mtd ) ;
2015-12-10 10:59:47 +03:00
struct nuc900_nand * nand = mtd_to_nuc900 ( 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 ) {
mtd: nand: force NAND_CMD_READID onto 8-bit bus
The NAND command helpers tend to automatically shift the column address
for x16 bus devices, since most commands expect a word address, not a
byte address. The Read ID command, however, expects an 8-bit address
(i.e., 0x00, 0x20, or 0x40 should not be translated to 0x00, 0x10, or
0x20).
This fixes the column address for a few drivers which imitate the
nand_base defaults. Note that I don't touch sh_flctl.c, since it already
handles this problem slightly differently (note its comment "READID is
always performed using an 8-bit bus").
I have not tested this patch, as I only have x8 parts up for testing at
this point. Hopefully that can change soon...
Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Tested-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Tested-By: Pekon Gupta <pekon@ti.com>
2014-01-30 02:08:12 +04:00
if ( chip - > options & NAND_BUSWIDTH_16 & &
! nand_opcode_8bits ( command ) )
2009-07-10 11:17:27 +04:00
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 :
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 ) )
2014-02-18 00:03:08 +04:00
__raw_writel ( val | NAND_EN , nand - > reg + REG_FMICSR ) ;
2009-07-10 11:17:27 +04:00
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 ;
2015-12-10 11:00:15 +03:00
struct mtd_info * mtd ;
2009-07-10 11:17:27 +04:00
struct resource * res ;
2013-12-26 05:44:59 +04:00
nuc900_nand = devm_kzalloc ( & pdev - > dev , sizeof ( struct nuc900_nand ) ,
GFP_KERNEL ) ;
2010-01-01 15:16:47 +03:00
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 ) ;
2015-12-10 11:00:15 +03:00
mtd = nand_to_mtd ( chip ) ;
2009-07-10 11:17:27 +04:00
2015-12-10 11:00:15 +03:00
mtd - > dev . parent = & pdev - > dev ;
2010-01-01 15:16:47 +03:00
spin_lock_init ( & nuc900_nand - > lock ) ;
2009-07-10 11:17:27 +04:00
2013-12-26 05:44:59 +04:00
nuc900_nand - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( nuc900_nand - > clk ) )
return - ENOENT ;
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 ;
2016-04-08 13:23:47 +03:00
chip - > ecc . algo = NAND_ECC_HAMMING ;
2009-07-10 11:17:27 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-12-26 05:44:59 +04:00
nuc900_nand - > reg = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( nuc900_nand - > reg ) )
return PTR_ERR ( nuc900_nand - > reg ) ;
2009-07-10 11:17:27 +04:00
2010-01-01 15:16:47 +03:00
nuc900_nand_enable ( nuc900_nand ) ;
2009-07-10 11:17:27 +04:00
2015-12-10 11:00:15 +03:00
if ( nand_scan ( mtd , 1 ) )
2013-12-26 05:44:59 +04:00
return - ENXIO ;
2009-07-10 11:17:27 +04:00
2015-12-10 11:00:15 +03:00
mtd_device_register ( 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
2013-12-26 05:44:59 +04:00
return 0 ;
2009-07-10 11:17:27 +04:00
}
2012-11-19 22:26:04 +04:00
static int 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
2015-12-10 11:00:15 +03:00
nand_release ( nand_to_mtd ( & nuc900_nand - > chip ) ) ;
2010-01-01 15:16:47 +03:00
clk_disable ( nuc900_nand - > clk ) ;
2009-07-10 11:17:27 +04:00
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
} ,
} ;
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 " ) ;