/*
 *  drivers/mtd/stw_nand.c
 *
 *  Copyright (C) 2000 Steven J. Hill (sjhill@cotw.com)
 *
 * $Id: nand.c,v 1.12 2001/10/02 15:05:14 dwmw2 Exp $
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

/*
 * This driver is dramatically changed to fit ATI's implementation of 
 * the NAND flash controller which is based on a "pflash" state machine 
 * instead of the GPIO pin direct-access mode.
 * 
 * Chin Zhou (czhou@ati.com), ATI Technologies Inc.
 *
 */

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

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

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

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

#include "stw_flash.h"


/* Strategies for NAND invalid block handling */

/*
  Block Erase:  Never erase invalid blocks, and update invalid block table (IBT) if 
      a new invalid block is detected.
  
  Read/Write:
    - Update IBT if any new invalid blocks are found.
    - Logical-physical block mapping for invalid block replacement.
    - ECC correction for 1-bit error.
*/

/* 
   Logical-physical block mapping table scheme:
   - Create logical block table which links to valid good blocks, 
     skip invalid blocks;
   - Make sure to have contineous logical partitions;
   - adjust the offset of logical blocks to the offset of the valid physical blocks.

   Invalid Block Tables (IBT) and special purpose blocks in NAND
   - First two good blocks are used for invalid block tables (IBT)
   - Third good block is used for MMON Linux boot lines for dual-boot 
     Linux kernel images.
   - Each special purpose block has its own magic number.
*/

/* Error in WR/RD operations according to Samsung specifications */
/*
   - Erase failure: 
       Status read after Erase, do block replacement, update IBT.
   - Program failure: 
       Status read after Program, do block replacement, using L-P table.
   - Verify after Program (read back): 
       ECC correction or block replacement.
   - Read error: 
       ECC correction for single bit error.
*/

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
extern uint32_t nand_logic_block[2*STW_MAX_NUM_BLOCKS];
extern uint8_t  nand_phy_block[2*STW_MAX_NUM_BLOCKS];
#endif


uint32_t  adj_offset, lb_num;
uint32_t  poffset;

/* START OPTIMIZED PAGE BUFFER WITH DWORD NAND READ ACCESS */

unsigned char fast_pflash_pagebuf[512];
unsigned int  fast_pflash_pagenum = 0xFFFFFFFF;

#define FAST_PFLASH_WRITE_8( x, y ) { \
  fast_pflash_pagenum = 0xFFFFFFFF; /* invalidate cache */ \
  PFLASH_WRITE_8( (x), (y) ); \
}

unsigned char FAST_PFLASH_READ_8( unsigned int offset )
{
  unsigned int pagenum = offset >> 9;

  if( fast_pflash_pagenum != pagenum ) {
    /* request is outside of current cached page */
    unsigned int idx;
    unsigned int pmem = (offset & 0xFFFFFE00);

    for( idx = 0; idx < 512; idx += 4, pmem += 4 ) {
      *((unsigned int*)(fast_pflash_pagebuf + idx)) = 
	                            PFLASH_READ_32( pmem );
    }
    fast_pflash_pagenum = pagenum;
  }

  /* fulfill request from current cached page */
  return fast_pflash_pagebuf[offset & 0x1FF];
}

/* END OPTIMIZED PAGE BUFFER WITH DWORD NAND READ ACCESS */


/*
 * NAND low-level MTD interface functions
 */
static int nand_read (struct mtd_info *mtd, 
		      loff_t from, 
		      size_t len,
		      size_t *retlen, 
		      u_char *buf);

static int nand_read_ecc (struct mtd_info *mtd, 
			  loff_t from, 
			  size_t len,
			  size_t *retlen, 
			  u_char *buf, 
			  u_char *ecc_code);

static int nand_read_oob (struct mtd_info *mtd, 
			  loff_t from, 
			  size_t len,
			  size_t *retlen, 
			  u_char *buf);

static int nand_write (struct mtd_info *mtd, 
		       loff_t to, size_t len,
		       size_t *retlen, const u_char *buf);

static int nand_write_ecc (struct mtd_info *mtd, 
			   loff_t to, 
			   size_t len,
			   size_t *retlen, 
			   const u_char *buf,
			   u_char *ecc_code);

static int nand_write_oob (struct mtd_info *mtd, 
			   loff_t to, 
			   size_t len,
			   size_t *retlen, 
			   const u_char *buf);

static int nand_erase (struct mtd_info *mtd, 
		       struct erase_info *instr);

static void nand_sync (struct mtd_info *mtd);

static uint32_t stw_nand_block_map(uint32_t *offset);


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

/* 
   Supported command for the STWX225 NAND device are:
       NAND_CMD_READ0
       NAND_CMD_READ1
       NAND_CMD_READOOB    0x50
       NAND_CMD_SEQIN      0x80
       NAND_CMD_PAGEPROG   0x10

       NAND_CMD_ERASE1     0x60
       NAND_CMD_STATUS     0x70
       NAND_CMD_READID     0x90
       NAND_CMD_RESET      0xff


   Comments on the ATI's pflash state-machine implementation:
   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);

    - ATI hardware implementation is based on automatic page-program:
      NAND controller issue 80h command first, then issue 10h command
      either when clear_state_machine or address reach the end of the page.
*/

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

