2005-04-17 02:20:36 +04:00
/*
* Linux driver for Disk - On - Chip Millennium Plus
*
* ( c ) 2002 - 2003 Greg Ungerer < gerg @ snapgear . com >
* ( c ) 2002 - 2003 SnapGear Inc
* ( c ) 1999 Machine Vision Holdings , Inc .
* ( c ) 1999 , 2000 David Woodhouse < dwmw2 @ infradead . org >
*
* Released under GPL
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <asm/errno.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/bitops.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/doc2000.h>
/* #define ECC_DEBUG */
/* I have no idea why some DoC chips can not use memcop_form|to_io().
* This may be due to the different revisions of the ASIC controller built - in or
* simplily a QA / Bug issue . Who knows ? ? If you have trouble , please uncomment
* this : */
# undef USE_MEMCPY
static int doc_read ( struct mtd_info * mtd , loff_t from , size_t len ,
size_t * retlen , u_char * buf ) ;
static int doc_write ( struct mtd_info * mtd , loff_t to , size_t len ,
size_t * retlen , const u_char * buf ) ;
2006-05-29 05:26:58 +04:00
static int doc_read_oob ( struct mtd_info * mtd , loff_t ofs ,
struct mtd_oob_ops * ops ) ;
static int doc_write_oob ( struct mtd_info * mtd , loff_t ofs ,
struct mtd_oob_ops * ops ) ;
2005-04-17 02:20:36 +04:00
static int doc_erase ( struct mtd_info * mtd , struct erase_info * instr ) ;
static struct mtd_info * docmilpluslist = NULL ;
/* Perform the required delay cycles by writing to the NOP register */
static void DoC_Delay ( void __iomem * docptr , int cycles )
{
int i ;
for ( i = 0 ; ( i < cycles ) ; i + + )
WriteDOC ( 0 , docptr , Mplus_NOP ) ;
}
# define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1)
/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
static int _DoC_WaitReady ( void __iomem * docptr )
{
unsigned int c = 0xffff ;
2011-07-19 21:06:09 +04:00
pr_debug ( " _DoC_WaitReady called for out-of-line wait \n " ) ;
2005-04-17 02:20:36 +04:00
/* Out-of-line routine to wait for chip response */
while ( ( ( ReadDOC ( docptr , Mplus_FlashControl ) & CDSN_CTRL_FR_B_MASK ) ! = CDSN_CTRL_FR_B_MASK ) & & - - c )
;
if ( c = = 0 )
2011-07-19 21:06:09 +04:00
pr_debug ( " _DoC_WaitReady timed out. \n " ) ;
2005-04-17 02:20:36 +04:00
return ( c = = 0 ) ;
}
static inline int DoC_WaitReady ( void __iomem * docptr )
{
/* This is inline, to optimise the common case, where it's ready instantly */
int ret = 0 ;
/* read form NOP register should be issued prior to the read from CDSNControl
see Software Requirement 11.4 item 2. */
DoC_Delay ( docptr , 4 ) ;
if ( ( ReadDOC ( docptr , Mplus_FlashControl ) & CDSN_CTRL_FR_B_MASK ) ! = CDSN_CTRL_FR_B_MASK )
/* Call the out-of-line routine to wait */
ret = _DoC_WaitReady ( docptr ) ;
return ret ;
}
2011-03-31 05:57:33 +04:00
/* For some reason the Millennium Plus seems to occasionally put itself
2005-04-17 02:20:36 +04:00
* into reset mode . For me this happens randomly , with no pattern that I
* can detect . M - systems suggest always check this on any block level
* operation and setting to normal mode if in reset mode .
*/
static inline void DoC_CheckASIC ( void __iomem * docptr )
{
/* Make sure the DoC is in normal mode */
if ( ( ReadDOC ( docptr , Mplus_DOCControl ) & DOC_MODE_NORMAL ) = = 0 ) {
WriteDOC ( ( DOC_MODE_NORMAL | DOC_MODE_MDWREN ) , docptr , Mplus_DOCControl ) ;
WriteDOC ( ~ ( DOC_MODE_NORMAL | DOC_MODE_MDWREN ) , docptr , Mplus_CtrlConfirm ) ;
}
}
/* DoC_Command: Send a flash command to the flash chip through the Flash
* command register . Need 2 Write Pipeline Terminates to complete send .
*/
2006-01-15 00:20:43 +03:00
static void DoC_Command ( void __iomem * docptr , unsigned char command ,
2005-04-17 02:20:36 +04:00
unsigned char xtraflags )
{
WriteDOC ( command , docptr , Mplus_FlashCmd ) ;
WriteDOC ( command , docptr , Mplus_WritePipeTerm ) ;
WriteDOC ( command , docptr , Mplus_WritePipeTerm ) ;
}
/* DoC_Address: Set the current address for the flash chip through the Flash
* Address register . Need 2 Write Pipeline Terminates to complete send .
*/
static inline void DoC_Address ( struct DiskOnChip * doc , int numbytes ,
unsigned long ofs , unsigned char xtraflags1 ,
unsigned char xtraflags2 )
{
void __iomem * docptr = doc - > virtadr ;
/* Allow for possible Mill Plus internal flash interleaving */
ofs > > = doc - > interleave ;
switch ( numbytes ) {
case 1 :
/* Send single byte, bits 0-7. */
WriteDOC ( ofs & 0xff , docptr , Mplus_FlashAddress ) ;
break ;
case 2 :
/* Send bits 9-16 followed by 17-23 */
WriteDOC ( ( ofs > > 9 ) & 0xff , docptr , Mplus_FlashAddress ) ;
WriteDOC ( ( ofs > > 17 ) & 0xff , docptr , Mplus_FlashAddress ) ;
break ;
case 3 :
/* Send 0-7, 9-16, then 17-23 */
WriteDOC ( ofs & 0xff , docptr , Mplus_FlashAddress ) ;
WriteDOC ( ( ofs > > 9 ) & 0xff , docptr , Mplus_FlashAddress ) ;
WriteDOC ( ( ofs > > 17 ) & 0xff , docptr , Mplus_FlashAddress ) ;
break ;
default :
return ;
}
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
}
/* DoC_SelectChip: Select a given flash chip within the current floor */
static int DoC_SelectChip ( void __iomem * docptr , int chip )
{
/* No choice for flash chip on Millennium Plus */
return 0 ;
}
/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
static int DoC_SelectFloor ( void __iomem * docptr , int floor )
{
WriteDOC ( ( floor & 0x3 ) , docptr , Mplus_DeviceSelect ) ;
return 0 ;
}
/*
* Translate the given offset into the appropriate command and offset .
* This does the mapping using the 16 bit interleave layout defined by
* M - Systems , and looks like this for a sector pair :
* + - - - - - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - +
* | 0 - - - 511 | 512 - 517 | 518 - 519 | 520 - 521 | 522 - - - 1033 | 1034 - 1039 | 1040 - 1055 |
* + - - - - - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - +
* | Data 0 | ECC 0 | Flags0 | Flags1 | Data 1 | ECC 1 | OOB 1 + 2 |
* + - - - - - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - + - - - - - - - - - - - +
*/
/* FIXME: This lives in INFTL not here. Other users of flash devices
may not want it */
static unsigned int DoC_GetDataOffset ( struct mtd_info * mtd , loff_t * from )
{
struct DiskOnChip * this = mtd - > priv ;
if ( this - > interleave ) {
unsigned int ofs = * from & 0x3ff ;
unsigned int cmd ;
if ( ofs < 512 ) {
cmd = NAND_CMD_READ0 ;
ofs & = 0x1ff ;
} else if ( ofs < 1014 ) {
cmd = NAND_CMD_READ1 ;
ofs = ( ofs & 0x1ff ) + 10 ;
} else {
cmd = NAND_CMD_READOOB ;
ofs = ofs - 1014 ;
}
* from = ( * from & ~ 0x3ff ) | ofs ;
return cmd ;
} else {
/* No interleave */
if ( ( * from ) & 0x100 )
return NAND_CMD_READ1 ;
return NAND_CMD_READ0 ;
}
}
static unsigned int DoC_GetECCOffset ( struct mtd_info * mtd , loff_t * from )
{
unsigned int ofs , cmd ;
if ( * from & 0x200 ) {
cmd = NAND_CMD_READOOB ;
ofs = 10 + ( * from & 0xf ) ;
} else {
cmd = NAND_CMD_READ1 ;
ofs = ( * from & 0xf ) ;
}
* from = ( * from & ~ 0x3ff ) | ofs ;
return cmd ;
}
static unsigned int DoC_GetFlagsOffset ( struct mtd_info * mtd , loff_t * from )
{
unsigned int ofs , cmd ;
cmd = NAND_CMD_READ1 ;
ofs = ( * from & 0x200 ) ? 8 : 6 ;
* from = ( * from & ~ 0x3ff ) | ofs ;
return cmd ;
}
static unsigned int DoC_GetHdrOffset ( struct mtd_info * mtd , loff_t * from )
{
unsigned int ofs , cmd ;
cmd = NAND_CMD_READOOB ;
ofs = ( * from & 0x200 ) ? 24 : 16 ;
* from = ( * from & ~ 0x3ff ) | ofs ;
return cmd ;
}
static inline void MemReadDOC ( void __iomem * docptr , unsigned char * buf , int len )
{
# ifndef USE_MEMCPY
int i ;
for ( i = 0 ; i < len ; i + + )
buf [ i ] = ReadDOC ( docptr , Mil_CDSN_IO + i ) ;
# else
memcpy_fromio ( buf , docptr + DoC_Mil_CDSN_IO , len ) ;
# endif
}
static inline void MemWriteDOC ( void __iomem * docptr , unsigned char * buf , int len )
{
# ifndef USE_MEMCPY
int i ;
for ( i = 0 ; i < len ; i + + )
WriteDOC ( buf [ i ] , docptr , Mil_CDSN_IO + i ) ;
# else
memcpy_toio ( docptr + DoC_Mil_CDSN_IO , buf , len ) ;
# endif
}
/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
static int DoC_IdentChip ( struct DiskOnChip * doc , int floor , int chip )
{
int mfr , id , i , j ;
volatile char dummy ;
void __iomem * docptr = doc - > virtadr ;
/* Page in the required floor/chip */
DoC_SelectFloor ( docptr , floor ) ;
DoC_SelectChip ( docptr , chip ) ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( ( DOC_FLASH_CE | DOC_FLASH_WP ) , docptr , Mplus_FlashSelect ) ;
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_RESET , 0 ) ;
DoC_WaitReady ( docptr ) ;
2005-11-07 14:15:40 +03:00
/* Read the NAND chip ID: 1. Send ReadID command */
2005-04-17 02:20:36 +04:00
DoC_Command ( docptr , NAND_CMD_READID , 0 ) ;
2005-11-07 14:15:40 +03:00
/* Read the NAND chip ID: 2. Send address byte zero */
2005-04-17 02:20:36 +04:00
DoC_Address ( doc , 1 , 0x00 , 0 , 0x00 ) ;
WriteDOC ( 0 , docptr , Mplus_FlashControl ) ;
DoC_WaitReady ( docptr ) ;
/* Read the manufacturer and device id codes of the flash device through
CDSN IO register see Software Requirement 11.4 item 5. */
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
mfr = ReadDOC ( docptr , Mil_CDSN_IO ) ;
if ( doc - > interleave )
dummy = ReadDOC ( docptr , Mil_CDSN_IO ) ; /* 2 way interleave */
id = ReadDOC ( docptr , Mil_CDSN_IO ) ;
if ( doc - > interleave )
dummy = ReadDOC ( docptr , Mil_CDSN_IO ) ; /* 2 way interleave */
dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ;
dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ;
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
/* No response - return failure */
if ( mfr = = 0xff | | mfr = = 0 )
return 0 ;
for ( i = 0 ; nand_flash_ids [ i ] . name ! = NULL ; i + + ) {
if ( id = = nand_flash_ids [ i ] . id ) {
/* Try to identify manufacturer */
for ( j = 0 ; nand_manuf_ids [ j ] . id ! = 0x0 ; j + + ) {
if ( nand_manuf_ids [ j ] . id = = mfr )
break ;
}
printk ( KERN_INFO " Flash chip found: Manufacturer ID: %2.2X, "
" Chip ID: %2.2X (%s:%s) \n " , mfr , id ,
nand_manuf_ids [ j ] . name , nand_flash_ids [ i ] . name ) ;
doc - > mfr = mfr ;
doc - > id = id ;
doc - > chipshift = ffs ( ( nand_flash_ids [ i ] . chipsize < < 20 ) ) - 1 ;
doc - > erasesize = nand_flash_ids [ i ] . erasesize < < doc - > interleave ;
break ;
}
}
if ( nand_flash_ids [ i ] . name = = NULL )
return 0 ;
return 1 ;
}
/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
static void DoC_ScanChips ( struct DiskOnChip * this )
{
int floor , chip ;
int numchips [ MAX_FLOORS_MPLUS ] ;
int ret ;
this - > numchips = 0 ;
this - > mfr = 0 ;
this - > id = 0 ;
/* Work out the intended interleave setting */
this - > interleave = 0 ;
if ( this - > ChipID = = DOC_ChipID_DocMilPlus32 )
this - > interleave = 1 ;
/* Check the ASIC agrees */
2005-11-07 14:15:40 +03:00
if ( ( this - > interleave < < 2 ) ! =
2005-04-17 02:20:36 +04:00
( ReadDOC ( this - > virtadr , Mplus_Configuration ) & 4 ) ) {
u_char conf = ReadDOC ( this - > virtadr , Mplus_Configuration ) ;
printk ( KERN_NOTICE " Setting DiskOnChip Millennium Plus interleave to %s \n " ,
this - > interleave ? " on (16-bit) " : " off (8-bit) " ) ;
conf ^ = 4 ;
WriteDOC ( conf , this - > virtadr , Mplus_Configuration ) ;
}
/* For each floor, find the number of valid chips it contains */
for ( floor = 0 , ret = 1 ; floor < MAX_FLOORS_MPLUS ; floor + + ) {
numchips [ floor ] = 0 ;
for ( chip = 0 ; chip < MAX_CHIPS_MPLUS & & ret ! = 0 ; chip + + ) {
ret = DoC_IdentChip ( this , floor , chip ) ;
if ( ret ) {
numchips [ floor ] + + ;
this - > numchips + + ;
}
}
}
/* If there are none at all that we recognise, bail */
if ( ! this - > numchips ) {
printk ( " No flash chips recognised. \n " ) ;
return ;
}
/* Allocate an array to hold the information for each chip */
this - > chips = kmalloc ( sizeof ( struct Nand ) * this - > numchips , GFP_KERNEL ) ;
if ( ! this - > chips ) {
printk ( " MTD: No memory for allocating chip info structures \n " ) ;
return ;
}
2005-11-07 14:15:40 +03:00
/* Fill out the chip array with {floor, chipno} for each
2005-04-17 02:20:36 +04:00
* detected chip in the device . */
for ( floor = 0 , ret = 0 ; floor < MAX_FLOORS_MPLUS ; floor + + ) {
for ( chip = 0 ; chip < numchips [ floor ] ; chip + + ) {
this - > chips [ ret ] . floor = floor ;
this - > chips [ ret ] . chip = chip ;
this - > chips [ ret ] . curadr = 0 ;
this - > chips [ ret ] . curmode = 0x50 ;
ret + + ;
}
}
/* Calculate and print the total size of the device */
this - > totlen = this - > numchips * ( 1 < < this - > chipshift ) ;
printk ( KERN_INFO " %d flash chips found. Total DiskOnChip size: %ld MiB \n " ,
this - > numchips , this - > totlen > > 20 ) ;
}
static int DoCMilPlus_is_alias ( struct DiskOnChip * doc1 , struct DiskOnChip * doc2 )
{
int tmp1 , tmp2 , retval ;
if ( doc1 - > physadr = = doc2 - > physadr )
return 1 ;
/* Use the alias resolution register which was set aside for this
* purpose . If it ' s value is the same on both chips , they might
* be the same chip , and we write to one and check for a change in
* the other . It ' s unclear if this register is usuable in the
* DoC 2000 ( it ' s in the Millennium docs ) , but it seems to work . */
tmp1 = ReadDOC ( doc1 - > virtadr , Mplus_AliasResolution ) ;
tmp2 = ReadDOC ( doc2 - > virtadr , Mplus_AliasResolution ) ;
if ( tmp1 ! = tmp2 )
return 0 ;
2005-11-07 14:15:40 +03:00
2005-04-17 02:20:36 +04:00
WriteDOC ( ( tmp1 + 1 ) % 0xff , doc1 - > virtadr , Mplus_AliasResolution ) ;
tmp2 = ReadDOC ( doc2 - > virtadr , Mplus_AliasResolution ) ;
if ( tmp2 = = ( tmp1 + 1 ) % 0xff )
retval = 1 ;
else
retval = 0 ;
/* Restore register contents. May not be necessary, but do it just to
* be safe . */
WriteDOC ( tmp1 , doc1 - > virtadr , Mplus_AliasResolution ) ;
return retval ;
}
2006-05-08 17:05:05 +04:00
/* This routine is found from the docprobe code by symbol_get(),
* which will bump the use count of this module . */
void DoCMilPlus_init ( struct mtd_info * mtd )
2005-04-17 02:20:36 +04:00
{
struct DiskOnChip * this = mtd - > priv ;
struct DiskOnChip * old = NULL ;
/* We must avoid being called twice for the same device. */
if ( docmilpluslist )
old = docmilpluslist - > priv ;
while ( old ) {
if ( DoCMilPlus_is_alias ( this , old ) ) {
printk ( KERN_NOTICE " Ignoring DiskOnChip Millennium "
" Plus at 0x%lX - already configured \n " ,
this - > physadr ) ;
iounmap ( this - > virtadr ) ;
kfree ( mtd ) ;
return ;
}
if ( old - > nextdoc )
old = old - > nextdoc - > priv ;
else
old = NULL ;
}
mtd - > name = " DiskOnChip Millennium Plus " ;
printk ( KERN_NOTICE " DiskOnChip Millennium Plus found at "
" address 0x%lX \n " , this - > physadr ) ;
mtd - > type = MTD_NANDFLASH ;
mtd - > flags = MTD_CAP_NANDFLASH ;
2012-02-03 11:43:14 +04:00
mtd - > writebufsize = mtd - > writesize = 512 ;
2005-04-17 02:20:36 +04:00
mtd - > oobsize = 16 ;
2012-03-12 01:21:11 +04:00
mtd - > ecc_strength = 2 ;
2005-04-17 02:20:36 +04:00
mtd - > owner = THIS_MODULE ;
2012-01-30 16:58:32 +04:00
mtd - > _erase = doc_erase ;
mtd - > _read = doc_read ;
mtd - > _write = doc_write ;
mtd - > _read_oob = doc_read_oob ;
mtd - > _write_oob = doc_write_oob ;
2005-04-17 02:20:36 +04:00
this - > curfloor = - 1 ;
this - > curchip = - 1 ;
/* Ident all the chips present. */
DoC_ScanChips ( this ) ;
if ( ! this - > totlen ) {
kfree ( mtd ) ;
iounmap ( this - > virtadr ) ;
} else {
this - > nextdoc = docmilpluslist ;
docmilpluslist = mtd ;
mtd - > size = this - > totlen ;
mtd - > erasesize = this - > erasesize ;
2011-05-23 13:23:40 +04:00
mtd_device_register ( mtd , NULL , 0 ) ;
2005-04-17 02:20:36 +04:00
return ;
}
}
2006-05-10 19:16:13 +04:00
EXPORT_SYMBOL_GPL ( DoCMilPlus_init ) ;
2005-04-17 02:20:36 +04:00
#if 0
static int doc_dumpblk ( struct mtd_info * mtd , loff_t from )
{
int i ;
loff_t fofs ;
struct DiskOnChip * this = mtd - > priv ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ from > > ( this - > chipshift ) ] ;
unsigned char * bp , buf [ 1056 ] ;
char c [ 32 ] ;
from & = ~ 0x3ff ;
/* Don't allow read past end of device */
if ( from > = this - > totlen )
return - EINVAL ;
DoC_CheckASIC ( docptr ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( ( DOC_FLASH_CE | DOC_FLASH_WP ) , docptr , Mplus_FlashSelect ) ;
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_RESET , 0 ) ;
DoC_WaitReady ( docptr ) ;
fofs = from ;
DoC_Command ( docptr , DoC_GetDataOffset ( mtd , & fofs ) , 0 ) ;
DoC_Address ( this , 3 , fofs , 0 , 0x00 ) ;
WriteDOC ( 0 , docptr , Mplus_FlashControl ) ;
DoC_WaitReady ( docptr ) ;
/* disable the ECC engine */
WriteDOC ( DOC_ECC_RESET , docptr , Mplus_ECCConf ) ;
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
/* Read the data via the internal pipeline through CDSN IO
register , see Pipelined Read Operations 11.3 */
MemReadDOC ( docptr , buf , 1054 ) ;
buf [ 1054 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
buf [ 1055 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
memset ( & c [ 0 ] , 0 , sizeof ( c ) ) ;
printk ( " DUMP OFFSET=%x: \n " , ( int ) from ) ;
for ( i = 0 , bp = & buf [ 0 ] ; ( i < 1056 ) ; i + + ) {
if ( ( i % 16 ) = = 0 )
printk ( " %08x: " , i ) ;
printk ( " %02x " , * bp ) ;
c [ ( i & 0xf ) ] = ( ( * bp > = 0x20 ) & & ( * bp < = 0x7f ) ) ? * bp : ' . ' ;
bp + + ;
if ( ( ( i + 1 ) % 16 ) = = 0 )
printk ( " %s \n " , c ) ;
}
printk ( " \n " ) ;
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
return 0 ;
}
# endif
static int doc_read ( struct mtd_info * mtd , loff_t from , size_t len ,
size_t * retlen , u_char * buf )
{
int ret , i ;
volatile char dummy ;
loff_t fofs ;
2006-06-28 12:11:33 +04:00
unsigned char syndrome [ 6 ] , eccbuf [ 6 ] ;
2005-04-17 02:20:36 +04:00
struct DiskOnChip * this = mtd - > priv ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ from > > ( this - > chipshift ) ] ;
/* Don't allow a single read to cross a 512-byte block boundary */
2005-11-07 14:15:40 +03:00
if ( from + len > ( ( from | 0x1ff ) + 1 ) )
2005-04-17 02:20:36 +04:00
len = ( ( from | 0x1ff ) + 1 ) - from ;
DoC_CheckASIC ( docptr ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( ( DOC_FLASH_CE | DOC_FLASH_WP ) , docptr , Mplus_FlashSelect ) ;
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_RESET , 0 ) ;
DoC_WaitReady ( docptr ) ;
fofs = from ;
DoC_Command ( docptr , DoC_GetDataOffset ( mtd , & fofs ) , 0 ) ;
DoC_Address ( this , 3 , fofs , 0 , 0x00 ) ;
WriteDOC ( 0 , docptr , Mplus_FlashControl ) ;
DoC_WaitReady ( docptr ) ;
2006-06-28 12:11:33 +04:00
/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
WriteDOC ( DOC_ECC_RESET , docptr , Mplus_ECCConf ) ;
WriteDOC ( DOC_ECC_EN , docptr , Mplus_ECCConf ) ;
2005-04-17 02:20:36 +04:00
/* Let the caller know we completed it */
* retlen = len ;
2006-06-28 12:11:33 +04:00
ret = 0 ;
2005-04-17 02:20:36 +04:00
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
2006-06-28 12:11:33 +04:00
/* Read the data via the internal pipeline through CDSN IO
register , see Pipelined Read Operations 11.3 */
MemReadDOC ( docptr , buf , len ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* Read the ECC data following raw data */
MemReadDOC ( docptr , eccbuf , 4 ) ;
eccbuf [ 4 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
eccbuf [ 5 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* Flush the pipeline */
dummy = ReadDOC ( docptr , Mplus_ECCConf ) ;
dummy = ReadDOC ( docptr , Mplus_ECCConf ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* Check the ECC Status */
if ( ReadDOC ( docptr , Mplus_ECCConf ) & 0x80 ) {
int nb_errors ;
/* There was an ECC error */
2005-04-17 02:20:36 +04:00
# ifdef ECC_DEBUG
2006-06-28 12:11:33 +04:00
printk ( " DiskOnChip ECC Error: Read at %lx \n " , ( long ) from ) ;
2005-04-17 02:20:36 +04:00
# endif
2011-06-24 01:12:08 +04:00
/* Read the ECC syndrome through the DiskOnChip ECC logic.
2006-06-28 12:11:33 +04:00
These syndrome will be all ZERO when there is no error */
for ( i = 0 ; i < 6 ; i + + )
syndrome [ i ] = ReadDOC ( docptr , Mplus_ECCSyndrome0 + i ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
nb_errors = doc_decode_ecc ( buf , syndrome ) ;
2005-04-17 02:20:36 +04:00
# ifdef ECC_DEBUG
2006-06-28 12:11:33 +04:00
printk ( " ECC Errors corrected: %x \n " , nb_errors ) ;
2005-04-17 02:20:36 +04:00
# endif
2006-06-28 12:11:33 +04:00
if ( nb_errors < 0 ) {
/* We return error, but have actually done the
read . Not that this can be told to user - space , via
sys_read ( ) , but at least MTD - aware stuff can know
about it by checking * retlen */
2005-04-17 02:20:36 +04:00
# ifdef ECC_DEBUG
printk ( " %s(%d): Millennium Plus ECC error (from=0x%x: \n " ,
__FILE__ , __LINE__ , ( int ) from ) ;
printk ( " syndrome= %02x:%02x:%02x:%02x:%02x: "
" %02x \n " ,
syndrome [ 0 ] , syndrome [ 1 ] , syndrome [ 2 ] ,
syndrome [ 3 ] , syndrome [ 4 ] , syndrome [ 5 ] ) ;
printk ( " eccbuf= %02x:%02x:%02x:%02x:%02x: "
" %02x \n " ,
eccbuf [ 0 ] , eccbuf [ 1 ] , eccbuf [ 2 ] ,
eccbuf [ 3 ] , eccbuf [ 4 ] , eccbuf [ 5 ] ) ;
# endif
ret = - EIO ;
}
2006-06-28 12:11:33 +04:00
}
2005-04-17 02:20:36 +04:00
# ifdef PSYCHO_DEBUG
2006-06-28 12:11:33 +04:00
printk ( " ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X \n " ,
( long ) from , eccbuf [ 0 ] , eccbuf [ 1 ] , eccbuf [ 2 ] , eccbuf [ 3 ] ,
eccbuf [ 4 ] , eccbuf [ 5 ] ) ;
2005-04-17 02:20:36 +04:00
# endif
2006-06-28 12:11:33 +04:00
/* disable the ECC engine */
WriteDOC ( DOC_ECC_DIS , docptr , Mplus_ECCConf ) ;
2005-04-17 02:20:36 +04:00
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
return ret ;
}
static int doc_write ( struct mtd_info * mtd , loff_t to , size_t len ,
size_t * retlen , const u_char * buf )
{
int i , before , ret = 0 ;
loff_t fto ;
volatile char dummy ;
2006-06-28 12:11:33 +04:00
char eccbuf [ 6 ] ;
2005-04-17 02:20:36 +04:00
struct DiskOnChip * this = mtd - > priv ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ to > > ( this - > chipshift ) ] ;
/* Don't allow writes which aren't exactly one block (512 bytes) */
if ( ( to & 0x1ff ) | | ( len ! = 0x200 ) )
return - EINVAL ;
/* Determine position of OOB flags, before or after data */
before = ( this - > interleave & & ( to & 0x200 ) ) ;
DoC_CheckASIC ( docptr ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( DOC_FLASH_CE , docptr , Mplus_FlashSelect ) ;
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_RESET , 0 ) ;
DoC_WaitReady ( docptr ) ;
/* Set device to appropriate plane of flash */
fto = to ;
WriteDOC ( DoC_GetDataOffset ( mtd , & fto ) , docptr , Mplus_FlashCmd ) ;
/* On interleaved devices the flags for 2nd half 512 are before data */
2008-02-05 10:44:47 +03:00
if ( before )
2005-04-17 02:20:36 +04:00
fto - = 2 ;
/* issue the Serial Data In command to initial the Page Program process */
DoC_Command ( docptr , NAND_CMD_SEQIN , 0x00 ) ;
DoC_Address ( this , 3 , fto , 0x00 , 0x00 ) ;
/* Disable the ECC engine */
WriteDOC ( DOC_ECC_RESET , docptr , Mplus_ECCConf ) ;
2006-06-28 12:11:33 +04:00
if ( before ) {
/* Write the block status BLOCK_USED (0x5555) */
WriteDOC ( 0x55 , docptr , Mil_CDSN_IO ) ;
WriteDOC ( 0x55 , docptr , Mil_CDSN_IO ) ;
2005-04-17 02:20:36 +04:00
}
2006-06-28 12:11:33 +04:00
/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
WriteDOC ( DOC_ECC_EN | DOC_ECC_RW , docptr , Mplus_ECCConf ) ;
2005-04-17 02:20:36 +04:00
MemWriteDOC ( docptr , ( unsigned char * ) buf , len ) ;
2006-06-28 12:11:33 +04:00
/* Write ECC data to flash, the ECC info is generated by
the DiskOnChip ECC logic see Reed - Solomon EDC / ECC 11.1 */
DoC_Delay ( docptr , 3 ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* Read the ECC data through the DiskOnChip ECC logic */
for ( i = 0 ; i < 6 ; i + + )
eccbuf [ i ] = ReadDOC ( docptr , Mplus_ECCSyndrome0 + i ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* disable the ECC engine */
WriteDOC ( DOC_ECC_DIS , docptr , Mplus_ECCConf ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
/* Write the ECC data to flash */
MemWriteDOC ( docptr , eccbuf , 6 ) ;
2005-04-17 02:20:36 +04:00
2006-06-28 12:11:33 +04:00
if ( ! before ) {
/* Write the block status BLOCK_USED (0x5555) */
WriteDOC ( 0x55 , docptr , Mil_CDSN_IO + 6 ) ;
WriteDOC ( 0x55 , docptr , Mil_CDSN_IO + 7 ) ;
}
2005-04-17 02:20:36 +04:00
# ifdef PSYCHO_DEBUG
2006-06-28 12:11:33 +04:00
printk ( " OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X \n " ,
( long ) to , eccbuf [ 0 ] , eccbuf [ 1 ] , eccbuf [ 2 ] , eccbuf [ 3 ] ,
eccbuf [ 4 ] , eccbuf [ 5 ] ) ;
2005-04-17 02:20:36 +04:00
# endif
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
/* Commit the Page Program command and wait for ready
see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_PAGEPROG , 0x00 ) ;
DoC_WaitReady ( docptr ) ;
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5. */
DoC_Command ( docptr , NAND_CMD_STATUS , 0 ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
DoC_Delay ( docptr , 2 ) ;
if ( ( dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ) & 1 ) {
printk ( " MTD: Error 0x%x programming at 0x%x \n " , dummy , ( int ) to ) ;
/* Error in programming
FIXME : implement Bad Block Replacement ( in nftl . c ? ? ) */
ret = - EIO ;
}
dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ;
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
/* Let the caller know we completed it */
* retlen = len ;
return ret ;
}
2006-05-29 05:26:58 +04:00
static int doc_read_oob ( struct mtd_info * mtd , loff_t ofs ,
struct mtd_oob_ops * ops )
2005-04-17 02:20:36 +04:00
{
loff_t fofs , base ;
struct DiskOnChip * this = mtd - > priv ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ ofs > > this - > chipshift ] ;
size_t i , size , got , want ;
2006-05-29 05:26:58 +04:00
uint8_t * buf = ops - > oobbuf ;
size_t len = ops - > len ;
2011-08-31 05:45:40 +04:00
BUG_ON ( ops - > mode ! = MTD_OPS_PLACE_OOB ) ;
2006-05-29 05:26:58 +04:00
ofs + = ops - > ooboffs ;
2005-04-17 02:20:36 +04:00
DoC_CheckASIC ( docptr ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( ( DOC_FLASH_CE | DOC_FLASH_WP ) , docptr , Mplus_FlashSelect ) ;
/* disable the ECC engine */
WriteDOC ( DOC_ECC_RESET , docptr , Mplus_ECCConf ) ;
DoC_WaitReady ( docptr ) ;
/* Maximum of 16 bytes in the OOB region, so limit read to that */
if ( len > 16 )
len = 16 ;
got = 0 ;
want = len ;
for ( i = 0 ; ( ( i < 3 ) & & ( want > 0 ) ) ; i + + ) {
/* Figure out which region we are accessing... */
fofs = ofs ;
base = ofs & 0xf ;
if ( ! this - > interleave ) {
DoC_Command ( docptr , NAND_CMD_READOOB , 0 ) ;
size = 16 - base ;
} else if ( base < 6 ) {
DoC_Command ( docptr , DoC_GetECCOffset ( mtd , & fofs ) , 0 ) ;
size = 6 - base ;
} else if ( base < 8 ) {
DoC_Command ( docptr , DoC_GetFlagsOffset ( mtd , & fofs ) , 0 ) ;
size = 8 - base ;
} else {
DoC_Command ( docptr , DoC_GetHdrOffset ( mtd , & fofs ) , 0 ) ;
size = 16 - base ;
}
if ( size > want )
size = want ;
/* Issue read command */
DoC_Address ( this , 3 , fofs , 0 , 0x00 ) ;
WriteDOC ( 0 , docptr , Mplus_FlashControl ) ;
DoC_WaitReady ( docptr ) ;
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
MemReadDOC ( docptr , & buf [ got ] , size - 2 ) ;
buf [ got + size - 2 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
buf [ got + size - 1 ] = ReadDOC ( docptr , Mplus_LastDataRead ) ;
ofs + = size ;
got + = size ;
want - = size ;
}
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
2006-05-29 05:26:58 +04:00
ops - > retlen = len ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-05-29 05:26:58 +04:00
static int doc_write_oob ( struct mtd_info * mtd , loff_t ofs ,
struct mtd_oob_ops * ops )
2005-04-17 02:20:36 +04:00
{
volatile char dummy ;
loff_t fofs , base ;
struct DiskOnChip * this = mtd - > priv ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ ofs > > this - > chipshift ] ;
size_t i , size , got , want ;
int ret = 0 ;
2006-05-29 05:26:58 +04:00
uint8_t * buf = ops - > oobbuf ;
size_t len = ops - > len ;
2011-08-31 05:45:40 +04:00
BUG_ON ( ops - > mode ! = MTD_OPS_PLACE_OOB ) ;
2006-05-29 05:26:58 +04:00
ofs + = ops - > ooboffs ;
2005-04-17 02:20:36 +04:00
DoC_CheckASIC ( docptr ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( DOC_FLASH_CE , docptr , Mplus_FlashSelect ) ;
/* Maximum of 16 bytes in the OOB region, so limit write to that */
if ( len > 16 )
len = 16 ;
got = 0 ;
want = len ;
for ( i = 0 ; ( ( i < 3 ) & & ( want > 0 ) ) ; i + + ) {
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_RESET , 0 ) ;
DoC_WaitReady ( docptr ) ;
/* Figure out which region we are accessing... */
fofs = ofs ;
base = ofs & 0x0f ;
if ( ! this - > interleave ) {
WriteDOC ( NAND_CMD_READOOB , docptr , Mplus_FlashCmd ) ;
size = 16 - base ;
} else if ( base < 6 ) {
WriteDOC ( DoC_GetECCOffset ( mtd , & fofs ) , docptr , Mplus_FlashCmd ) ;
size = 6 - base ;
} else if ( base < 8 ) {
WriteDOC ( DoC_GetFlagsOffset ( mtd , & fofs ) , docptr , Mplus_FlashCmd ) ;
size = 8 - base ;
} else {
WriteDOC ( DoC_GetHdrOffset ( mtd , & fofs ) , docptr , Mplus_FlashCmd ) ;
size = 16 - base ;
}
if ( size > want )
size = want ;
/* Issue the Serial Data In command to initial the Page Program process */
DoC_Command ( docptr , NAND_CMD_SEQIN , 0x00 ) ;
DoC_Address ( this , 3 , fofs , 0 , 0x00 ) ;
/* Disable the ECC engine */
WriteDOC ( DOC_ECC_RESET , docptr , Mplus_ECCConf ) ;
/* Write the data via the internal pipeline through CDSN IO
register , see Pipelined Write Operations 11.2 */
MemWriteDOC ( docptr , ( unsigned char * ) & buf [ got ] , size ) ;
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
WriteDOC ( 0x00 , docptr , Mplus_WritePipeTerm ) ;
/* Commit the Page Program command and wait for ready
see Software Requirement 11.4 item 1. */
DoC_Command ( docptr , NAND_CMD_PAGEPROG , 0x00 ) ;
DoC_WaitReady ( docptr ) ;
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5. */
DoC_Command ( docptr , NAND_CMD_STATUS , 0x00 ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
DoC_Delay ( docptr , 2 ) ;
if ( ( dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ) & 1 ) {
printk ( " MTD: Error 0x%x programming oob at 0x%x \n " ,
dummy , ( int ) ofs ) ;
/* FIXME: implement Bad Block Replacement */
2006-05-29 05:26:58 +04:00
ops - > retlen = 0 ;
2005-04-17 02:20:36 +04:00
ret = - EIO ;
}
dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ;
ofs + = size ;
got + = size ;
want - = size ;
}
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
2006-05-29 05:26:58 +04:00
ops - > retlen = len ;
2005-04-17 02:20:36 +04:00
return ret ;
}
int doc_erase ( struct mtd_info * mtd , struct erase_info * instr )
{
volatile char dummy ;
struct DiskOnChip * this = mtd - > priv ;
__u32 ofs = instr - > addr ;
__u32 len = instr - > len ;
void __iomem * docptr = this - > virtadr ;
struct Nand * mychip = & this - > chips [ ofs > > this - > chipshift ] ;
DoC_CheckASIC ( docptr ) ;
2005-11-07 14:15:40 +03:00
if ( len ! = mtd - > erasesize )
2005-04-17 02:20:36 +04:00
printk ( KERN_WARNING " MTD: Erase not right size (%x != %x)n " ,
len , mtd - > erasesize ) ;
/* Find the chip which is to be used and select it */
if ( this - > curfloor ! = mychip - > floor ) {
DoC_SelectFloor ( docptr , mychip - > floor ) ;
DoC_SelectChip ( docptr , mychip - > chip ) ;
} else if ( this - > curchip ! = mychip - > chip ) {
DoC_SelectChip ( docptr , mychip - > chip ) ;
}
this - > curfloor = mychip - > floor ;
this - > curchip = mychip - > chip ;
instr - > state = MTD_ERASE_PENDING ;
/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
WriteDOC ( DOC_FLASH_CE , docptr , Mplus_FlashSelect ) ;
DoC_Command ( docptr , NAND_CMD_RESET , 0x00 ) ;
DoC_WaitReady ( docptr ) ;
DoC_Command ( docptr , NAND_CMD_ERASE1 , 0 ) ;
DoC_Address ( this , 2 , ofs , 0 , 0x00 ) ;
DoC_Command ( docptr , NAND_CMD_ERASE2 , 0 ) ;
DoC_WaitReady ( docptr ) ;
instr - > state = MTD_ERASING ;
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5. */
DoC_Command ( docptr , NAND_CMD_STATUS , 0 ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
dummy = ReadDOC ( docptr , Mplus_ReadPipeInit ) ;
if ( ( dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ) & 1 ) {
printk ( " MTD: Error 0x%x erasing at 0x%x \n " , dummy , ofs ) ;
/* FIXME: implement Bad Block Replacement (in nftl.c ??) */
instr - > state = MTD_ERASE_FAILED ;
} else {
instr - > state = MTD_ERASE_DONE ;
}
dummy = ReadDOC ( docptr , Mplus_LastDataRead ) ;
/* Disable flash internally */
WriteDOC ( 0 , docptr , Mplus_FlashSelect ) ;
mtd_erase_callback ( instr ) ;
return 0 ;
}
/****************************************************************************
*
* Module stuff
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void __exit cleanup_doc2001plus ( void )
{
struct mtd_info * mtd ;
struct DiskOnChip * this ;
while ( ( mtd = docmilpluslist ) ) {
this = mtd - > priv ;
docmilpluslist = this - > nextdoc ;
2005-11-07 14:15:40 +03:00
2011-05-23 13:23:40 +04:00
mtd_device_unregister ( mtd ) ;
2005-11-07 14:15:40 +03:00
2005-04-17 02:20:36 +04:00
iounmap ( this - > virtadr ) ;
kfree ( this - > chips ) ;
kfree ( mtd ) ;
}
}
module_exit ( cleanup_doc2001plus ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Greg Ungerer <gerg@snapgear.com> et al. " ) ;
MODULE_DESCRIPTION ( " Driver for DiskOnChip Millennium Plus " ) ;