2010-07-17 11:15:29 +00:00
/*
* Copyright ( C ) 2009 - 2010 , Lars - Peter Clausen < lars @ metafoo . de >
* JZ4740 SoC NAND controller driver
*
* 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 ; either version 2 of the License , or ( at your
* option ) any later version .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/ioport.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/partitions.h>
# include <linux/gpio.h>
# include <asm/mach-jz4740/jz4740_nand.h>
# define JZ_REG_NAND_CTRL 0x50
# define JZ_REG_NAND_ECC_CTRL 0x100
# define JZ_REG_NAND_DATA 0x104
# define JZ_REG_NAND_PAR0 0x108
# define JZ_REG_NAND_PAR1 0x10C
# define JZ_REG_NAND_PAR2 0x110
# define JZ_REG_NAND_IRQ_STAT 0x114
# define JZ_REG_NAND_IRQ_CTRL 0x118
# define JZ_REG_NAND_ERR(x) (0x11C + ((x) << 2))
# define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
# define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
# define JZ_NAND_ECC_CTRL_RS BIT(2)
# define JZ_NAND_ECC_CTRL_RESET BIT(1)
# define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
# define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
# define JZ_NAND_STATUS_PAD_FINISH BIT(4)
# define JZ_NAND_STATUS_DEC_FINISH BIT(3)
# define JZ_NAND_STATUS_ENC_FINISH BIT(2)
# define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
# define JZ_NAND_STATUS_ERROR BIT(0)
# define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1)
# define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1)
2012-03-29 19:17:01 +02:00
# define JZ_NAND_CTRL_ASSERT_CHIP_MASK 0xaa
2010-07-17 11:15:29 +00:00
# define JZ_NAND_MEM_CMD_OFFSET 0x08000
2012-03-29 19:17:01 +02:00
# define JZ_NAND_MEM_ADDR_OFFSET 0x10000
2010-07-17 11:15:29 +00:00
struct jz_nand {
struct mtd_info mtd ;
struct nand_chip chip ;
void __iomem * base ;
struct resource * mem ;
2012-03-29 19:17:01 +02:00
unsigned char banks [ JZ_NAND_NUM_BANKS ] ;
void __iomem * bank_base [ JZ_NAND_NUM_BANKS ] ;
struct resource * bank_mem [ JZ_NAND_NUM_BANKS ] ;
int selected_bank ;
2010-07-17 11:15:29 +00:00
struct jz_nand_platform_data * pdata ;
bool is_reading ;
} ;
static inline struct jz_nand * mtd_to_jz_nand ( struct mtd_info * mtd )
{
return container_of ( mtd , struct jz_nand , mtd ) ;
}
2012-03-29 19:17:01 +02:00
static void jz_nand_select_chip ( struct mtd_info * mtd , int chipnr )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
struct nand_chip * chip = mtd - > priv ;
uint32_t ctrl ;
int banknr ;
ctrl = readl ( nand - > base + JZ_REG_NAND_CTRL ) ;
ctrl & = ~ JZ_NAND_CTRL_ASSERT_CHIP_MASK ;
if ( chipnr = = - 1 ) {
banknr = - 1 ;
} else {
banknr = nand - > banks [ chipnr ] - 1 ;
chip - > IO_ADDR_R = nand - > bank_base [ banknr ] ;
chip - > IO_ADDR_W = nand - > bank_base [ banknr ] ;
}
writel ( ctrl , nand - > base + JZ_REG_NAND_CTRL ) ;
nand - > selected_bank = banknr ;
}
2010-07-17 11:15:29 +00:00
static void jz_nand_cmd_ctrl ( struct mtd_info * mtd , int dat , unsigned int ctrl )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
struct nand_chip * chip = mtd - > priv ;
uint32_t reg ;
2012-03-29 19:17:01 +02:00
void __iomem * bank_base = nand - > bank_base [ nand - > selected_bank ] ;
BUG_ON ( nand - > selected_bank < 0 ) ;
2010-07-17 11:15:29 +00:00
if ( ctrl & NAND_CTRL_CHANGE ) {
BUG_ON ( ( ctrl & NAND_ALE ) & & ( ctrl & NAND_CLE ) ) ;
if ( ctrl & NAND_ALE )
2012-03-29 19:17:01 +02:00
bank_base + = JZ_NAND_MEM_ADDR_OFFSET ;
2010-07-17 11:15:29 +00:00
else if ( ctrl & NAND_CLE )
2012-03-29 19:17:01 +02:00
bank_base + = JZ_NAND_MEM_CMD_OFFSET ;
chip - > IO_ADDR_W = bank_base ;
2010-07-17 11:15:29 +00:00
reg = readl ( nand - > base + JZ_REG_NAND_CTRL ) ;
if ( ctrl & NAND_NCE )
2012-03-29 19:17:01 +02:00
reg | = JZ_NAND_CTRL_ASSERT_CHIP ( nand - > selected_bank ) ;
2010-07-17 11:15:29 +00:00
else
2012-03-29 19:17:01 +02:00
reg & = ~ JZ_NAND_CTRL_ASSERT_CHIP ( nand - > selected_bank ) ;
2010-07-17 11:15:29 +00:00
writel ( reg , nand - > base + JZ_REG_NAND_CTRL ) ;
}
if ( dat ! = NAND_CMD_NONE )
writeb ( dat , chip - > IO_ADDR_W ) ;
}
static int jz_nand_dev_ready ( struct mtd_info * mtd )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
return gpio_get_value_cansleep ( nand - > pdata - > busy_gpio ) ;
}
static void jz_nand_hwctl ( struct mtd_info * mtd , int mode )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
uint32_t reg ;
writel ( 0 , nand - > base + JZ_REG_NAND_IRQ_STAT ) ;
reg = readl ( nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
reg | = JZ_NAND_ECC_CTRL_RESET ;
reg | = JZ_NAND_ECC_CTRL_ENABLE ;
reg | = JZ_NAND_ECC_CTRL_RS ;
switch ( mode ) {
case NAND_ECC_READ :
reg & = ~ JZ_NAND_ECC_CTRL_ENCODING ;
nand - > is_reading = true ;
break ;
case NAND_ECC_WRITE :
reg | = JZ_NAND_ECC_CTRL_ENCODING ;
nand - > is_reading = false ;
break ;
default :
break ;
}
writel ( reg , nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
}
static int jz_nand_calculate_ecc_rs ( struct mtd_info * mtd , const uint8_t * dat ,
uint8_t * ecc_code )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
uint32_t reg , status ;
int i ;
unsigned int timeout = 1000 ;
static uint8_t empty_block_ecc [ ] = { 0xcd , 0x9d , 0x90 , 0x58 , 0xf4 ,
0x8b , 0xff , 0xb7 , 0x6f } ;
if ( nand - > is_reading )
return 0 ;
do {
status = readl ( nand - > base + JZ_REG_NAND_IRQ_STAT ) ;
} while ( ! ( status & JZ_NAND_STATUS_ENC_FINISH ) & & - - timeout ) ;
if ( timeout = = 0 )
return - 1 ;
reg = readl ( nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
reg & = ~ JZ_NAND_ECC_CTRL_ENABLE ;
writel ( reg , nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
for ( i = 0 ; i < 9 ; + + i )
ecc_code [ i ] = readb ( nand - > base + JZ_REG_NAND_PAR0 + i ) ;
/* If the written data is completly 0xff, we also want to write 0xff as
* ecc , otherwise we will get in trouble when doing subpage writes . */
if ( memcmp ( ecc_code , empty_block_ecc , 9 ) = = 0 )
memset ( ecc_code , 0xff , 9 ) ;
return 0 ;
}
static void jz_nand_correct_data ( uint8_t * dat , int index , int mask )
{
int offset = index & 0x7 ;
uint16_t data ;
index + = ( index > > 3 ) ;
data = dat [ index ] ;
data | = dat [ index + 1 ] < < 8 ;
mask ^ = ( data > > offset ) & 0x1ff ;
data & = ~ ( 0x1ff < < offset ) ;
data | = ( mask < < offset ) ;
dat [ index ] = data & 0xff ;
dat [ index + 1 ] = ( data > > 8 ) & 0xff ;
}
static int jz_nand_correct_ecc_rs ( struct mtd_info * mtd , uint8_t * dat ,
uint8_t * read_ecc , uint8_t * calc_ecc )
{
struct jz_nand * nand = mtd_to_jz_nand ( mtd ) ;
int i , error_count , index ;
uint32_t reg , status , error ;
uint32_t t ;
unsigned int timeout = 1000 ;
t = read_ecc [ 0 ] ;
if ( t = = 0xff ) {
for ( i = 1 ; i < 9 ; + + i )
t & = read_ecc [ i ] ;
t & = dat [ 0 ] ;
t & = dat [ nand - > chip . ecc . size / 2 ] ;
t & = dat [ nand - > chip . ecc . size - 1 ] ;
if ( t = = 0xff ) {
for ( i = 1 ; i < nand - > chip . ecc . size - 1 ; + + i )
t & = dat [ i ] ;
if ( t = = 0xff )
return 0 ;
}
}
for ( i = 0 ; i < 9 ; + + i )
writeb ( read_ecc [ i ] , nand - > base + JZ_REG_NAND_PAR0 + i ) ;
reg = readl ( nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
reg | = JZ_NAND_ECC_CTRL_PAR_READY ;
writel ( reg , nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
do {
status = readl ( nand - > base + JZ_REG_NAND_IRQ_STAT ) ;
} while ( ! ( status & JZ_NAND_STATUS_DEC_FINISH ) & & - - timeout ) ;
if ( timeout = = 0 )
return - 1 ;
reg = readl ( nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
reg & = ~ JZ_NAND_ECC_CTRL_ENABLE ;
writel ( reg , nand - > base + JZ_REG_NAND_ECC_CTRL ) ;
if ( status & JZ_NAND_STATUS_ERROR ) {
if ( status & JZ_NAND_STATUS_UNCOR_ERROR )
return - 1 ;
error_count = ( status & JZ_NAND_STATUS_ERR_COUNT ) > > 29 ;
for ( i = 0 ; i < error_count ; + + i ) {
error = readl ( nand - > base + JZ_REG_NAND_ERR ( i ) ) ;
index = ( ( error > > 16 ) & 0x1ff ) - 1 ;
if ( index > = 0 & & index < 512 )
jz_nand_correct_data ( dat , index , error & 0x1ff ) ;
}
return error_count ;
}
return 0 ;
}
static int jz_nand_ioremap_resource ( struct platform_device * pdev ,
2012-03-29 19:17:01 +02:00
const char * name , struct resource * * res , void * __iomem * base )
2010-07-17 11:15:29 +00:00
{
int ret ;
* res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , name ) ;
if ( ! * res ) {
dev_err ( & pdev - > dev , " Failed to get platform %s memory \n " , name ) ;
ret = - ENXIO ;
goto err ;
}
* res = request_mem_region ( ( * res ) - > start , resource_size ( * res ) ,
pdev - > name ) ;
if ( ! * res ) {
dev_err ( & pdev - > dev , " Failed to request %s memory region \n " , name ) ;
ret = - EBUSY ;
goto err ;
}
* base = ioremap ( ( * res ) - > start , resource_size ( * res ) ) ;
if ( ! * base ) {
dev_err ( & pdev - > dev , " Failed to ioremap %s memory region \n " , name ) ;
ret = - EBUSY ;
goto err_release_mem ;
}
return 0 ;
err_release_mem :
release_mem_region ( ( * res ) - > start , resource_size ( * res ) ) ;
err :
* res = NULL ;
* base = NULL ;
return ret ;
}
2012-11-22 12:16:28 +02:00
static inline void jz_nand_iounmap_resource ( struct resource * res ,
void __iomem * base )
2012-03-29 19:17:01 +02:00
{
iounmap ( base ) ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
}
2012-11-22 12:16:28 +02:00
static int jz_nand_detect_bank ( struct platform_device * pdev ,
struct jz_nand * nand , unsigned char bank ,
size_t chipnr , uint8_t * nand_maf_id ,
2012-12-21 13:19:05 -08:00
uint8_t * nand_dev_id )
{
2012-03-29 19:17:01 +02:00
int ret ;
int gpio ;
char gpio_name [ 9 ] ;
char res_name [ 6 ] ;
uint32_t ctrl ;
struct mtd_info * mtd = & nand - > mtd ;
struct nand_chip * chip = & nand - > chip ;
/* Request GPIO port. */
gpio = JZ_GPIO_MEM_CS0 + bank - 1 ;
sprintf ( gpio_name , " NAND CS%d " , bank ) ;
ret = gpio_request ( gpio , gpio_name ) ;
if ( ret ) {
dev_warn ( & pdev - > dev ,
" Failed to request %s gpio %d: %d \n " ,
gpio_name , gpio , ret ) ;
goto notfound_gpio ;
}
/* Request I/O resource. */
sprintf ( res_name , " bank%d " , bank ) ;
ret = jz_nand_ioremap_resource ( pdev , res_name ,
& nand - > bank_mem [ bank - 1 ] ,
& nand - > bank_base [ bank - 1 ] ) ;
if ( ret )
goto notfound_resource ;
/* Enable chip in bank. */
jz_gpio_set_function ( gpio , JZ_GPIO_FUNC_MEM_CS0 ) ;
ctrl = readl ( nand - > base + JZ_REG_NAND_CTRL ) ;
ctrl | = JZ_NAND_CTRL_ENABLE_CHIP ( bank - 1 ) ;
writel ( ctrl , nand - > base + JZ_REG_NAND_CTRL ) ;
if ( chipnr = = 0 ) {
/* Detect first chip. */
ret = nand_scan_ident ( mtd , 1 , NULL ) ;
if ( ret )
goto notfound_id ;
/* Retrieve the IDs from the first chip. */
chip - > select_chip ( mtd , 0 ) ;
chip - > cmdfunc ( mtd , NAND_CMD_RESET , - 1 , - 1 ) ;
chip - > cmdfunc ( mtd , NAND_CMD_READID , 0x00 , - 1 ) ;
* nand_maf_id = chip - > read_byte ( mtd ) ;
* nand_dev_id = chip - > read_byte ( mtd ) ;
} else {
/* Detect additional chip. */
chip - > select_chip ( mtd , chipnr ) ;
chip - > cmdfunc ( mtd , NAND_CMD_RESET , - 1 , - 1 ) ;
chip - > cmdfunc ( mtd , NAND_CMD_READID , 0x00 , - 1 ) ;
if ( * nand_maf_id ! = chip - > read_byte ( mtd )
| | * nand_dev_id ! = chip - > read_byte ( mtd ) ) {
ret = - ENODEV ;
goto notfound_id ;
}
/* Update size of the MTD. */
chip - > numchips + + ;
mtd - > size + = chip - > chipsize ;
}
dev_info ( & pdev - > dev , " Found chip %i on bank %i \n " , chipnr , bank ) ;
return 0 ;
notfound_id :
dev_info ( & pdev - > dev , " No chip found on bank %i \n " , bank ) ;
ctrl & = ~ ( JZ_NAND_CTRL_ENABLE_CHIP ( bank - 1 ) ) ;
writel ( ctrl , nand - > base + JZ_REG_NAND_CTRL ) ;
jz_gpio_set_function ( gpio , JZ_GPIO_FUNC_NONE ) ;
jz_nand_iounmap_resource ( nand - > bank_mem [ bank - 1 ] ,
nand - > bank_base [ bank - 1 ] ) ;
notfound_resource :
gpio_free ( gpio ) ;
notfound_gpio :
return ret ;
}
2012-11-19 13:23:07 -05:00
static int jz_nand_probe ( struct platform_device * pdev )
2010-07-17 11:15:29 +00:00
{
int ret ;
struct jz_nand * nand ;
struct nand_chip * chip ;
struct mtd_info * mtd ;
struct jz_nand_platform_data * pdata = pdev - > dev . platform_data ;
2012-03-29 19:17:01 +02:00
size_t chipnr , bank_idx ;
uint8_t nand_maf_id = 0 , nand_dev_id = 0 ;
2010-07-17 11:15:29 +00:00
nand = kzalloc ( sizeof ( * nand ) , GFP_KERNEL ) ;
if ( ! nand ) {
dev_err ( & pdev - > dev , " Failed to allocate device structure. \n " ) ;
return - ENOMEM ;
}
ret = jz_nand_ioremap_resource ( pdev , " mmio " , & nand - > mem , & nand - > base ) ;
if ( ret )
goto err_free ;
if ( pdata & & gpio_is_valid ( pdata - > busy_gpio ) ) {
ret = gpio_request ( pdata - > busy_gpio , " NAND busy pin " ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Failed to request busy gpio %d: %d \n " ,
pdata - > busy_gpio , ret ) ;
2012-03-29 19:17:01 +02:00
goto err_iounmap_mmio ;
2010-07-17 11:15:29 +00:00
}
}
mtd = & nand - > mtd ;
chip = & nand - > chip ;
mtd - > priv = chip ;
mtd - > owner = THIS_MODULE ;
mtd - > name = " jz4740-nand " ;
chip - > ecc . hwctl = jz_nand_hwctl ;
chip - > ecc . calculate = jz_nand_calculate_ecc_rs ;
chip - > ecc . correct = jz_nand_correct_ecc_rs ;
chip - > ecc . mode = NAND_ECC_HW_OOB_FIRST ;
chip - > ecc . size = 512 ;
chip - > ecc . bytes = 9 ;
2012-04-25 12:06:06 -07:00
chip - > ecc . strength = 4 ;
2010-07-17 11:15:29 +00:00
if ( pdata )
chip - > ecc . layout = pdata - > ecc_layout ;
chip - > chip_delay = 50 ;
chip - > cmd_ctrl = jz_nand_cmd_ctrl ;
2012-03-29 19:17:01 +02:00
chip - > select_chip = jz_nand_select_chip ;
2010-07-17 11:15:29 +00:00
if ( pdata & & gpio_is_valid ( pdata - > busy_gpio ) )
chip - > dev_ready = jz_nand_dev_ready ;
nand - > pdata = pdata ;
platform_set_drvdata ( pdev , nand ) ;
2012-03-29 19:17:01 +02:00
/* We are going to autodetect NAND chips in the banks specified in the
* platform data . Although nand_scan_ident ( ) can detect multiple chips ,
* it requires those chips to be numbered consecuitively , which is not
* always the case for external memory banks . And a fixed chip - to - bank
* mapping is not practical either , since for example Dingoo units
* produced at different times have NAND chips in different banks .
*/
chipnr = 0 ;
for ( bank_idx = 0 ; bank_idx < JZ_NAND_NUM_BANKS ; bank_idx + + ) {
unsigned char bank ;
/* If there is no platform data, look for NAND in bank 1,
* which is the most likely bank since it is the only one
* that can be booted from .
*/
bank = pdata ? pdata - > banks [ bank_idx ] : bank_idx ^ 1 ;
if ( bank = = 0 )
break ;
if ( bank > JZ_NAND_NUM_BANKS ) {
dev_warn ( & pdev - > dev ,
" Skipping non-existing bank: %d \n " , bank ) ;
continue ;
}
/* The detection routine will directly or indirectly call
* jz_nand_select_chip ( ) , so nand - > banks has to contain the
* bank we ' re checking .
*/
nand - > banks [ chipnr ] = bank ;
if ( jz_nand_detect_bank ( pdev , nand , bank , chipnr ,
& nand_maf_id , & nand_dev_id ) = = 0 )
chipnr + + ;
else
nand - > banks [ chipnr ] = 0 ;
}
if ( chipnr = = 0 ) {
dev_err ( & pdev - > dev , " No NAND chips found \n " ) ;
goto err_gpio_busy ;
2010-07-17 11:15:29 +00:00
}
if ( pdata & & pdata - > ident_callback ) {
pdata - > ident_callback ( pdev , chip , & pdata - > partitions ,
& pdata - > num_partitions ) ;
}
ret = nand_scan_tail ( mtd ) ;
if ( ret ) {
2012-03-29 19:17:01 +02:00
dev_err ( & pdev - > dev , " Failed to scan NAND \n " ) ;
goto err_unclaim_banks ;
2010-07-17 11:15:29 +00:00
}
mtd: do not use plain 0 as NULL
The first 3 arguments of 'mtd_device_parse_register()' are pointers,
but many callers pass '0' instead of 'NULL'. Fix this globally. Thanks
to coccinelle for making it easy to do with the following semantic patch:
@@
expression mtd, types, parser_data, parts, nr_parts;
@@
(
-mtd_device_parse_register(mtd, 0, parser_data, parts, nr_parts)
+mtd_device_parse_register(mtd, NULL, parser_data, parts, nr_parts)
|
-mtd_device_parse_register(mtd, types, 0, parts, nr_parts)
+mtd_device_parse_register(mtd, types, NULL, parts, nr_parts)
|
-mtd_device_parse_register(mtd, types, parser_data, 0, nr_parts)
+mtd_device_parse_register(mtd, types, parser_data, NULL, nr_parts)
)
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-03-09 19:24:26 +02:00
ret = mtd_device_parse_register ( mtd , NULL , NULL ,
pdata ? pdata - > partitions : NULL ,
pdata ? pdata - > num_partitions : 0 ) ;
2010-07-17 11:15:29 +00:00
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add mtd device \n " ) ;
goto err_nand_release ;
}
dev_info ( & pdev - > dev , " Successfully registered JZ4740 NAND driver \n " ) ;
return 0 ;
err_nand_release :
2012-03-29 19:17:01 +02:00
nand_release ( mtd ) ;
err_unclaim_banks :
while ( chipnr - - ) {
unsigned char bank = nand - > banks [ chipnr ] ;
gpio_free ( JZ_GPIO_MEM_CS0 + bank - 1 ) ;
jz_nand_iounmap_resource ( nand - > bank_mem [ bank - 1 ] ,
nand - > bank_base [ bank - 1 ] ) ;
}
writel ( 0 , nand - > base + JZ_REG_NAND_CTRL ) ;
err_gpio_busy :
if ( pdata & & gpio_is_valid ( pdata - > busy_gpio ) )
gpio_free ( pdata - > busy_gpio ) ;
2010-07-17 11:15:29 +00:00
err_iounmap_mmio :
2012-03-29 19:17:01 +02:00
jz_nand_iounmap_resource ( nand - > mem , nand - > base ) ;
2010-07-17 11:15:29 +00:00
err_free :
kfree ( nand ) ;
return ret ;
}
2012-11-19 13:26:04 -05:00
static int jz_nand_remove ( struct platform_device * pdev )
2010-07-17 11:15:29 +00:00
{
struct jz_nand * nand = platform_get_drvdata ( pdev ) ;
2012-03-29 19:17:01 +02:00
struct jz_nand_platform_data * pdata = pdev - > dev . platform_data ;
size_t i ;
2010-07-17 11:15:29 +00:00
nand_release ( & nand - > mtd ) ;
/* Deassert and disable all chips */
writel ( 0 , nand - > base + JZ_REG_NAND_CTRL ) ;
2012-03-29 19:17:01 +02:00
for ( i = 0 ; i < JZ_NAND_NUM_BANKS ; + + i ) {
unsigned char bank = nand - > banks [ i ] ;
if ( bank ! = 0 ) {
jz_nand_iounmap_resource ( nand - > bank_mem [ bank - 1 ] ,
nand - > bank_base [ bank - 1 ] ) ;
gpio_free ( JZ_GPIO_MEM_CS0 + bank - 1 ) ;
}
}
if ( pdata & & gpio_is_valid ( pdata - > busy_gpio ) )
gpio_free ( pdata - > busy_gpio ) ;
jz_nand_iounmap_resource ( nand - > mem , nand - > base ) ;
2010-07-17 11:15:29 +00:00
kfree ( nand ) ;
return 0 ;
}
2010-11-11 19:02:47 +01:00
static struct platform_driver jz_nand_driver = {
2010-07-17 11:15:29 +00:00
. probe = jz_nand_probe ,
2012-11-19 13:21:24 -05:00
. remove = jz_nand_remove ,
2010-07-17 11:15:29 +00:00
. driver = {
. name = " jz4740-nand " ,
. owner = THIS_MODULE ,
} ,
} ;
2011-11-27 20:45:03 +08:00
module_platform_driver ( jz_nand_driver ) ;
2010-07-17 11:15:29 +00:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_DESCRIPTION ( " NAND controller driver for JZ4740 SoC " ) ;
MODULE_ALIAS ( " platform:jz4740-nand " ) ;