/*
 * Send command to NAND device, ATI's hardware implementation.
 */
static void nand_command (struct mtd_info *mtd, 
			  unsigned command,
			  int column, 
			  int page_addr,
			  CHIP_SEL chipsel, 
			  uint32_t timeout)
{
	
  uint32_t blk_address;
  uint32_t regVal;
  uint32_t status;
	
  if (chipsel != CS1)
    printk ("nand_cmd: %x: not CS1\n", CS1);
  switch (command) {

  case NAND_CMD_RESET:
	  pflash_clear_state_machine(timeout);
	  blk_address = pflash_get_chip_start_address(chipsel);
	  regVal = blk_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_RESET); 
	  if ((status = pflash_wait_trigger(timeout)) != PFLASH_OK)
	    printk ("Nand:Reset NOT OK\n");
	  break;
	
  case NAND_CMD_READID:
	  pflash_clear_state_machine(timeout);
	  blk_address = pflash_get_chip_start_address(chipsel);
	  regVal = blk_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(timeout);
	  if(status == PFLASH_OK)
	    {    
	      while(GETFLD_REGMM32(PFLASH_STATUS, ID_READ_DONE) == 0)
		{
		  udelay(1000);
		  if(--timeout == 0)
		    {
		      printk("nand_command: timeout waiting for ID_READ.\n");
		      return;
		    }
		}
	      
	    } else {
		 printk("nand_command: ID_READ:Failed.\n");
	    }
	  break;

  case NAND_CMD_STATUS:
	  /*
	  pflash_clear_state_machine(timeout);
	  blk_address = pflash_get_chip_start_address(chipsel);
	  regVal = blk_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_READ_STATUS); 
	  status = pflash_wait_trigger(timeout);
	  */
	  break;

  case NAND_CMD_ERASE1:
	  /*
	  pflash_clear_state_machine(timeout);
	  blk_address = pflash_get_chip_start_address(chipsel);
	  regVal = blk_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_BLOCK_ERASE); 
	  status = pflash_wait_trigger(timeout);
	  */
	  break;
	  
  default:
	  printk(KERN_INFO "Unknown NAND command is issued.\n");
	  break;

  }
  udelay (10);
}

/*
 * NAND read
 */
static int nand_read (struct mtd_info *mtd, 
		      loff_t from, 
		      size_t len,
		      size_t *retlen, 
		      u_char *buf)
{
#ifdef CONFIG_MTD_NAND_ECC
  struct nand_chip *this = mtd->priv;
  return nand_read_ecc (mtd, from, len, retlen, buf, 
			this->ecc_code_buf);
#else
  return nand_read_ecc (mtd, from, len, retlen, buf, NULL);
#endif
}

/*
 * NAND read with ECC
 */
