2006-05-23 13:43:28 +04:00
/*
* drivers / mtd / ndfc . c
*
* Overview :
2008-12-10 16:16:34 +03:00
* Platform independent driver for NDFC ( NanD Flash Controller )
2006-05-23 13:43:28 +04:00
* integrated into EP440 cores
*
2008-12-10 16:16:34 +03:00
* Ported to an OF platform driver by Sean MacLennan
*
* The NDFC supports multiple chips , but this driver only supports a
* single chip since I do not have access to any boards with
* multiple chips .
*
2006-05-23 13:43:28 +04:00
* Author : Thomas Gleixner
*
* Copyright 2006 IBM
2008-12-10 16:16:34 +03:00
* Copyright 2008 PIKA Technologies
* Sean MacLennan < smaclennan @ pikatech . com >
2006-05-23 13:43:28 +04:00
*
* 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 .
*
*/
# include <linux/module.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/nand_ecc.h>
# include <linux/mtd/partitions.h>
# include <linux/mtd/ndfc.h>
# include <linux/mtd/mtd.h>
2008-12-10 16:16:34 +03:00
# include <linux/of_platform.h>
2006-05-23 13:43:28 +04:00
# include <asm/io.h>
struct ndfc_controller {
2008-12-10 16:16:34 +03:00
struct of_device * ofdev ;
void __iomem * ndfcbase ;
struct mtd_info mtd ;
struct nand_chip chip ;
int chip_select ;
struct nand_hw_control ndfc_control ;
# ifdef CONFIG_MTD_PARTITIONS
struct mtd_partition * parts ;
# endif
2006-05-23 13:43:28 +04:00
} ;
static struct ndfc_controller ndfc_ctrl ;
static void ndfc_select_chip ( struct mtd_info * mtd , int chip )
{
uint32_t ccr ;
struct ndfc_controller * ndfc = & ndfc_ctrl ;
2008-12-10 16:16:34 +03:00
ccr = in_be32 ( ndfc - > ndfcbase + NDFC_CCR ) ;
2006-05-23 13:43:28 +04:00
if ( chip > = 0 ) {
ccr & = ~ NDFC_CCR_BS_MASK ;
2008-12-10 16:16:34 +03:00
ccr | = NDFC_CCR_BS ( chip + ndfc - > chip_select ) ;
2006-05-23 13:43:28 +04:00
} else
ccr | = NDFC_CCR_RESET_CE ;
2008-12-10 16:16:34 +03:00
out_be32 ( ndfc - > ndfcbase + NDFC_CCR , ccr ) ;
2006-05-23 13:43:28 +04:00
}
2006-05-24 01:25:53 +04:00
static void ndfc_hwcontrol ( struct mtd_info * mtd , int cmd , unsigned int ctrl )
2006-05-23 13:43:28 +04:00
{
2006-06-22 15:06:43 +04:00
struct ndfc_controller * ndfc = & ndfc_ctrl ;
2006-05-23 13:43:28 +04:00
2006-05-24 01:25:53 +04:00
if ( cmd = = NAND_CMD_NONE )
return ;
if ( ctrl & NAND_CLE )
2006-06-22 15:06:43 +04:00
writel ( cmd & 0xFF , ndfc - > ndfcbase + NDFC_CMD ) ;
2006-05-24 01:25:53 +04:00
else
2006-06-22 15:06:43 +04:00
writel ( cmd & 0xFF , ndfc - > ndfcbase + NDFC_ALE ) ;
2006-05-23 13:43:28 +04:00
}
static int ndfc_ready ( struct mtd_info * mtd )
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
2008-12-10 16:16:34 +03:00
return in_be32 ( ndfc - > ndfcbase + NDFC_STAT ) & NDFC_STAT_IS_READY ;
2006-05-23 13:43:28 +04:00
}
static void ndfc_enable_hwecc ( struct mtd_info * mtd , int mode )
{
uint32_t ccr ;
struct ndfc_controller * ndfc = & ndfc_ctrl ;
2008-12-10 16:16:34 +03:00
ccr = in_be32 ( ndfc - > ndfcbase + NDFC_CCR ) ;
2006-05-23 13:43:28 +04:00
ccr | = NDFC_CCR_RESET_ECC ;
2008-12-10 16:16:34 +03:00
out_be32 ( ndfc - > ndfcbase + NDFC_CCR , ccr ) ;
2006-05-23 13:43:28 +04:00
wmb ( ) ;
}
static int ndfc_calculate_ecc ( struct mtd_info * mtd ,
const u_char * dat , u_char * ecc_code )
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
uint32_t ecc ;
uint8_t * p = ( uint8_t * ) & ecc ;
wmb ( ) ;
2008-12-10 16:16:34 +03:00
ecc = in_be32 ( ndfc - > ndfcbase + NDFC_ECC ) ;
/* The NDFC uses Smart Media (SMC) bytes order */
ecc_code [ 0 ] = p [ 2 ] ;
ecc_code [ 1 ] = p [ 1 ] ;
2006-05-23 13:43:28 +04:00
ecc_code [ 2 ] = p [ 3 ] ;
return 0 ;
}
/*
* Speedups for buffer read / write / verify
*
* NDFC allows 32 bit read / write of data . So we can speed up the buffer
* functions . No further checking , as nand_base will always read / write
* page aligned .
*/
static void ndfc_read_buf ( struct mtd_info * mtd , uint8_t * buf , int len )
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
uint32_t * p = ( uint32_t * ) buf ;
for ( ; len > 0 ; len - = 4 )
2008-12-10 16:16:34 +03:00
* p + + = in_be32 ( ndfc - > ndfcbase + NDFC_DATA ) ;
2006-05-23 13:43:28 +04:00
}
static void ndfc_write_buf ( struct mtd_info * mtd , const uint8_t * buf , int len )
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
uint32_t * p = ( uint32_t * ) buf ;
for ( ; len > 0 ; len - = 4 )
2008-12-10 16:16:34 +03:00
out_be32 ( ndfc - > ndfcbase + NDFC_DATA , * p + + ) ;
2006-05-23 13:43:28 +04:00
}
static int ndfc_verify_buf ( struct mtd_info * mtd , const uint8_t * buf , int len )
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
uint32_t * p = ( uint32_t * ) buf ;
for ( ; len > 0 ; len - = 4 )
2008-12-10 16:16:34 +03:00
if ( * p + + ! = in_be32 ( ndfc - > ndfcbase + NDFC_DATA ) )
2006-05-23 13:43:28 +04:00
return - EFAULT ;
return 0 ;
}
/*
* Initialize chip structure
*/
2008-12-10 16:16:34 +03:00
static int ndfc_chip_init ( struct ndfc_controller * ndfc ,
struct device_node * node )
2006-05-23 13:43:28 +04:00
{
2008-12-10 16:16:34 +03:00
# ifdef CONFIG_MTD_PARTITIONS
# ifdef CONFIG_MTD_CMDLINE_PARTS
static const char * part_types [ ] = { " cmdlinepart " , NULL } ;
# else
static const char * part_types [ ] = { NULL } ;
# endif
# endif
struct device_node * flash_np ;
struct nand_chip * chip = & ndfc - > chip ;
int ret ;
2006-05-23 13:43:28 +04:00
chip - > IO_ADDR_R = ndfc - > ndfcbase + NDFC_DATA ;
chip - > IO_ADDR_W = ndfc - > ndfcbase + NDFC_DATA ;
2006-05-24 01:25:53 +04:00
chip - > cmd_ctrl = ndfc_hwcontrol ;
2006-05-23 13:43:28 +04:00
chip - > dev_ready = ndfc_ready ;
chip - > select_chip = ndfc_select_chip ;
chip - > chip_delay = 50 ;
chip - > controller = & ndfc - > ndfc_control ;
chip - > read_buf = ndfc_read_buf ;
chip - > write_buf = ndfc_write_buf ;
chip - > verify_buf = ndfc_verify_buf ;
2006-05-23 14:00:46 +04:00
chip - > ecc . correct = nand_correct_data ;
chip - > ecc . hwctl = ndfc_enable_hwecc ;
chip - > ecc . calculate = ndfc_calculate_ecc ;
chip - > ecc . mode = NAND_ECC_HW ;
chip - > ecc . size = 256 ;
chip - > ecc . bytes = 3 ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
ndfc - > mtd . priv = chip ;
ndfc - > mtd . owner = THIS_MODULE ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
flash_np = of_get_next_child ( node , NULL ) ;
if ( ! flash_np )
2006-05-23 13:43:28 +04:00
return - ENODEV ;
2008-12-10 16:16:34 +03:00
ndfc - > mtd . name = kasprintf ( GFP_KERNEL , " %s.%s " ,
ndfc - > ofdev - > dev . bus_id , flash_np - > name ) ;
if ( ! ndfc - > mtd . name ) {
ret = - ENOMEM ;
goto err ;
2006-05-23 13:43:28 +04:00
}
2008-12-10 16:16:34 +03:00
ret = nand_scan ( & ndfc - > mtd , 1 ) ;
if ( ret )
goto err ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
# ifdef CONFIG_MTD_PARTITIONS
ret = parse_mtd_partitions ( & ndfc - > mtd , part_types , & ndfc - > parts , 0 ) ;
if ( ret < 0 )
goto err ;
# ifdef CONFIG_MTD_OF_PARTS
if ( ret = = 0 ) {
ret = of_mtd_parse_partitions ( & ndfc - > ofdev - > dev , flash_np ,
& ndfc - > parts ) ;
if ( ret < 0 )
goto err ;
}
2006-05-23 13:43:28 +04:00
# endif
2008-12-10 16:16:34 +03:00
if ( ret > 0 )
ret = add_mtd_partitions ( & ndfc - > mtd , ndfc - > parts , ret ) ;
else
# endif
ret = add_mtd_device ( & ndfc - > mtd ) ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
err :
of_node_put ( flash_np ) ;
if ( ret )
kfree ( ndfc - > mtd . name ) ;
return ret ;
2006-05-23 13:43:28 +04:00
}
2008-12-10 16:16:34 +03:00
static int __devinit ndfc_probe ( struct of_device * ofdev ,
const struct of_device_id * match )
2006-05-23 13:43:28 +04:00
{
struct ndfc_controller * ndfc = & ndfc_ctrl ;
2008-12-10 16:16:34 +03:00
const u32 * reg ;
u32 ccr ;
int err , len ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
spin_lock_init ( & ndfc - > ndfc_control . lock ) ;
init_waitqueue_head ( & ndfc - > ndfc_control . wq ) ;
ndfc - > ofdev = ofdev ;
dev_set_drvdata ( & ofdev - > dev , ndfc ) ;
/* Read the reg property to get the chip select */
reg = of_get_property ( ofdev - > node , " reg " , & len ) ;
if ( reg = = NULL | | len ! = 12 ) {
dev_err ( & ofdev - > dev , " unable read reg property (%d) \n " , len ) ;
return - ENOENT ;
}
ndfc - > chip_select = reg [ 0 ] ;
ndfc - > ndfcbase = of_iomap ( ofdev - > node , 0 ) ;
2006-05-23 13:43:28 +04:00
if ( ! ndfc - > ndfcbase ) {
2008-12-10 16:16:34 +03:00
dev_err ( & ofdev - > dev , " failed to get memory \n " ) ;
2006-05-23 13:43:28 +04:00
return - EIO ;
}
2008-12-10 16:16:34 +03:00
ccr = NDFC_CCR_BS ( ndfc - > chip_select ) ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
/* It is ok if ccr does not exist - just default to 0 */
reg = of_get_property ( ofdev - > node , " ccr " , NULL ) ;
if ( reg )
ccr | = * reg ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
out_be32 ( ndfc - > ndfcbase + NDFC_CCR , ccr ) ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
/* Set the bank settings if given */
reg = of_get_property ( ofdev - > node , " bank-settings " , NULL ) ;
if ( reg ) {
int offset = NDFC_BCFG0 + ( ndfc - > chip_select < < 2 ) ;
out_be32 ( ndfc - > ndfcbase + offset , * reg ) ;
}
err = ndfc_chip_init ( ndfc , ofdev - > node ) ;
if ( err ) {
iounmap ( ndfc - > ndfcbase ) ;
return err ;
}
2006-05-23 13:43:28 +04:00
return 0 ;
}
2008-12-10 16:16:34 +03:00
static int __devexit ndfc_remove ( struct of_device * ofdev )
2006-05-23 13:43:28 +04:00
{
2008-12-10 16:16:34 +03:00
struct ndfc_controller * ndfc = dev_get_drvdata ( & ofdev - > dev ) ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
nand_release ( & ndfc - > mtd ) ;
2006-05-23 13:43:28 +04:00
return 0 ;
}
2008-12-10 16:16:34 +03:00
static const struct of_device_id ndfc_match [ ] = {
{ . compatible = " ibm,ndfc " , } ,
{ }
2006-05-23 13:43:28 +04:00
} ;
2008-12-10 16:16:34 +03:00
MODULE_DEVICE_TABLE ( of , ndfc_match ) ;
2006-05-23 13:43:28 +04:00
2008-12-10 16:16:34 +03:00
static struct of_platform_driver ndfc_driver = {
. driver = {
. name = " ndfc " ,
2006-05-23 13:43:28 +04:00
} ,
2008-12-10 16:16:34 +03:00
. match_table = ndfc_match ,
. probe = ndfc_probe ,
. remove = __devexit_p ( ndfc_remove ) ,
2006-05-23 13:43:28 +04:00
} ;
static int __init ndfc_nand_init ( void )
{
2008-12-10 16:16:34 +03:00
return of_register_platform_driver ( & ndfc_driver ) ;
2006-05-23 13:43:28 +04:00
}
static void __exit ndfc_nand_exit ( void )
{
2008-12-10 16:16:34 +03:00
of_unregister_platform_driver ( & ndfc_driver ) ;
2006-05-23 13:43:28 +04:00
}
module_init ( ndfc_nand_init ) ;
module_exit ( ndfc_nand_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Thomas Gleixner <tglx@linutronix.de> " ) ;
2008-12-10 16:16:34 +03:00
MODULE_DESCRIPTION ( " OF Platform driver for NDFC " ) ;