/*
  Copyright (C) 2003  Chin Zhou (czhou@ati.com), ATI Technologies Inc.

  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; version 2
  of the License, June 1991.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
  MA  02111-1307, USA.
*/

/*
 *  drivers/mtd/nand/stw_flash.c
 *
 *  Descriptions:
 *
 *  A hardware specific device (low-level) driver for the NAND 
 * flash device which is used on the STWX225 board.  
 * This driver is based on ATI's specific NAND/PFLASH controller 
 * state machine.  The NAND flash device is Samsung 
 * K9F2808U0B - YVB0, two devices. 
 *
 */

/*
 * Support for NAND flash modules on ATI Xilleon 225 Set Top Wonder
 *
 */

#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/ioport.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>

#include <asm/io.h>
#include <asm/ati/xilleon.h>

#ifdef CONFIG_MTD_NAND_ECC
#include <linux/mtd/nand_ecc.h>
#endif

#include "stw_flash.h"

/* STW NAND flash related */

uint32_t  pflash_ready = 0;
uint32_t  pflash_mask;
uint32_t  ibt_found;
int32_t   ibt_cs = CS_INVALID;
int32_t   ibt_block0 = IBT_UNKNOWN_BLOCK;
int32_t   ibt_block1 = IBT_UNKNOWN_BLOCK;

char blockbuffer[16384];

union
{
  char array[16384];
  IBT_DATA data;
} 
ibt;


/************************************************************************/

/*
  STWX225 NAND flash low-level device routines, ported from the 
  monitor program, and used for test and debug purposes.
*/

/************************************************************************/
/*
   Supported command for the STWX225 NAND device are:
       NAND_CMD_READ0
       NAND_CMD_READ1

       NAND_CMD_READOOB    0x50
       NAND_CMD_WRITEOOB   
       
       NAND_CMD_PAGEPROG   0x10
       NAND_CMD_ERASE1     0x60

       NAND_CMD_STATUS     0x70
       NAND_CMD_READID     0x90

       NAND_CMD_RESET      0xff

  Also supported command for software ECC:

  Comments on the ATI's pflash state-machine:
  Need to clear read_cycle and write_cycle bits for the read/write 
  transition; transitions between the main and OOB areas, 
  to set/reset SPARE_EN of the PFLASH_CNTL.

  Resiger accesses always need 32-bit Dword, while PCU NAND flash access 
  can be in Byte/word/dword.

  READID:
  STATUS:
    - set PFLASH_BE_ADDR  because of 2 device CS;
    - writeREG32 (command, PFLASH_CNTL);
    - readREG32 (PFLASH_ID_STAT_DATA);   (for STATUS and READID);
 

  RESET:
  ERASE1:

    - set PFLASH_BE_ADDR  because of 2 device CS;
    - writeREG32 (command, PFLASH_CNTL);

  READ0/READ1:
    - readPCU8/16/32(PCU_NAND_MAIN_ADDR);

  READOOB:
    - writeREG32 ( cmmand, PFLASH_CNTL )  (set the SPARE_EN);
    - readPCU8/16/32 (PCU_NAND_OOD_ADDR);

  WRITEOOB:
    - writeREG32 ( cmmand, PFLASH_CNTL )  (set the SPARE_EN);
    - writePCU8/16/32 (PCU_NAND_OOD_ADDR, byte/word/dword0;

  PAGEPROG:
    - writePCU8/16/32 (PCU_NAND_MAIN_ADDR, byte/word/dword);
*/
/****************************************************************************/

int pflash_init(void)
{
  uint32_t r;
  uint32_t size=0;
  uint32_t size0=0;
  uint32_t size1=0;

    
  /* detect if pflash exists */

  pflash_mask = 0;
  pflash_ready = (pflash_clear_state_machine(10) == 0);
   
  if(pflash_ready) {
    size1 = pflash_get_chip_size(CS1);
    if (size1 != 0) {
      size = size1;
      ibt_cs = CS1;
      pflash_mask |= PFLASH0_MASK;
      printk ("PFlash:INIT:CS1 size %dMb @ %x\n", size, pflash_get_chip_start_address (CS1));
    }
#ifndef CONFIG_ROKU_DESCHUTES /* Roku - Deschutes has Nand on CS1 only */
    size0 = pflash_get_chip_size(CS0);
    if (size0 != 0) {
      size = size0;
      ibt_cs = CS0;
      pflash_mask |= PFLASH1_MASK;
      printk ("PFlash:INIT:CS0 size %d\n", size);
    }
#endif	
    switch(size) {
    case 64:
      r = GETREG_REGMM32(PFLASH_CNTL);
      MODIFYFLD(r, PFLASH_CNTL, PF32MBIT_EN, 0);
      MODIFYFLD(r, PFLASH_CNTL, PF64MBIT_EN, 1);
      MODIFYFLD(r, PFLASH_CNTL, PF128MBIT_EN, 0); 
      SETREG_REGMM32(PFLASH_CNTL, r); 
      break;

    case 128:
      r = GETREG_REGMM32(PFLASH_CNTL);
      //printk("PFLASH_CNTL before MODIFYFLD: %x  ", r);
      MODIFYFLD(r, PFLASH_CNTL, PF32MBIT_EN, 0);
      MODIFYFLD(r, PFLASH_CNTL, PF64MBIT_EN, 0);
      MODIFYFLD(r, PFLASH_CNTL, PF128MBIT_EN, 1); 
      //printk("after MODIFYFLD: %x\n", r);
      SETREG_REGMM32(PFLASH_CNTL, r); 
      break;
    }
    if (pflash_find_IBT()) {
      printk(KERN_INFO "WARNING - pflash_init: pflash is disabled.\n");
      return 0;
    }
  } else {
    printk ("PFLASH: not ready/exist..\n");
  }

  return(pflash_mask);
}


/****************************************************************************/
/* 
   Returns total block numbers
*/ 
uint32_t pflash_get_block_numbers(void)
{
  uint32_t total_blk_num=0;

  if(pflash_ready) {
    if (GETFLD_REGMM32(PFLASH_CNTL,PF32MBIT_EN)) {
      total_blk_num = 512;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF64MBIT_EN)) {
      total_blk_num = 1024;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF128MBIT_EN)) {
      total_blk_num = 1024;
    }
  }


  return (total_blk_num);
}


