2006-10-19 20:24:35 +04:00
/*
* Copyright ( C ) 2003 Rick Bronson
*
* Derived from drivers / mtd / nand / autcpu12 . c
* Copyright ( c ) 2001 Thomas Gleixner ( gleixner @ autronix . de )
*
* Derived from drivers / mtd / spia . c
* Copyright ( C ) 2000 Steven J . Hill ( sjhill @ cotw . com )
*
2008-04-23 21:51:14 +04:00
*
* Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
* Richard Genoud ( richard . genoud @ gmail . com ) , Adeneo Copyright ( C ) 2007
*
* Derived from Das U - Boot source code
* ( u - boot - 1.1 .5 / board / atmel / at91sam9263ek / nand . c )
* ( C ) Copyright 2006 ATMEL Rousset , Lacressonniere Nicolas
*
*
2006-10-19 20:24:35 +04:00
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
2011-06-16 15:01:34 +04:00
# include <linux/dma-mapping.h>
2006-10-19 20:24:35 +04:00
# include <linux/slab.h>
# include <linux/module.h>
2009-05-27 19:19:39 +04:00
# include <linux/moduleparam.h>
2006-10-19 20:24:35 +04:00
# include <linux/platform_device.h>
2012-01-25 22:11:06 +04:00
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_gpio.h>
# include <linux/of_mtd.h>
2006-10-19 20:24:35 +04:00
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/partitions.h>
2011-04-13 17:55:17 +04:00
# include <linux/dmaengine.h>
2008-06-07 11:49:00 +04:00
# include <linux/gpio.h>
# include <linux/io.h>
2011-12-29 10:43:24 +04:00
# include <linux/platform_data/atmel.h>
2006-10-19 20:24:35 +04:00
2008-08-05 19:14:15 +04:00
# include <mach/cpu.h>
2006-10-19 20:24:35 +04:00
2011-01-18 09:36:05 +03:00
static int use_dma = 1 ;
module_param ( use_dma , int , 0 ) ;
2009-05-27 19:19:39 +04:00
static int on_flash_bbt = 0 ;
module_param ( on_flash_bbt , int , 0 ) ;
2008-04-23 21:51:14 +04:00
/* Register access macros */
# define ecc_readl(add, reg) \
2008-06-06 20:04:53 +04:00
__raw_readl ( add + ATMEL_ECC_ # # reg )
2008-04-23 21:51:14 +04:00
# define ecc_writel(add, reg, value) \
2008-06-06 20:04:53 +04:00
__raw_writel ( ( value ) , add + ATMEL_ECC_ # # reg )
2008-04-23 21:51:14 +04:00
2008-06-06 20:04:52 +04:00
# include "atmel_nand_ecc.h" /* Hardware ECC registers */
2008-04-23 21:51:14 +04:00
/* oob layout for large page size
* bad block info is on bytes 0 and 1
* the bytes have to be consecutives to avoid
* several NAND_CMD_RNDOUT during read
*/
2008-06-06 20:04:53 +04:00
static struct nand_ecclayout atmel_oobinfo_large = {
2008-04-23 21:51:14 +04:00
. eccbytes = 4 ,
. eccpos = { 60 , 61 , 62 , 63 } ,
. oobfree = {
{ 2 , 58 }
} ,
} ;
/* oob layout for small page size
* bad block info is on bytes 4 and 5
* the bytes have to be consecutives to avoid
* several NAND_CMD_RNDOUT during read
*/
2008-06-06 20:04:53 +04:00
static struct nand_ecclayout atmel_oobinfo_small = {
2008-04-23 21:51:14 +04:00
. eccbytes = 4 ,
. eccpos = { 0 , 1 , 2 , 3 } ,
. oobfree = {
{ 6 , 10 }
} ,
} ;
2008-06-06 20:04:53 +04:00
struct atmel_nand_host {
2006-10-19 20:24:35 +04:00
struct nand_chip nand_chip ;
struct mtd_info mtd ;
void __iomem * io_base ;
2011-01-18 09:36:05 +03:00
dma_addr_t io_phys ;
2012-01-25 22:11:06 +04:00
struct atmel_nand_data board ;
2008-04-23 21:51:14 +04:00
struct device * dev ;
void __iomem * ecc ;
2011-01-18 09:36:05 +03:00
struct completion comp ;
struct dma_chan * dma_chan ;
2006-10-19 20:24:35 +04:00
} ;
2011-01-18 09:36:05 +03:00
static int cpu_has_dma ( void )
{
return cpu_is_at91sam9rl ( ) | | cpu_is_at91sam9g45 ( ) ;
}
2008-04-26 20:51:12 +04:00
/*
* Enable NAND .
*/
2008-06-06 20:04:53 +04:00
static void atmel_nand_enable ( struct atmel_nand_host * host )
2008-04-26 20:51:12 +04:00
{
2012-01-25 22:11:06 +04:00
if ( gpio_is_valid ( host - > board . enable_pin ) )
gpio_set_value ( host - > board . enable_pin , 0 ) ;
2008-04-26 20:51:12 +04:00
}
/*
* Disable NAND .
*/
2008-06-06 20:04:53 +04:00
static void atmel_nand_disable ( struct atmel_nand_host * host )
2008-04-26 20:51:12 +04:00
{
2012-01-25 22:11:06 +04:00
if ( gpio_is_valid ( host - > board . enable_pin ) )
gpio_set_value ( host - > board . enable_pin , 1 ) ;
2008-04-26 20:51:12 +04:00
}
2006-10-19 20:24:35 +04:00
/*
* Hardware specific access to control - lines
*/
2008-06-06 20:04:53 +04:00
static void atmel_nand_cmd_ctrl ( struct mtd_info * mtd , int cmd , unsigned int ctrl )
2006-10-19 20:24:35 +04:00
{
struct nand_chip * nand_chip = mtd - > priv ;
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host = nand_chip - > priv ;
2006-10-19 20:24:35 +04:00
2008-04-26 20:51:12 +04:00
if ( ctrl & NAND_CTRL_CHANGE ) {
2008-04-24 18:51:29 +04:00
if ( ctrl & NAND_NCE )
2008-06-06 20:04:53 +04:00
atmel_nand_enable ( host ) ;
2008-04-24 18:51:29 +04:00
else
2008-06-06 20:04:53 +04:00
atmel_nand_disable ( host ) ;
2008-04-24 18:51:29 +04:00
}
2006-10-19 20:24:35 +04:00
if ( cmd = = NAND_CMD_NONE )
return ;
if ( ctrl & NAND_CLE )
2012-01-25 22:11:06 +04:00
writeb ( cmd , host - > io_base + ( 1 < < host - > board . cle ) ) ;
2006-10-19 20:24:35 +04:00
else
2012-01-25 22:11:06 +04:00
writeb ( cmd , host - > io_base + ( 1 < < host - > board . ale ) ) ;
2006-10-19 20:24:35 +04:00
}
/*
* Read the Device Ready pin .
*/
2008-06-06 20:04:53 +04:00
static int atmel_nand_device_ready ( struct mtd_info * mtd )
2006-10-19 20:24:35 +04:00
{
struct nand_chip * nand_chip = mtd - > priv ;
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host = nand_chip - > priv ;
2006-10-19 20:24:35 +04:00
2012-01-25 22:11:06 +04:00
return gpio_get_value ( host - > board . rdy_pin ) ^
! ! host - > board . rdy_pin_active_low ;
2006-10-19 20:24:35 +04:00
}
2012-02-02 15:54:25 +04:00
/*
* Minimal - overhead PIO for data access .
*/
static void atmel_read_buf8 ( struct mtd_info * mtd , u8 * buf , int len )
{
struct nand_chip * nand_chip = mtd - > priv ;
__raw_readsb ( nand_chip - > IO_ADDR_R , buf , len ) ;
}
static void atmel_read_buf16 ( struct mtd_info * mtd , u8 * buf , int len )
{
struct nand_chip * nand_chip = mtd - > priv ;
__raw_readsw ( nand_chip - > IO_ADDR_R , buf , len / 2 ) ;
}
static void atmel_write_buf8 ( struct mtd_info * mtd , const u8 * buf , int len )
{
struct nand_chip * nand_chip = mtd - > priv ;
__raw_writesb ( nand_chip - > IO_ADDR_W , buf , len ) ;
}
static void atmel_write_buf16 ( struct mtd_info * mtd , const u8 * buf , int len )
{
struct nand_chip * nand_chip = mtd - > priv ;
__raw_writesw ( nand_chip - > IO_ADDR_W , buf , len / 2 ) ;
}
2011-01-18 09:36:05 +03:00
static void dma_complete_func ( void * completion )
{
complete ( completion ) ;
}
static int atmel_nand_dma_op ( struct mtd_info * mtd , void * buf , int len ,
int is_read )
{
struct dma_device * dma_dev ;
enum dma_ctrl_flags flags ;
dma_addr_t dma_src_addr , dma_dst_addr , phys_addr ;
struct dma_async_tx_descriptor * tx = NULL ;
dma_cookie_t cookie ;
struct nand_chip * chip = mtd - > priv ;
struct atmel_nand_host * host = chip - > priv ;
void * p = buf ;
int err = - EIO ;
enum dma_data_direction dir = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE ;
2011-03-31 14:33:15 +04:00
if ( buf > = high_memory )
goto err_buf ;
2011-01-18 09:36:05 +03:00
dma_dev = host - > dma_chan - > device ;
flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_SRC_UNMAP |
DMA_COMPL_SKIP_DEST_UNMAP ;
phys_addr = dma_map_single ( dma_dev - > dev , p , len , dir ) ;
if ( dma_mapping_error ( dma_dev - > dev , phys_addr ) ) {
dev_err ( host - > dev , " Failed to dma_map_single \n " ) ;
goto err_buf ;
}
if ( is_read ) {
dma_src_addr = host - > io_phys ;
dma_dst_addr = phys_addr ;
} else {
dma_src_addr = phys_addr ;
dma_dst_addr = host - > io_phys ;
}
tx = dma_dev - > device_prep_dma_memcpy ( host - > dma_chan , dma_dst_addr ,
dma_src_addr , len , flags ) ;
if ( ! tx ) {
dev_err ( host - > dev , " Failed to prepare DMA memcpy \n " ) ;
goto err_dma ;
}
init_completion ( & host - > comp ) ;
tx - > callback = dma_complete_func ;
tx - > callback_param = & host - > comp ;
cookie = tx - > tx_submit ( tx ) ;
if ( dma_submit_error ( cookie ) ) {
dev_err ( host - > dev , " Failed to do DMA tx_submit \n " ) ;
goto err_dma ;
}
dma_async_issue_pending ( host - > dma_chan ) ;
wait_for_completion ( & host - > comp ) ;
err = 0 ;
err_dma :
dma_unmap_single ( dma_dev - > dev , phys_addr , len , dir ) ;
err_buf :
if ( err ! = 0 )
dev_warn ( host - > dev , " Fall back to CPU I/O \n " ) ;
return err ;
}
static void atmel_read_buf ( struct mtd_info * mtd , u8 * buf , int len )
{
struct nand_chip * chip = mtd - > priv ;
2012-02-02 15:54:25 +04:00
struct atmel_nand_host * host = chip - > priv ;
2011-01-18 09:36:05 +03:00
2011-04-01 18:40:44 +04:00
if ( use_dma & & len > mtd - > oobsize )
/* only use DMA for bigger than oob size: better performances */
2011-01-18 09:36:05 +03:00
if ( atmel_nand_dma_op ( mtd , buf , len , 1 ) = = 0 )
return ;
2012-01-25 22:11:06 +04:00
if ( host - > board . bus_width_16 )
2012-02-02 15:54:25 +04:00
atmel_read_buf16 ( mtd , buf , len ) ;
else
atmel_read_buf8 ( mtd , buf , len ) ;
2011-01-18 09:36:05 +03:00
}
static void atmel_write_buf ( struct mtd_info * mtd , const u8 * buf , int len )
{
struct nand_chip * chip = mtd - > priv ;
2012-02-02 15:54:25 +04:00
struct atmel_nand_host * host = chip - > priv ;
2011-01-18 09:36:05 +03:00
2011-04-01 18:40:44 +04:00
if ( use_dma & & len > mtd - > oobsize )
/* only use DMA for bigger than oob size: better performances */
2011-01-18 09:36:05 +03:00
if ( atmel_nand_dma_op ( mtd , ( void * ) buf , len , 0 ) = = 0 )
return ;
2012-01-25 22:11:06 +04:00
if ( host - > board . bus_width_16 )
2012-02-02 15:54:25 +04:00
atmel_write_buf16 ( mtd , buf , len ) ;
else
atmel_write_buf8 ( mtd , buf , len ) ;
2011-01-18 09:36:05 +03:00
}
2008-04-23 21:51:14 +04:00
/*
* Calculate HW ECC
*
* function called after a write
*
* mtd : MTD block structure
* dat : raw data ( unused )
* ecc_code : buffer for ECC
*/
2008-06-06 20:04:53 +04:00
static int atmel_nand_calculate ( struct mtd_info * mtd ,
2008-04-23 21:51:14 +04:00
const u_char * dat , unsigned char * ecc_code )
{
struct nand_chip * nand_chip = mtd - > priv ;
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host = nand_chip - > priv ;
2008-04-23 21:51:14 +04:00
unsigned int ecc_value ;
/* get the first 2 ECC bytes */
2008-04-25 11:32:26 +04:00
ecc_value = ecc_readl ( host - > ecc , PR ) ;
2008-04-23 21:51:14 +04:00
2008-10-12 10:42:28 +04:00
ecc_code [ 0 ] = ecc_value & 0xFF ;
ecc_code [ 1 ] = ( ecc_value > > 8 ) & 0xFF ;
2008-04-23 21:51:14 +04:00
/* get the last 2 ECC bytes */
2008-06-06 20:04:53 +04:00
ecc_value = ecc_readl ( host - > ecc , NPR ) & ATMEL_ECC_NPARITY ;
2008-04-23 21:51:14 +04:00
2008-10-12 10:42:28 +04:00
ecc_code [ 2 ] = ecc_value & 0xFF ;
ecc_code [ 3 ] = ( ecc_value > > 8 ) & 0xFF ;
2008-04-23 21:51:14 +04:00
return 0 ;
}
/*
* HW ECC read page function
*
* mtd : mtd info structure
* chip : nand chip info structure
* buf : buffer to store read data
*/
2008-06-06 20:04:53 +04:00
static int atmel_nand_read_page ( struct mtd_info * mtd ,
2009-09-18 23:51:46 +04:00
struct nand_chip * chip , uint8_t * buf , int page )
2008-04-23 21:51:14 +04:00
{
int eccsize = chip - > ecc . size ;
int eccbytes = chip - > ecc . bytes ;
uint32_t * eccpos = chip - > ecc . layout - > eccpos ;
uint8_t * p = buf ;
uint8_t * oob = chip - > oob_poi ;
uint8_t * ecc_pos ;
int stat ;
2008-07-04 10:40:18 +04:00
/*
* Errata : ALE is incorrectly wired up to the ECC controller
* on the AP7000 , so it will include the address cycles in the
* ECC calculation .
*
* Workaround : Reset the parity registers before reading the
* actual data .
*/
if ( cpu_is_at32ap7000 ( ) ) {
struct atmel_nand_host * host = chip - > priv ;
ecc_writel ( host - > ecc , CR , ATMEL_ECC_RST ) ;
}
2008-04-23 21:51:14 +04:00
/* read the page */
chip - > read_buf ( mtd , p , eccsize ) ;
/* move to ECC position if needed */
if ( eccpos [ 0 ] ! = 0 ) {
/* This only works on large pages
* because the ECC controller waits for
* NAND_CMD_RNDOUTSTART after the
* NAND_CMD_RNDOUT .
* anyway , for small pages , the eccpos [ 0 ] = = 0
*/
chip - > cmdfunc ( mtd , NAND_CMD_RNDOUT ,
mtd - > writesize + eccpos [ 0 ] , - 1 ) ;
}
/* the ECC controller needs to read the ECC just after the data */
ecc_pos = oob + eccpos [ 0 ] ;
chip - > read_buf ( mtd , ecc_pos , eccbytes ) ;
/* check if there's an error */
stat = chip - > ecc . correct ( mtd , p , oob , NULL ) ;
if ( stat < 0 )
mtd - > ecc_stats . failed + + ;
else
mtd - > ecc_stats . corrected + = stat ;
/* get back to oob start (end of page) */
chip - > cmdfunc ( mtd , NAND_CMD_RNDOUT , mtd - > writesize , - 1 ) ;
/* read the oob */
chip - > read_buf ( mtd , oob , mtd - > oobsize ) ;
return 0 ;
}
/*
* HW ECC Correction
*
* function called after a read
*
* mtd : MTD block structure
* dat : raw data read from the chip
* read_ecc : ECC from the chip ( unused )
* isnull : unused
*
* Detect and correct a 1 bit error for a page
*/
2008-06-06 20:04:53 +04:00
static int atmel_nand_correct ( struct mtd_info * mtd , u_char * dat ,
2008-04-23 21:51:14 +04:00
u_char * read_ecc , u_char * isnull )
{
struct nand_chip * nand_chip = mtd - > priv ;
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host = nand_chip - > priv ;
2008-04-23 21:51:14 +04:00
unsigned int ecc_status ;
unsigned int ecc_word , ecc_bit ;
/* get the status from the Status Register */
ecc_status = ecc_readl ( host - > ecc , SR ) ;
/* if there's no error */
2008-06-06 20:04:53 +04:00
if ( likely ( ! ( ecc_status & ATMEL_ECC_RECERR ) ) )
2008-04-23 21:51:14 +04:00
return 0 ;
/* get error bit offset (4 bits) */
2008-06-06 20:04:53 +04:00
ecc_bit = ecc_readl ( host - > ecc , PR ) & ATMEL_ECC_BITADDR ;
2008-04-23 21:51:14 +04:00
/* get word address (12 bits) */
2008-06-06 20:04:53 +04:00
ecc_word = ecc_readl ( host - > ecc , PR ) & ATMEL_ECC_WORDADDR ;
2008-04-23 21:51:14 +04:00
ecc_word > > = 4 ;
/* if there are multiple errors */
2008-06-06 20:04:53 +04:00
if ( ecc_status & ATMEL_ECC_MULERR ) {
2008-04-23 21:51:14 +04:00
/* check if it is a freshly erased block
* ( filled with 0xff ) */
2008-06-06 20:04:53 +04:00
if ( ( ecc_bit = = ATMEL_ECC_BITADDR )
& & ( ecc_word = = ( ATMEL_ECC_WORDADDR > > 4 ) ) ) {
2008-04-23 21:51:14 +04:00
/* the block has just been erased, return OK */
return 0 ;
}
/* it doesn't seems to be a freshly
* erased block .
* We can ' t correct so many errors */
2008-06-06 20:04:53 +04:00
dev_dbg ( host - > dev , " atmel_nand : multiple errors detected. "
2008-04-23 21:51:14 +04:00
" Unable to correct. \n " ) ;
return - EIO ;
}
/* if there's a single bit error : we can correct it */
2008-06-06 20:04:53 +04:00
if ( ecc_status & ATMEL_ECC_ECCERR ) {
2008-04-23 21:51:14 +04:00
/* there's nothing much to do here.
* the bit error is on the ECC itself .
*/
2008-06-06 20:04:53 +04:00
dev_dbg ( host - > dev , " atmel_nand : one bit error on ECC code. "
2008-04-23 21:51:14 +04:00
" Nothing to correct \n " ) ;
return 0 ;
}
2008-06-06 20:04:53 +04:00
dev_dbg ( host - > dev , " atmel_nand : one bit error on data. "
2008-04-23 21:51:14 +04:00
" (word offset in the page : "
" 0x%x bit offset : 0x%x) \n " ,
ecc_word , ecc_bit ) ;
/* correct the error */
if ( nand_chip - > options & NAND_BUSWIDTH_16 ) {
/* 16 bits words */
( ( unsigned short * ) dat ) [ ecc_word ] ^ = ( 1 < < ecc_bit ) ;
} else {
/* 8 bits words */
dat [ ecc_word ] ^ = ( 1 < < ecc_bit ) ;
}
2008-06-06 20:04:53 +04:00
dev_dbg ( host - > dev , " atmel_nand : error corrected \n " ) ;
2008-04-23 21:51:14 +04:00
return 1 ;
}
/*
2008-07-04 10:40:18 +04:00
* Enable HW ECC : unused on most chips
2008-04-23 21:51:14 +04:00
*/
2008-07-04 10:40:18 +04:00
static void atmel_nand_hwctl ( struct mtd_info * mtd , int mode )
{
if ( cpu_is_at32ap7000 ( ) ) {
struct nand_chip * nand_chip = mtd - > priv ;
struct atmel_nand_host * host = nand_chip - > priv ;
ecc_writel ( host - > ecc , CR , ATMEL_ECC_RST ) ;
}
}
2008-04-23 21:51:14 +04:00
2012-01-25 22:11:06 +04:00
# if defined(CONFIG_OF)
static int __devinit atmel_of_init_port ( struct atmel_nand_host * host ,
struct device_node * np )
{
u32 val ;
int ecc_mode ;
struct atmel_nand_data * board = & host - > board ;
enum of_gpio_flags flags ;
if ( of_property_read_u32 ( np , " atmel,nand-addr-offset " , & val ) = = 0 ) {
if ( val > = 32 ) {
dev_err ( host - > dev , " invalid addr-offset %u \n " , val ) ;
return - EINVAL ;
}
board - > ale = val ;
}
if ( of_property_read_u32 ( np , " atmel,nand-cmd-offset " , & val ) = = 0 ) {
if ( val > = 32 ) {
dev_err ( host - > dev , " invalid cmd-offset %u \n " , val ) ;
return - EINVAL ;
}
board - > cle = val ;
}
ecc_mode = of_get_nand_ecc_mode ( np ) ;
board - > ecc_mode = ecc_mode < 0 ? NAND_ECC_SOFT : ecc_mode ;
board - > on_flash_bbt = of_get_nand_on_flash_bbt ( np ) ;
if ( of_get_nand_bus_width ( np ) = = 16 )
board - > bus_width_16 = 1 ;
board - > rdy_pin = of_get_gpio_flags ( np , 0 , & flags ) ;
board - > rdy_pin_active_low = ( flags = = OF_GPIO_ACTIVE_LOW ) ;
board - > enable_pin = of_get_gpio ( np , 1 ) ;
board - > det_pin = of_get_gpio ( np , 2 ) ;
return 0 ;
}
# else
static int __devinit atmel_of_init_port ( struct atmel_nand_host * host ,
struct device_node * np )
{
return - EINVAL ;
}
# endif
2006-10-19 20:24:35 +04:00
/*
* Probe for the NAND device .
*/
2008-06-06 20:04:53 +04:00
static int __init atmel_nand_probe ( struct platform_device * pdev )
2006-10-19 20:24:35 +04:00
{
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host ;
2006-10-19 20:24:35 +04:00
struct mtd_info * mtd ;
struct nand_chip * nand_chip ;
2008-04-23 21:51:14 +04:00
struct resource * regs ;
struct resource * mem ;
2012-01-25 22:11:06 +04:00
struct mtd_part_parser_data ppdata = { } ;
2006-10-19 20:24:35 +04:00
int res ;
2008-06-06 20:04:54 +04:00
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! mem ) {
printk ( KERN_ERR " atmel_nand: can't get I/O resource mem \n " ) ;
return - ENXIO ;
}
2006-10-19 20:24:35 +04:00
/* Allocate memory for the device structure (and zero it) */
2008-06-06 20:04:53 +04:00
host = kzalloc ( sizeof ( struct atmel_nand_host ) , GFP_KERNEL ) ;
2006-10-19 20:24:35 +04:00
if ( ! host ) {
2008-06-06 20:04:53 +04:00
printk ( KERN_ERR " atmel_nand: failed to allocate device structure. \n " ) ;
2006-10-19 20:24:35 +04:00
return - ENOMEM ;
}
2011-01-18 09:36:05 +03:00
host - > io_phys = ( dma_addr_t ) mem - > start ;
2011-06-09 20:13:32 +04:00
host - > io_base = ioremap ( mem - > start , resource_size ( mem ) ) ;
2006-10-19 20:24:35 +04:00
if ( host - > io_base = = NULL ) {
2008-06-06 20:04:53 +04:00
printk ( KERN_ERR " atmel_nand: ioremap failed \n " ) ;
2008-06-06 20:04:54 +04:00
res = - EIO ;
goto err_nand_ioremap ;
2006-10-19 20:24:35 +04:00
}
mtd = & host - > mtd ;
nand_chip = & host - > nand_chip ;
2008-04-23 21:51:14 +04:00
host - > dev = & pdev - > dev ;
2012-01-25 22:11:06 +04:00
if ( pdev - > dev . of_node ) {
res = atmel_of_init_port ( host , pdev - > dev . of_node ) ;
if ( res )
goto err_nand_ioremap ;
} else {
memcpy ( & host - > board , pdev - > dev . platform_data ,
sizeof ( struct atmel_nand_data ) ) ;
}
2006-10-19 20:24:35 +04:00
nand_chip - > priv = host ; /* link the private data structures */
mtd - > priv = nand_chip ;
mtd - > owner = THIS_MODULE ;
/* Set address of NAND IO lines */
nand_chip - > IO_ADDR_R = host - > io_base ;
nand_chip - > IO_ADDR_W = host - > io_base ;
2008-06-06 20:04:53 +04:00
nand_chip - > cmd_ctrl = atmel_nand_cmd_ctrl ;
2007-05-24 15:35:58 +04:00
2012-01-25 22:11:06 +04:00
if ( gpio_is_valid ( host - > board . rdy_pin ) )
2008-06-06 20:04:53 +04:00
nand_chip - > dev_ready = atmel_nand_device_ready ;
2007-05-24 15:35:58 +04:00
2012-01-25 22:11:06 +04:00
nand_chip - > ecc . mode = host - > board . ecc_mode ;
2011-12-29 10:43:24 +04:00
2008-04-23 21:51:14 +04:00
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 1 ) ;
2011-12-29 10:43:24 +04:00
if ( ! regs & & nand_chip - > ecc . mode = = NAND_ECC_HW ) {
2008-06-06 20:04:53 +04:00
printk ( KERN_ERR " atmel_nand: can't get I/O resource "
2008-04-23 21:51:14 +04:00
" regs \n Falling back on software ECC \n " ) ;
2011-12-29 10:43:24 +04:00
nand_chip - > ecc . mode = NAND_ECC_SOFT ;
2008-04-23 21:51:14 +04:00
}
2011-12-29 10:43:24 +04:00
if ( nand_chip - > ecc . mode = = NAND_ECC_HW ) {
2011-06-09 20:13:32 +04:00
host - > ecc = ioremap ( regs - > start , resource_size ( regs ) ) ;
2008-04-23 21:51:14 +04:00
if ( host - > ecc = = NULL ) {
2008-06-06 20:04:53 +04:00
printk ( KERN_ERR " atmel_nand: ioremap failed \n " ) ;
2008-04-23 21:51:14 +04:00
res = - EIO ;
goto err_ecc_ioremap ;
}
2008-06-06 20:04:53 +04:00
nand_chip - > ecc . calculate = atmel_nand_calculate ;
nand_chip - > ecc . correct = atmel_nand_correct ;
nand_chip - > ecc . hwctl = atmel_nand_hwctl ;
nand_chip - > ecc . read_page = atmel_nand_read_page ;
2008-04-23 21:51:14 +04:00
nand_chip - > ecc . bytes = 4 ;
2012-03-12 01:21:11 +04:00
nand_chip - > ecc . strength = 1 ;
2008-04-23 21:51:14 +04:00
}
2006-10-19 20:24:35 +04:00
nand_chip - > chip_delay = 20 ; /* 20us command delay time */
2012-01-25 22:11:06 +04:00
if ( host - > board . bus_width_16 ) /* 16-bit bus width */
2006-12-08 14:49:42 +03:00
nand_chip - > options | = NAND_BUSWIDTH_16 ;
2011-01-18 09:36:05 +03:00
nand_chip - > read_buf = atmel_read_buf ;
nand_chip - > write_buf = atmel_write_buf ;
2006-12-08 14:49:42 +03:00
2006-10-19 20:24:35 +04:00
platform_set_drvdata ( pdev , host ) ;
2008-06-06 20:04:53 +04:00
atmel_nand_enable ( host ) ;
2006-10-19 20:24:35 +04:00
2012-01-25 22:11:06 +04:00
if ( gpio_is_valid ( host - > board . det_pin ) ) {
if ( gpio_get_value ( host - > board . det_pin ) ) {
2009-05-27 19:19:39 +04:00
printk ( KERN_INFO " No SmartMedia card inserted. \n " ) ;
2009-11-11 23:47:06 +03:00
res = - ENXIO ;
2008-06-06 20:04:54 +04:00
goto err_no_card ;
2006-10-19 20:24:35 +04:00
}
}
2012-01-25 22:11:06 +04:00
if ( host - > board . on_flash_bbt | | on_flash_bbt ) {
2009-05-27 19:19:39 +04:00
printk ( KERN_INFO " atmel_nand: Use On Flash BBT \n " ) ;
2011-06-01 03:31:23 +04:00
nand_chip - > bbt_options | = NAND_BBT_USE_FLASH ;
2009-05-27 19:19:39 +04:00
}
2011-03-30 12:26:41 +04:00
if ( ! cpu_has_dma ( ) )
use_dma = 0 ;
if ( use_dma ) {
2011-01-18 09:36:05 +03:00
dma_cap_mask_t mask ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_MEMCPY , mask ) ;
2011-06-29 20:41:16 +04:00
host - > dma_chan = dma_request_channel ( mask , NULL , NULL ) ;
2011-01-18 09:36:05 +03:00
if ( ! host - > dma_chan ) {
dev_err ( host - > dev , " Failed to request DMA channel \n " ) ;
use_dma = 0 ;
}
}
if ( use_dma )
2011-03-30 12:26:40 +04:00
dev_info ( host - > dev , " Using %s for DMA transfers. \n " ,
dma_chan_name ( host - > dma_chan ) ) ;
2011-01-18 09:36:05 +03:00
else
dev_info ( host - > dev , " No DMA support for NAND access. \n " ) ;
2008-04-23 21:51:14 +04:00
/* first scan to find the device and get the page size */
2010-02-26 21:32:56 +03:00
if ( nand_scan_ident ( mtd , 1 , NULL ) ) {
2008-04-23 21:51:14 +04:00
res = - ENXIO ;
2008-06-06 20:04:54 +04:00
goto err_scan_ident ;
2008-04-23 21:51:14 +04:00
}
2008-10-12 10:42:28 +04:00
if ( nand_chip - > ecc . mode = = NAND_ECC_HW ) {
2008-04-23 21:51:14 +04:00
/* ECC is calculated for the whole page (1 step) */
nand_chip - > ecc . size = mtd - > writesize ;
/* set ECC page size and oob layout */
switch ( mtd - > writesize ) {
case 512 :
2008-06-06 20:04:53 +04:00
nand_chip - > ecc . layout = & atmel_oobinfo_small ;
ecc_writel ( host - > ecc , MR , ATMEL_ECC_PAGESIZE_528 ) ;
2008-04-23 21:51:14 +04:00
break ;
case 1024 :
2008-06-06 20:04:53 +04:00
nand_chip - > ecc . layout = & atmel_oobinfo_large ;
ecc_writel ( host - > ecc , MR , ATMEL_ECC_PAGESIZE_1056 ) ;
2008-04-23 21:51:14 +04:00
break ;
case 2048 :
2008-06-06 20:04:53 +04:00
nand_chip - > ecc . layout = & atmel_oobinfo_large ;
ecc_writel ( host - > ecc , MR , ATMEL_ECC_PAGESIZE_2112 ) ;
2008-04-23 21:51:14 +04:00
break ;
case 4096 :
2008-06-06 20:04:53 +04:00
nand_chip - > ecc . layout = & atmel_oobinfo_large ;
ecc_writel ( host - > ecc , MR , ATMEL_ECC_PAGESIZE_4224 ) ;
2008-04-23 21:51:14 +04:00
break ;
default :
/* page size not handled by HW ECC */
/* switching back to soft ECC */
nand_chip - > ecc . mode = NAND_ECC_SOFT ;
nand_chip - > ecc . calculate = NULL ;
nand_chip - > ecc . correct = NULL ;
nand_chip - > ecc . hwctl = NULL ;
nand_chip - > ecc . read_page = NULL ;
nand_chip - > ecc . postpad = 0 ;
nand_chip - > ecc . prepad = 0 ;
nand_chip - > ecc . bytes = 0 ;
break ;
}
}
/* second phase scan */
if ( nand_scan_tail ( mtd ) ) {
2006-10-19 20:24:35 +04:00
res = - ENXIO ;
2008-06-06 20:04:54 +04:00
goto err_scan_tail ;
2006-10-19 20:24:35 +04:00
}
2008-06-06 20:04:53 +04:00
mtd - > name = " atmel_nand " ;
2012-01-25 22:11:06 +04:00
ppdata . of_node = pdev - > dev . of_node ;
res = mtd_device_parse_register ( mtd , NULL , & ppdata ,
host - > board . parts , host - > board . num_parts ) ;
2006-10-19 20:24:35 +04:00
if ( ! res )
return res ;
2008-06-06 20:04:54 +04:00
err_scan_tail :
err_scan_ident :
err_no_card :
2008-06-06 20:04:53 +04:00
atmel_nand_disable ( host ) ;
2006-10-19 20:24:35 +04:00
platform_set_drvdata ( pdev , NULL ) ;
2011-01-18 09:36:05 +03:00
if ( host - > dma_chan )
dma_release_channel ( host - > dma_chan ) ;
2008-06-06 20:04:54 +04:00
if ( host - > ecc )
iounmap ( host - > ecc ) ;
err_ecc_ioremap :
2006-10-19 20:24:35 +04:00
iounmap ( host - > io_base ) ;
2008-06-06 20:04:54 +04:00
err_nand_ioremap :
2006-10-19 20:24:35 +04:00
kfree ( host ) ;
return res ;
}
/*
* Remove a NAND device .
*/
2008-07-04 10:40:16 +04:00
static int __exit atmel_nand_remove ( struct platform_device * pdev )
2006-10-19 20:24:35 +04:00
{
2008-06-06 20:04:53 +04:00
struct atmel_nand_host * host = platform_get_drvdata ( pdev ) ;
2006-10-19 20:24:35 +04:00
struct mtd_info * mtd = & host - > mtd ;
nand_release ( mtd ) ;
2008-06-06 20:04:53 +04:00
atmel_nand_disable ( host ) ;
2006-10-19 20:24:35 +04:00
2008-06-06 20:04:54 +04:00
if ( host - > ecc )
iounmap ( host - > ecc ) ;
2011-01-18 09:36:05 +03:00
if ( host - > dma_chan )
dma_release_channel ( host - > dma_chan ) ;
2006-10-19 20:24:35 +04:00
iounmap ( host - > io_base ) ;
kfree ( host ) ;
return 0 ;
}
2012-01-25 22:11:06 +04:00
# if defined(CONFIG_OF)
static const struct of_device_id atmel_nand_dt_ids [ ] = {
{ . compatible = " atmel,at91rm9200-nand " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , atmel_nand_dt_ids ) ;
# endif
2008-06-06 20:04:53 +04:00
static struct platform_driver atmel_nand_driver = {
2008-07-04 10:40:16 +04:00
. remove = __exit_p ( atmel_nand_remove ) ,
2006-10-19 20:24:35 +04:00
. driver = {
2008-06-06 20:04:53 +04:00
. name = " atmel_nand " ,
2006-10-19 20:24:35 +04:00
. owner = THIS_MODULE ,
2012-01-25 22:11:06 +04:00
. of_match_table = of_match_ptr ( atmel_nand_dt_ids ) ,
2006-10-19 20:24:35 +04:00
} ,
} ;
2008-06-06 20:04:53 +04:00
static int __init atmel_nand_init ( void )
2006-10-19 20:24:35 +04:00
{
2008-07-04 10:40:16 +04:00
return platform_driver_probe ( & atmel_nand_driver , atmel_nand_probe ) ;
2006-10-19 20:24:35 +04:00
}
2008-06-06 20:04:53 +04:00
static void __exit atmel_nand_exit ( void )
2006-10-19 20:24:35 +04:00
{
2008-06-06 20:04:53 +04:00
platform_driver_unregister ( & atmel_nand_driver ) ;
2006-10-19 20:24:35 +04:00
}
2008-06-06 20:04:53 +04:00
module_init ( atmel_nand_init ) ;
module_exit ( atmel_nand_exit ) ;
2006-10-19 20:24:35 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Rick Bronson " ) ;
2008-06-06 20:04:52 +04:00
MODULE_DESCRIPTION ( " NAND/SmartMedia driver for AT91 / AVR32 " ) ;
2008-06-06 20:04:53 +04:00
MODULE_ALIAS ( " platform:atmel_nand " ) ;