static int nand_read_ecc (struct mtd_info *mtd, 
			  loff_t from, 
			  size_t len,
			  size_t *retlen, 
			  u_char *buf, 
			  u_char *ecc_code)
{
  int j, col, page, state;
  int erase_state = 0;
  struct nand_chip *this = mtd->priv;
  DECLARE_WAITQUEUE(wait, current);
#ifdef CONFIG_MTD_NAND_ECC
  int ecc_result;
  size_t loclen;
  u_char ecc_calc[6];
#endif


  DEBUG ( MTD_DEBUG_LEVEL3,
	  "nand_read_ecc: from = 0x%08x, len = %i\n", 
	  (unsigned int) from, (int) len );

  /* Do not allow reads past end of device */
  if ((from + len) > (mtd->size) ) {
    DEBUG ( MTD_DEBUG_LEVEL0,
	    "nand_read_ecc: Attempt read beyond end of device\n" );
    *retlen = 0;
    return -EINVAL;
  }

  /* Grab the lock and see if the device is available */
 retry:
  spin_lock_bh (&this->chip_lock);
  switch (this->state) {
  case FL_READY:
    this->state = FL_READING;
    spin_unlock_bh (&this->chip_lock);
    break;

  case FL_ERASING:
    this->state = FL_READING;
    erase_state = 1;
    spin_unlock_bh (&this->chip_lock);
    break;

  default:
    set_current_state (TASK_UNINTERRUPTIBLE);
    add_wait_queue (&this->wq, &wait);
    spin_unlock_bh (&this->chip_lock);
    schedule();
    remove_wait_queue (&this->wq, &wait);
    goto retry;
  };

  /* First we calculate the starting page */
  page = from >> this->page_shift;

  /* Get raw starting column */
  col = from & (mtd->oobblock - 1);

  /* State machine for devices having pages larger than 256 bytes */
  state = (col < mtd->eccsize) ? 0 : 1;

  /* Calculate column address within ECC block context */
  col = (col >= mtd->eccsize) ? (col - mtd->eccsize) : col;

  /* Initialize return value */
  *retlen = 0;

  pflash_clear_state_machine(10);

  /* Loop until all data read */
  while (*retlen < len) {

#ifdef CONFIG_MTD_NAND_ECC

    loclen = *retlen;
    DEBUG ( MTD_DEBUG_LEVEL3, "loclen=0x%x, *retlen=0x%x\n", 
	    loclen, *retlen );

    /* Read in a block big enough for ECC */
    pflash_clear_state_machine(10);

    for (j=0 ; j < mtd->eccsize; j++){

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset = (uint32_t)from + loclen;
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = from+loclen;
#endif
      this->data_buf[j]= FAST_PFLASH_READ_8(adj_offset);
      loclen++;
    
    }

    /* Read OOB data */
    pflash_clear_state_machine_oob_enable(10);

    if (!state) {

      for (j=0 ; j<3 ; j++){
	pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset = (uint32_t)from + loclen + j;
	adj_offset = stw_nand_block_map(&poffset);
#else
	adj_offset = from+loclen+j;
#endif
	ecc_code[j] = PFLASH_READ_8(adj_offset);
      }
      pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset =   from+loclen+SPARE_AREA_U256_ECC0_OFFSET;
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = from+loclen+SPARE_AREA_U256_ECC0_OFFSET;
#endif

      ecc_code[3]= PFLASH_READ_8(adj_offset);
      pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset =   from+loclen + SPARE_AREA_U256_ECC1_OFFSET;
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = from+loclen + SPARE_AREA_U256_ECC1_OFFSET;
#endif
      ecc_code[4] = PFLASH_READ_8(adj_offset);
      pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset =   from+loclen+SPARE_AREA_U256_ECC2_OFFSET;
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = from+loclen+SPARE_AREA_U256_ECC2_OFFSET;
#endif
      ecc_code[5] = PFLASH_READ_8(adj_offset);
      pflash_clear_state_machine(10);

    }
		
    /* Calculate the ECC and verify/correct 1 bit */
    if (!state) {
      nand_calculate_ecc (&this->data_buf[0], &ecc_calc[0]);
      ecc_result = nand_correct_data (&this->data_buf[0],
				      &ecc_code[0], &ecc_calc[0]);

      DEBUG ( MTD_DEBUG_LEVEL3, 
	      "nand_read_ecc: ecc0=0x%x ecc1=0x%x ecc2=0x%x\n",
	      ecc_code[0], ecc_code[1], ecc_code[2]);
      DEBUG ( MTD_DEBUG_LEVEL3, 
	      "nand_read_ecc: cecc0=0x%x cecc1=0x%x cecc2=0x%x\n",
	      ecc_calc[0], ecc_calc[1], ecc_calc[2]);
    }
    else {
      nand_calculate_ecc (&this->data_buf[0], &ecc_calc[3]);
      ecc_result = nand_correct_data (&this->data_buf[0],
				      &ecc_code[3], &ecc_calc[3]);

      DEBUG ( MTD_DEBUG_LEVEL3, 
	      "nand_read_ecc: ecc3=0x%x ecc4=0x%x ecc5=0x%x\n",
	      ecc_code[3], ecc_code[4], ecc_code[5]);
      DEBUG ( MTD_DEBUG_LEVEL3, 
	      "nand_read_ecc: cecc3=0x%x cecc4=0x%x cecc5=0x%x\n",
	      ecc_calc[3], ecc_calc[4], ecc_calc[5]);
    }

    if (ecc_result == -1) {
      DEBUG (MTD_DEBUG_LEVEL3,
	     "nand_read_ecc: " \
	     "Failed ECC read, page 0x%08x\n", page);
      spin_lock_bh (&this->chip_lock);
      if (erase_state)
	this->state = FL_ERASING;
      else
	this->state = FL_READY;
      wake_up (&this->wq);
      spin_unlock_bh (&this->chip_lock);
      return -EIO;
    }
		
    /* Read the data from ECC data buffer into return buffer */
    if ((*retlen + (mtd->eccsize - col)) >= len) {
      while (*retlen < len)
	buf[(*retlen)++] = this->data_buf[col++];
      /* We're done */
      continue;
    }
    else
      for (j=col ; j < mtd->eccsize ; j++)
	buf[(*retlen)++] = this->data_buf[j];

#else  /* CONFIG_MTD_NAND_ECC */

    /* Read the data directly into the return buffer */ 
    //printk ("nf:Flash-Base %x, len %d\n", PFLASH_BASE, len);
    if ((*retlen + (mtd->eccsize - col)) >= len) {
      while (*retlen < len){

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset =   from+(*retlen);
	adj_offset = stw_nand_block_map(&poffset);
#else
	adj_offset = from+(*retlen);
#endif
	buf[(*retlen)++] = FAST_PFLASH_READ_8(adj_offset);
      }
      /* We're done */
      continue;
    }
    else
      for (j=col ; j < mtd->eccsize; j++) {

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset =   from+(*retlen);
	adj_offset = stw_nand_block_map(&poffset);
	/*printk ("nf:read: len %d, retlen %d, adj_offset 0x%x, poffset %0x\n",
	  len, *retlen, adj_offset, poffset);*/
#else
	adj_offset = from+(*retlen);
#endif
	buf[(*retlen)++] = FAST_PFLASH_READ_8(adj_offset);
      }

#endif  /* CONFIG_MTD_NAND_ECC */

    /*
     * If the amount of data to be read is greater than
     * (256 - col), then all subsequent reads will take
     * place on page or half-page (in the case of 512 byte
     * page devices) aligned boundaries and the column
     * address will be zero. Setting the column address to
     * to zero after the first read allows us to simplify
     * the reading of data and the if/else statements above.
     */
    if (col)
      col = 0x00;

    /* Increment page address */
    if ( (mtd->oobblock == 256) || state )
      page++;

    /* Toggle state machine */
    if (mtd->oobblock == 512)
      state = state ? 0 : 1;
  }

  /* Wake up anyone waiting on the device */
  spin_lock_bh (&this->chip_lock);
  if (erase_state)
    this->state = FL_ERASING;
  else
    this->state = FL_READY;
  wake_up (&this->wq);
  spin_unlock_bh (&this->chip_lock);

  /* Return OK */
  return 0;

}