/****************************************************************************/

uint32_t pflash_get_page_numbers(void)
{
  uint32_t total_page_num=0;

  if(pflash_ready) {
    if (GETFLD_REGMM32(PFLASH_CNTL,PF32MBIT_EN)) {
      total_page_num = 16;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF64MBIT_EN)) {
      total_page_num = 16;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF128MBIT_EN)) {
      total_page_num = 32;
    }
  }

  return (total_page_num);
}


/****************************************************************************/

uint32_t pflash_get_block_size(void)    /* in Bytes */
{
  uint32_t block_size=0;

  if(pflash_ready) {
    if (GETFLD_REGMM32(PFLASH_CNTL,PF32MBIT_EN)) {
      block_size = 8192;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF64MBIT_EN)) {
      block_size = 8192;
    }
    else if (GETFLD_REGMM32(PFLASH_CNTL,PF128MBIT_EN)) {
      block_size = 16384;
    }
  }
  return (block_size);
}


/****************************************************************************/

uint32_t pflash_get_chip_start_address(CHIP_SEL chipsel)
{
  uint32_t address=0x00000000;


  if(pflash_ready) {
    if(chipsel == CS1) {
      if (GETFLD_REGMM32(PFLASH_CNTL,PF32MBIT_EN)) {
	address = 0x00400000;
      }
      else if (GETFLD_REGMM32(PFLASH_CNTL,PF64MBIT_EN)) {
	address = 0x00800000;
      }
      else if (GETFLD_REGMM32(PFLASH_CNTL,PF128MBIT_EN)) {
	address = 0x01000000;
      }
      /* some other device is selected in STRAPS_VALUE register */
      else { 
	printk("ERROR: PFLASH (NAND) boot ROM device not found!\n");
	address = 0xFFFFFFFF;//error
      }
    }
  }
  return (address);
}


/****************************************************************************/
/* 
   This is for command: BLOCK_ERASE, RESET, STATUS, and READID.
*/
   
uint32_t pflash_get_block_start_address( CHIP_SEL chipsel, 
					 uint32_t block )
{    
  uint32_t block_start_addr=0;
  uint32_t nbr_bytes;
  uint32_t chip_start_addr;

  if(pflash_ready) {
    nbr_bytes = pflash_get_block_size();
    chip_start_addr = pflash_get_chip_start_address(chipsel);
    block_start_addr = chip_start_addr + (block * nbr_bytes); 

    DEBUG(MTD_DEBUG_LEVEL3,
	  "chip_start_addr: %x  block_start_addr: %x\n",
	  chip_start_addr, block_start_addr);

  }

  return (block_start_addr);
}


/************************************************************************/

int pflash_chip_erase(CHIP_SEL chipsel, uint32_t timeout)
{
    uint32_t block;
    uint32_t blocks = pflash_get_block_numbers();
    int      error;

    printk ("Erasing CHIP\n");
    if(pflash_find_IBT() == 0) {
      for(block=0; block<blocks; block++) {
	if(pflash_block_invalid(chipsel, block, 0) == 0) {
	  printk(".");
	  if((error = pflash_block_erase(chipsel, block, timeout, 0))) 
	    printk ("PFLASH:block %d Erase failed\n", block);
	    return error;
	}
	else {
	  printk("B");
	}
      }
      printk("\n");
    }

    return 0;

}


/************************************************************************/

int pflash_page_write(CHIP_SEL chipsel, 
		    uint32_t   block, 
		    uint32_t   page, 
		    char      *buffer, 
		    uint32_t   flags)
{
    uint32_t offset;
    uint32_t blocksize;
    uint32_t chipoffset;
    uint32_t pageoffset;
    uint32_t blockoffset;
    uint32_t status=0;
#ifdef CONFIG_MTD_NAND_ECC
    uint8_t  calc_ecc_lower[3];
    uint8_t  calc_ecc_upper[3];
#endif

    if(pflash_ready) {
      if(!(flags & PFF_ALLOW_IBT)) {
	if(pflash_find_IBT() == 0) {
	  if(pflash_block_invalid(chipsel, block, flags)) {
	    printk(KERN_ERR 
		   "page_write: attempt to write to an invalid block, not allowed\n");
	    return -1;
	  }
	}
      }

#ifdef CONFIG_ROKU_DESCHUTES
      /* xilleon.h has PFLASH_BASE as CS1 base, so don't duplicate it here -- narayan */
      chipoffset = 0;
#else
      chipoffset = pflash_get_chip_start_address(chipsel);
#endif /* CONFIG_ROKU_DESCHUTES */
      blocksize = pflash_get_block_size();
      blockoffset = block * blocksize;
      pageoffset = page * PFLASH_PAGE_SIZE;

#ifdef CONFIG_MTD_NAND_ECC
      if(flags & PFF_ECC) {
	nand_calculate_ecc(buffer, calc_ecc_lower);
	nand_calculate_ecc(buffer+(PFLASH_PAGE_SIZE/2), calc_ecc_upper);
      }
#endif

      pflash_clear_state_machine(10);

      for(offset=0; offset<PFLASH_PAGE_SIZE; offset++) {
	SETMEM_PFLASH_8(chipoffset + blockoffset + pageoffset + offset, *buffer++);
      }

      status = pflash_get_status(10);
      pflash_clear_state_machine(10);

#ifdef CONFIG_MTD_NAND_ECC
      if(flags & PFF_ECC) {
	/* update ecc */
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC0_OFFSET, calc_ecc_lower[0]);
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC1_OFFSET, calc_ecc_lower[1]);
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC2_OFFSET, calc_ecc_lower[2]);
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_U256_ECC0_OFFSET, calc_ecc_upper[0]);
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_U256_ECC1_OFFSET, calc_ecc_upper[1]);
	pflash_set_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_U256_ECC2_OFFSET, calc_ecc_upper[2]);
      }
#endif
    }

    return status;
}


/*****************************************************************************/

