2007-10-03 00:56:05 +04:00
/* linux/drivers/mtd/nand/bf5xx_nand.c
*
2008-04-25 08:07:31 +04:00
* Copyright 2006 - 2008 Analog Devices Inc .
2007-10-03 00:56:05 +04:00
* http : //blackfin.uclinux.org/
* Bryan Wu < bryan . wu @ analog . com >
*
2008-02-03 18:22:34 +03:00
* Blackfin BF5xx on - chip NAND flash controller driver
2007-10-03 00:56:05 +04:00
*
* Derived from drivers / mtd / nand / s3c2410 . c
* Copyright ( c ) 2007 Ben Dooks < ben @ simtec . co . uk >
*
* Derived from drivers / mtd / nand / cafe . c
* Copyright © 2006 Red Hat , Inc .
* Copyright © 2006 David Woodhouse < dwmw2 @ infradead . org >
*
* Changelog :
* 12 - Jun - 2007 Bryan Wu : Initial version
* 18 - Jul - 2007 Bryan Wu :
* - ECC_HW and ECC_SW supported
* - DMA supported in ECC_HW
* - YAFFS tested as rootfs in both ECC_HW and ECC_SW
*
* TODO :
* Enable JFFS2 over NAND as rootfs
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/ioport.h>
# include <linux/platform_device.h>
# include <linux/delay.h>
# include <linux/dma-mapping.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/bitops.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/nand_ecc.h>
# include <linux/mtd/partitions.h>
# include <asm/blackfin.h>
# include <asm/dma.h>
# include <asm/cacheflush.h>
# include <asm/nand.h>
# include <asm/portmux.h>
# define DRV_NAME "bf5xx-nand"
# define DRV_VERSION "1.2"
# define DRV_AUTHOR "Bryan Wu <bryan.wu@analog.com>"
# define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
# ifdef CONFIG_MTD_NAND_BF5XX_HWECC
static int hardware_ecc = 1 ;
# else
static int hardware_ecc ;
# endif
2008-04-25 08:07:31 +04:00
static const unsigned short bfin_nfc_pin_req [ ] =
2007-10-30 12:08:29 +03:00
{ P_NAND_CE ,
P_NAND_RB ,
P_NAND_D0 ,
P_NAND_D1 ,
P_NAND_D2 ,
P_NAND_D3 ,
P_NAND_D4 ,
P_NAND_D5 ,
P_NAND_D6 ,
P_NAND_D7 ,
P_NAND_WE ,
P_NAND_RE ,
P_NAND_CLE ,
P_NAND_ALE ,
0 } ;
2007-10-03 00:56:05 +04:00
2008-07-30 23:35:01 +04:00
# ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
static uint8_t bbt_pattern [ ] = { 0xff } ;
static struct nand_bbt_descr bootrom_bbt = {
. options = 0 ,
. offs = 63 ,
. len = 1 ,
. pattern = bbt_pattern ,
} ;
static struct nand_ecclayout bootrom_ecclayout = {
. eccbytes = 24 ,
. eccpos = {
0x8 * 0 , 0x8 * 0 + 1 , 0x8 * 0 + 2 ,
0x8 * 1 , 0x8 * 1 + 1 , 0x8 * 1 + 2 ,
0x8 * 2 , 0x8 * 2 + 1 , 0x8 * 2 + 2 ,
0x8 * 3 , 0x8 * 3 + 1 , 0x8 * 3 + 2 ,
0x8 * 4 , 0x8 * 4 + 1 , 0x8 * 4 + 2 ,
0x8 * 5 , 0x8 * 5 + 1 , 0x8 * 5 + 2 ,
0x8 * 6 , 0x8 * 6 + 1 , 0x8 * 6 + 2 ,
0x8 * 7 , 0x8 * 7 + 1 , 0x8 * 7 + 2
} ,
. oobfree = {
{ 0x8 * 0 + 3 , 5 } ,
{ 0x8 * 1 + 3 , 5 } ,
{ 0x8 * 2 + 3 , 5 } ,
{ 0x8 * 3 + 3 , 5 } ,
{ 0x8 * 4 + 3 , 5 } ,
{ 0x8 * 5 + 3 , 5 } ,
{ 0x8 * 6 + 3 , 5 } ,
{ 0x8 * 7 + 3 , 5 } ,
}
} ;
# endif
2007-10-03 00:56:05 +04:00
/*
* Data structures for bf5xx nand flash controller driver
*/
/* bf5xx nand info */
struct bf5xx_nand_info {
/* mtd info */
struct nand_hw_control controller ;
struct mtd_info mtd ;
struct nand_chip chip ;
/* platform info */
struct bf5xx_nand_platform * platform ;
/* device info */
struct device * device ;
/* DMA stuff */
struct completion dma_completion ;
} ;
/*
* Conversion functions
*/
static struct bf5xx_nand_info * mtd_to_nand_info ( struct mtd_info * mtd )
{
return container_of ( mtd , struct bf5xx_nand_info , mtd ) ;
}
static struct bf5xx_nand_info * to_nand_info ( struct platform_device * pdev )
{
return platform_get_drvdata ( pdev ) ;
}
static struct bf5xx_nand_platform * to_nand_plat ( struct platform_device * pdev )
{
return pdev - > dev . platform_data ;
}
/*
* struct nand_chip interface function pointers
*/
/*
* bf5xx_nand_hwcontrol
*
* Issue command and address cycles to the chip
*/
static void bf5xx_nand_hwcontrol ( struct mtd_info * mtd , int cmd ,
unsigned int ctrl )
{
if ( cmd = = NAND_CMD_NONE )
return ;
while ( bfin_read_NFC_STAT ( ) & WB_FULL )
cpu_relax ( ) ;
if ( ctrl & NAND_CLE )
bfin_write_NFC_CMD ( cmd ) ;
else
bfin_write_NFC_ADDR ( cmd ) ;
SSYNC ( ) ;
}
/*
* bf5xx_nand_devready ( )
*
* returns 0 if the nand is busy , 1 if it is ready
*/
static int bf5xx_nand_devready ( struct mtd_info * mtd )
{
unsigned short val = bfin_read_NFC_IRQSTAT ( ) ;
if ( ( val & NBUSYIRQ ) = = NBUSYIRQ )
return 1 ;
else
return 0 ;
}
/*
* ECC functions
* These allow the bf5xx to use the controller ' s ECC
* generator block to ECC the data as it passes through
*/
/*
* ECC error correction function
*/
static int bf5xx_nand_correct_data_256 ( struct mtd_info * mtd , u_char * dat ,
u_char * read_ecc , u_char * calc_ecc )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
u32 syndrome [ 5 ] ;
u32 calced , stored ;
int i ;
unsigned short failing_bit , failing_byte ;
u_char data ;
calced = calc_ecc [ 0 ] | ( calc_ecc [ 1 ] < < 8 ) | ( calc_ecc [ 2 ] < < 16 ) ;
stored = read_ecc [ 0 ] | ( read_ecc [ 1 ] < < 8 ) | ( read_ecc [ 2 ] < < 16 ) ;
syndrome [ 0 ] = ( calced ^ stored ) ;
/*
* syndrome 0 : all zero
* No error in data
* No action
*/
if ( ! syndrome [ 0 ] | | ! calced | | ! stored )
return 0 ;
/*
* sysdrome 0 : only one bit is one
* ECC data was incorrect
* No action
*/
if ( hweight32 ( syndrome [ 0 ] ) = = 1 ) {
dev_err ( info - > device , " ECC data was incorrect! \n " ) ;
return 1 ;
}
syndrome [ 1 ] = ( calced & 0x7FF ) ^ ( stored & 0x7FF ) ;
syndrome [ 2 ] = ( calced & 0x7FF ) ^ ( ( calced > > 11 ) & 0x7FF ) ;
syndrome [ 3 ] = ( stored & 0x7FF ) ^ ( ( stored > > 11 ) & 0x7FF ) ;
syndrome [ 4 ] = syndrome [ 2 ] ^ syndrome [ 3 ] ;
for ( i = 0 ; i < 5 ; i + + )
dev_info ( info - > device , " syndrome[%d] 0x%08x \n " , i , syndrome [ i ] ) ;
dev_info ( info - > device ,
" calced[0x%08x], stored[0x%08x] \n " ,
calced , stored ) ;
/*
* sysdrome 0 : exactly 11 bits are one , each parity
* and parity ' pair is 1 & 0 or 0 & 1.
* 1 - bit correctable error
* Correct the error
*/
if ( hweight32 ( syndrome [ 0 ] ) = = 11 & & syndrome [ 4 ] = = 0x7FF ) {
dev_info ( info - > device ,
" 1-bit correctable error, correct it. \n " ) ;
dev_info ( info - > device ,
" syndrome[1] 0x%08x \n " , syndrome [ 1 ] ) ;
failing_bit = syndrome [ 1 ] & 0x7 ;
failing_byte = syndrome [ 1 ] > > 0x3 ;
data = * ( dat + failing_byte ) ;
data = data ^ ( 0x1 < < failing_bit ) ;
* ( dat + failing_byte ) = data ;
return 0 ;
}
/*
* sysdrome 0 : random data
* More than 1 - bit error , non - correctable error
* Discard data , mark bad block
*/
dev_err ( info - > device ,
" More than 1-bit error, non-correctable error. \n " ) ;
dev_err ( info - > device ,
" Please discard data, mark bad block \n " ) ;
return 1 ;
}
static int bf5xx_nand_correct_data ( struct mtd_info * mtd , u_char * dat ,
u_char * read_ecc , u_char * calc_ecc )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
struct bf5xx_nand_platform * plat = info - > platform ;
unsigned short page_size = ( plat - > page_size ? 512 : 256 ) ;
int ret ;
ret = bf5xx_nand_correct_data_256 ( mtd , dat , read_ecc , calc_ecc ) ;
/* If page size is 512, correct second 256 bytes */
if ( page_size = = 512 ) {
dat + = 256 ;
read_ecc + = 8 ;
calc_ecc + = 8 ;
2008-07-30 23:34:59 +04:00
ret | = bf5xx_nand_correct_data_256 ( mtd , dat , read_ecc , calc_ecc ) ;
2007-10-03 00:56:05 +04:00
}
return ret ;
}
static void bf5xx_nand_enable_hwecc ( struct mtd_info * mtd , int mode )
{
return ;
}
static int bf5xx_nand_calculate_ecc ( struct mtd_info * mtd ,
const u_char * dat , u_char * ecc_code )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
struct bf5xx_nand_platform * plat = info - > platform ;
u16 page_size = ( plat - > page_size ? 512 : 256 ) ;
u16 ecc0 , ecc1 ;
u32 code [ 2 ] ;
u8 * p ;
/* first 4 bytes ECC code for 256 page size */
ecc0 = bfin_read_NFC_ECC0 ( ) ;
ecc1 = bfin_read_NFC_ECC1 ( ) ;
2008-07-30 23:35:00 +04:00
code [ 0 ] = ( ecc0 & 0x7ff ) | ( ( ecc1 & 0x7ff ) < < 11 ) ;
2007-10-03 00:56:05 +04:00
dev_dbg ( info - > device , " returning ecc 0x%08x \n " , code [ 0 ] ) ;
2008-01-30 12:18:18 +03:00
/* first 3 bytes in ecc_code for 256 page size */
p = ( u8 * ) code ;
memcpy ( ecc_code , p , 3 ) ;
2007-10-03 00:56:05 +04:00
/* second 4 bytes ECC code for 512 page size */
if ( page_size = = 512 ) {
ecc0 = bfin_read_NFC_ECC2 ( ) ;
ecc1 = bfin_read_NFC_ECC3 ( ) ;
2008-07-30 23:35:00 +04:00
code [ 1 ] = ( ecc0 & 0x7ff ) | ( ( ecc1 & 0x7ff ) < < 11 ) ;
2008-01-30 12:18:18 +03:00
/* second 3 bytes in ecc_code for second 256
* bytes of 512 page size
*/
p = ( u8 * ) ( code + 1 ) ;
memcpy ( ( ecc_code + 3 ) , p , 3 ) ;
2007-10-03 00:56:05 +04:00
dev_dbg ( info - > device , " returning ecc 0x%08x \n " , code [ 1 ] ) ;
}
return 0 ;
}
/*
* PIO mode for buffer writing and reading
*/
static void bf5xx_nand_read_buf ( struct mtd_info * mtd , uint8_t * buf , int len )
{
int i ;
unsigned short val ;
/*
* Data reads are requested by first writing to NFC_DATA_RD
* and then reading back from NFC_READ .
*/
for ( i = 0 ; i < len ; i + + ) {
while ( bfin_read_NFC_STAT ( ) & WB_FULL )
cpu_relax ( ) ;
/* Contents do not matter */
bfin_write_NFC_DATA_RD ( 0x0000 ) ;
SSYNC ( ) ;
while ( ( bfin_read_NFC_IRQSTAT ( ) & RD_RDY ) ! = RD_RDY )
cpu_relax ( ) ;
buf [ i ] = bfin_read_NFC_READ ( ) ;
val = bfin_read_NFC_IRQSTAT ( ) ;
val | = RD_RDY ;
bfin_write_NFC_IRQSTAT ( val ) ;
SSYNC ( ) ;
}
}
static uint8_t bf5xx_nand_read_byte ( struct mtd_info * mtd )
{
uint8_t val ;
bf5xx_nand_read_buf ( mtd , & val , 1 ) ;
return val ;
}
static void bf5xx_nand_write_buf ( struct mtd_info * mtd ,
const uint8_t * buf , int len )
{
int i ;
for ( i = 0 ; i < len ; i + + ) {
while ( bfin_read_NFC_STAT ( ) & WB_FULL )
cpu_relax ( ) ;
bfin_write_NFC_DATA_WR ( buf [ i ] ) ;
SSYNC ( ) ;
}
}
static void bf5xx_nand_read_buf16 ( struct mtd_info * mtd , uint8_t * buf , int len )
{
int i ;
u16 * p = ( u16 * ) buf ;
len > > = 1 ;
/*
* Data reads are requested by first writing to NFC_DATA_RD
* and then reading back from NFC_READ .
*/
bfin_write_NFC_DATA_RD ( 0x5555 ) ;
SSYNC ( ) ;
for ( i = 0 ; i < len ; i + + )
p [ i ] = bfin_read_NFC_READ ( ) ;
}
static void bf5xx_nand_write_buf16 ( struct mtd_info * mtd ,
const uint8_t * buf , int len )
{
int i ;
u16 * p = ( u16 * ) buf ;
len > > = 1 ;
for ( i = 0 ; i < len ; i + + )
bfin_write_NFC_DATA_WR ( p [ i ] ) ;
SSYNC ( ) ;
}
/*
* DMA functions for buffer writing and reading
*/
static irqreturn_t bf5xx_nand_dma_irq ( int irq , void * dev_id )
{
struct bf5xx_nand_info * info = dev_id ;
clear_dma_irqstat ( CH_NFC ) ;
disable_dma ( CH_NFC ) ;
complete ( & info - > dma_completion ) ;
return IRQ_HANDLED ;
}
2009-05-26 14:24:13 +04:00
static void bf5xx_nand_dma_rw ( struct mtd_info * mtd ,
2007-10-03 00:56:05 +04:00
uint8_t * buf , int is_read )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
struct bf5xx_nand_platform * plat = info - > platform ;
unsigned short page_size = ( plat - > page_size ? 512 : 256 ) ;
unsigned short val ;
dev_dbg ( info - > device , " mtd->%p, buf->%p, is_read %d \n " ,
mtd , buf , is_read ) ;
/*
* Before starting a dma transfer , be sure to invalidate / flush
* the cache over the address range of your DMA buffer to
* prevent cache coherency problems . Otherwise very subtle bugs
* can be introduced to your driver .
*/
if ( is_read )
invalidate_dcache_range ( ( unsigned int ) buf ,
( unsigned int ) ( buf + page_size ) ) ;
else
flush_dcache_range ( ( unsigned int ) buf ,
( unsigned int ) ( buf + page_size ) ) ;
/*
* This register must be written before each page is
* transferred to generate the correct ECC register
* values .
*/
bfin_write_NFC_RST ( 0x1 ) ;
SSYNC ( ) ;
disable_dma ( CH_NFC ) ;
clear_dma_irqstat ( CH_NFC ) ;
/* setup DMA register with Blackfin DMA API */
set_dma_config ( CH_NFC , 0x0 ) ;
set_dma_start_addr ( CH_NFC , ( unsigned long ) buf ) ;
2009-05-26 14:24:14 +04:00
/* The DMAs have different size on BF52x and BF54x */
# ifdef CONFIG_BF52x
set_dma_x_count ( CH_NFC , ( page_size > > 1 ) ) ;
set_dma_x_modify ( CH_NFC , 2 ) ;
val = DI_EN | WDSIZE_16 ;
# endif
# ifdef CONFIG_BF54x
2007-10-03 00:56:05 +04:00
set_dma_x_count ( CH_NFC , ( page_size > > 2 ) ) ;
set_dma_x_modify ( CH_NFC , 4 ) ;
val = DI_EN | WDSIZE_32 ;
2009-05-26 14:24:14 +04:00
# endif
/* setup write or read operation */
2007-10-03 00:56:05 +04:00
if ( is_read )
val | = WNR ;
set_dma_config ( CH_NFC , val ) ;
enable_dma ( CH_NFC ) ;
/* Start PAGE read/write operation */
if ( is_read )
bfin_write_NFC_PGCTL ( 0x1 ) ;
else
bfin_write_NFC_PGCTL ( 0x2 ) ;
wait_for_completion ( & info - > dma_completion ) ;
}
static void bf5xx_nand_dma_read_buf ( struct mtd_info * mtd ,
uint8_t * buf , int len )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
struct bf5xx_nand_platform * plat = info - > platform ;
unsigned short page_size = ( plat - > page_size ? 512 : 256 ) ;
dev_dbg ( info - > device , " mtd->%p, buf->%p, int %d \n " , mtd , buf , len ) ;
if ( len = = page_size )
bf5xx_nand_dma_rw ( mtd , buf , 1 ) ;
else
bf5xx_nand_read_buf ( mtd , buf , len ) ;
}
static void bf5xx_nand_dma_write_buf ( struct mtd_info * mtd ,
const uint8_t * buf , int len )
{
struct bf5xx_nand_info * info = mtd_to_nand_info ( mtd ) ;
struct bf5xx_nand_platform * plat = info - > platform ;
unsigned short page_size = ( plat - > page_size ? 512 : 256 ) ;
dev_dbg ( info - > device , " mtd->%p, buf->%p, len %d \n " , mtd , buf , len ) ;
if ( len = = page_size )
bf5xx_nand_dma_rw ( mtd , ( uint8_t * ) buf , 0 ) ;
else
bf5xx_nand_write_buf ( mtd , buf , len ) ;
}
/*
* System initialization functions
*/
static int bf5xx_nand_dma_init ( struct bf5xx_nand_info * info )
{
int ret ;
/* Do not use dma */
if ( ! hardware_ecc )
return 0 ;
init_completion ( & info - > dma_completion ) ;
/* Request NFC DMA channel */
ret = request_dma ( CH_NFC , " BF5XX NFC driver " ) ;
if ( ret < 0 ) {
dev_err ( info - > device , " unable to get DMA channel \n " ) ;
return ret ;
}
2009-03-04 23:01:29 +03:00
# ifdef CONFIG_BF54x
/* Setup DMAC1 channel mux for NFC which shared with SDH */
bfin_write_DMAC1_PERIMUX ( bfin_read_DMAC1_PERIMUX ( ) & ~ 1 ) ;
SSYNC ( ) ;
# endif
2009-03-04 23:01:30 +03:00
set_dma_callback ( CH_NFC , bf5xx_nand_dma_irq , info ) ;
2007-10-03 00:56:05 +04:00
/* Turn off the DMA channel first */
disable_dma ( CH_NFC ) ;
return 0 ;
}
2008-07-30 23:35:04 +04:00
static void bf5xx_nand_dma_remove ( struct bf5xx_nand_info * info )
{
/* Free NFC DMA channel */
if ( hardware_ecc )
free_dma ( CH_NFC ) ;
}
2007-10-03 00:56:05 +04:00
/*
* BF5XX NFC hardware initialization
* - pin mux setup
* - clear interrupt status
*/
static int bf5xx_nand_hw_init ( struct bf5xx_nand_info * info )
{
int err = 0 ;
unsigned short val ;
struct bf5xx_nand_platform * plat = info - > platform ;
/* setup NFC_CTL register */
dev_info ( info - > device ,
" page_size=%d, data_width=%d, wr_dly=%d, rd_dly=%d \n " ,
( plat - > page_size ? 512 : 256 ) ,
( plat - > data_width ? 16 : 8 ) ,
plat - > wr_dly , plat - > rd_dly ) ;
val = ( plat - > page_size < < NFC_PG_SIZE_OFFSET ) |
( plat - > data_width < < NFC_NWIDTH_OFFSET ) |
( plat - > rd_dly < < NFC_RDDLY_OFFSET ) |
( plat - > rd_dly < < NFC_WRDLY_OFFSET ) ;
dev_dbg ( info - > device , " NFC_CTL is 0x%04x \n " , val ) ;
bfin_write_NFC_CTL ( val ) ;
SSYNC ( ) ;
/* clear interrupt status */
bfin_write_NFC_IRQMASK ( 0x0 ) ;
SSYNC ( ) ;
val = bfin_read_NFC_IRQSTAT ( ) ;
bfin_write_NFC_IRQSTAT ( val ) ;
SSYNC ( ) ;
/* DMA initialization */
if ( bf5xx_nand_dma_init ( info ) )
err = - ENXIO ;
return err ;
}
/*
* Device management interface
*/
2009-03-04 23:01:29 +03:00
static int __devinit bf5xx_nand_add_partition ( struct bf5xx_nand_info * info )
2007-10-03 00:56:05 +04:00
{
struct mtd_info * mtd = & info - > mtd ;
# ifdef CONFIG_MTD_PARTITIONS
struct mtd_partition * parts = info - > platform - > partitions ;
int nr = info - > platform - > nr_partitions ;
return add_mtd_partitions ( mtd , parts , nr ) ;
# else
return add_mtd_device ( mtd ) ;
# endif
}
2008-07-30 23:35:02 +04:00
static int __devexit bf5xx_nand_remove ( struct platform_device * pdev )
2007-10-03 00:56:05 +04:00
{
struct bf5xx_nand_info * info = to_nand_info ( pdev ) ;
struct mtd_info * mtd = NULL ;
platform_set_drvdata ( pdev , NULL ) ;
/* first thing we need to do is release all our mtds
* and their partitions , then go through freeing the
* resources used
*/
mtd = & info - > mtd ;
if ( mtd ) {
nand_release ( mtd ) ;
kfree ( mtd ) ;
}
peripheral_free_list ( bfin_nfc_pin_req ) ;
2008-07-30 23:35:04 +04:00
bf5xx_nand_dma_remove ( info ) ;
2007-10-03 00:56:05 +04:00
/* free the common resources */
kfree ( info ) ;
return 0 ;
}
/*
* bf5xx_nand_probe
*
* called by device layer when it finds a device matching
* one our driver can handled . This code checks to see if
* it can allocate all necessary resources then calls the
* nand layer to look for devices
*/
2008-07-30 23:35:02 +04:00
static int __devinit bf5xx_nand_probe ( struct platform_device * pdev )
2007-10-03 00:56:05 +04:00
{
struct bf5xx_nand_platform * plat = to_nand_plat ( pdev ) ;
struct bf5xx_nand_info * info = NULL ;
struct nand_chip * chip = NULL ;
struct mtd_info * mtd = NULL ;
int err = 0 ;
dev_dbg ( & pdev - > dev , " (%p) \n " , pdev ) ;
2008-07-30 23:35:04 +04:00
if ( ! plat ) {
dev_err ( & pdev - > dev , " no platform specific information \n " ) ;
return - EINVAL ;
}
2008-04-25 08:07:31 +04:00
if ( peripheral_request_list ( bfin_nfc_pin_req , DRV_NAME ) ) {
2008-07-30 23:35:03 +04:00
dev_err ( & pdev - > dev , " requesting Peripherals failed \n " ) ;
2008-04-25 08:07:31 +04:00
return - EFAULT ;
}
2007-10-03 00:56:05 +04:00
info = kzalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
if ( info = = NULL ) {
dev_err ( & pdev - > dev , " no memory for flash info \n " ) ;
err = - ENOMEM ;
2008-07-30 23:35:04 +04:00
goto out_err_kzalloc ;
2007-10-03 00:56:05 +04:00
}
platform_set_drvdata ( pdev , info ) ;
spin_lock_init ( & info - > controller . lock ) ;
init_waitqueue_head ( & info - > controller . wq ) ;
info - > device = & pdev - > dev ;
info - > platform = plat ;
/* initialise chip data struct */
chip = & info - > chip ;
if ( plat - > data_width )
chip - > options | = NAND_BUSWIDTH_16 ;
chip - > options | = NAND_CACHEPRG | NAND_SKIP_BBTSCAN ;
chip - > read_buf = ( plat - > data_width ) ?
bf5xx_nand_read_buf16 : bf5xx_nand_read_buf ;
chip - > write_buf = ( plat - > data_width ) ?
bf5xx_nand_write_buf16 : bf5xx_nand_write_buf ;
chip - > read_byte = bf5xx_nand_read_byte ;
chip - > cmd_ctrl = bf5xx_nand_hwcontrol ;
chip - > dev_ready = bf5xx_nand_devready ;
chip - > priv = & info - > mtd ;
chip - > controller = & info - > controller ;
chip - > IO_ADDR_R = ( void __iomem * ) NFC_READ ;
chip - > IO_ADDR_W = ( void __iomem * ) NFC_DATA_WR ;
chip - > chip_delay = 0 ;
/* initialise mtd info data struct */
mtd = & info - > mtd ;
mtd - > priv = chip ;
mtd - > owner = THIS_MODULE ;
/* initialise the hardware */
err = bf5xx_nand_hw_init ( info ) ;
2008-07-30 23:35:04 +04:00
if ( err )
goto out_err_hw_init ;
2007-10-03 00:56:05 +04:00
/* setup hardware ECC data struct */
if ( hardware_ecc ) {
2008-07-30 23:35:01 +04:00
# ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC
chip - > badblock_pattern = & bootrom_bbt ;
chip - > ecc . layout = & bootrom_ecclayout ;
# endif
2007-10-03 00:56:05 +04:00
if ( plat - > page_size = = NFC_PG_SIZE_256 ) {
chip - > ecc . bytes = 3 ;
chip - > ecc . size = 256 ;
} else if ( plat - > page_size = = NFC_PG_SIZE_512 ) {
chip - > ecc . bytes = 6 ;
chip - > ecc . size = 512 ;
}
chip - > read_buf = bf5xx_nand_dma_read_buf ;
chip - > write_buf = bf5xx_nand_dma_write_buf ;
chip - > ecc . calculate = bf5xx_nand_calculate_ecc ;
chip - > ecc . correct = bf5xx_nand_correct_data ;
chip - > ecc . mode = NAND_ECC_HW ;
chip - > ecc . hwctl = bf5xx_nand_enable_hwecc ;
} else {
chip - > ecc . mode = NAND_ECC_SOFT ;
}
/* scan hardware nand chip and setup mtd info data struct */
if ( nand_scan ( mtd , 1 ) ) {
err = - ENXIO ;
2008-07-30 23:35:04 +04:00
goto out_err_nand_scan ;
2007-10-03 00:56:05 +04:00
}
/* add NAND partition */
bf5xx_nand_add_partition ( info ) ;
dev_dbg ( & pdev - > dev , " initialised ok \n " ) ;
return 0 ;
2008-07-30 23:35:04 +04:00
out_err_nand_scan :
bf5xx_nand_dma_remove ( info ) ;
out_err_hw_init :
platform_set_drvdata ( pdev , NULL ) ;
kfree ( info ) ;
out_err_kzalloc :
peripheral_free_list ( bfin_nfc_pin_req ) ;
2007-10-03 00:56:05 +04:00
return err ;
}
/* PM Support */
# ifdef CONFIG_PM
static int bf5xx_nand_suspend ( struct platform_device * dev , pm_message_t pm )
{
struct bf5xx_nand_info * info = platform_get_drvdata ( dev ) ;
return 0 ;
}
static int bf5xx_nand_resume ( struct platform_device * dev )
{
struct bf5xx_nand_info * info = platform_get_drvdata ( dev ) ;
return 0 ;
}
# else
# define bf5xx_nand_suspend NULL
# define bf5xx_nand_resume NULL
# endif
/* driver device registration */
static struct platform_driver bf5xx_nand_driver = {
. probe = bf5xx_nand_probe ,
2008-07-30 23:35:02 +04:00
. remove = __devexit_p ( bf5xx_nand_remove ) ,
2007-10-03 00:56:05 +04:00
. suspend = bf5xx_nand_suspend ,
. resume = bf5xx_nand_resume ,
. driver = {
. name = DRV_NAME ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init bf5xx_nand_init ( void )
{
printk ( KERN_INFO " %s, Version %s (c) 2007 Analog Devices, Inc. \n " ,
DRV_DESC , DRV_VERSION ) ;
return platform_driver_register ( & bf5xx_nand_driver ) ;
}
static void __exit bf5xx_nand_exit ( void )
{
platform_driver_unregister ( & bf5xx_nand_driver ) ;
}
module_init ( bf5xx_nand_init ) ;
module_exit ( bf5xx_nand_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( DRV_AUTHOR ) ;
MODULE_DESCRIPTION ( DRV_DESC ) ;
2008-04-19 00:44:27 +04:00
MODULE_ALIAS ( " platform: " DRV_NAME ) ;