/*
  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_init.c
 *
 *  Descriptions:
 *
 *  An initialization module for the MTD NAND flash device which is used 
 *  on the ATI STWX225 board.  This driver is based on ATI's specific
 *  NAND/PFLASH controller state machine, and provide basic interface 
 *  between the device specific layer and standard MTD-NAND layer. 
 *
 */

/*
 * 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 <linux/mtd/nand_ids.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"

extern void dump_mtd_info(struct mtd_info *mtd);
extern void dump_nand_chip_info(struct nand_chip *this);

/* STW NAND flash related */


#define NAND_TOTAL_FS_SIZE       (16<<20L) /* 16 Mbytes */
#define NAND_ROOT_FS_SIZE        (12 << 20l) /* 10 Mb */
#define NAND_KERNEL_FS_SIZE      (3<<20l) /* 3 Mb */
#define NAND_HOME_FS_SIZE        (NAND_TOTAL_FS_SIZE - NAND_ROOT_FS_SIZE - NAND_KERNEL_FS_SIZE)

#define NAND_STW_OFFSET          0
#define NAND_STW_SIZE            (16<<20L)

extern int  pflash_ready;

/* MTD structure for STWX225 board */
static struct mtd_info *stwx225_mtd = NULL;

/*
 * Define partitions for flash device, need to be block-aligned
 */
static struct mtd_partition partition_info[] = {
	{ name: "MTD NAND Boot partition",
	  offset: 0,
	  size: NAND_KERNEL_FS_SIZE},
	{ name: "MTD NAND partition 1 - ROOT FS",
	  offset: NAND_KERNEL_FS_SIZE,
	  size: NAND_ROOT_FS_SIZE},
	{ name: "MTD NAND partition 2 - Home CRAMFS",
	  offset: NAND_ROOT_FS_SIZE+NAND_KERNEL_FS_SIZE,
	  size: NAND_HOME_FS_SIZE },
};

#define NUM_PARTITIONS           (sizeof(partition_info)/sizeof (partition_info [0]))
#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
/* start block 0 */
#ifdef CONFIG_ROKU_DESCHUTES /* On Deschutes Pflash is on CS1 (and only 1) */
/*
 * I have trouble doing bad-block map on CS1 in the original code.
 * If Roku ever puts 2 Nand flash on CS0 and CS1 this has to be changed.
 * The bad-block table seems to be on CS0 flash..In any case this
 * whole thing is a mess. If we get time this needs to be re-done..
 */
uint8_t  nand_phy_block[STW_MAX_NUM_BLOCKS]; /* XXX - this really needs to be a bit-map (narayan)*/
uint32_t nand_logic_block[STW_MAX_NUM_BLOCKS];
#define CS1_NAND_MAP_LOC 0 /* We do not keep the map for CS0 */
#else /* not roku */
uint32_t nand_logic_block[2*STW_MAX_NUM_BLOCKS];
uint8_t  nand_phy_block[2*STW_MAX_NUM_BLOCKS];
#define CS1_NAND_MAP_LOC STW_MAX_NUM_BLOCKS
#endif /* CONFIG_ROKU_DESCHUTES */
#endif /* CONFIG_NAND_BAD_BLOCK_CHECK */
#define STW_NUM_LOGIC_BLOCKS (sizeof (nand_logic_block)/sizeof (nand_logic_block [0]))
/*
 * Main initialization routine
 */