int pflash_page_verify(CHIP_SEL chipsel, 
                       uint32_t block, 
		       uint32_t page, 
		       char *buffer, 
		       uint32_t flags)
{
  static char pagebuffer[PFLASH_PAGE_SIZE];
  uint32_t offset;
  uint8_t  actual;
  uint8_t  expected;
  int      error;

  if(pflash_ready) {
    if((error = pflash_page_read(chipsel, block, page, pagebuffer, flags))) {
      return error;
    }

    for(offset=0; offset<PFLASH_PAGE_SIZE; offset++) {
      actual = pagebuffer[offset];
      expected = buffer[offset];

      if(actual != expected){
	printk(KERN_ERR
	       "page_verify: chip=%d, block=%d, page=%d, offset=%d\n",
	       chipsel, block, page, offset);
	printk(KERN_ERR
	       "page_verufyL actual=0x%02X, expected=0x%02X\n", 
	       actual, expected);
	return PFLASH_VERIFY_FAILED;
      }
    }
    return 0;
  }
  return -1;

}


/*****************************************************************************/

int pflash_page_read(CHIP_SEL chipsel, 
		     uint32_t block, 
		     uint32_t page, 
		     char    *buffer, 
		     uint32_t flags)
{
  uint32_t offset;
  uint32_t chipoffset;
  uint32_t blockoffset;
  uint32_t pageoffset;
  uint32_t blocksize;
  uint32_t *p = (uint32_t *) buffer;
#ifdef CONFIG_MTD_NAND_ECC
    uint8_t read_ecc[3];
    uint8_t calc_ecc[3];
#endif

    if(pflash_ready){
      blocksize = pflash_get_block_size();
#ifdef CONFIG_ROKU_DESCHUTES
      /* xilleon.h has PFLASH_BASE as CS1 base, so don't duplicate it here -- narayan */
      chipoffset = 0;
#else
      chipoffset = pflash_get_chip_start_address(chipsel);
#endif /* CONFIG_ROKU_DESCHUTES */
      blockoffset = block * blocksize;
      pageoffset = page * PFLASH_PAGE_SIZE;

      pflash_clear_state_machine(10);

      for(offset=0; offset<PFLASH_PAGE_SIZE; offset+=4){
	*p++ = GETMEM_PFLASH_32(chipoffset + blockoffset + pageoffset + offset);
      }

      pflash_clear_state_machine(10);

#ifdef CONFIG_MTD_NAND_ECC
      if(flags & PFF_ECC) {
	/* lower half of page */
	nand_calculate_ecc(buffer, calc_ecc);

	pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC0_OFFSET, &read_ecc[0]);
	pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC1_OFFSET, &read_ecc[1]);
	pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
			SPARE_AREA_L256_ECC2_OFFSET, &read_ecc[2]);

	/*
	  printk("\n[L] read_ecc = %02X %02X %02X    calc_ecc = %02X %02X %02X\n", 
	  read_ecc[0], read_ecc[1], read_ecc[2], 
	  calc_ecc[0], calc_ecc[1], calc_ecc[2]);
	*/

	if(nand_correct_data(buffer, read_ecc, calc_ecc) == -1) {
	  printk(KERN_ERR 
		 "page_read: ECC correction: chip=%d, block=%d, page=%d (lower)\n", 
		 chipsel, block, page);

	return -1;
      }

      /* upper half of page */
      nand_calculate_ecc(buffer+(PFLASH_PAGE_SIZE/2), calc_ecc);

      pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
		      SPARE_AREA_U256_ECC0_OFFSET, &read_ecc[0]);
      pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
		      SPARE_AREA_U256_ECC1_OFFSET, &read_ecc[1]);
      pflash_get_spare8(chipoffset + blockoffset + pageoffset + 
		      SPARE_AREA_U256_ECC2_OFFSET, &read_ecc[2]);

      /*
	printk("[U] read_ecc = %02X %02X %02X    calc_ecc = %02X %02X %02X\n", 
	read_ecc[0], read_ecc[1], read_ecc[2], 
	calc_ecc[0], calc_ecc[1], calc_ecc[2]);
      */

      if(nand_correct_data(buffer+(PFLASH_PAGE_SIZE/2), read_ecc, calc_ecc) == -1) {
	printk(KERN_ERR 
	       "page_read: pflash ECC correction: chip=%d, block=%d, page=%d (upper)\n", 
	       chipsel, block, page);

	return -1;
      }
    }
#endif

    return 0;
}
    return -1;

}


/*****************************************************************************/

int pflash_block_erase( CHIP_SEL chipsel, 
			uint32_t block, 
			uint32_t timeout,
			uint32_t flags )
{

  uint32_t status=PFLASH_OK;
  uint32_t val;
  uint32_t start_offset;

  if(pflash_ready) {
    if(pflash_find_IBT() == 0) {
      if(pflash_block_invalid(chipsel, block, flags)) {
	printk(KERN_ERR "block_erase: attempt to erase an invalid block\n");
	return(PFLASH_BLOCK_ERASE_NOT_DONE);
      }
      else {
	pflash_clear_state_machine(10);
	start_offset = pflash_get_block_start_address(chipsel, block);
	val = start_offset >>PFLASH_BE_ADDR__BLOCK_ERASE_ADDR__SHIFT;
	/* set the address */
	SETFLD_REGMM32(PFLASH_BE_ADDR,BLOCK_ERASE_ADDR, val);  
	/* trigger the block erase */
	SETFLD_REGMM32(PFLASH_CNTL, CMD_TRIG, PFLASH_CMD_BLOCK_ERASE); 
	status = pflash_wait_trigger(10);

	if(status == PFLASH_OK) {
	  while(GETFLD_REGMM32(PFLASH_STATUS, BLOCK_ERASE_DONE) == 0) {
	    udelay(1000);
	    if(--timeout == 0) {
	      printk("block_erase: timeout waiting for BLOCK_ERASE_DONE\n");
	      return(PFLASH_BLOCK_ERASE_NOT_DONE);
	    }
	  }
	  status = pflash_get_status(10);
	  SETFLD_REGMM32(PFLASH_STATUS, BLOCK_ERASE_DONE, 0x0);
	  pflash_clear_state_machine(10);
	}
      }
    }
  }

  return(status);

}