/*
 * NAND read out-of-band
 */
static int nand_read_oob (struct mtd_info *mtd, 
			  loff_t from, 
			  size_t len,
			  size_t *retlen, 
			  u_char *buf)
{

  return 0;
}

/*
 * NAND write
 */
static int nand_write (struct mtd_info *mtd, 
		       loff_t to, 
		       size_t len,
		       size_t *retlen, 
		       const u_char *buf)
{
#ifdef CONFIG_MTD_NAND_ECC
  struct nand_chip *this = mtd->priv;
	
  return nand_write_ecc (mtd, to, len, retlen, buf, this->ecc_code_buf);
#else
  return nand_write_ecc (mtd, to, len, retlen, buf, NULL);
#endif
}

/*
 * NAND write with ECC
 */
static int nand_write_ecc (struct mtd_info *mtd, 
			   loff_t to, 
			   size_t len,
			   size_t *retlen, 
			   const u_char *buf, 
			   u_char *ecc_code)
{
  int i,j, page, col, cnt;
  struct nand_chip *this = mtd->priv;
  DECLARE_WAITQUEUE(wait, current);

#ifdef CONFIG_MTD_NAND_ECC
  int ecc_bytes = (mtd->oobblock == 512) ? 6 : 3;
#endif

  DEBUG (MTD_DEBUG_LEVEL3,
	 "nand_write_ecc: to = 0x%08x, len = %0x%x\n", 
	 (unsigned int) to, (int) len);

  /* Do not allow write past end of page */
  if ((to + len) > mtd->size) {
    DEBUG (MTD_DEBUG_LEVEL0,
	   "nand_write_ecc: Attempted write past end of device\n");
    return -EINVAL;
  }

 retry:
  /* Grab the lock and see if the device is available */
  spin_lock_bh (&this->chip_lock);
  switch (this->state) {
  case FL_READY:
    this->state = FL_WRITING;
    spin_unlock_bh (&this->chip_lock);
    break;

  default:
    set_current_state (TASK_UNINTERRUPTIBLE);
    add_wait_queue (&this->wq, &wait);
    spin_unlock_bh (&this->chip_lock);
    schedule();

    remove_wait_queue (&this->wq, &wait);
    goto retry;
  };

  /* Shift to get page */
  page = ((int) to) >> this->page_shift;

  /* Get the starting column */
  col = to & (mtd->oobblock - 1);

  /* Initialize return length value */
  *retlen = 0;

  DEBUG( MTD_DEBUG_LEVEL3, "nand_write_ecc col: %x page: %x\n", 
	 col, page );

  /* Loop until all data is written */
  while (*retlen < len) {
    /* Write data into buffer */
    if ((col + len) >= mtd->oobblock)
      for(i=col, cnt=0 ; i < mtd->oobblock ; i++, cnt++)
	this->data_buf[i] = buf[(*retlen + cnt)];
    else
      for(i=col, cnt=0 ; cnt < (len - *retlen) ; i++, cnt++)
	this->data_buf[i] = buf[(*retlen + cnt)];

#ifdef CONFIG_MTD_NAND_ECC
    /* Zero out the ECC array */
    for (i=0 ; i < 6 ; i++)
      ecc_code[i] = 0x00;

	  /* Calculate and write the ECC if we have enough data */
    if ((col < mtd->eccsize) && 
	((col + (len - *retlen)) >= mtd->eccsize)) {

      DEBUG( MTD_DEBUG_LEVEL3, "NAND_ECC write_ecc - col=%x\n", col);

      for (i=0 ; i < col ; i++) {

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset   = to-col+i;
	adj_offset = stw_nand_block_map(&poffset);
#else
	adj_offset = to-col+i;
#endif
	this->data_buf[i] = FAST_PFLASH_READ_8(adj_offset); 

      }
      nand_calculate_ecc (&this->data_buf[0], &ecc_code[0]);
      DEBUG( MTD_DEBUG_LEVEL3, "ecc_code L256: 0x%lx 0x%lx 0x%lx\n",
	     ecc_code[0], ecc_code[1], ecc_code[2]);

      /* pay attention to the OOB offset for the ECC data */
      for (i=0 ; i<3 ; i++)
	this->data_buf[(mtd->oobblock + i)] = ecc_code[i];
    }
		
    /* Calculate and write the second ECC if we have enough data */
    if ((mtd->oobblock == 512) &&
	((col + (len - *retlen)) >= mtd->oobblock)) {
      nand_calculate_ecc (&this->data_buf[256], &ecc_code[3]);

	    /* pay attention to the OOB offset for the ECC data */ 
      this->data_buf[(mtd->oobblock + SPARE_AREA_U256_ECC0_OFFSET)] 
	= ecc_code[3];
      this->data_buf[(mtd->oobblock + SPARE_AREA_U256_ECC1_OFFSET)] 
	= ecc_code[4];
      this->data_buf[(mtd->oobblock + SPARE_AREA_U256_ECC2_OFFSET)] 
	= ecc_code[5];

      DEBUG( MTD_DEBUG_LEVEL3, "ecc_code U256: 0x%lx 0x%lx 0x%lx\n",
	     ecc_code[3], ecc_code[4], ecc_code[5]);
      DEBUG( MTD_DEBUG_LEVEL3, "bad block marker: 0x%x\n", 
	     this->data_buf[mtd->oobblock+SPARE_AREA_VALID_BLOCK_OFFSET]);

    }
		  
    /* Write ones */
    this->data_buf[516] = 0xff; 
    this->data_buf[mtd->oobblock+SPARE_AREA_VALID_BLOCK_OFFSET] = 0xff;
    for (i=(ecc_bytes+2) ; i < mtd->oobsize ; i++)
      this->data_buf[(mtd->oobblock + i)] = 0xff;
#else
    /* Write ones */
    for (i=mtd->oobblock ; i < (mtd->oobblock + mtd->oobsize) ; i++)
      this->data_buf[i] = 0xff;
#endif

    /* Write pre-padding bytes into buffer */
    for (i=0 ; i < col ; i++)
      this->data_buf[i] = 0xff;

	  /* Write post-padding bytes into buffer */
    if ((col + (len - *retlen)) < mtd->oobblock) {
      for(i=(col + cnt) ; i < mtd->oobblock ; i++)
	this->data_buf[i] = 0xff;
    }

    /* Write out complete page of data */
    pflash_clear_state_machine(10); 
    for (i=0 ; i < (mtd->oobblock + mtd->oobsize) ; i++) {
      if (i == (mtd->oobblock)) {
	pflash_clear_state_machine_oob_enable(10);
      }

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      if(i>=mtd->oobblock) {
	j = i-mtd->oobblock;
	poffset = to-col+j+(*retlen);
	adj_offset = stw_nand_block_map(&poffset);
      }
      else {
	poffset = to-col+i+(*retlen);
	adj_offset = stw_nand_block_map(&poffset); 
      }
#else
      if (i>=mtd->oobblock) {
	j = i-mtd->oobblock;
	adj_offset = to-col+j+(*retlen);
      }
      else
	adj_offset = to-col+i+(*retlen);
#endif
      FAST_PFLASH_WRITE_8( (adj_offset), this->data_buf[i] );
    }

#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
    /*
     * The NAND device assumes that it is always writing to
     * a cleanly erased page. Hence, it performs its internal
     * write verification only on bits that transitioned from
     * 1 to 0. The device does NOT verify the whole page on a
     * byte by byte basis. It is possible that the page was
     * not completely erased or the page is becoming unusable
     * due to wear. The read with ECC would catch the error
     * later when the ECC page check fails, but we would rather
     * catch it early in the page write stage. Better to write
     * no data than invalid data.
     */

    pflash_clear_state_machine(10);		

    DEBUG ( MTD_DEBUG_LEVEL3, 
	    "NAND_VERIFY_WRITE - col=0x%x, cnt=0x%x, *retlen=0x%x\n",
	    col, cnt, *retlen );

    /* Loop through and verify the data */
    for (i=col ; i < cnt ; i++) {

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset =   to-col+i+(*retlen);
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = to-col+i+(*retlen);
#endif
      if (this->data_buf[i] != FAST_PFLASH_READ_8(adj_offset)) {
	DEBUG (MTD_DEBUG_LEVEL0,
	       "nand_write_ecc: " \
	       "Failed write verify, page 0x%08x, " \
	       "%6i bytes were succesful\n",
	       page, *retlen);

	spin_lock_bh (&this->chip_lock);
	this->state = FL_READY;
	wake_up (&this->wq);
	spin_unlock_bh (&this->chip_lock);
	return -EIO;
      }
    }

#ifdef CONFIG_MTD_NAND_ECC
      /*
       * We also want to check that the ECC bytes wrote
       * correctly for the same reasons stated above.
       */
      /* READ OOB */
      pflash_clear_state_machine_oob_enable(10);
      for (i=0 ; i < (ecc_bytes-2) ; i++) {
	pflash_clear_state_machine_oob_enable(10);
#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset =   to+i;
	adj_offset = stw_nand_block_map(&poffset);
#else
	adj_offset = to+i;
#endif
	if ((PFLASH_READ_8(adj_offset) != ecc_code[i]) && ecc_code[i]) {
	  DEBUG (MTD_DEBUG_LEVEL0,
		 "nand_write_ecc: Failed ECC write " \
		 "verify, page 0x%08x, " \
		 "%6i bytes were succesful, ecc_code[%x]=0x%0x\n",
		 page, i, i, ecc_code[i]);

	  spin_lock_bh (&this->chip_lock);
	  this->state = FL_READY;
	  wake_up (&this->wq);
	  spin_unlock_bh (&this->chip_lock);
	  return -EIO;
	}
      }

      pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
      poffset   = to + SPARE_AREA_U256_ECC1_OFFSET;
      adj_offset = stw_nand_block_map(&poffset);
#else
      adj_offset = to + SPARE_AREA_U256_ECC1_OFFSET;
#endif
	if ((PFLASH_READ_8(adj_offset) != ecc_code[4]) && ecc_code[4]) {
	  DEBUG (MTD_DEBUG_LEVEL0,
		 "nand_write_ecc: Failed ECC write " \
		 "verify, page 0x%08x, " \
		 "%6i bytes were succesful, ecc_code[%x]=0x%0x\n",
		 page, 4, 4, ecc_code[4]);

	  spin_lock_bh (&this->chip_lock);
	  this->state = FL_READY;
	  wake_up (&this->wq);
	  spin_unlock_bh (&this->chip_lock);
	  return -EIO;
	}

	pflash_clear_state_machine_oob_enable(10);

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
	poffset   = to + SPARE_AREA_U256_ECC2_OFFSET;
	adj_offset = stw_nand_block_map(&poffset);
#else
	adj_offset = to + SPARE_AREA_U256_ECC2_OFFSET;
#endif
	if ((PFLASH_READ_8(adj_offset) != ecc_code[5]) && ecc_code[5]) {
	  DEBUG (MTD_DEBUG_LEVEL0,
		 "nand_write_ecc: Failed ECC write " \
		 "verify, page 0x%08x, " \
		 "%6i bytes were succesful, ecc_code[%x]=0x%0x\n",
		 page, 5, 5, ecc_code[5]);

	  spin_lock_bh (&this->chip_lock);
	  this->state = FL_READY;
	  wake_up (&this->wq);
	  spin_unlock_bh (&this->chip_lock);
	  return -EIO;
	}

	pflash_clear_state_machine(10);
#endif
#endif
	  
	/*
	 * If we are writing a large amount of data and/or it
	 * crosses page or half-page boundaries, we set the
	 * the column to zero. It simplifies the program logic.
	 */
	if (col)
	  col = 0x00;

	/* Update written bytes count */
	*retlen += cnt;

	/* Increment page address */
	page++;

	pflash_clear_state_machine(10);

  }  /* while len loop */


  /* Wake up anyone waiting on the device */
  spin_lock_bh (&this->chip_lock);
  this->state = FL_READY;
  wake_up (&this->wq);
  spin_unlock_bh (&this->chip_lock);

  /* Return OK */
  *retlen = len;
  return 0;

}

