@ -1,7 +1,7 @@
/*
* U - Boot command for OneNAND support
*
* Copyright ( C ) 2005 - 2007 Samsung Electronics
* Copyright ( C ) 2005 - 2008 Samsung Electronics
* Kyungmin Park < kyungmin . park @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
@ -11,6 +11,7 @@
# include <common.h>
# include <command.h>
# include <malloc.h>
# include <linux/mtd/compat.h>
# include <linux/mtd/mtd.h>
@ -18,159 +19,468 @@
# include <asm/io.h>
extern struct mtd_info onenand_mtd ;
extern struct onenand_chip onenand_chip ;
static struct mtd_info * mtd ;
static loff_t next_ofs ;
static loff_t skip_ofs ;
static inline int str2long ( char * p , ulong * num )
{
char * endptr ;
* num = simple_strtoul ( p , & endptr , 16 ) ;
return ( * p ! = ' \0 ' & & * endptr = = ' \0 ' ) ? 1 : 0 ;
}
static int arg_off_size ( int argc , char * argv [ ] , ulong * off , size_t * size )
{
if ( argc > = 1 ) {
if ( ! ( str2long ( argv [ 0 ] , off ) ) ) {
printf ( " '%s' is not a number \n " , argv [ 0 ] ) ;
return - 1 ;
}
} else {
* off = 0 ;
}
if ( argc > = 2 ) {
if ( ! ( str2long ( argv [ 1 ] , ( ulong * ) size ) ) ) {
printf ( " '%s' is not a number \n " , argv [ 1 ] ) ;
return - 1 ;
}
} else {
* size = mtd - > size - * off ;
}
if ( ( * off + * size ) > mtd - > size ) {
printf ( " total chip size (0x%x) exceeded! \n " , mtd - > size ) ;
return - 1 ;
}
if ( * size = = mtd - > size )
puts ( " whole chip \n " ) ;
else
printf ( " offset 0x%lx, size 0x%x \n " , * off , * size ) ;
return 0 ;
}
static int onenand_block_read ( loff_t from , size_t len ,
size_t * retlen , u_char * buf , int oob )
{
struct onenand_chip * this = mtd - > priv ;
int blocks = ( int ) len > > this - > erase_shift ;
int blocksize = ( 1 < < this - > erase_shift ) ;
loff_t ofs = from ;
struct mtd_oob_ops ops = {
. retlen = 0 ,
} ;
int ret ;
if ( oob )
ops . ooblen = blocksize ;
else
ops . len = blocksize ;
while ( blocks ) {
ret = mtd - > block_isbad ( mtd , ofs ) ;
if ( ret ) {
printk ( " Bad blocks %d at 0x%x \n " ,
( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
ofs + = blocksize ;
continue ;
}
if ( oob )
ops . oobbuf = buf ;
else
ops . datbuf = buf ;
ops . retlen = 0 ;
ret = mtd - > read_oob ( mtd , ofs , & ops ) ;
if ( ret ) {
printk ( " Read failed 0x%x, %d \n " , ( u32 ) ofs , ret ) ;
ofs + = blocksize ;
continue ;
}
ofs + = blocksize ;
buf + = blocksize ;
blocks - - ;
* retlen + = ops . retlen ;
}
return 0 ;
}
static int onenand_block_write ( loff_t to , size_t len ,
size_t * retlen , const u_char * buf )
{
struct onenand_chip * this = mtd - > priv ;
int blocks = len > > this - > erase_shift ;
int blocksize = ( 1 < < this - > erase_shift ) ;
loff_t ofs ;
size_t _retlen = 0 ;
int ret ;
if ( to = = next_ofs ) {
next_ofs = to + len ;
to + = skip_ofs ;
} else {
next_ofs = to + len ;
skip_ofs = 0 ;
}
ofs = to ;
while ( blocks ) {
ret = mtd - > block_isbad ( mtd , ofs ) ;
if ( ret ) {
printk ( " Bad blocks %d at 0x%x \n " ,
( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
skip_ofs + = blocksize ;
goto next ;
}
ret = mtd - > write ( mtd , ofs , blocksize , & _retlen , buf ) ;
if ( ret ) {
printk ( " Write failed 0x%x, %d " , ( u32 ) ofs , ret ) ;
skip_ofs + = blocksize ;
goto next ;
}
buf + = blocksize ;
blocks - - ;
* retlen + = _retlen ;
next :
ofs + = blocksize ;
}
return 0 ;
}
static int onenand_block_erase ( u32 start , u32 size , int force )
{
struct onenand_chip * this = mtd - > priv ;
struct erase_info instr = {
. callback = NULL ,
} ;
loff_t ofs ;
int ret ;
int blocksize = 1 < < this - > erase_shift ;
for ( ofs = start ; ofs < ( start + size ) ; ofs + = blocksize ) {
ret = mtd - > block_isbad ( mtd , ofs ) ;
if ( ret & & ! force ) {
printf ( " Skip erase bad block %d at 0x%x \n " ,
( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
continue ;
}
instr . addr = ofs ;
instr . len = blocksize ;
instr . priv = force ;
instr . mtd = mtd ;
ret = mtd - > erase ( mtd , & instr ) ;
if ( ret ) {
printf ( " erase failed block %d at 0x%x \n " ,
( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
continue ;
}
}
return 0 ;
}
static int onenand_block_test ( u32 start , u32 size )
{
struct onenand_chip * this = mtd - > priv ;
struct erase_info instr = {
. callback = NULL ,
. priv = 0 ,
} ;
int blocks ;
loff_t ofs ;
int blocksize = 1 < < this - > erase_shift ;
int start_block , end_block ;
size_t retlen ;
u_char * buf ;
u_char * verify_buf ;
int ret ;
buf = malloc ( blocksize ) ;
if ( ! buf ) {
printf ( " Not enough malloc space available! \n " ) ;
return - 1 ;
}
verify_buf = malloc ( blocksize ) ;
if ( ! verify_buf ) {
printf ( " Not enough malloc space available! \n " ) ;
return - 1 ;
}
start_block = start > > this - > erase_shift ;
end_block = ( start + size ) > > this - > erase_shift ;
/* Protect boot-loader from badblock testing */
if ( start_block < 2 )
start_block = 2 ;
if ( end_block > ( mtd - > size > > this - > erase_shift ) )
end_block = mtd - > size > > this - > erase_shift ;
blocks = start_block ;
ofs = start ;
while ( blocks < end_block ) {
printf ( " \r Testing block %d at 0x%x " , ( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
ret = mtd - > block_isbad ( mtd , ofs ) ;
if ( ret ) {
printf ( " Skip erase bad block %d at 0x%x \n " ,
( u32 ) ( ofs > > this - > erase_shift ) , ( u32 ) ofs ) ;
goto next ;
}
instr . addr = ofs ;
instr . len = blocksize ;
ret = mtd - > erase ( mtd , & instr ) ;
if ( ret ) {
printk ( " Erase failed 0x%x, %d \n " , ( u32 ) ofs , ret ) ;
goto next ;
}
ret = mtd - > write ( mtd , ofs , blocksize , & retlen , buf ) ;
if ( ret ) {
printk ( " Write failed 0x%x, %d \n " , ( u32 ) ofs , ret ) ;
goto next ;
}
ret = mtd - > read ( mtd , ofs , blocksize , & retlen , verify_buf ) ;
if ( ret ) {
printk ( " Read failed 0x%x, %d \n " , ( u32 ) ofs , ret ) ;
goto next ;
}
if ( memcmp ( buf , verify_buf , blocksize ) )
printk ( " \n Read/Write test failed at 0x%x \n " , ( u32 ) ofs ) ;
next :
ofs + = blocksize ;
blocks + + ;
}
printf ( " ...Done \n " ) ;
free ( buf ) ;
free ( verify_buf ) ;
return 0 ;
}
static int onenand_dump ( struct mtd_info * mtd , ulong off , int only_oob )
{
int i ;
u_char * datbuf , * oobbuf , * p ;
struct mtd_oob_ops ops ;
loff_t addr ;
datbuf = malloc ( mtd - > writesize + mtd - > oobsize ) ;
oobbuf = malloc ( mtd - > oobsize ) ;
if ( ! datbuf | | ! oobbuf ) {
puts ( " No memory for page buffer \n " ) ;
return 1 ;
}
off & = ~ ( mtd - > writesize - 1 ) ;
addr = ( loff_t ) off ;
memset ( & ops , 0 , sizeof ( ops ) ) ;
ops . datbuf = datbuf ;
ops . oobbuf = oobbuf ; /* must exist, but oob data will be appended to ops.datbuf */
ops . len = mtd - > writesize ;
ops . ooblen = mtd - > oobsize ;
ops . retlen = 0 ;
i = mtd - > read_oob ( mtd , addr , & ops ) ;
if ( i < 0 ) {
printf ( " Error (%d) reading page %08lx \n " , i , off ) ;
free ( datbuf ) ;
free ( oobbuf ) ;
return 1 ;
}
printf ( " Page %08lx dump: \n " , off ) ;
i = mtd - > writesize > > 4 ;
p = datbuf ;
while ( i - - ) {
if ( ! only_oob )
printf ( " \t %02x %02x %02x %02x %02x %02x %02x %02x "
" %02x %02x %02x %02x %02x %02x %02x %02x \n " ,
p [ 0 ] , p [ 1 ] , p [ 2 ] , p [ 3 ] , p [ 4 ] , p [ 5 ] , p [ 6 ] , p [ 7 ] ,
p [ 8 ] , p [ 9 ] , p [ 10 ] , p [ 11 ] , p [ 12 ] , p [ 13 ] , p [ 14 ] ,
p [ 15 ] ) ;
p + = 16 ;
}
puts ( " OOB: \n " ) ;
i = mtd - > oobsize > > 3 ;
while ( i - - ) {
printf ( " \t %02x %02x %02x %02x %02x %02x %02x %02x \n " ,
p [ 0 ] , p [ 1 ] , p [ 2 ] , p [ 3 ] , p [ 4 ] , p [ 5 ] , p [ 6 ] , p [ 7 ] ) ;
p + = 8 ;
}
free ( datbuf ) ;
free ( oobbuf ) ;
return 0 ;
}
int do_onenand ( cmd_tbl_t * cmdtp , int flag , int argc , char * argv [ ] )
{
int ret = 0 ;
struct onenand_chip * this ;
int blocksize ;
ulong addr , ofs ;
size_t len , retlen = 0 ;
int ret ;
char * cmd , * s ;
mtd = & onenand_mtd ;
this = mtd - > priv ;
blocksize = ( 1 < < this - > erase_shift ) ;
cmd = argv [ 1 ] ;
switch ( argc ) {
case 0 :
case 1 :
printf ( " Usage: \n %s \n " , cmdtp - > usage ) ;
return 1 ;
goto usage ;
case 2 :
if ( strncmp ( argv [ 1 ] , " open " , 4 ) = = 0 ) {
onenand_init ( ) ;
if ( strcmp ( cmd , " info " ) = = 0 ) {
printf ( " %s \n " , mtd - > name ) ;
return 0 ;
}
if ( strcmp ( cmd , " bad " ) = = 0 ) {
/* Currently only one OneNAND device is supported */
printf ( " \n Device %d bad blocks: \n " , 0 ) ;
for ( ofs = 0 ; ofs < mtd - > size ; ofs + = mtd - > erasesize ) {
if ( mtd - > block_isbad ( mtd , ofs ) )
printf ( " %08x \n " , ( u32 ) ofs ) ;
}
return 0 ;
}
printf ( " %s \n " , onenand_mtd . name ) ;
return 0 ;
default :
/* At least 4 args */
if ( strncmp ( argv [ 1 ] , " erase " , 5 ) = = 0 ) {
struct erase_info instr = {
. callback = NULL ,
} ;
ulong start , end ;
ulong block ;
char * endtail ;
if ( strncmp ( argv [ 2 ] , " block " , 5 ) = = 0 ) {
start = simple_strtoul ( argv [ 3 ] , NULL , 10 ) ;
endtail = strchr ( argv [ 3 ] , ' - ' ) ;
end = simple_strtoul ( endtail + 1 , NULL , 10 ) ;
} else {
start = simple_strtoul ( argv [ 2 ] , NULL , 10 ) ;
end = simple_strtoul ( argv [ 3 ] , NULL , 10 ) ;
start > > = onenand_chip . erase_shift ;
end > > = onenand_chip . erase_shift ;
/* Don't include the end block */
end - - ;
}
/*
* Syntax is :
* 0 1 2 3 4
* onenand erase [ force ] [ off size ]
*/
if ( ( strcmp ( cmd , " erase " ) = = 0 ) | | ( strcmp ( cmd , " test " ) = = 0 ) ) {
int force = argc > 2 & & ! strcmp ( " force " , argv [ 2 ] ) ;
int o = force ? 3 : 2 ;
int erase ;
if ( ! end | | end < 0 )
end = start ;
erase = strcmp ( cmd , " erase " ) = = 0 ; /* 1 = erase, 0 = test */
printf ( " \n OneNAND %s: " , erase ? " erase " : " test " ) ;
printf ( " Erase block from %lu to %lu \n " , start , end ) ;
/* skip first two or three arguments, look for offset and size */
if ( arg_off_size ( argc - o , argv + o , & ofs , & len ) ! = 0 )
return 1 ;
for ( block = start ; block < = end ; block + + ) {
instr . addr = block < < onenand_chip . erase_shift ;
instr . len = 1 < < onenand_chip . erase_shift ;
ret = onenand_erase ( & onenand_mtd , & instr ) ;
if ( ret ) {
printf ( " erase failed %lu \n " , block ) ;
break ;
}
}
if ( erase )
ret = onenand_block_erase ( ofs , len , force ) ;
else
ret = onenand_block_test ( ofs , len ) ;
return 0 ;
printf ( " %s \n " , ret ? " ERROR " : " OK " ) ;
return ret = = 0 ? 0 : 1 ;
}
if ( strncmp ( argv [ 1 ] , " read " , 4 ) = = 0 ) {
ulong addr = simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
ulong ofs = simple_strtoul ( argv [ 3 ] , NULL , 16 ) ;
size_t len = simple_strtoul ( argv [ 4 ] , NULL , 16 ) ;
int oob = strncmp ( argv [ 1 ] , " read.oob " , 8 ) ? 0 : 1 ;
struct mtd_oob_ops ops ;
if ( strncmp ( cmd , " read " , 4 ) = = 0 | | strncmp ( cmd , " write " , 5 ) = = 0 ) {
int read ;
int oob = 0 ;
ops . mode = MTD_OOB_PLACE ;
if ( argc < 4 )
goto usage ;
if ( oob ) {
ops . len = 0 ;
ops . datbuf = NULL ;
ops . ooblen = len ;
ops . oobbuf = ( u_char * ) addr ;
} else {
ops . len = len ;
ops . datbuf = ( u_char * ) addr ;
ops . ooblen = 0 ;
ops . oobbuf = NULL ;
}
ops . retlen = ops . oobretlen = 0 ;
addr = ( ulong ) simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
onenand_mtd . read_oob ( & onenand_mtd , ofs , & ops ) ;
printf ( " Done \n " ) ;
read = strncmp ( cmd , " read " , 4 ) = = 0 ; /* 1 = read, 0 = write */
printf ( " \n OneNAND %s: " , read ? " read " : " write " ) ;
if ( arg_off_size ( argc - 3 , argv + 3 , & ofs , & len ) ! = 0 )
return 1 ;
return 0 ;
}
s = strchr ( cmd , ' . ' ) ;
if ( ( s ! = NULL ) & & ( ! strcmp ( s , " .oob " ) ) )
oob = 1 ;
if ( strncmp ( argv [ 1 ] , " write " , 5 ) = = 0 ) {
ulong addr = simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
ulong ofs = simple_strtoul ( argv [ 3 ] , NULL , 16 ) ;
size_t len = simple_strtoul ( argv [ 4 ] , NULL , 16 ) ;
size_t retlen = 0 ;
if ( read ) {
ret = onenand_block_read ( ofs , len , & retlen ,
( u8 * ) addr , oob ) ;
} else {
ret = onenand_block_write ( ofs , len , & retlen ,
( u8 * ) addr ) ;
}
onenand_write ( & onenand_mtd , ofs , len , & retlen ,
( u_char * ) addr ) ;
printf ( " Done \n " ) ;
printf ( " %d bytes %s: %s \n " , retlen ,
read ? " read " : " written " , ret ? " ERROR " : " OK " ) ;
return 0 ;
return ret = = 0 ? 0 : 1 ;
}
if ( strncmp ( argv [ 1 ] , " block " , 5 ) = = 0 ) {
ulong addr = simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
ulong block = simple_strtoul ( argv [ 3 ] , NULL , 10 ) ;
ulong page = simple_strtoul ( argv [ 4 ] , NULL , 10 ) ;
size_t len = simple_strtol ( argv [ 5 ] , NULL , 10 ) ;
ulong ofs ;
int oob = strncmp ( argv [ 1 ] , " block.oob " , 9 ) ? 0 : 1 ;
struct mtd_oob_ops ops ;
ops . mode = MTD_OOB_PLACE ;
if ( strcmp ( cmd , " markbad " ) = = 0 ) {
addr = ( ulong ) simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
int ret = mtd - > block_markbad ( mtd , addr ) ;
if ( ret = = 0 ) {
printf ( " block 0x%08lx successfully marked as bad \n " ,
( ulong ) addr ) ;
return 0 ;
} else {
printf ( " block 0x%08lx NOT marked as bad! ERROR %d \n " ,
( ulong ) addr , ret ) ;
}
return 1 ;
}
ofs = block < < onenand_chip . erase_shift ;
if ( page )
ofs + = page < < onenand_chip . page_shift ;
if ( strncmp ( cmd , " dump " , 4 ) = = 0 ) {
if ( argc < 3 )
goto usage ;
if ( ! len ) {
if ( oob )
ops . ooblen = 64 ;
else
ops . len = 512 ;
}
s = strchr ( cmd , ' . ' ) ;
ofs = ( int ) simple_strtoul ( argv [ 2 ] , NULL , 16 ) ;
if ( oob ) {
ops . datbuf = NULL ;
ops . oobbuf = ( u_char * ) addr ;
} else {
ops . datbuf = ( u_char * ) addr ;
ops . oobbuf = NULL ;
}
ops . retlen = ops . oobretlen = 0 ;
if ( s ! = NULL & & strcmp ( s , " .oob " ) = = 0 )
ret = onenand_dump ( mtd , ofs , 1 ) ;
else
ret = onenand_dump ( mtd , ofs , 0 ) ;
onenand_read_oob ( & onenand_mtd , ofs , & ops ) ;
return 0 ;
return ret = = 0 ? 1 : 0 ;
}
break ;
}
return 0 ;
usage :
printf ( " Usage: \n %s \n " , cmdtp - > usage ) ;
return 1 ;
}
U_BOOT_CMD (
onenand , 6 , 1 , do_onenand ,
" onenand - OneNAND sub-system \n " ,
" info - show available OneNAND devices \n "
" onenand read[.oob] addr ofs len - read data at ofs with len to addr \n "
" onenand write addr ofs len - write data at ofs with len from addr \n "
" onenand erase saddr eaddr - erase block start addr to end addr \n "
" onenand block[.oob] addr block [page] [len] - "
" read data with (block [, page]) to addr "
" info - show available OneNAND devices \n "
" onenand bad - show bad blocks \n "
" onenand read[.oob] addr off size \n "
" onenand write[.oob] addr off size \n "
" read/write 'size' bytes starting at offset 'off' \n "
" to/from memory address 'addr', skipping bad blocks. \n "
" onenand erase [force] [off size] - erase 'size' bytes from \n "
" onenand test [off size] - test 'size' bytes from \n "
" offset 'off' (entire device if not specified) \n "
" onenand dump[.oob] off - dump page \n "
" onenand markbad off - mark bad block at offset (UNSAFE) \n "
) ;