/****************************************************************************/

int pflash_block_erase_verify( CHIP_SEL chipsel, 
			       uint32_t block )
{
  uint32_t status=0;
  uint32_t offset;
  uint32_t blocksize;
  uint32_t flashbase;
  uint32_t blockoffset;
  uint8_t actual;

  if(pflash_ready) {
#ifdef CONFIG_ROKU_DESCHUTES
      /* xilleon.h has PFLASH_BASE as CS1 base, so don't duplicate it here -- narayan */
    flashbase = 0; /* IN the GETMEM_PFLASH_8 includes PFLASH_BASE -- */
#else
    flashbase = pflash_get_chip_start_address(chipsel);
#endif /* CONFIG_ROKU_DESCHUTES */
    blocksize = pflash_get_block_size();
    blockoffset = block * blocksize;

    pflash_clear_state_machine(10);

    for(offset=0; offset<blocksize; offset++) {
      actual = GETMEM_PFLASH_8(flashbase + blockoffset + offset);

      if(actual != 0xFF) {
	printk("block_erase_verify: block=%d, offset=%d, actual=0x%02X, expected=0xFF\n", 
	       block, offset, actual);
	status = PFLASH_VERIFY_FAILED;
	break;
      }
    }
    pflash_clear_state_machine(10);
  }
  return status;
}

/*****************************************************************************/


int pflash_block_read( CHIP_SEL chipsel, 
		       uint32_t block, 
		       char *buffer,
		       uint32_t flags)
{

  uint32_t blocksize;
  uint32_t page;

  if(pflash_ready) {
    blocksize = pflash_get_block_size();

    for(page=0; page<(blocksize/PFLASH_PAGE_SIZE); page++) {
      if(pflash_page_read(chipsel, block, page, buffer, flags)) {
	return -1;
      }

      buffer += PFLASH_PAGE_SIZE;
    }

    return 0;
  }

  return -1;

}


/*****************************************************************************/


int pflash_block_write( CHIP_SEL chipsel, 
			uint32_t block, 
			char *buffer,
			uint32_t flags )
{

  uint32_t blocksize;
  uint32_t page;

  if(pflash_ready){
    if(!(flags & PFF_ALLOW_IBT)) {
      if(pflash_find_IBT() == 0) {
	if(pflash_block_invalid(chipsel, block, flags)) {
	  printk(KERN_ERR 
		 "block_write: attempt to write to an invalid block, not allowed\n");
	  return -1;
	}
      }
    }

    blocksize = pflash_get_block_size();

    for(page=0; page<(blocksize/PFLASH_PAGE_SIZE); page++) {
      if(pflash_page_write(chipsel, block, page, buffer, flags)) {
	return -1;
      }
      buffer += PFLASH_PAGE_SIZE;
    }

    return 0;
  }

  return -1;

}


/*****************************************************************************/

int pflash_block_verify( CHIP_SEL chipsel, 
			 uint32_t block, 
			 char *buffer,
			 uint32_t flags)
{

  uint32_t blocksize;
  uint32_t page;
  int      error;

  if(pflash_ready) {
    blocksize = pflash_get_block_size();

    for(page=0; page<(blocksize/PFLASH_PAGE_SIZE); page++) {
      if((error = pflash_page_verify(chipsel, block, page, buffer, flags))) {
	return error;
      }

      buffer += PFLASH_PAGE_SIZE;
    }

    return 0;
  }

  return -1;

}


/*****************************************************************************/

int pflash_wait_trigger(uint32_t timeout)
{

  if(pflash_ready) {
    while(GETFLD_REGMM32(PFLASH_CNTL, CMD_TRIG) != 0) {
      udelay(1000);

      if(--timeout == 0) {
	printk("pflashWaitCMDTriggerDone: timeout waiting for CMD_TRIG\n");
	return(PFLASH_CMD_TRIG_BUSY);
      }
    }
  }
  return PFLASH_OK;
}


/*****************************************************************************/

int pflash_get_status(uint32_t timeout)
{
  uint32_t status=PFLASH_OK;

  if(pflash_ready) {
    while(!(GETFLD_REGMM32(PFLASH_STATUS, STATUS_RD_DONE))) {
      udelay(1000);

      if(--timeout == 0) {
	printk("pflashGetStatus: timeout waiting for STATUS_RD_DONE\n");

	return PFLASH_TIME_OUT;
      }
    }

    //if( status = GETFLD_REGMM32(PFLASH_STATUS, STATUS_ERROR) ) {
    if( GETFLD_REGMM32(PFLASH_STATUS, STATUS_ERROR) ) {
      SETFLD_REGMM32(PFLASH_STATUS, STATUS_ERROR, 0x0);
      status = PFLASH_STATUS_ERROR;
    }
    /* reset the done flag */
    SETFLD_REGMM32(PFLASH_STATUS, STATUS_RD_DONE, 0x0); 

    pflash_clear_state_machine(10);
  }
  return status;
}


/*****************************************************************************/

int pflash_is_read_cycle(uint32_t timeout )
{
  
  return (GETFLD_REGMM32(PFLASH_CNTL, READ_CYCLE));

}


/*****************************************************************************/

int pflash_is_write_cycle(uint32_t timeout )
{

  return (GETFLD_REGMM32(PFLASH_CNTL, WRITE_CYCLE));
}


/*****************************************************************************/

int pflash_clear_state_machine(uint32_t timeout)
{
    uint32_t data;

    /* Wait for SEPROM_BUSY to go low */
    while(GETFLD_REGMM32(SFLASH_CNTL2_STATUS, SEPROM_BUSY)) {
      udelay(1000);

      if(--timeout == 0)
        {
	  printk(KERN_INFO "\nERROR: timeout waiting for SEPROM_BUSY\n");
	  return(PFLASH_SEPROM_BUSY);
        }
    }

    /* Clear The Read/Write cycle of state machine */
    data = GETREG_REGMM32(PFLASH_CNTL);
    MODIFYFLD(data, PFLASH_CNTL, CMD_TRIG,    0);
    MODIFYFLD(data, PFLASH_CNTL, READ_CYCLE,  0);
    MODIFYFLD(data, PFLASH_CNTL, WRITE_CYCLE, 0);
    MODIFYFLD(data, PFLASH_CNTL, SPARE_EN, 0);
    SETREG_REGMM32(PFLASH_CNTL, data);

    /* Clear the status */
    SETREG_REGMM32(PFLASH_STATUS, 0);

    return 0;
}