int __init stwx225_init (void)
{

  struct nand_chip *this;
  uint32_t stw_bb=0, bi, lcount, gcount, nb;
  uint32_t pf_block_start, pf_block_end;
  uint8_t  np;

  printk(KERN_INFO "STWX225 NAND Flash initializing...\n");

  /* Allocate memory for MTD device structure and private data */
  stwx225_mtd = kmalloc (sizeof(struct mtd_info) 
			 + sizeof (struct nand_chip), GFP_KERNEL);
  if (!stwx225_mtd) {
    printk ("Unable to allocate memory for NAND MTD.\n");
    return -ENOMEM;
  }

  /* Get pointer to private data */
  this = (struct nand_chip *) (&stwx225_mtd[1]);

  /* Initialize structures */
  memset((char *) stwx225_mtd, 0, sizeof(struct mtd_info));
  memset((char *) this, 0, sizeof(struct nand_chip));

  /* Link the private data with the MTD structure */
  stwx225_mtd->priv = this;

  pflash_init();

  /* Scan to find existance of the device */
  if (nand_scan (stwx225_mtd)) {
    printk ("NAND:FAILED scan\n");
    kfree (stwx225_mtd);
    return -ENXIO;
  }

  /* for debug purpose */
  dump_mtd_info(stwx225_mtd);
  dump_nand_chip_info(this);

  /* Allocate memory for internal data buffer */
  this->data_buf = kmalloc (sizeof(u_char) * 
			    (stwx225_mtd->oobblock + stwx225_mtd->oobsize), 
			    GFP_KERNEL);
  if (!this->data_buf) {
    printk ("Unable to allocate NAND data buffer for STWX225.\n");
    kfree (stwx225_mtd);
    return -ENOMEM;
  }


#ifdef CONFIG_NAND_BAD_BLOCK_CHECK
  /* 
     Create logical and physical blocks mapping table to make sure 
     the logical blocks used by the root file system are always good.
     
     NOTES: IBTs are created, check the block magic number, and IBTs
     before erasing any blocks. Make sure do not erase invalid and 
     special blocks/

  */

  /* Read IBTs, the tables should be created by MMON or EJTAG program */
  if (pflash_read_IBT()) {
    printk(KERN_WARNING "stwx225_init - IBT not found.\n"); 
  }
  
  /* detect special and bad blocks, using IBTs */
  /*printk(KERN_INFO "Creating logical block table...\n");*/
  for (bi=0; bi<STW_MAX_NUM_BLOCKS; bi++) {
    /* check attributes of the block */
#ifndef CONFIG_ROKU_DESCHUTES /* On Deschutes Pflash is on CS1 */
    if ( pflash_block_invalid(CS0, bi, 0) == 0 ) {
      nand_phy_block[bi] = PFLASH_VALID_BLOCK_VALUE;
    }
    else {
      /* printk(KERN_INFO "Special or invalid block detected - cs: %x block: 0x%x.\n",
	 CS0, bi); */
      nand_phy_block[bi] = PFLASH_INVALID_BLOCK_VALUE;
    }
#endif
    if ( pflash_block_invalid(CS1, bi, 0) == 0 ) {
      nand_phy_block[bi+CS1_NAND_MAP_LOC] = PFLASH_VALID_BLOCK_VALUE;
    }
    else {
      printk(KERN_INFO "Special or invalid block detected - cs: %x block: 0x%x.\n",
	 CS1, bi);
      nand_phy_block[bi+CS1_NAND_MAP_LOC] = PFLASH_INVALID_BLOCK_VALUE;
    }
  }

  /* then, create the logical - valid physical block mapping table */
  /*
  fs_block_start = (NAND_ROOT_FS_OFFSET >> BLOCK_NUM_TOTAL_SHIFT);
  fs_block_end = fs_block_start + 
                   (NAND_ROOT_FS_SIZE >> BLOCK_NUM_TOTAL_SHIFT);
  */

  pf_block_start = (NAND_STW_OFFSET >> BLOCK_NUM_TOTAL_SHIFT);
  pf_block_end = pf_block_start + 
                   (NAND_STW_SIZE >> BLOCK_NUM_TOTAL_SHIFT);

  printk(KERN_INFO "NAND:Block_start: 0x%x block end: 0x%x\n", 
     pf_block_start, pf_block_end);

  for ( lcount = 0; lcount < STW_NUM_LOGIC_BLOCKS; lcount++ ){
    nand_logic_block[lcount]=lcount;
  }

  stw_bb = 0;
  gcount = 0;
  for ( lcount = pf_block_start; lcount < pf_block_end; lcount++ ) {
    if ( (lcount+stw_bb) >= STW_NUM_LOGIC_BLOCKS ) {
      printk(KERN_INFO "Reach the capacity of valid blocks (total: 0x%3x).\n", lcount);
      break;
    }
    if ( nand_phy_block[lcount] == PFLASH_VALID_BLOCK_VALUE ) {
      nand_logic_block[gcount] = lcount;
      gcount++;
    }
    else {
      stw_bb++;
      /* printk(KERN_INFO "Special or invalid physical block: 0x%3x, total: 0x%x\n", 
	 lcount, stw_bb); */
    }
  }

  for (np=0; np<NUM_PARTITIONS; np++) {
    nb = (partition_info[np].offset >> BLOCK_NUM_TOTAL_SHIFT);
    printk(/*KERN_INFO*/ "NAND partition %x logical block: 0x%3x physical block: 0x%3x\n", 
	   np, nb, nand_logic_block[nb]);
  }
  /*
   * Reduce the last partition size by the number of blocks we cannot use, plus the number of IBTs
   */
  partition_info [NUM_PARTITIONS-1].size -= ((stw_bb+2) << BLOCK_NUM_TOTAL_SHIFT);
  printk(KERN_INFO "Creating logical block table done.[good %d, bad %d]\n", gcount, stw_bb);
#endif

  /* Register the partitions */
  add_mtd_partitions(stwx225_mtd, 
		     (struct mtd_partition*) partition_info, 
		     (int)NUM_PARTITIONS);

  printk(KERN_INFO "STW4X225 NAND Flash initialization done.\n");
	
  /* Return OK */
  return 0;
}
module_init(stwx225_init);

/*
 * Clean up routine
 */
#ifdef MODULE
static void __exit stwx225_cleanup (void)
{

  struct nand_chip *this = (struct nand_chip *) &stwx225_mtd[1];

  /* Unregister the device */
  del_mtd_device (stwx225_mtd);
  del_mtd_partitions(stwx225_mtd);

  /* Free internal data buffer */
  kfree (this->data_buf);

  /* Free the MTD device structure */
  kfree (stwx225_mtd);
}
module_exit(stwx225_cleanup);
#endif

MODULE_LICENSE("GPL");
MODULE_AUTHOR("czhou@ati.com");
MODULE_DESCRIPTION("STWX225 NAND flash device interface");



