2007-10-17 01:10:40 +02:00
/*
* drivers / mtd / nand / orion_nand . c
*
* NAND support for Marvell Orion SoC platforms
*
* Tzachi Perelstein < tzachi @ marvell . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2012-04-18 11:06:41 +01:00
# include <linux/of.h>
2007-10-17 01:10:40 +02:00
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/partitions.h>
2012-02-19 11:01:22 +01:00
# include <linux/clk.h>
# include <linux/err.h>
2014-10-16 06:58:35 +02:00
# include <linux/io.h>
2007-10-17 01:10:40 +02:00
# include <asm/sizes.h>
2012-08-24 15:21:54 +02:00
# include <linux/platform_data/mtd-orion_nand.h>
2007-10-17 01:10:40 +02:00
2017-03-27 20:02:07 +02:00
struct orion_nand_info {
struct nand_chip chip ;
struct clk * clk ;
} ;
2007-10-17 01:10:40 +02:00
static void orion_nand_cmd_ctrl ( struct mtd_info * mtd , int cmd , unsigned int ctrl )
{
2015-12-01 12:03:04 +01:00
struct nand_chip * nc = mtd_to_nand ( mtd ) ;
2015-12-10 09:00:41 +01:00
struct orion_nand_data * board = nand_get_controller_data ( nc ) ;
2007-10-17 01:10:40 +02:00
u32 offs ;
if ( cmd = = NAND_CMD_NONE )
return ;
if ( ctrl & NAND_CLE )
offs = ( 1 < < board - > cle ) ;
else if ( ctrl & NAND_ALE )
offs = ( 1 < < board - > ale ) ;
else
return ;
if ( nc - > options & NAND_BUSWIDTH_16 )
offs < < = 1 ;
writeb ( cmd , nc - > IO_ADDR_W + offs ) ;
}
2009-05-31 22:25:40 -04:00
static void orion_nand_read_buf ( struct mtd_info * mtd , uint8_t * buf , int len )
{
2015-12-01 12:03:04 +01:00
struct nand_chip * chip = mtd_to_nand ( mtd ) ;
2009-05-31 22:25:40 -04:00
void __iomem * io_base = chip - > IO_ADDR_R ;
uint64_t * buf64 ;
int i = 0 ;
while ( len & & ( unsigned long ) buf & 7 ) {
* buf + + = readb ( io_base ) ;
len - - ;
}
buf64 = ( uint64_t * ) buf ;
while ( i < len / 8 ) {
2010-04-23 13:17:47 -04:00
/*
* Since GCC has no proper constraint ( PR 43518 )
* force x variable to r2 / r3 registers as ldrd instruction
* requires first register to be even .
*/
register uint64_t x asm ( " r2 " ) ;
2009-08-20 09:19:53 +02:00
asm volatile ( " ldrd \t %0, [%1] " : " =&r " ( x ) : " r " ( io_base ) ) ;
2009-05-31 22:25:40 -04:00
buf64 [ i + + ] = x ;
}
i * = 8 ;
while ( i < len )
buf [ i + + ] = readb ( io_base ) ;
}
2007-10-17 01:10:40 +02:00
static int __init orion_nand_probe ( struct platform_device * pdev )
{
2017-03-27 20:02:07 +02:00
struct orion_nand_info * info ;
2007-10-17 01:10:40 +02:00
struct mtd_info * mtd ;
struct nand_chip * nc ;
struct orion_nand_data * board ;
2009-12-14 16:48:34 -05:00
struct resource * res ;
2007-10-17 01:10:40 +02:00
void __iomem * io_base ;
int ret = 0 ;
2012-04-18 11:06:41 +01:00
u32 val = 0 ;
2007-10-17 01:10:40 +02:00
2017-03-27 20:02:07 +02:00
info = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct orion_nand_info ) ,
2014-10-16 06:58:35 +02:00
GFP_KERNEL ) ;
2017-03-27 20:02:07 +02:00
if ( ! info )
2014-10-16 06:58:35 +02:00
return - ENOMEM ;
2017-03-27 20:02:07 +02:00
nc = & info - > chip ;
2015-12-10 09:00:17 +01:00
mtd = nand_to_mtd ( nc ) ;
2007-10-17 01:10:40 +02:00
2009-12-14 16:48:34 -05:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2014-10-16 06:58:35 +02:00
io_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
2009-12-14 16:48:34 -05:00
2014-10-16 06:58:35 +02:00
if ( IS_ERR ( io_base ) )
return PTR_ERR ( io_base ) ;
2007-10-17 01:10:40 +02:00
2012-04-18 11:06:41 +01:00
if ( pdev - > dev . of_node ) {
board = devm_kzalloc ( & pdev - > dev , sizeof ( struct orion_nand_data ) ,
GFP_KERNEL ) ;
2014-10-16 06:58:35 +02:00
if ( ! board )
return - ENOMEM ;
2012-04-18 11:06:41 +01:00
if ( ! of_property_read_u32 ( pdev - > dev . of_node , " cle " , & val ) )
board - > cle = ( u8 ) val ;
else
board - > cle = 0 ;
if ( ! of_property_read_u32 ( pdev - > dev . of_node , " ale " , & val ) )
board - > ale = ( u8 ) val ;
else
board - > ale = 1 ;
if ( ! of_property_read_u32 ( pdev - > dev . of_node ,
" bank-width " , & val ) )
board - > width = ( u8 ) val * 8 ;
else
board - > width = 8 ;
if ( ! of_property_read_u32 ( pdev - > dev . of_node ,
" chip-delay " , & val ) )
board - > chip_delay = ( u8 ) val ;
2013-07-30 17:18:33 +09:00
} else {
board = dev_get_platdata ( & pdev - > dev ) ;
}
2007-10-17 01:10:40 +02:00
2015-06-10 22:38:58 +02:00
mtd - > dev . parent = & pdev - > dev ;
2007-10-17 01:10:40 +02:00
2015-12-10 09:00:41 +01:00
nand_set_controller_data ( nc , board ) ;
2015-10-30 20:33:25 -07:00
nand_set_flash_node ( nc , pdev - > dev . of_node ) ;
2007-10-17 01:10:40 +02:00
nc - > IO_ADDR_R = nc - > IO_ADDR_W = io_base ;
nc - > cmd_ctrl = orion_nand_cmd_ctrl ;
2009-05-31 22:25:40 -04:00
nc - > read_buf = orion_nand_read_buf ;
2007-10-17 01:10:40 +02:00
nc - > ecc . mode = NAND_ECC_SOFT ;
2016-04-08 12:23:48 +02:00
nc - > ecc . algo = NAND_ECC_HAMMING ;
2007-10-17 01:10:40 +02:00
2008-05-04 19:25:52 -11:00
if ( board - > chip_delay )
nc - > chip_delay = board - > chip_delay ;
2012-04-18 11:06:41 +01:00
WARN ( board - > width > 16 ,
" %d bit bus width out of range " ,
board - > width ) ;
2007-10-17 01:10:40 +02:00
if ( board - > width = = 16 )
nc - > options | = NAND_BUSWIDTH_16 ;
2010-04-20 10:26:18 +01:00
if ( board - > dev_ready )
nc - > dev_ready = board - > dev_ready ;
2017-03-27 20:02:07 +02:00
platform_set_drvdata ( pdev , info ) ;
2007-10-17 01:10:40 +02:00
2012-02-19 11:01:22 +01:00
/* Not all platforms can gate the clock, so it is not
an error if the clock does not exists . */
2017-03-27 20:02:07 +02:00
info - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2017-03-27 20:02:08 +02:00
if ( IS_ERR ( info - > clk ) ) {
ret = PTR_ERR ( info - > clk ) ;
if ( ret = = - ENOENT ) {
info - > clk = NULL ;
} else {
dev_err ( & pdev - > dev , " failed to get clock! \n " ) ;
return ret ;
}
}
clk_prepare_enable ( info - > clk ) ;
2012-02-19 11:01:22 +01:00
2016-11-04 19:42:55 +09:00
ret = nand_scan ( mtd , 1 ) ;
if ( ret )
2007-10-17 01:10:40 +02:00
goto no_dev ;
mtd - > name = " orion_nand " ;
2015-10-30 20:33:25 -07:00
ret = mtd_device_register ( mtd , board - > parts , board - > nr_parts ) ;
2007-10-17 01:10:40 +02:00
if ( ret ) {
nand_release ( mtd ) ;
goto no_dev ;
}
return 0 ;
no_dev :
2017-03-27 20:02:08 +02:00
clk_disable_unprepare ( info - > clk ) ;
2007-10-17 01:10:40 +02:00
return ret ;
}
2012-11-19 13:26:04 -05:00
static int orion_nand_remove ( struct platform_device * pdev )
2007-10-17 01:10:40 +02:00
{
2017-03-27 20:02:07 +02:00
struct orion_nand_info * info = platform_get_drvdata ( pdev ) ;
struct nand_chip * chip = & info - > chip ;
struct mtd_info * mtd = nand_to_mtd ( chip ) ;
2007-10-17 01:10:40 +02:00
nand_release ( mtd ) ;
2017-03-27 20:02:08 +02:00
clk_disable_unprepare ( info - > clk ) ;
2012-02-19 11:01:22 +01:00
2007-10-17 01:10:40 +02:00
return 0 ;
}
2012-04-18 11:06:41 +01:00
# ifdef CONFIG_OF
2014-05-07 17:50:23 +09:00
static const struct of_device_id orion_nand_of_match_table [ ] = {
2012-07-18 19:22:54 +02:00
{ . compatible = " marvell,orion-nand " , } ,
2012-04-18 11:06:41 +01:00
{ } ,
} ;
2015-09-18 00:14:02 +02:00
MODULE_DEVICE_TABLE ( of , orion_nand_of_match_table ) ;
2012-04-18 11:06:41 +01:00
# endif
2007-10-17 01:10:40 +02:00
static struct platform_driver orion_nand_driver = {
2012-11-19 13:21:24 -05:00
. remove = orion_nand_remove ,
2007-10-17 01:10:40 +02:00
. driver = {
. name = " orion_nand " ,
2012-04-18 11:06:41 +01:00
. of_match_table = of_match_ptr ( orion_nand_of_match_table ) ,
2007-10-17 01:10:40 +02:00
} ,
} ;
2013-03-05 13:29:20 +09:00
module_platform_driver_probe ( orion_nand_driver , orion_nand_probe ) ;
2007-10-17 01:10:40 +02:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Tzachi Perelstein " ) ;
MODULE_DESCRIPTION ( " NAND glue for Orion platforms " ) ;
2008-04-18 13:44:27 -07:00
MODULE_ALIAS ( " platform:orion_nand " ) ;