/*****************************************************************************/

int pflash_clear_state_machine_oob_enable(uint32_t timeout)
{
  uint32_t data;

  /* Wait for SEPROM_BUSY to go low */
  while(GETFLD_REGMM32(SFLASH_CNTL2_STATUS, SEPROM_BUSY)) {
    udelay(1000);

    if(--timeout == 0) {
      printk(KERN_INFO "\nERROR: timeout waiting for SEPROM_BUSY\n");
      return(PFLASH_SEPROM_BUSY);
    }
  }

  /* Clear The Read/Write cycle of state machine */
  data = GETREG_REGMM32(PFLASH_CNTL);
  MODIFYFLD(data, PFLASH_CNTL, CMD_TRIG,    0);
  MODIFYFLD(data, PFLASH_CNTL, READ_CYCLE,  0);
  MODIFYFLD(data, PFLASH_CNTL, WRITE_CYCLE, 0);
  MODIFYFLD(data, PFLASH_CNTL, SPARE_EN, 1);
  SETREG_REGMM32(PFLASH_CNTL, data);

  /* Clear the status */
  SETREG_REGMM32(PFLASH_STATUS, 0);

  return 0;
}


/*****************************************************************************/

uint32_t pflash_get_chipID( CHIP_SEL chipsel, 
			    uint32_t timeout )
{
  uint32_t status = PFLASH_OK;
  uint32_t regVal = 0;
  uint32_t start_address;
  uint32_t id = 0;


  if(pflash_ready) {
    pflash_clear_state_machine(10);

    start_address = pflash_get_chip_start_address(chipsel);
    regVal = start_address >> PFLASH_BE_ADDR__BLOCK_ERASE_ADDR__SHIFT;
    SETFLD_REGMM32(PFLASH_BE_ADDR, BLOCK_ERASE_ADDR, regVal); 
    SETFLD_REGMM32(PFLASH_CNTL, CMD_TRIG, PFLASH_CMD_ID_READ); 
    status = pflash_wait_trigger(10);

    if(status == PFLASH_OK) {    
      while(GETFLD_REGMM32(PFLASH_STATUS, ID_READ_DONE) == 0) {
	udelay(1000);

	if(--timeout == 0) {

	  printk("\nERROR: timeout waiting for ID_READ. \n");

	  return 0;
	}
      }

      id = GETFLD_REGMM32(PFLASH_ID_STAT_DATA, PF_ID_DATA);

      SETFLD_REGMM32(PFLASH_STATUS, ID_READ_DONE, 0x0);

      pflash_clear_state_machine(10);
    }
  }

  return id;

}


/*****************************************************************************/

char *pflash_get_chipID_string(CHIP_SEL chipsel)
{
  uint32_t id;
  static char s[32];

  if(pflash_ready) {
    id = pflash_get_chipID(chipsel, 10);

    switch(id & 0xFF) {
    case SAMSUNG_ID:
      strcpy(s, "Samsung ");

      switch((id & 0xFF00) >> 8) {
      case SAMSUNG_K9F6408Q0C:  strcat(s, "K9F6408Q0");  break;
      case SAMSUNG_K9F6408U0C:  strcat(s, "K9F6408U0");  break;
      case SAMSUNG_K9F2808Q0C:  strcat(s, "K9F2808Q0");  break;
      case SAMSUNG_K9F2808U0C:  strcat(s, "K9F2808U0");  break;
      case SAMSUNG_K9F2816Q0C:  strcat(s, "K9F2816Q0");  break;
      case SAMSUNG_K9F2816U0C:  strcat(s, "K9F2816U0");  break;
      case SAMSUNG_K9F5608Q0B:  strcat(s, "K9F5608Q0");  break;
      case SAMSUNG_K9F5608U0B:  strcat(s, "K9F5608U0");  break;
      case SAMSUNG_K9F5616Q0B:  strcat(s, "K9F5616Q0");  break;
      case SAMSUNG_K9F5616U0B:  strcat(s, "K9F5616U0");  break;
      case SAMSUNG_K9F1208Q0A:  strcat(s, "K9F1208Q0");  break;
      case SAMSUNG_K9F1208U0A:  strcat(s, "K9F1208U0");  break;
      case SAMSUNG_K9F1216Q0A:  strcat(s, "K9F1216Q0");  break;
      case SAMSUNG_K9F1216U0A:  strcat(s, "K9F1216U0");  break;
      default:                  strcat(s, "unknown");    break;
      }
      break;

    case TOSHIBA_ID:
      strcpy(s, "Toshiba ");

      switch((id & 0xFF00) >> 8) {

      case TOSHIBA_TC58V16BFT:  strcat(s, "TC58V16FT");  break;
      case TOSHIBA_TC58V32AFT:  strcat(s, "TC58V32FT");  break;
      case TOSHIBA_TC58V64AFT:  strcat(s, "TC58V64FT");  break;
      case TOSHIBA_TC58128FT :  strcat(s, "TC58128FT");  break;
      case TOSHIBA_TC58256FT :  strcat(s, "TC58256FT");  break;
      case TOSHIBA_TH58512FT :  strcat(s, "TH58512FT");  break;
      case TOSHIBA_TH58100FT :  strcat(s, "TH58100FT");  break;
      default:                  strcat(s, "unknown");    break;
      }
      break;

    case AMD_ID:
      strcpy(s, "AMD ");

      switch((id & 0xFF00) >> 8) {

      case AMD_AM30LV0064D:     strcat(s, "AM30LV0064"); break;
      default:                  strcat(s, "unknown");    break;
      }
      break;

    default:
      strcpy(s, "Unknown");
      break;
    }
  }
  else {
    strcpy(s, "Not found");
  }

  return s;

}