/*
 * NAND write out-of-band
 */
static int nand_write_oob (struct mtd_info *mtd, 
			   loff_t to, 
			   size_t len,
			   size_t *retlen, 
			   const u_char *buf)
{

  return 0;
}

/*
 * NAND erase a block, do not erase special and invalid blocks which marked as
 * non-zero values in IBTs.
 */
static int nand_erase (struct mtd_info *mtd, 
		       struct erase_info *instr)
{

  int page, len, status, pages_per_block,ret;
  struct nand_chip *this = mtd->priv;
  CHIP_SEL   erase_cs;
  uint32_t   start_addr, pflash_be_num, pflash_be_addr; 
  uint32_t   trial_num = 10;

  DECLARE_WAITQUEUE(wait, current);

  /*printk ("Nand:Erase.. addr %x, len 0x%x=%d\n", instr->addr, instr->len, instr->len);*/
  DEBUG (MTD_DEBUG_LEVEL3,
	 "nand_erase: start = 0x%08x, len = %i\n",
	 (unsigned int) instr->addr, (unsigned int) instr->len);

  /* Start address must align on block boundary */
  if (instr->addr & (mtd->erasesize - 1)) {
    DEBUG (MTD_DEBUG_LEVEL0,
	   "nand_erase: Unaligned address\n");
    return -EINVAL;
  }

