2008-03-11 22:33:13 +03:00
/*
* Freescale UPM NAND driver .
*
* Copyright © 2007 - 2008 MontaVista Software , Inc .
*
* Author : Anton Vorontsov < avorontsov @ ru . mvista . com >
*
* 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/kernel.h>
# include <linux/module.h>
2008-06-09 12:19:08 +04:00
# include <linux/delay.h>
2008-03-11 22:33:13 +03:00
# include <linux/mtd/nand.h>
# include <linux/mtd/nand_ecc.h>
# include <linux/mtd/partitions.h>
# include <linux/mtd/mtd.h>
# include <linux/of_platform.h>
# include <linux/of_gpio.h>
# include <linux/io.h>
# include <asm/fsl_lbc.h>
2009-03-30 14:02:43 +04:00
# define FSL_UPM_WAIT_RUN_PATTERN 0x1
# define FSL_UPM_WAIT_WRITE_BYTE 0x2
# define FSL_UPM_WAIT_WRITE_BUFFER 0x4
2008-03-11 22:33:13 +03:00
struct fsl_upm_nand {
struct device * dev ;
struct mtd_info mtd ;
struct nand_chip chip ;
int last_ctrl ;
# ifdef CONFIG_MTD_PARTITIONS
struct mtd_partition * parts ;
# endif
struct fsl_upm upm ;
uint8_t upm_addr_offset ;
uint8_t upm_cmd_offset ;
void __iomem * io_base ;
2009-03-30 14:02:42 +04:00
int rnb_gpio [ NAND_MAX_CHIPS ] ;
uint32_t mchip_offsets [ NAND_MAX_CHIPS ] ;
uint32_t mchip_count ;
uint32_t mchip_number ;
2008-06-09 12:19:08 +04:00
int chip_delay ;
2009-03-30 14:02:43 +04:00
uint32_t wait_flags ;
2008-03-11 22:33:13 +03:00
} ;
# define to_fsl_upm_nand(mtd) container_of(mtd, struct fsl_upm_nand, mtd)
static int fun_chip_ready ( struct mtd_info * mtd )
{
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
2009-03-30 14:02:42 +04:00
if ( gpio_get_value ( fun - > rnb_gpio [ fun - > mchip_number ] ) )
2008-03-11 22:33:13 +03:00
return 1 ;
dev_vdbg ( fun - > dev , " busy \n " ) ;
return 0 ;
}
static void fun_wait_rnb ( struct fsl_upm_nand * fun )
{
2009-03-30 14:02:42 +04:00
if ( fun - > rnb_gpio [ fun - > mchip_number ] > = 0 ) {
int cnt = 1000000 ;
2008-03-11 22:33:13 +03:00
while ( - - cnt & & ! fun_chip_ready ( & fun - > mtd ) )
cpu_relax ( ) ;
2008-06-09 12:19:08 +04:00
if ( ! cnt )
dev_err ( fun - > dev , " tired waiting for RNB \n " ) ;
} else {
ndelay ( 100 ) ;
2008-03-11 22:33:13 +03:00
}
}
static void fun_cmd_ctrl ( struct mtd_info * mtd , int cmd , unsigned int ctrl )
{
2009-03-30 14:02:42 +04:00
struct nand_chip * chip = mtd - > priv ;
2008-03-11 22:33:13 +03:00
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
2009-03-30 14:02:42 +04:00
u32 mar ;
2008-03-11 22:33:13 +03:00
if ( ! ( ctrl & fun - > last_ctrl ) ) {
fsl_upm_end_pattern ( & fun - > upm ) ;
if ( cmd = = NAND_CMD_NONE )
return ;
fun - > last_ctrl = ctrl & ( NAND_ALE | NAND_CLE ) ;
}
if ( ctrl & NAND_CTRL_CHANGE ) {
if ( ctrl & NAND_ALE )
fsl_upm_start_pattern ( & fun - > upm , fun - > upm_addr_offset ) ;
else if ( ctrl & NAND_CLE )
fsl_upm_start_pattern ( & fun - > upm , fun - > upm_cmd_offset ) ;
}
2009-03-30 14:02:42 +04:00
mar = ( cmd < < ( 32 - fun - > upm . width ) ) |
fun - > mchip_offsets [ fun - > mchip_number ] ;
fsl_upm_run_pattern ( & fun - > upm , chip - > IO_ADDR_R , mar ) ;
2008-03-11 22:33:13 +03:00
2009-03-30 14:02:43 +04:00
if ( fun - > wait_flags & FSL_UPM_WAIT_RUN_PATTERN )
fun_wait_rnb ( fun ) ;
2008-03-11 22:33:13 +03:00
}
2009-03-30 14:02:42 +04:00
static void fun_select_chip ( struct mtd_info * mtd , int mchip_nr )
{
struct nand_chip * chip = mtd - > priv ;
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
if ( mchip_nr = = - 1 ) {
chip - > cmd_ctrl ( mtd , NAND_CMD_NONE , 0 | NAND_CTRL_CHANGE ) ;
2009-11-03 22:49:18 +03:00
} else if ( mchip_nr > = 0 & & mchip_nr < NAND_MAX_CHIPS ) {
2009-03-30 14:02:42 +04:00
fun - > mchip_number = mchip_nr ;
chip - > IO_ADDR_R = fun - > io_base + fun - > mchip_offsets [ mchip_nr ] ;
chip - > IO_ADDR_W = chip - > IO_ADDR_R ;
} else {
BUG ( ) ;
}
}
2008-03-11 22:33:13 +03:00
static uint8_t fun_read_byte ( struct mtd_info * mtd )
{
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
return in_8 ( fun - > chip . IO_ADDR_R ) ;
}
static void fun_read_buf ( struct mtd_info * mtd , uint8_t * buf , int len )
{
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
int i ;
for ( i = 0 ; i < len ; i + + )
buf [ i ] = in_8 ( fun - > chip . IO_ADDR_R ) ;
}
static void fun_write_buf ( struct mtd_info * mtd , const uint8_t * buf , int len )
{
struct fsl_upm_nand * fun = to_fsl_upm_nand ( mtd ) ;
int i ;
for ( i = 0 ; i < len ; i + + ) {
out_8 ( fun - > chip . IO_ADDR_W , buf [ i ] ) ;
2009-03-30 14:02:43 +04:00
if ( fun - > wait_flags & FSL_UPM_WAIT_WRITE_BYTE )
fun_wait_rnb ( fun ) ;
2008-03-11 22:33:13 +03:00
}
2009-03-30 14:02:43 +04:00
if ( fun - > wait_flags & FSL_UPM_WAIT_WRITE_BUFFER )
fun_wait_rnb ( fun ) ;
2008-03-11 22:33:13 +03:00
}
2008-09-18 20:50:26 +04:00
static int __devinit fun_chip_init ( struct fsl_upm_nand * fun ,
const struct device_node * upm_np ,
const struct resource * io_res )
2008-03-11 22:33:13 +03:00
{
int ret ;
2008-09-18 20:50:26 +04:00
struct device_node * flash_np ;
2008-03-11 22:33:13 +03:00
# ifdef CONFIG_MTD_PARTITIONS
static const char * part_types [ ] = { " cmdlinepart " , NULL , } ;
# endif
fun - > chip . IO_ADDR_R = fun - > io_base ;
fun - > chip . IO_ADDR_W = fun - > io_base ;
fun - > chip . cmd_ctrl = fun_cmd_ctrl ;
2008-06-09 12:19:08 +04:00
fun - > chip . chip_delay = fun - > chip_delay ;
2008-03-11 22:33:13 +03:00
fun - > chip . read_byte = fun_read_byte ;
fun - > chip . read_buf = fun_read_buf ;
fun - > chip . write_buf = fun_write_buf ;
fun - > chip . ecc . mode = NAND_ECC_SOFT ;
2009-03-30 14:02:42 +04:00
if ( fun - > mchip_count > 1 )
fun - > chip . select_chip = fun_select_chip ;
2008-03-11 22:33:13 +03:00
2009-03-30 14:02:42 +04:00
if ( fun - > rnb_gpio [ 0 ] > = 0 )
2008-03-11 22:33:13 +03:00
fun - > chip . dev_ready = fun_chip_ready ;
fun - > mtd . priv = & fun - > chip ;
fun - > mtd . owner = THIS_MODULE ;
2008-09-18 20:50:26 +04:00
flash_np = of_get_next_child ( upm_np , NULL ) ;
if ( ! flash_np )
return - ENODEV ;
fun - > mtd . name = kasprintf ( GFP_KERNEL , " %x.%s " , io_res - > start ,
flash_np - > name ) ;
if ( ! fun - > mtd . name ) {
ret = - ENOMEM ;
goto err ;
}
2009-03-30 14:02:42 +04:00
ret = nand_scan ( & fun - > mtd , fun - > mchip_count ) ;
2008-03-11 22:33:13 +03:00
if ( ret )
2008-09-18 20:50:26 +04:00
goto err ;
2008-03-11 22:33:13 +03:00
# ifdef CONFIG_MTD_PARTITIONS
ret = parse_mtd_partitions ( & fun - > mtd , part_types , & fun - > parts , 0 ) ;
2008-09-18 20:50:26 +04:00
# ifdef CONFIG_MTD_OF_PARTS
2008-11-27 12:46:13 +03:00
if ( ret = = 0 ) {
ret = of_mtd_parse_partitions ( fun - > dev , flash_np , & fun - > parts ) ;
if ( ret < 0 )
goto err ;
}
2008-09-18 20:50:26 +04:00
# endif
2008-03-11 22:33:13 +03:00
if ( ret > 0 )
2008-09-18 20:50:26 +04:00
ret = add_mtd_partitions ( & fun - > mtd , fun - > parts , ret ) ;
else
2008-03-11 22:33:13 +03:00
# endif
2008-09-18 20:50:26 +04:00
ret = add_mtd_device ( & fun - > mtd ) ;
err :
of_node_put ( flash_np ) ;
return ret ;
2008-03-11 22:33:13 +03:00
}
static int __devinit fun_probe ( struct of_device * ofdev ,
const struct of_device_id * ofid )
{
struct fsl_upm_nand * fun ;
struct resource io_res ;
const uint32_t * prop ;
2009-03-30 14:02:42 +04:00
int rnb_gpio ;
2008-03-11 22:33:13 +03:00
int ret ;
int size ;
2009-03-30 14:02:42 +04:00
int i ;
2008-03-11 22:33:13 +03:00
fun = kzalloc ( sizeof ( * fun ) , GFP_KERNEL ) ;
if ( ! fun )
return - ENOMEM ;
ret = of_address_to_resource ( ofdev - > node , 0 , & io_res ) ;
if ( ret ) {
dev_err ( & ofdev - > dev , " can't get IO base \n " ) ;
goto err1 ;
}
ret = fsl_upm_find ( io_res . start , & fun - > upm ) ;
if ( ret ) {
dev_err ( & ofdev - > dev , " can't find UPM \n " ) ;
goto err1 ;
}
prop = of_get_property ( ofdev - > node , " fsl,upm-addr-offset " , & size ) ;
if ( ! prop | | size ! = sizeof ( uint32_t ) ) {
dev_err ( & ofdev - > dev , " can't get UPM address offset \n " ) ;
ret = - EINVAL ;
2009-03-30 14:02:42 +04:00
goto err1 ;
2008-03-11 22:33:13 +03:00
}
fun - > upm_addr_offset = * prop ;
prop = of_get_property ( ofdev - > node , " fsl,upm-cmd-offset " , & size ) ;
if ( ! prop | | size ! = sizeof ( uint32_t ) ) {
dev_err ( & ofdev - > dev , " can't get UPM command offset \n " ) ;
ret = - EINVAL ;
2009-03-30 14:02:42 +04:00
goto err1 ;
2008-03-11 22:33:13 +03:00
}
fun - > upm_cmd_offset = * prop ;
2009-03-30 14:02:42 +04:00
prop = of_get_property ( ofdev - > node ,
" fsl,upm-addr-line-cs-offsets " , & size ) ;
if ( prop & & ( size / sizeof ( uint32_t ) ) > 0 ) {
fun - > mchip_count = size / sizeof ( uint32_t ) ;
if ( fun - > mchip_count > = NAND_MAX_CHIPS ) {
dev_err ( & ofdev - > dev , " too much multiple chips \n " ) ;
goto err1 ;
}
for ( i = 0 ; i < fun - > mchip_count ; i + + )
fun - > mchip_offsets [ i ] = prop [ i ] ;
} else {
fun - > mchip_count = 1 ;
}
for ( i = 0 ; i < fun - > mchip_count ; i + + ) {
fun - > rnb_gpio [ i ] = - 1 ;
rnb_gpio = of_get_gpio ( ofdev - > node , i ) ;
if ( rnb_gpio > = 0 ) {
ret = gpio_request ( rnb_gpio , dev_name ( & ofdev - > dev ) ) ;
if ( ret ) {
dev_err ( & ofdev - > dev ,
" can't request RNB gpio #%d \n " , i ) ;
goto err2 ;
}
gpio_direction_input ( rnb_gpio ) ;
fun - > rnb_gpio [ i ] = rnb_gpio ;
} else if ( rnb_gpio = = - EINVAL ) {
dev_err ( & ofdev - > dev , " RNB gpio #%d is invalid \n " , i ) ;
2008-03-11 22:33:13 +03:00
goto err2 ;
}
}
2008-06-09 12:19:08 +04:00
prop = of_get_property ( ofdev - > node , " chip-delay " , NULL ) ;
if ( prop )
fun - > chip_delay = * prop ;
else
fun - > chip_delay = 50 ;
2009-03-30 14:02:43 +04:00
prop = of_get_property ( ofdev - > node , " fsl,upm-wait-flags " , & size ) ;
if ( prop & & size = = sizeof ( uint32_t ) )
fun - > wait_flags = * prop ;
else
fun - > wait_flags = FSL_UPM_WAIT_RUN_PATTERN |
FSL_UPM_WAIT_WRITE_BYTE ;
2008-03-11 22:33:13 +03:00
fun - > io_base = devm_ioremap_nocache ( & ofdev - > dev , io_res . start ,
2009-03-30 14:02:42 +04:00
io_res . end - io_res . start + 1 ) ;
2008-03-11 22:33:13 +03:00
if ( ! fun - > io_base ) {
ret = - ENOMEM ;
goto err2 ;
}
fun - > dev = & ofdev - > dev ;
fun - > last_ctrl = NAND_CLE ;
2008-09-18 20:50:26 +04:00
ret = fun_chip_init ( fun , ofdev - > node , & io_res ) ;
2008-03-11 22:33:13 +03:00
if ( ret )
goto err2 ;
dev_set_drvdata ( & ofdev - > dev , fun ) ;
return 0 ;
err2 :
2009-03-30 14:02:42 +04:00
for ( i = 0 ; i < fun - > mchip_count ; i + + ) {
if ( fun - > rnb_gpio [ i ] < 0 )
break ;
gpio_free ( fun - > rnb_gpio [ i ] ) ;
}
2008-03-11 22:33:13 +03:00
err1 :
kfree ( fun ) ;
return ret ;
}
static int __devexit fun_remove ( struct of_device * ofdev )
{
struct fsl_upm_nand * fun = dev_get_drvdata ( & ofdev - > dev ) ;
2009-03-30 14:02:42 +04:00
int i ;
2008-03-11 22:33:13 +03:00
nand_release ( & fun - > mtd ) ;
2008-09-18 20:50:26 +04:00
kfree ( fun - > mtd . name ) ;
2008-03-11 22:33:13 +03:00
2009-03-30 14:02:42 +04:00
for ( i = 0 ; i < fun - > mchip_count ; i + + ) {
if ( fun - > rnb_gpio [ i ] < 0 )
break ;
gpio_free ( fun - > rnb_gpio [ i ] ) ;
}
2008-03-11 22:33:13 +03:00
kfree ( fun ) ;
return 0 ;
}
static struct of_device_id of_fun_match [ ] = {
{ . compatible = " fsl,upm-nand " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_fun_match ) ;
static struct of_platform_driver of_fun_driver = {
. name = " fsl,upm-nand " ,
. match_table = of_fun_match ,
. probe = fun_probe ,
. remove = __devexit_p ( fun_remove ) ,
} ;
static int __init fun_module_init ( void )
{
return of_register_platform_driver ( & of_fun_driver ) ;
}
module_init ( fun_module_init ) ;
static void __exit fun_module_exit ( void )
{
of_unregister_platform_driver ( & of_fun_driver ) ;
}
module_exit ( fun_module_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Anton Vorontsov <avorontsov@ru.mvista.com> " ) ;
MODULE_DESCRIPTION ( " Driver for NAND chips working through Freescale "
" LocalBus User-Programmable Machine " ) ;