/*****************************************************************************/

int pflash_get_chip_size(CHIP_SEL chipsel)
{
  uint32_t id;
  int size;

  id = pflash_get_chipID(chipsel, 10);
  size = 0;

  switch(id & 0xFF) {
  case SAMSUNG_ID:
    switch((id & 0xFF00) >> 8) {
    case SAMSUNG_K9F6408Q0C:  size =   64;  break;
    case SAMSUNG_K9F6408U0C:  size =   64;  break;
    case SAMSUNG_K9F2808Q0C:  size =  128;  break;
    case SAMSUNG_K9F2808U0C:  size =  128;  break;
    case SAMSUNG_K9F2816Q0C:  size =  128;  break;
    case SAMSUNG_K9F2816U0C:  size =  128;  break;
    case SAMSUNG_K9F5608Q0B:  size =  256;  break;
    case SAMSUNG_K9F5608U0B:  size =  256;  break;
    case SAMSUNG_K9F5616Q0B:  size =  256;  break;
    case SAMSUNG_K9F5616U0B:  size =  256;  break;
    case SAMSUNG_K9F1208Q0A:  size =  512;  break;
    case SAMSUNG_K9F1208U0A:  size =  512;  break;
    case SAMSUNG_K9F1216Q0A:  size =  512;  break;
    case SAMSUNG_K9F1216U0A:  size =  512;  break;
    }
    break;

  case TOSHIBA_ID:
    switch((id & 0xFF00) >> 8) {

    case TOSHIBA_TC58V16BFT:  size =   16;  break;
    case TOSHIBA_TC58V32AFT:  size =   32;  break;
    case TOSHIBA_TC58V64AFT:  size =   64;  break;
    case TOSHIBA_TC58128FT :  size =  128;  break;
    case TOSHIBA_TC58256FT :  size =  256;  break;
    case TOSHIBA_TH58512FT :  size =  512;  break;
    case TOSHIBA_TH58100FT :  size = 1024;  break;
    }
    break;

  case AMD_ID:
    switch((id & 0xFF00) >> 8) {

    case AMD_AM30LV0064D:     size =   64;  break;
    }
    break;
  }
    
  return size;
}

/*****************************************************************************/

int pflash_get_spare8( uint32_t offset, 
		       uint8_t *data )
{

  pflash_clear_state_machine(10);
  SETFLD_REGMM32(PFLASH_CNTL, SPARE_EN, 1);
  *data = PFLASH_READ_8(offset);
  SETFLD_REGMM32(PFLASH_CNTL, SPARE_EN, 0);
  pflash_clear_state_machine(10);
  /* printk(KERN_INFO "pflash_get_spare8 - offset: %x  data: %x\n", 
     offset, *data); */

  return 0;
}


/*****************************************************************************/

int pflash_set_spare8( uint32_t offset, 
		       uint32_t data )
{

  pflash_clear_state_machine(10);
  SETFLD_REGMM32(PFLASH_CNTL, SPARE_EN, 1);
  PFLASH_WRITE_8(offset, data);
  SETFLD_REGMM32(PFLASH_CNTL, SPARE_EN, 0);
  pflash_clear_state_machine(10);
  return 0;

}

/*****************************************************************************/

int pflash_block_invalid( CHIP_SEL chipsel, 
			  uint32_t block,
			  uint32_t flags)
{
  
  if ((ibt_cs == CS0) && (chipsel == CS1))
    block += pflash_get_block_numbers();

  if (ibt.data.table[block] == IBT_GOOD_BLOCK) return 0;

  if (flags & PFF_ALLOW_IBT) {
    if (ibt.data.table[block] == IBT_TABLE_0_BLOCK ) return 0;
    if (ibt.data.table[block] == IBT_TABLE_1_BLOCK ) return 0;
  }


  if (flags & PFF_ALLOW_MB) {
    if (ibt.data.table[block] == IBT_MONITOR_BOOT_BLOCK ) return 0;
  }

  return 1;
  
}


/*****************************************************************************/

int pflash_set_block_invalid(CHIP_SEL chipsel, 
			     uint32_t block)
{
  if ((ibt_cs == CS0) && (chipsel == CS1) )
    block += pflash_get_block_numbers();

  if(ibt.data.table[block] == IBT_GOOD_BLOCK) {
    ibt.data.table[block] = IBT_DETECTED_BAD_BLOCK;
  }

  return pflash_write_IBTs();
}


/*****************************************************************************/

int pflash_set_autoexec(char *command, uint32_t *rblock)
{
    uint32_t block;
    uint32_t total_blocks;

    total_blocks = pflash_get_block_numbers();

    /* search for an existing monitor boot block */
    if(pflash_get_autoexec(&block) == 0) {
      printk(KERN_INFO "Creating autoexec block...\n");
      
      /* create a new monitor boot block in the first available good block */
      for(block=0; block<total_blocks; block++) {
	if(pflash_block_invalid(ibt_cs, block, PFF_ALLOW_MB) == 0) {
	  ibt.data.table[block] = IBT_MONITOR_BOOT_BLOCK;
	  if(pflash_write_IBTs()) {
	    printk(KERN_ERR "set_autoexec: unable to create autoexec block\n");
	    return -1;
	  }

	  break;
	}
      }
    }
    else {
      printk(KERN_INFO "Updating autoexec block...\n");
    }

    if (block == total_blocks) {
        printk(KERN_ERR "set_autoexec: unable to create autoexec block\n");
    }
    else {
      if(pflash_block_erase(ibt_cs, block, 10, PFF_ALLOW_MB) == 0) {
	if(pflash_block_erase_verify(ibt_cs, block) == 0) {
	  memset(blockbuffer, 0, sizeof(blockbuffer));
	  *((uint32_t *) blockbuffer) = MONITORBOOT_MAGIC;
	  strncpy(&blockbuffer[4], command, sizeof(blockbuffer)-5);
	  if (pflash_block_write(ibt_cs, block, 
				 blockbuffer, PFF_ECC | PFF_ALLOW_MB) == 0) {
	    if(pflash_block_verify(ibt_cs, block, blockbuffer, PFF_ECC) == 0) {
	      *rblock = block;

	      return 0;
	    }
	    else {
	      printk(KERN_ERR "set_autoExec: autoexec block verify error\n");
	    }
	  }
	  else {
	    printk(KERN_ERR "set_autoexec: unable to write autoexec block\n");
	  }
	}
	else {
	  printk(KERN_ERR "set_autoexec: autoexec block erase verify error\n");
	}
      }
      else {
	printk(KERN_ERR "set_autoexec: unable to erase autoexec block\n");
      }
    }

    return -1;
}