  /* Length must align on block boundary */
  if (instr->len & (mtd->erasesize - 1)) {
    DEBUG (MTD_DEBUG_LEVEL0,
	   "nand_erase: Length not block aligned\n");
    return -EINVAL;
  }

  /* Do not allow erase past end of device */
  if ((instr->len + instr->addr) > mtd->size) {
    DEBUG (MTD_DEBUG_LEVEL0,
	   "nand_erase: Erase past end of device\n");
    return -EINVAL;
  }

retry:
  /* Grab the lock and see if the device is available */
  spin_lock_bh (&this->chip_lock);

  switch (this->state) {
  case FL_READY:
    this->state = FL_ERASING;
    break;

  default:
    set_current_state (TASK_UNINTERRUPTIBLE);
    add_wait_queue (&this->wq, &wait);
    spin_unlock_bh (&this->chip_lock);
    schedule();

    remove_wait_queue (&this->wq, &wait);
    goto retry;
  };

  /* Shift to get first page */
  page = (int) (instr->addr >> this->page_shift);

  /* Calculate pages in each block */
  pages_per_block = mtd->erasesize / mtd->oobblock;


  /* Loop through the pages */
  len = instr->len;
  start_addr = instr->addr;
  pflash_clear_state_machine(10);

  instr->state = MTD_ERASING;
  while (len) {
#ifndef CONFIG_ROKU_DESCHUTES /* On Deschutes Pflash is on CS1 */
    if ( start_addr < PFLASH_NAND_CS1_OFFSET ) 
      erase_cs = CS0;
    else 
#endif /* CONFIG_ROKU_DESCHUTES */
      erase_cs = CS1;

    /* erase the blocks, ATI's state-machine mode */
    pflash_be_addr = start_addr >> 
      PFLASH_BE_ADDR__BLOCK_ERASE_ADDR__SHIFT;
    pflash_be_num = pflash_be_addr >> 
      STW_NAND_BLOCK_NUM_SHIFT;
#ifdef CONFIG_NAND_BAD_BLOCK_CHECK /* Added by narayan */
    /*printk ("Nand-ERase Log-BN %d, Phy-BN %d, be_addr %x ", pflash_be_num,
	      nand_logic_block [pflash_be_num], pflash_be_addr);*/
    pflash_be_num = nand_logic_block [pflash_be_num]; /* get real phy block */
    pflash_be_addr = (pflash_be_num << STW_NAND_BLOCK_NUM_SHIFT) 
      | (PFLASH_BASE >> PFLASH_BE_ADDR__BLOCK_ERASE_ADDR__SHIFT); /* PFLASH_BASE is non-zero */
    /*printk ("new be_addr %x\n", pflash_be_addr);*/
#endif
    DEBUG (MTD_DEBUG_LEVEL3,  
	   "pflash_be_addr: %x pflash_be_num: %x erase_cs: %x\n", 
	   pflash_be_addr, pflash_be_num, erase_cs);

    /* Add bad block checking here, do not erase it 
       if a special and invalid block detected.
    */
    if (pflash_block_invalid(erase_cs, pflash_be_num, 0) == 0 ) {
      /* set the address */
      SETFLD_REGMM32(PFLASH_BE_ADDR,BLOCK_ERASE_ADDR, pflash_be_addr);    
      /* trigger the block erase */
      SETFLD_REGMM32(PFLASH_CNTL, CMD_TRIG, PFLASH_CMD_BLOCK_ERASE); 
      status = pflash_wait_trigger(10);
      
      /* 
	 check erase failure
      */
      if(status == PFLASH_OK) {
	while(GETFLD_REGMM32(PFLASH_STATUS, BLOCK_ERASE_DONE) == 0) { 
	  udelay(1000);
	  if (--trial_num == 0) {
	    printk(KERN_WARNING "timeout waiting for BLOCK_ERASE_DONE\n");
	    /* 
	       marking this block invalid in IBTs for block relpacement 
	    */
	    if (pflash_set_block_invalid(erase_cs, pflash_be_num)) {
	      printk(KERN_ERR "set_block_invalid failed.\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);
      } else {
	printk ("TIMO:Block Erase-Trigger\n");
      }
    }
    else{
      /* report invalid blocks for display purpose, do NOT erase it */
      printk(KERN_WARNING "Erase:...IB at: 0x%x(block: 0x%x)...\n", 
	     start_addr, pflash_be_num);
    }
    
    /* Increment page address and decrement length */
    len -= mtd->erasesize;
    /* increment to next block */
    start_addr += mtd->erasesize;
    page += pages_per_block;

    /* Release the spin lock */
    spin_unlock_bh (&this->chip_lock);

  erase_retry:
    /* Check the state and sleep if it changed */
    spin_lock_bh (&this->chip_lock);
    if (this->state == FL_ERASING) {
      continue;
    }
    else {
      set_current_state (TASK_UNINTERRUPTIBLE);
      add_wait_queue (&this->wq, &wait);
      spin_unlock_bh (&this->chip_lock);
      schedule();

      remove_wait_queue (&this->wq, &wait);
      goto erase_retry;
    }
  }
  instr->state = MTD_ERASE_DONE;
  spin_unlock_bh (&this->chip_lock);
	
  ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;;
  /* Do call back function */
  if (!ret && instr->callback)
    instr->callback (instr);

	/* The device is ready */
  spin_lock_bh (&this->chip_lock);
  this->state = FL_READY;
  spin_unlock_bh (&this->chip_lock);

  /* Return OK */
  /*printk ("NF:erase done\n");*/
  return ret;
}

/*
 * NAND sync
 */
static void nand_sync (struct mtd_info *mtd)
{
  struct nand_chip *this = mtd->priv;
  DECLARE_WAITQUEUE(wait, current);

  DEBUG (MTD_DEBUG_LEVEL3, "nand_sync: called\n");

retry:
  /* Grab the spinlock */
  spin_lock_bh(&this->chip_lock);
  
  /* See what's going on */
  switch(this->state) {
  case FL_READY:
  case FL_SYNCING:
    this->state = FL_SYNCING;
    spin_unlock_bh (&this->chip_lock);
    break;

  default:
    /* Not an idle state */
    add_wait_queue (&this->wq, &wait);
    spin_unlock_bh (&this->chip_lock);
    schedule ();

    remove_wait_queue (&this->wq, &wait);
    goto retry;
  }

  /* Lock the device */
  spin_lock_bh (&this->chip_lock);

  /* Set the device to be ready again */
  if (this->state == FL_SYNCING) {
    this->state = FL_READY;
    wake_up (&this->wq);
  }

  /* Unlock the device */
  spin_unlock_bh (&this->chip_lock);

}


/*
 * Scan for the NAND devices using ATI's state machine implementation.
 */
int nand_scan (struct mtd_info *mtd)
{
  int i, id, nand_maf_id, nand_dev_id;
  struct nand_chip *this = mtd->priv;


  /* Send the command for reading device ID */
#ifndef CONFIG_ROKU_DESCHUTES /* On Deschutes Pflash is on CS1 */
  nand_command (mtd, NAND_CMD_READID, 0x00, -1, CS0, 0);
#else
  nand_command (mtd, NAND_CMD_READID, 0x00, -1, CS1, 0); /* narayan changed use CS1*/
#endif
  id = GETFLD_REGMM32(PFLASH_ID_STAT_DATA, PF_ID_DATA);
  SETFLD_REGMM32(PFLASH_STATUS, ID_READ_DONE, 0x0);
  pflash_clear_state_machine(10);
  nand_maf_id = (id & 0xff);
  nand_dev_id = (id >> 8);

  /* Read manufacturer and device IDs */
  /*printk("NAND ChipID: %x  %x  %x\n", id, nand_maf_id, nand_dev_id);
    printk("NAND ChipId: %x ChipId String: %s\n",  
    pflash_get_chipID(CS1,10), pflash_get_chipID_string(CS1));
    id = pflash_get_chipID(CS1, 10);
    nand_maf_id = (id & 0xff);
    nand_dev_id = (id >> 8);*/

  /* Print and store flash device information */
  for (i = 0; nand_flash_ids[i].name != NULL; i++) {
    if (nand_maf_id == nand_flash_ids[i].manufacture_id &&
	nand_dev_id == nand_flash_ids[i].model_id) {
      if (!mtd->size) {
	mtd->name = nand_flash_ids[i].name;
	mtd->erasesize = nand_flash_ids[i].erasesize;
	mtd->size = 2*(1 << nand_flash_ids[i].chipshift);
	mtd->eccsize = 256;
	if (nand_flash_ids[i].page256) {
	  mtd->oobblock = 256;
	  mtd->oobsize = 8;
	  this->page_shift = 8;
	}
	else {
	  mtd->oobblock = 512;
	  mtd->oobsize = 16;
	  this->page_shift = 9;
	}
      }
      printk (KERN_INFO "NAND device: Manufacture ID: " \
	      "0x%02x, Chip ID: 0x%02x (%s) Chip size: 0x%x\n",
	      nand_maf_id, nand_dev_id, mtd->name, mtd->size);
      break;
    }
  }

  /* Initialize state and spinlock */
  this->state = FL_READY;
  init_waitqueue_head(&this->wq);
  spin_lock_init(&this->chip_lock);

  /* Print warning message for no device */
  if (!mtd->size) {
    printk (KERN_WARNING "No NAND device found!!!\n");
    return 1;
  }

  /* Fill in remaining MTD driver data */
  mtd->type = MTD_NANDFLASH;
  mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;
  mtd->module = THIS_MODULE;
  mtd->ecctype = MTD_ECC_SW;
  mtd->erase = nand_erase;
  mtd->point = NULL;
  mtd->unpoint = NULL;
  mtd->read = nand_read;
  mtd->write = nand_write;
  mtd->read_ecc = nand_read_ecc;
  mtd->write_ecc = nand_write_ecc;
  mtd->read_oob = nand_read_oob;
  mtd->write_oob = nand_write_oob;
  mtd->sync = nand_sync;
  mtd->readv = NULL;
  mtd->writev = NULL;
  mtd->lock = NULL;
  mtd->unlock = NULL;
  mtd->suspend = NULL;
  mtd->resume = NULL;
	
  return 0;
}

#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
static uint32_t stw_nand_block_map(uint32_t *offset)
{
  uint32_t  loc_adj_off;

  /* check if offset adjust is needed */
  lb_num = (*offset) >> BLOCK_NUM_TOTAL_SHIFT;
  if ( nand_logic_block[lb_num] < lb_num ) {
    printk(KERN_WARNING "block mapping failed!\n");
    return -EINVAL;
  }
  else {
    loc_adj_off = (*offset) + 
      ( ( nand_logic_block[lb_num]-lb_num ) << BLOCK_NUM_TOTAL_SHIFT );
  }

  /* for debugging purpose */
  /*
  printk(KERN_INFO "offset: 0x%x adj_off: 0x%x lblock: 0x%x pblock: 0x%x\n",
	 *offset, loc_adj_off, lb_num, nand_logic_block[lb_num] );
	 */

  return loc_adj_off;
}
#endif
EXPORT_SYMBOL(nand_scan);