/************************************************************************/

char * pflash_get_autoexec(uint32_t *rblock)
{
    uint32_t block;
    uint32_t total_blocks;

    total_blocks = pflash_get_block_numbers();

    /* search for an existing monitor boot block */
    for(block=0; block<total_blocks; block++){
      if(ibt.data.table[block] == IBT_MONITOR_BOOT_BLOCK) {
	if(pflash_block_read(ibt_cs, block, blockbuffer, PFF_ECC) == 0) {
	  if(*((uint32_t *) blockbuffer) == MONITORBOOT_MAGIC) {
	    *rblock = block;
	    return &blockbuffer[4];
	  }
	}
      }
    }

    return 0;
}


/**************************************************************************/
/* 
   Add IBT access routines 
 */

/************************************************************************/
/* This function is currently for Samsung NAND flash only */

int pflash_block_factory_invalid(CHIP_SEL chipsel, 
				 uint32_t block)
{
    uint8_t  page0_value;
    uint8_t  page1_value;
    uint32_t start_offset;

    /* TODO: add check for Toshiba and use its algorithm */

    start_offset = pflash_get_block_start_address(chipsel, block);

    pflash_get_spare8(start_offset + SPARE_AREA_VALID_BLOCK_OFFSET, 
		      &page0_value);
    pflash_get_spare8(start_offset + SPARE_AREA_VALID_BLOCK_OFFSET + 
		      PFLASH_PAGE_SIZE, &page1_value);

    if((page0_value != SPARE_AREA_VALID_BLOCK_VALUE) || 
       (page1_value != SPARE_AREA_VALID_BLOCK_VALUE)) {
        printk(KERN_INFO 
	       "block_factory_invalid: bad block=%d, page0=0x%02X, page1=0x%02X\n", 
	       block, page0_value, page1_value);
    
	return PFLASH_BLOCK_INVALID;
    }
    else {
      return PFLASH_OK;
    }
}


/************************************************************************/

int pflash_write_IBTs(void)
{
  int retval = 0;

  ibt.data.timestamp += 1;

  if(pflash_write_IBT0()) retval = -1;
  if(pflash_write_IBT1()) retval = -1;

  return retval;

}


/************************************************************************/

int pflash_write_IBT0(void)
{

  if(ibt_block0 != IBT_UNKNOWN_BLOCK) {
    /* printk(KERN_INFO "Erasing block %d...\n", ibt_block0); */

       if ( pflash_block_erase(ibt_cs, ibt_block0, 10, PFF_ALLOW_IBT) ) {
	 printk(KERN_ERR "write IBT0: unable to erase chip %d block %d\n", 
		ibt_cs, ibt_block0);
	 return -1;
       }
       else {
	 printk("Writing pflash IBT0 to block %d...\n", ibt_block0);
	 ibt.data.magic = MAKE_MAGIC('I','B','T','0');
	 if( pflash_block_write(ibt_cs, ibt_block0, ibt.array, PFF_ECC | PFF_ALLOW_IBT)) {
	   printk(KERN_ERR "Write IBT0: unable to write chip %d block %d\n", 
		  ibt_cs, ibt_block0);

	   return -1;
	 }
	 else {
	   if(pflash_block_verify(ibt_cs, ibt_block0, ibt.array, PFF_ECC)) {
	     printk(KERN_ERR "write IBT0: verify error\n");
	     
	     return -1;
	   }
	 }
       }
  }

  return 0;
}

/************************************************************************/

int pflash_write_IBT1(void)
{
    if(ibt_block1 != IBT_UNKNOWN_BLOCK) {
      /* printk("Erasing block %d...\n", ibt_block1); */

      if(pflash_block_erase(ibt_cs, ibt_block1, 10, PFF_ALLOW_IBT)) {
	printk(KERN_ERR "write IBT 1: unable to erase chip %d block %d\n", 
	       ibt_cs, ibt_block1);

	return -1;
      }
      else {
	printk("Writing  IBT 1 to block %d...\n", ibt_block1);

	ibt.data.magic = MAKE_MAGIC('I','B','T','1');
	if(pflash_block_write(ibt_cs, ibt_block1, ibt.array, PFF_ECC | PFF_ALLOW_IBT)){
	  printk(KERN_ERR "write IBT 1: unable to write chip %d block %d\n", 
		 ibt_cs, ibt_block1);

	return -1;
      }
	else {
	  if(pflash_block_verify(ibt_cs, ibt_block1, ibt.array, PFF_ECC)) {
	    printk(KERN_ERR "write IBT 1: verify error\n");
	    
	    return -1;
	  }
	}
      }
    }

    return 0;
}


/*****************************************************************************/

int pflash_read_IBT(void)
{
    uint32_t block;
    char     pagebuffer[PFLASH_PAGE_SIZE];
    uint32_t total_blocks;
    uint32_t ibt_timestamp0 = 0;
    uint32_t ibt_timestamp1 = 0;

    ibt_block0 = IBT_UNKNOWN_BLOCK;
    ibt_block1 = IBT_UNKNOWN_BLOCK;

    total_blocks = pflash_get_block_numbers();

    /* 
       search for the invalid block tables by 
       looking for the magic numbers (IBT0/IBT1)
    */
    for(block=0; block<total_blocks; block++) {
      if(pflash_page_read(ibt_cs, block, 0, pagebuffer, PFF_ECC) == 0) {
	if(pagebuffer[0] == 'I' && pagebuffer[1] == 'B' && pagebuffer[2] == 'T') {
	  if(pagebuffer[3] == '0') {
	    ibt_block0 = block;
	    /*printk ("ibt-block-0 found at %d\n", ibt_block0);*/
	  } else if(pagebuffer[3] == '1')
	    ibt_block1 = block;

	  if((ibt_block0 != IBT_UNKNOWN_BLOCK) && (ibt_block1 != IBT_UNKNOWN_BLOCK)) {
	    break;
	  }
	}
      }
    }

    /* attempt to read invalid block table #0 */
    if(ibt_block0 != IBT_UNKNOWN_BLOCK) {
      /* printk("Reading pflash invalid block table 0...\n"); */

      if(pflash_block_read(ibt_cs, ibt_block0, ibt.array, PFF_ECC) == 0) {
	ibt_timestamp0 = ibt.data.timestamp;
      }
      else {
	printk(KERN_INFO "WARNING - read_IBT: unable to read table 0\n");
      }
    }
    else {
      printk(KERN_INFO "WARNING - read_IBT: table 0 not found\n");
    }

    /* attempt to read invalid block table #1 */
    if(ibt_block1 != IBT_UNKNOWN_BLOCK) {
      /* printk("Reading pflash IBT 1...\n"); */
      if(pflash_block_read(ibt_cs, ibt_block1, ibt.array, PFF_ECC) == 0) {
	ibt_timestamp1 = ibt.data.timestamp;
      }
      else {
	printk(KERN_INFO "WARNING - read_IBT: unable to read table 1\n");
      }
    }
    else {
      printk(KERN_INFO "WARNING - read_IBT: table 1 not found\n");
      ibt_timestamp1 = 0;
    }

    if(ibt_timestamp0 || ibt_timestamp1) {
      if(ibt_timestamp0 < ibt_timestamp1) {
	/* read table #1 data */
	if(pflash_block_read(ibt_cs, ibt_block1, ibt.array, PFF_ECC) == 0) {
	  /* overwrite table #0 with table #1 data */
	  pflash_write_IBT0();
	  
	  return 0;
	}
      }
      else if (ibt_timestamp0 > ibt_timestamp1) {
	/* read table #0 data */
	if(pflash_block_read(ibt_cs, ibt_block0, ibt.array, PFF_ECC) == 0) {
	  /* overwrite table #1 with table #0 data */
	  pflash_write_IBT1();

	  return 0;
	}
      }
      else {
	/* read table #0 data */
	if(pflash_block_read(ibt_cs, ibt_block0, ibt.array, PFF_ECC) == 0) {
	  /*printk ("IBT-0 is OK\n");*/
	  return 0;
	}
      }
    }

    /* neither IBT could be read */
    return -1;

}

/************************************************************************/

int pflash_find_IBT(void)
{
    if(!ibt_found)
    {
      
      /* should always find the tables which were created by MMON or EJTAG tools */
      if(pflash_read_IBT()) {
	printk(KERN_ERR 
	       "pflash_find_IBT: Unable to find table...Should never happen!\n");
	return -1;
      }
      ibt_found = 1;
    }

    return 0;

}


/************************************************************************/

void dump_nand_chip_info (struct nand_chip *this)
{

  DEBUG (MTD_DEBUG_LEVEL3, "nand_chip struct:@%x\n", this);
  DEBUG (MTD_DEBUG_LEVEL3, "IO_ADDR: \n");
  DEBUG (MTD_DEBUG_LEVEL3, "CTRL_ADDR: \n");
  DEBUG (MTD_DEBUG_LEVEL3, "CLE: \n");
  DEBUG (MTD_DEBUG_LEVEL3, "ALE: \n");
  DEBUG (MTD_DEBUG_LEVEL3, "NCE: \n");
  DEBUG (MTD_DEBUG_LEVEL3, "chip_lock: 0x%x\n", this->chip_lock);
  DEBUG (MTD_DEBUG_LEVEL3, "wait_queue: 0x%x\n", this->wq);
  DEBUG (MTD_DEBUG_LEVEL3, "nand_state: 0x%x\n", this->state);
  DEBUG (MTD_DEBUG_LEVEL3, "page_shift: 0x%x\n", this->page_shift);

#ifdef CONFIG_MTD_NAND_ECC
  DEBUG (MTD_DEBUG_LEVEL3, "ecc_code_buf: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", 
	 this->ecc_code_buf[0], this->ecc_code_buf[1], this->ecc_code_buf[2], 
	 this->ecc_code_buf[3], this->ecc_code_buf[4], this->ecc_code_buf[5]);
#endif

}


/*****************************************************************************/

void dump_mtd_info (struct mtd_info *mtd)
{

  DEBUG (MTD_DEBUG_LEVEL3, "mtd_info struct: @ %x\n", mtd);
  DEBUG (MTD_DEBUG_LEVEL3, "type: 0x%x\n", mtd->type);
  DEBUG (MTD_DEBUG_LEVEL3, "flags: 0x%x\n", mtd->flags);
  DEBUG (MTD_DEBUG_LEVEL3, "total size: 0x%x\n", mtd->size);
  DEBUG (MTD_DEBUG_LEVEL3, "erasesize: 0x%x\n", mtd->erasesize);
  DEBUG (MTD_DEBUG_LEVEL3, "oobblock: 0x%x\n", mtd->oobblock);
  DEBUG (MTD_DEBUG_LEVEL3, "oobsize: 0x%x\n", mtd->oobsize);
  DEBUG (MTD_DEBUG_LEVEL3, "ecctype: 0x%x\n", mtd->ecctype );
  DEBUG (MTD_DEBUG_LEVEL3, "eccsize: 0x%x\n", mtd->eccsize);
  DEBUG (MTD_DEBUG_LEVEL3, "NAND name: %s\n", mtd->name);
  DEBUG (MTD_DEBUG_LEVEL3, "index: 0x%x\n", mtd->index);
  DEBUG (MTD_DEBUG_LEVEL3, "number of erase regions: 0x%x\n", mtd->numeraseregions);
  DEBUG (MTD_DEBUG_LEVEL3, "mtd->read: 0x%x\n", mtd->read);
  DEBUG (MTD_DEBUG_LEVEL3, "mtd->read_oob: 0x%x\n", mtd->read_oob);

}











