/*
 *
 * Copyright 2006 Sony Corporation.
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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
 *
 */

/* #define DEBUG_X86 */

#include<linux/config.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/proc_fs.h>
#include<linux/delay.h>
#include<linux/errno.h>
#include<linux/sched.h>
#include<linux/moduleparam.h>
#include<linux/notifier.h>
#include<linux/kernel.h>
#include<linux/interrupt.h>
#include<linux/bitops.h>
#include<linux/slab.h>
#include<linux/fs.h>
#include<linux/types.h>
#include<linux/reboot.h>
#include<linux/proc_fs.h>
#include<linux/fcntl.h>
#include<linux/seq_file.h>
#include<linux/cdev.h>
#include<linux/devfs_fs.h>
#include<linux/devfs_fs_kernel.h>

#ifndef DEBUG_X86
#include<linux/mtd/mtd.h>
#include<linux/mtd/nand.h>
#include<linux/mtd/nand_ecc.h>
#include<linux/mtd/compatmac.h>
#include<linux/mtd/partitions.h>
#endif

#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#include<asm/delay.h>

#ifdef DEBUG_X86
#include"../icx1087_nand_bios/icx1087_nand_bios.h"
#include"icx1087_nvp.h"
#else
#include"icx1087_nand_base.h"
#include"icx1087_nand_bios.h"
#include<asm/arch/icx1087_nvp.h>
#endif

/*******************/
/*@ Define & Macro */
/*******************/

/* #define NVP_DEBUG */

/* Device Major Number 0=dynamic */
#define MAJOR_NUMBER 0

/* Loader Control Block Search Count */
#define LCB_SEARCH_COUNT 512

/* Nand Read Retry Count */
#define READ_RETRY 2

/* Init Flag */
#define INIF_REG_MJR 0x00000001
#define INIF_ADD_CDV 0x00000002
#define INIF_MAK_DFS 0x00000004

/* Signature */
#define SIGN_LOAD 0x64414F6C /* Loader Control Block   "lOAd" */
#define SIGN_PART 0x7A524170 /* Partition Table Page   "pARt" */
#define SIGN_BADB 0x00000000 /* Bad Block                     */
#define SIGN_NUSE 0xFFFFFFFF /* Not Use Block or Page         */

/* Area ID */
#define AID_MSC 0x70
#define AID_BTI 0x71
#define AID_HDI 0x72
#define AID_INI 0x73
#define AID_FUI 0x74
#define AID_LBI 0x75
#define AID_RV6 0x76
#define AID_SCR 0x77
#define AID_EKB 0x78
#define AID_RV9 0x79
#define AID_RVA 0x7A
#define AID_RVB 0x7B
#define AID_RVC 0x7C
#define AID_RVD 0x7D
#define AID_RVE 0x7E
#define AID_RVF 0x7F
#define AID_BAD 0x00
#define AID_NOU 0xFF

/* NAND Geometry */
#define PG_SIZE              (2*1024)
#define BLK_SIZE           (256*1024)
#define PG_PER_BLK (BLK_SIZE/PG_SIZE)

/* Read/Write Mode */
#define MODE_DRV 0x00000000
#define MODE_USR 0x00000001
#define MODE_WRT 0x00000002
#define MODE_RED 0x00000004
#define MODE_ERR 0x80000000

/* Macro */
#define b2p(_a)        ((_a)<<7) /* block -> page  */
#define p2b(_a)        ((_a)>>7) /* page  -> block */
#define getwp(_a)      (((_a)+PG_SIZE-1)/PG_SIZE)
#define maximum(_a,_b) ((_a)>(_b)?(_a):(_b))
#define minimum(_a,_b) ((_a)<(_b)?(_a):(_b))
#define zc(_a)         (sizeof(_a)/sizeof(zone_info_t))

#ifdef DEBUG_X86
#define MALLOC(_a,_b) vmalloc((_a))
#define FREE(_a)      vfree((_a))
#else
#define MALLOC(_a,_b) kmalloc((_a),(_b))
#define FREE(_a)      kfree((_a))
#endif

#ifdef NVP_DEBUG
#define dbgprint(fmt, args...) printk(fmt, ##args)
#else
#define dbgprint(fmt, args...)
#endif

typedef unsigned long  dword;
typedef unsigned short word;
typedef unsigned char  byte;

/**************/
/*@ Structure */
/**************/

/* Signature Page [2048 Byte] */
typedef struct {
	dword signature;  /* signature */
	dword dummy[511]; /* dummy     */
}sig_page_t;

/* Partition Table Page [2048 Byte] */
typedef struct{
	dword signature;  /* "pARt"                           */
	dword kernel;     /* kernel area start block          */
	dword param;      /* parameter area start block       */
	dword nand;       /* nand control area start block    */
	dword virphys;    /* virtual physicalarea start block */
	dword dummy[507]; /* dummy                            */
}prt_page_t;

/* node information */
typedef struct{
	int area;
	int zone;
}node_info_t;

/* zone information */
typedef struct{
	int offset;
	int size;
}zone_info_t;

/* area information */
typedef struct{
	const byte         id;         /* identify                    */
	const int          size;       /* area size (page count)      */
	      dword        serial;     /* serial number               */
	      int          block;      /* valid block number          */
	      int          page;       /* valid page (block offset)   */
	const zone_info_t *zone;
	const int          zonecount;
}area_info_t;

/**************/
/*@ Prototype */
/**************/

int icx1087_nvp_init(void);
void icx1087_nvp_exit(void);

static int icx1087_nvp_open(
	struct inode *inode,
	struct file *file
);
static int icx1087_nvp_release(
	struct inode *inode,
	struct file *file
);
static ssize_t icx1087_nvp_write(
	struct file *file,
	const char __user *buf,
	size_t size,
	loff_t *f_pos
);
static ssize_t icx1087_nvp_read(
	struct file *file,
	char __user *buf,
	size_t size,
	loff_t *f_pos
);
static int icx1087_nvp_ioctl(
	struct inode *inode,
	struct file *file,
	unsigned int cmd,
	dword arg
);

static int icx1087_nvp_erase_all(void);
static int icx1087_nvp_get_partition_info(void);
static int icx1087_nvp_analyze_partition(void);
static int icx1087_nvp_search_last_page(int area);
static int icx1087_nvp_get_area_number(byte id);
static void icx1087_nvp_print_stat(void);

static int icx1087_nvp_write_zone_data(int mode, int area, int zone, int size, byte *buf);
static int icx1087_nvp_read_zone_data(int mode, int area, int zone, int size, byte *buf);

static int icx1087_nvp_write_area_data(int mode, int area, int offset, int size, byte *buf);
static int icx1087_nvp_read_area_data(int mode, int area, int offset, int size, byte *buf);
static int icx1087_nvp_write_area_page(int area, int *block, int *page, byte *buf, byte *stat);
static int icx1087_nvp_read_area_page(int area, int block, int page, byte *buf);
static int icx1087_nvp_copy_area(
	int dstblk,
	int dstpag,
	int srcblk,
	int srcpag,
	int count
);

static int icx1087_nvp_init_block_status_table(void);
static int icx1087_nvp_alloc_block(byte id);
static int icx1087_nvp_free_block(int block);
static int icx1087_nvp_defect_block(int block);
static int icx1087_nvp_set_block_id(int block, byte id);

static int icx1087_nvp_nand_init(void);
#if 0
static int icx1087_nvp_nand_erase_write_block(
	int chip,   /* almost case is 0.    */
	int page,   /* head page of block   */
	byte *buf,  /* 256KByte buffer      */
	byte *oob   /* oob of all pages     */
);
#endif
static int icx1087_nvp_nand_write_page(
	int chip,   /* almost case is 0.    */
	int page,   /* absolute page number */
	byte *buf,  /* 2KByte buffer        */
	byte *oob   /* oob data             */
);
#if 0
static int icx1087_nvp_nand_read_block(
	int chip,   /* almost case is 0.    */
	int page,   /* head page of block   */
	byte *buf,  /* 256KByte buffer      */
	byte *oob   /* oob of head page     */
);
#endif
static int icx1087_nvp_nand_read_page(
	int chip,   /* almost case is 0.    */
	int page,   /* absolute page number */
	byte *buf,  /* 2KByte buffer        */
	byte *oob   /* oob data             */
);
static int icx1087_nvp_nand_erase_block(
	int chip,   /* almost case is 0.    */
	int page    /* head page of block   */
);

/********************/
/*@ Global Variable */
/********************/

struct file_operations ICX1087_NVP_Fops = {
	.owner =    THIS_MODULE,
	.open =     icx1087_nvp_open,
	.release =  icx1087_nvp_release,
	.write =    icx1087_nvp_write,
	.read =     icx1087_nvp_read,
	.ioctl =    icx1087_nvp_ioctl,
};

static dword InitFlag=0;
static int MajorNumber=MAJOR_NUMBER;

static int DriverReady=0;

static int LoaderControlBlock=-1;
static int KernelStartBlock=-1;
static int KernelBlockCount=-1;
static int PartitionStartBlock=-1;
static int PartitionBlockCount=-1;
static int NandcontrolStartBlock=-1;
static int NandcontrolBlockCount=-1;
static int VirphysStartBlock=-1;

/* node -> area & zone table */
static const node_info_t NodeInfo[]={
	/* +-----------Area Number         */
	/* |   +-------Zone Number         */
	/* |   |                           */
	{  0,  0 }, /*  0 APP -> MSC - APP */
	{  1, -1 }, /*  1 BTI -> BTI       */
	{  2, -1 }, /*  2 HDI -> HDI       */
	{  3,  0 }, /*  3 CNG -> INI - CNG */
	{  3,  3 }, /*  4 SER -> INI - SER */
	{  7,  5 }, /*  5 DG0 -> DBG - DG0 */
	{  7,  6 }, /*  6 DG1 -> DBG - DG1 */
	{  7,  0 }, /*  7 DCC -> DBG - DCC */
	{  0,  1 }, /*  8 MDL -> MSC - MDL */
	{  0,  8 }, /*  9 FUP -> MSC - FUP */
	{  0,  7 }, /* 10 BOK -> MSC - BOK */
	{  3,  1 }, /* 11 SHP -> INI - SHP */
	{  7,  1 }, /* 12 DBA -> DBG - DBA */
	{  7,  2 }, /* 13 DBV -> DBG - DBV */
	{  8,  0 }, /* 14 TC0 -> TRC - TC0 */
	{  8,  1 }, /* 15 TC1 -> TRC - TC1 */
	{  3,  2 }, /* 16 MID -> INI - MID */
	{  0,  6 }, /* 17 TST -> MSC - TST */
	{  0,  5 }, /* 18 GTY -> MSC - GTY */
	{  4, -1 }, /* 19 FUI -> FUI       */
	{  5, -1 }, /* 20 LBI -> LBI       */
	{  0,  4 }, /* 21 DOR -> MSC - DOR */
	{  0,  3 }, /* 22 EDW -> MSC - EDW */
	{  7,  3 }, /* 23 UBP -> DBG - STR */
};
static const int NodeCount=sizeof(NodeInfo)/sizeof(node_info_t);

/* Misc Parameter Page [2048 Byte] */
static const zone_info_t MSCZone[]={
	/*   +------------Offset                */
	/*   |     +------Size                  */
	/*   |     |                            */
	{    0, 2000}, /* application parameter */
	{ 2000,    8}, /* middleware parameter  */
	{ 2008,   16}, /* dummy                 */
	{ 2024,    4}, /* early shutdown        */
	{ 2028,    4}, /* cng debug or release  */
	{ 2032,    4}, /* getty boot mode       */
	{ 2036,    4}, /* test mode             */
	{ 2040,    4}, /* loader beep ok        */
	{ 2044,    4}  /* firmware update       */
};

/* Init Parameter Page [2048 Byte] */
static const zone_info_t INIZone[]={
	/*   +------------Offset            */
	/*   |     +------Size              */
	/*   |     |                        */
	{    0,  704}, /* configuration     */
	{  704,   32}, /* ship information  */
	{  736,   64}, /* model information */
	{  800,   16}, /* serial number     */
	{  816, 1232}  /* dummy             */
};

/* Debug Parameter Page [2048 Byte] */
static const zone_info_t DBGZone[]={
	/*   +------------Offset       */
	/*   |     +------Size         */
	/*   |     |                   */
	{    0,   20}, /* Debug Clock  */
	{   20,  160}, /* Debug Audio  */
	{  180,  520}, /* Debug Video  */
	{  700,   32}, /* Debug String */
	{  732,  292}, /* dummy        */
	{ 1024,  512}, /* debug 0      */
	{ 1536,  512}  /* debug 1      */
};

/* Trace Parameter Page [32K Byte]   */
static const zone_info_t TRCZone[]={
	/*   +------------Offset       */
	/*   |     +------Size         */
	/*   |     |                   */
	{    0,16384}, /* TRC 0        */
	{16384,16384}  /* TRC 1        */
};

static area_info_t Area[]={
	/*   +--------------------------------------id                     */
	/*   |      +-------------------------------size (page count)      */
	/*   |      | +-----------------------------serial                 */
	/*   |      | |  +--------------------------block                  */
	/*   |      | |  |  +-----------------------page                   */
	/*   |      | |  |  |    +------------------zone info              */
	/*   |      | |  |  |    |        +---------zone count             */
	/*   |      | |  |  |    |        |                                */
	{ AID_MSC,  1,0,-1,-1,MSCZone,zc(MSCZone)}, /*  0: Misc Parameter  */
	{ AID_BTI,128,0,-1,-1,NULL,             0}, /*  1: Boot Image Data */
	{ AID_HDI,128,0,-1,-1,NULL,             0}, /*  2: Hold Image Data */
	{ AID_INI,  1,0,-1,-1,INIZone,zc(INIZone)}, /*  3: Ini Information */
	{ AID_FUI,128,0,-1,-1,NULL,             0}, /*  4: F-Up Image Data */
	{ AID_LBI,128,0,-1,-1,NULL,             0}, /*  5: LBat Image Data */
	{ AID_RV6,  1,0,-1,-1,NULL,             0}, /*  6: Reserved        */
	{ AID_SCR,  1,0,-1,-1,DBGZone,zc(DBGZone)}, /*  7: Debug Data      */
	{ AID_EKB, 16,0,-1,-1,TRCZone,zc(TRCZone)}, /*  8: Trace Zone      */
	{ AID_RV9,  1,0,-1,-1,NULL,             0}, /*  9: Reserved        */
	{ AID_RVA,  1,0,-1,-1,NULL,             0}, /* 10: Reserved        */
	{ AID_RVB,  1,0,-1,-1,NULL,             0}, /* 11: Reserved        */
	{ AID_RVC,  1,0,-1,-1,NULL,             0}, /* 12: Reserved        */
	{ AID_RVD,  1,0,-1,-1,NULL,             0}, /* 13: Reserved        */
	{ AID_RVE,  1,0,-1,-1,NULL,             0}, /* 14: Reserved        */
	{ AID_RVF,  1,0,-1,-1,NULL,             0}  /* 15: Reserved        */
};

static const int AreaCount=sizeof(Area)/sizeof(area_info_t);

static byte *BlockStatus=NULL;

static byte *Buffer=NULL;
static byte *WrBuf=NULL;
static byte *RdBuf=NULL;
static byte *TpBuf=NULL;

static struct mtd_info         Mtd;
static struct icx1087_nand_dev Nand;

static struct semaphore        Semaphore;
static struct cdev             CDev;

/************************/
/*@ User Entry Routines */
/************************/

int icx1087_nvp_init(void)
{
	dev_t dev;
	int rv;
	int i;
#ifdef CONFIG_DEVFS_FS
	char buf[32];
#endif

	printk(KERN_INFO "Starting NVP.\n");

	/****************************/
	/* Initialize Private  Data */
	/****************************/

	init_MUTEX(&Semaphore);

	/* alloc buffer */
	Buffer=(byte *)MALLOC(2048*2,GFP_KERNEL);
	if(Buffer==NULL){
		printk(KERN_ERR "icx1087_nvp: kmalloc(Buffer) error.\n");
		icx1087_nvp_exit();
		return(-ENOMEM);
	}
	WrBuf=(byte *)MALLOC(2048,GFP_KERNEL);
	if(WrBuf==NULL){
		printk(KERN_ERR "icx1087_nvp: kmalloc(WrBuf) error.\n");
		icx1087_nvp_exit();
		return(-ENOMEM);
	}
	RdBuf=(byte *)MALLOC(2048,GFP_KERNEL);
	if(RdBuf==NULL){
		printk(KERN_ERR "icx1087_nvp: kmalloc(RdBuf) error.\n");
		icx1087_nvp_exit();
		return(-ENOMEM);
	}
	TpBuf=(byte *)MALLOC(2048,GFP_KERNEL);
	if(TpBuf==NULL){
		printk(KERN_ERR "icx1087_nvp: kmalloc(TpBuf) error.\n");
		icx1087_nvp_exit();
		return(-ENOMEM);
	}

	/* init nand driver */
	rv=icx1087_nvp_nand_init();
	if(rv<0){
		printk(KERN_ERR "icx1087_nvp: nand_init error.\n");
		icx1087_nvp_exit();
		return(-EIO);
	}

	/* get param partition info */
	rv=icx1087_nvp_get_partition_info();
	if(rv<0){
		icx1087_nvp_exit();
		return(-EIO);
	}

	/* alloc block status table */
	BlockStatus=(byte *)MALLOC(PartitionBlockCount,GFP_KERNEL);
	if(BlockStatus==NULL){
		printk(KERN_ERR "icx1087_nvp: kmalloc(BlockStatus) error.\n");
		icx1087_nvp_exit();
		return(-ENOMEM);
	}

	/* init block status table */
	icx1087_nvp_init_block_status_table();

	/* analyze partition */
	icx1087_nvp_analyze_partition();

	/**************/
	/* Add Driver */
	/**************/

	/* fixed major */
	if(MajorNumber!=0){
		dev=MKDEV(MajorNumber,0);
		rv=register_chrdev_region(dev,1,"icx1087_nvp");
	}

	/* dynamic major */
	else{
		rv=alloc_chrdev_region(&dev,0,1,"icx1087_nvp");
		MajorNumber=MAJOR(dev);
	}

	if(rv<0){
		printk(KERN_ERR "icx1087_nvp: can't get major number. %d\n",MajorNumber);
		icx1087_nvp_exit();
		return(rv);
	}
	InitFlag=InitFlag|INIF_REG_MJR;

	/* initialize cdev */
	cdev_init(&CDev,&ICX1087_NVP_Fops);
	CDev.owner=THIS_MODULE;

	/* add driver */
	/* rv=cdev_add(&CDev,dev,NodeCount+1); */
	rv=cdev_add(&CDev,dev,31);
	if(rv){
		printk(KERN_ERR "icx1087_nvp: cdev_add error. %d\n",rv);
		icx1087_nvp_exit();
		return(rv);
	}
	InitFlag=InitFlag|INIF_ADD_CDV;

#ifdef CONFIG_DEVFS_FS

	/* create devfs dir */
	rv=devfs_mk_dir("icx1087_nvp");
	if(rv){
		printk(KERN_ERR "icx1087_nvp: devfs_mk_dir error. %d\n",rv);
		icx1087_nvp_exit();
		return(rv);
	}
	InitFlag=InitFlag|INIF_MAK_DFS;

	/* create raw i/o node */
	rv=devfs_mk_cdev(
		MKDEV(MajorNumber,30),
		S_IFCHR|S_IRUSR|S_IWUSR,
		"icx1087_nvp/io"
	);
	if(rv){
		printk(KERN_ERR "icx1087_nvp: devfs_mk_cdev(io) error. %d\n",rv);
		icx1087_nvp_exit();
		return(rv);
	}

	/* create node */
	for(i=0;i<NodeCount;i++){

		if(NodeInfo[i].area==-1)
			continue;

		sprintf(buf,"icx1087_nvp/%d",i);
		rv=devfs_mk_cdev(
			MKDEV(MajorNumber,i),
			S_IFCHR|S_IRUSR|S_IWUSR,
			buf
		);
		if(rv){
			printk(KERN_ERR "icx1087_nvp: devfs_mk_cdev(%d) error. %d\n",i,rv);
			icx1087_nvp_exit();
			return(rv);
		}
	}

#endif

	icx1087_nvp_print_stat();

	/* set driver ready */
	DriverReady=1;

	return(0);
}

void icx1087_nvp_exit(void)
{

#ifdef CONFIG_DEVFS_FS
	/* delete devfs */
	if(InitFlag&INIF_MAK_DFS)
		devfs_remove("icx1087_nvp");
#endif

	/* del driver */
	if(InitFlag&INIF_ADD_CDV)
		cdev_del(&CDev);

	/* release major */
	if(InitFlag&INIF_REG_MJR)
		unregister_chrdev_region(MKDEV(MajorNumber,0),1);

	if(BlockStatus)
		FREE(BlockStatus);

	if(TpBuf)
		FREE(TpBuf);

	if(WrBuf)
		FREE(WrBuf);

	if(RdBuf)
		FREE(RdBuf);

	if(Buffer)
		FREE(Buffer);

	return;
}

static int icx1087_nvp_open(struct inode *inode, struct file *file)
{
	/* int node; */

	/* node=iminor(inode); */

	return(0);
}

static int icx1087_nvp_release(struct inode *inode, struct file *file)
{
	/* int node; */

	/* node=iminor(inode); */

	return(0);
}

static ssize_t icx1087_nvp_write(
	struct file *file,
	const char __user *buf,
	size_t size,
	loff_t *f_pos
)
{
	int node;
	int area;
	int zone;
	int count;
	int rv;

	node=iminor((struct inode *)file->f_dentry->d_inode);

	/* check area number */
	if(node<0 || node>=NodeCount){
		printk(KERN_ERR "icx1087_nvp: invalid node. %d\n",node);
		return(-ENODEV);
	}

	area=NodeInfo[node].area;
	zone=NodeInfo[node].zone;

	/* get semaphore */
	if(down_interruptible(&Semaphore))
		return(-ERESTARTSYS);

	rv=icx1087_nvp_write_zone_data(MODE_USR,area,zone,size,(byte *)buf);
	if(rv<0){
		up(&Semaphore);
		return(rv);
	}

	count=rv;

	/* free semaphore */
	up(&Semaphore);

	return(count);
}

static ssize_t icx1087_nvp_read(
	struct file *file,
	char __user *buf,
	size_t size,
	loff_t *f_pos
)
{
	int node;
	int area;
	int zone;
	int count;
	int rv;

	node=iminor((struct inode *)file->f_dentry->d_inode);

	/* check area number */
	if(node<0 || node>=NodeCount){
		printk(KERN_ERR "icx1087_nvp: invalid node. %d\n",node);
		return(-ENODEV);
	}

	area=NodeInfo[node].area;
	zone=NodeInfo[node].zone;

	/* get semaphore */
	if(down_interruptible(&Semaphore))
		return(-ERESTARTSYS);

	rv=icx1087_nvp_read_zone_data(MODE_USR,area,zone,size,(byte *)buf);
	if(rv<0){
		up(&Semaphore);
		return(rv);
	}

	count=rv;

	/* free semaphore */
	up(&Semaphore);

	return(count);
}

static int icx1087_nvp_ioctl(
	struct inode *inode,
	struct file  *file,
	unsigned int  cmd,
	dword         arg
)
{
	icx1087_nvp_ioctl_t *dbg;
	int rv;
	int i;

	/* get semaphore */
	if(down_interruptible(&Semaphore))
		return(-ERESTARTSYS);

	/* check parameter */
	switch(cmd) {

		case ICX1087_NVP_SHOW:
			icx1087_nvp_print_stat();
			for(i=0;i<PartitionBlockCount;i++){
				if(i%16==0)
					printk("  %3d",PartitionStartBlock+i);
				printk(" %02X",BlockStatus[i]);
				if(i%16==7)
					printk(" -");
				if(i%16==15)
					printk("\n");
			}
			if(i%16!=0)
				printk("\n");
			break;

		case ICX1087_NVP_WRITE:
			rv=copy_from_user((void *)Buffer,(void *)arg,sizeof(icx1087_nvp_ioctl_t));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_from_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			dbg=(icx1087_nvp_ioctl_t *)Buffer;
			rv=icx1087_nvp_nand_write_page(0,dbg->page,dbg->data,dbg->oob);
			if(rv<0){
				printk(KERN_ERR "icx1087_nvp: write error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_READ:
			rv=copy_from_user((void *)Buffer,(void *)arg,sizeof(icx1087_nvp_ioctl_t));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_from_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			dbg=(icx1087_nvp_ioctl_t *)Buffer;
			rv=icx1087_nvp_nand_read_page(0,dbg->page,dbg->data,dbg->oob);
			if(rv<0){
				printk(KERN_ERR "icx1087_nvp: read error\n");
				up(&Semaphore);
				return(-EIO);
			}
			rv=copy_to_user((void *)arg,(void *)Buffer,sizeof(icx1087_nvp_ioctl_t));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_ERASE:
			rv=copy_from_user((void *)Buffer,(void *)arg,sizeof(icx1087_nvp_ioctl_t));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_from_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			dbg=(icx1087_nvp_ioctl_t *)Buffer;
			rv=icx1087_nvp_nand_erase_block(0,dbg->page);
			if(rv<0){
				printk(KERN_ERR "icx1087_nvp: erase error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_KSB:
			rv=copy_to_user((void *)arg,(void *)&KernelStartBlock,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_KBC:
			rv=copy_to_user((void *)arg,(void *)&KernelBlockCount,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_PSB:
			rv=copy_to_user((void *)arg,(void *)&PartitionStartBlock,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_PBC:
			rv=copy_to_user((void *)arg,(void *)&PartitionBlockCount,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_NSB:
			rv=copy_to_user((void *)arg,(void *)&NandcontrolStartBlock,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_NBC:
			rv=copy_to_user((void *)arg,(void *)&NandcontrolBlockCount,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_GET_CSB:
			rv=copy_to_user((void *)arg,(void *)&VirphysStartBlock,sizeof(int));
			if(rv!=0){
				printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
				up(&Semaphore);
				return(-EIO);
			}
			break;

		case ICX1087_NVP_ERASE_ALL:
			rv=icx1087_nvp_erase_all();
			if(rv!=0){
				up(&Semaphore);
				return(-EIO);
			}
			break;

		default:
			printk(KERN_ERR "icx1087_nvp: illegal request\n");
			up(&Semaphore);
			return(-ENOTTY);
	}

	/* free semaphore */
	up(&Semaphore);

	return(0);
}

/********************/
/*@ Export Routines */
/********************/

int icx1087_nvp_write_data(int node, byte *buf, int size)
{
	int area;
	int zone;
	int count;
	int rv;

	if(DriverReady==0)
		return(-EBUSY);

	/* check area number */
	if(node<0 || node>=NodeCount){
		printk(KERN_ERR "icx1087_nvp: invalid node. %d\n",node);
		return(-ENODEV);
	}

	area=NodeInfo[node].area;
	zone=NodeInfo[node].zone;

	/* get semaphore */
	if(down_interruptible(&Semaphore))
		return(-ERESTARTSYS);

	rv=icx1087_nvp_write_zone_data(MODE_DRV,area,zone,size,buf);
	if(rv<0){
		up(&Semaphore);
		return(rv);
	}

	count=rv;

	/* free semaphore */
	up(&Semaphore);

	return(count);
}

int icx1087_nvp_read_data(int node, byte *buf, int size)
{
	int area;
	int zone;
	int count;
	int rv;

	if(DriverReady==0)
		return(-EBUSY);

	/* check area number */
	if(node<0 || node>=NodeCount){
		printk(KERN_ERR "icx1087_nvp: invalid node. %d\n",node);
		return(-ENODEV);
	}

	area=NodeInfo[node].area;
	zone=NodeInfo[node].zone;

	/* get semaphore */
	if(down_interruptible(&Semaphore))
		return(-ERESTARTSYS);

	rv=icx1087_nvp_read_zone_data(MODE_DRV,area,zone,size,buf);
	if(rv<0){
		up(&Semaphore);
		return(rv);
	}

	count=rv;

	/* free semaphore */
	up(&Semaphore);

	return(count);
}

/************************/
/*@ Initialize Routines */
/************************/

static int icx1087_nvp_erase_all()
{
	int i;

	/* erase nvp area */
	for(i=0;i<PartitionBlockCount;i++)
		icx1087_nvp_nand_erase_block(0,b2p(PartitionStartBlock+i));

	/* init block status table */
	icx1087_nvp_init_block_status_table();

	/* init area info */
	for(i=0;i<AreaCount;i++){
		Area[i].serial=0;
		Area[i].block=-1;
		Area[i].page=-1;
	}

	/* analyze partition */
	icx1087_nvp_analyze_partition();

	return(0);
}

static int icx1087_nvp_get_partition_info()
{
#ifndef DEBUG_X86

	sig_page_t *sig;
	prt_page_t *prt;
	int got;
	int rv;
	int i;

	sig=(sig_page_t *)Buffer;
	prt=(prt_page_t *)(Buffer+2048);

	got=0;

	for(i=1;i<LCB_SEARCH_COUNT;i++){

		/* read first page of block */
		rv=icx1087_nvp_nand_read_page(0,b2p(i),(byte *)sig,NULL);
		if(rv<0)
			continue;

		/* check signature */
		if(got!=0){ /* have valid data */
			if(sig->signature!=SIGN_LOAD
			&& sig->signature!=SIGN_BADB)
				break;
		}
		if(sig->signature!=SIGN_LOAD)
			continue;

		/* read second page of block */
		rv=icx1087_nvp_nand_read_page(0,b2p(i)+1,(byte *)prt,NULL);
		if(rv<0)
			continue;

		KernelStartBlock=(int)prt->kernel;
		KernelBlockCount=(int)(prt->param-prt->kernel);
		PartitionStartBlock=(int)prt->param;
		PartitionBlockCount=(int)(prt->nand-prt->param);
		NandcontrolStartBlock=(int)prt->nand;
		NandcontrolBlockCount=(int)(prt->virphys-prt->nand);
		VirphysStartBlock=(int)prt->virphys;
		got=i;
	}

	if(got==0){
		printk(
			KERN_ERR
			"icx1087_nvp: no valid loader control block\n"
		);
		/* return(-EIO); */
		LoaderControlBlock=1;
		KernelStartBlock=-1;
		KernelBlockCount=-1;
		PartitionStartBlock=128;
		PartitionBlockCount=64;
		NandcontrolStartBlock=192;
		NandcontrolBlockCount=16;
		VirphysStartBlock=208;
		return(0);
	}

	LoaderControlBlock=got;

#else

	LoaderControlBlock=1;
	KernelStartBlock=-1;
	KernelBlockCount=-1;
	PartitionStartBlock=128;
	PartitionBlockCount=64;
	NandcontrolStartBlock=192;
	NandcontrolBlockCount=16;
	VirphysStartBlock=208;

#endif

	return(0);
}

static int icx1087_nvp_analyze_partition()
{
	int    area;
	byte   oob[6];
	dword  serial;
	int    rv;
	int    i;

	/* Search Valid Block & Bad Block */
	for(
		i=PartitionStartBlock;
		i<PartitionStartBlock+PartitionBlockCount;
		i++
	){

		/* read first page of block */
		rv=icx1087_nvp_nand_read_page(0,b2p(i),Buffer,oob);
		if(rv<0)
			continue;

		/* get area number */
		area=icx1087_nvp_get_area_number(oob[0]);
		if(area<0){
			if(oob[0]!=AID_NOU)
				icx1087_nvp_set_block_id(i,AID_BAD); /* not erase */
			continue;
		}

		/* calc serial number */
		serial=(oob[5]<<24)|(oob[4]<<16)|(oob[3]<<8)|(oob[2]);

		/* if old data */
		if(serial<=Area[area].serial)
			continue;

		/* if multi page area */
		if(Area[area].size!=1){

			/* read last page of first data in block */
			rv=icx1087_nvp_nand_read_page(0,b2p(i)+Area[area].size-1,Buffer,oob);

			/* mismatch id */
			if(oob[0]!=Area[area].id)
				continue;

			/* mismatch serial */
			if(((oob[5]<<24)|(oob[4]<<16)|(oob[3]<<8)|(oob[2]))!=serial+Area[area].size-1)
				continue;
		}

		/* valid block */
		Area[area].block=i;
		Area[area].serial=serial;
	}

	/* search last page in block */
	for(i=0;i<AreaCount;i++){
		if(Area[i].block!=-1){
			rv=icx1087_nvp_search_last_page(i);
			if(rv<0)
				Area[i].block=-1;
		}
	}

	/* set valid block */
	for(i=0;i<AreaCount;i++){
		if(Area[i].block!=-1)
			icx1087_nvp_set_block_id(Area[i].block,Area[i].id);
	}

	return(0);
}

static int icx1087_nvp_search_last_page(int area)
{
	byte        oob[6];
	dword       serial;
	int         got;
	int         rv;
	int         i;

	got=0;

	Area[area].page=0;
	Area[area].serial=0;

	for(i=0;i<PG_PER_BLK;i=i+Area[area].size){

		/* read last page ob data */
		rv=icx1087_nvp_nand_read_page(
			0,
			b2p(Area[area].block)+i+Area[area].size-1,
			Buffer,
			oob
		);
		if(rv<0)
			continue; /* maybe bad page */

		/* already got and virgin -> goooood */
		if(got && oob[0]==AID_NOU)
			break;

		if(oob[0]!=Area[area].id)
			continue; /* maybe bad page or virgin */

		serial=(oob[5]<<24)|(oob[4]<<16)|(oob[3]<<8)|(oob[2]);
		if(serial>Area[area].serial){
			Area[area].page=i;
			Area[area].serial=serial;
			got=1;
		}
	}

	if(got==0)
		return(-EIO);

	return(0);
}

int icx1087_nvp_get_area_number(byte id)
{
	int i;

	for(i=0;i<AreaCount;i++){
		if(Area[i].id==id)
			break;
	}

	if(i==AreaCount)
		return(-1);

	return(i);
}

static void icx1087_nvp_print_stat()
{
	int i;

	printk(
		KERN_INFO
		"  LCB:%d KSB:%d KBC:%d PSB:%d PBC:%d\n",
		LoaderControlBlock,
		KernelStartBlock,
		KernelBlockCount,
		PartitionStartBlock,
		PartitionBlockCount
	);

	printk(KERN_INFO "  ID  SZ  BK   PG   SERIAL\n");
	for(i=0;i<AreaCount;i++){
		if(Area[i].block!=-1){
			printk(KERN_INFO "  %02X %3d %3d %4d %08X\n",
				Area[i].id,
				Area[i].size,
				Area[i].block,
				Area[i].page,
				(int)Area[i].serial
			);
		}
	}
}

/*************************/
/*@ Zone Access Routines */
/*************************/

static int icx1087_nvp_write_zone_data(int mode, int area, int zone, int size, byte *buf)
{
	int offset;
	int rv;

	dbgprint("**** ZONE WRITE *****\n");

	if(area<0 || area>=AreaCount){
		printk(KERN_ERR "icx1087_nvp: invalid area. %d\n",area);
		return(-ENODEV);
	}

	if(zone>=Area[area].zonecount){
		printk(KERN_ERR "icx1087_nvp: invalid zone. %d-%d\n",area,zone);
		return(-ENODEV);
	}

	dbgprint("area=%d\n",area);
	dbgprint("zone=%d\n",zone);
	dbgprint("size=%d\n",size);

	if(zone<0){
		offset=0;
		size=minimum(size,Area[area].size*PG_SIZE);
	}
	else{
		offset=Area[area].zone[zone].offset;
		size=minimum(size,Area[area].zone[zone].size);
	}

	rv=icx1087_nvp_write_area_data(mode,area,offset,size,buf);
	if(rv<0)
		return(rv);

	return(rv);
}

static int icx1087_nvp_read_zone_data(int mode, int area, int zone, int size, byte *buf)
{
	int offset;
	int rv;

	dbgprint("**** ZONE READ *****\n");

	if(area<0 || area>=AreaCount){
		printk(KERN_ERR "icx1087_nvp: invalid area. %d\n",area);
		return(-ENODEV);
	}

	if(zone>=Area[area].zonecount){
		printk(KERN_ERR "icx1087_nvp: invalid zone. %d-%d\n",area,zone);
		return(-ENODEV);
	}

	dbgprint("area=%d\n",area);
	dbgprint("zone=%d\n",zone);
	dbgprint("size=%d\n",size);

	if(zone<0){
		offset=0;
		size=minimum(size,Area[area].size*PG_SIZE);
	}
	else{
		offset=Area[area].zone[zone].offset;
		size=minimum(size,Area[area].zone[zone].size);
	}

	rv=icx1087_nvp_read_area_data(mode,area,offset,size,buf);
	if(rv<0)
		return(rv);

	return(rv);
}

/*************************/
/*@ Area Access Routines */
/*************************/

static int icx1087_nvp_write_area_data(int mode, int area, int offset, int size, byte *buf)
{
	byte stat;
	int block;
	int page;
	int remain;
	int count;
	int delta;
	int rv;
	int i;

	dbgprint("**** AREA WRITE *****\n");

	/* area number check */
	if(area<0 || area>=AreaCount){
		printk(KERN_ERR "icx1087_nvp: invalid area. %d\n",area);
		return(-ENODEV);
	}

	if(offset<0 || offset>Area[area].size*PG_SIZE){
		printk(KERN_ERR "icx1087_nvp: invalid offset. %d\n",offset);
		return(-EINVAL);
	}

	if(size<0){
		printk(KERN_ERR "icx1087_nvp: invalid size. %d\n",size);
		return(-EINVAL);
	}

	dbgprint("area=%d\n",area);
	dbgprint("offset=%d\n",offset);
	dbgprint("size=%d\n",size);

	block=Area[area].block;
	stat=AID_NOU;

	if(block<0){
		block=icx1087_nvp_alloc_block(Area[area].id);
		if(block<0)
			return(-EIO);
		page=0;
		dbgprint("block(init)=%d\n",block);
		dbgprint("page=%d\n",page);
	}
	else{
		page=Area[area].page+Area[area].size;
		if(page>=PG_PER_BLK){
			block=icx1087_nvp_alloc_block(Area[area].id);
			if(block<0)
				return(-EIO);
			page=0;
			dbgprint("block(nospc)=%d\n",block);
			dbgprint("page=%d\n",page);
		}
		else{
			dbgprint("block=%d\n",block);
			dbgprint("page=%d\n",page);
		}
	}

	remain=minimum(size,Area[area].size*PG_SIZE-offset);
	count=0;

	for(i=0;i<Area[area].size;i++){

		dbgprint("  offset=%d\n",offset);
		dbgprint("  remain=%d\n",remain);

		/* read page nand ... */
		rv=icx1087_nvp_read_area_page(area,Area[area].block,Area[area].page+i,WrBuf);
		if(rv<0)
			return(rv);

		if(offset<PG_SIZE && remain>0){

			delta=minimum(remain,PG_SIZE-offset);
			dbgprint("  delta=%d\n",delta);

			/* copy user buffer to local buffer */
			if(mode&MODE_USR){
				rv=copy_from_user(WrBuf+offset,buf,delta);
				if(rv!=0){
					printk(KERN_ERR "icx1087_nvp: copy_from_user error.\n");
					return(-EIO);
				}
			}
			else{
				memcpy(WrBuf+offset,buf,delta);
			}

			remain=remain-delta;
			count=count+delta;
			buf=buf+delta;
		}

		offset=maximum(offset-PG_SIZE,0);

		/* write page nand ... */
		rv=icx1087_nvp_write_area_page(area,&block,&page,WrBuf,&stat);
		if(rv<0)
			return(-EIO);

		page=page+1;
	}

	/* free pre block */
	if(block!=Area[area].block){
		if(stat==AID_BAD)
			icx1087_nvp_defect_block(Area[area].block);
		else
			icx1087_nvp_free_block(Area[area].block);
		Area[area].block=block;
	}

	/* update page info */
	Area[area].page=page-Area[area].size;

	return(count);
}

static int icx1087_nvp_read_area_data(int mode, int area, int offset, int size, byte *buf)
{
	int block;
	int page;
	int remain;
	int count;
	int delta;
	int rv;
	int i;

	dbgprint("**** AREA READ *****\n");

	/* area number check */
	if(area<0 || area>=AreaCount){
		printk(KERN_ERR "icx1087_nvp: invalid area. %d\n",area);
		return(-ENODEV);
	}

	if(offset<0 || offset>Area[area].size*PG_SIZE){
		printk(KERN_ERR "icx1087_nvp: invalid offset. %d\n",offset);
		return(-EINVAL);
	}

	if(size<0){
		printk(KERN_ERR "icx1087_nvp: invalid size. %d\n",size);
		return(-EINVAL);
	}

	dbgprint("area=%d\n",area);
	dbgprint("offset=%d\n",offset);
	dbgprint("size=%d\n",size);

	block=Area[area].block;
	page=Area[area].page;
	dbgprint("block=%d\n",block);
	dbgprint("page=%d\n",page);

	remain=minimum(size,Area[area].size*PG_SIZE-offset);
	count=0;

	for(i=0;i<Area[area].size;i++){

		dbgprint("  offset=%d\n",offset);
		dbgprint("  remain=%d\n",remain);

		if(offset<PG_SIZE && remain>0){

			/* read page nand ... */
			rv=icx1087_nvp_read_area_page(area,block,page,RdBuf);
			if(rv<0)
				return(rv);

			delta=minimum(remain,PG_SIZE-offset);
			dbgprint("  delta=%d\n",delta);

			/* copy local buffer to user buffer */
			if(mode&MODE_USR){
				rv=copy_to_user(buf,RdBuf+offset,delta);
				if(rv!=0){
					printk(KERN_ERR "icx1087_nvp: copy_to_user error\n");
					return(-EIO);
				}
			}
			else{
				memcpy(buf,RdBuf+offset,delta);
			}

			remain=remain-delta;
			count=count+delta;
			buf=buf+delta;
		}

		offset=maximum(offset-PG_SIZE,0);

		page=page+1;
	}

	return(count);
}

static int icx1087_nvp_write_area_page(int area, int *block, int *page, byte *buf, byte *stat)
{
	byte  oob[6];
	int   rv;
	int   t_block;
	int   t_page;
	dword dw;

	while(1){

		Area[area].serial=Area[area].serial+1;
		dw=Area[area].serial;

		oob[0]=Area[area].id;
		oob[1]=0;
		oob[2]=(dw    )&0xFF;
		oob[3]=(dw>> 8)&0xFF;
		oob[4]=(dw>>16)&0xFF;
		oob[5]=(dw>>24)&0xFF;

		rv=icx1087_nvp_nand_write_page(0,b2p(*block)+*page,buf,oob);
		if(rv>=0){
			/* TODO no retry */
			rv=icx1087_nvp_nand_read_page(0,b2p(*block)+*page,TpBuf,oob);
			if(rv>=0)
				break;
		}

		/* if write error */
		while(1){

			/* alloc new block */
			t_block=icx1087_nvp_alloc_block(Area[area].id);
			if(t_block<0)
				return(-EIO);
			t_page=0;

			dbgprint("block(error)=%d\n",t_block);
			dbgprint("page=%d\n",t_page);

			/* copy */
			rv=icx1087_nvp_copy_area(
				t_block,
				t_page,
				*block,
				*page/Area[area].size*Area[area].size,
				*page%Area[area].size
			);
			t_page=*page%Area[area].size;
			if(rv>=0)
				break;

			/* regist defect */
			if(t_block!=Area[area].block)
				icx1087_nvp_defect_block(t_block);
		}

		/* regist defect */
		if(*block!=Area[area].block)
			icx1087_nvp_defect_block(*block);
		else
			*stat=AID_BAD;

		*block=t_block;
		*page=t_page;
	}

	return(0);
}

static int icx1087_nvp_read_area_page(int area, int block, int page, byte *buf)
{
	byte  oob[6];
	int rv;

	if(block<0 || page<0){
		memset(buf,0xFF,PG_SIZE);
		return(0);
	}

	rv=icx1087_nvp_nand_read_page(0,b2p(block)+page,buf,oob);
	if(rv<0)
		return(rv);

	return(0);
}

static int icx1087_nvp_copy_area(
	int dstblk,
	int dstpag,
	int srcblk,
	int srcpag,
	int count
)
{
	byte oob[6];
	int rv;
	int i;

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

		rv=icx1087_nvp_nand_read_page(0,b2p(srcblk)+srcpag+i,TpBuf,oob);
		if(rv<0)
			break;

		rv=icx1087_nvp_nand_write_page(0,b2p(dstblk)+dstpag+i,TpBuf,oob);
		if(rv<0)
			break;

		rv=icx1087_nvp_nand_read_page(0,b2p(dstblk)+dstpag+i,TpBuf,oob);
		if(rv<0)
			break;
	}

	if(i!=count)
		return(-EIO);

	return(0);
}

/*************************/
/*@ Block Status Routine */
/*************************/

static int icx1087_nvp_init_block_status_table()
{
	int i;

	for(i=0;i<PartitionBlockCount;i++)
		BlockStatus[i]=AID_NOU;

	return(0);
}

static int icx1087_nvp_alloc_block(byte id)
{
	int level;
	int rsv;
	int cnt;
	int rnd;
	int rv;
	int i;

	for(level=0;level<2;level++){

		while(1){

			/* count available blocks */
			cnt=0;
			for(i=0;i<PartitionBlockCount;i++){
				if(BlockStatus[i]==AID_NOU)
					cnt++;
			}

			/* no available block */
			if(cnt==0)
				break;

			/* make random value */
			rnd=(jiffies)%cnt;

			/* choice avilable block */
			for(i=0;i<PartitionBlockCount;i++){

				if(BlockStatus[i]==AID_NOU){

					if(rnd==0)
						break;

					rnd--;
				}
			}
			rsv=PartitionStartBlock+i;

			/* block erase */
			rv=icx1087_nvp_nand_erase_block(0,b2p(rsv));
			if(rv>=0){
				icx1087_nvp_set_block_id(rsv,id);
				return(rsv);
			}

			/* regist defect block */
			icx1087_nvp_defect_block(rsv);
		}

		if(level==0){
			printk(KERN_ERR "icx1087_nvp: try to use bad block.\n");

			/* clear bad mark */
			for(i=0;i<PartitionBlockCount;i++){
				if(BlockStatus[i]==AID_BAD)
					BlockStatus[i]=AID_NOU;
			}
		}
	}

	printk(KERN_ERR "icx1087_nvp: no available block\n");

	return(-1);
}

static int icx1087_nvp_free_block(int block)
{
	int rv;

	rv=icx1087_nvp_set_block_id(block,AID_NOU);

	return(rv);
}

static int icx1087_nvp_defect_block(int block)
{
	byte oob[6];
	int rv;

	printk(KERN_INFO "icx1087_nvp: defect block: %d\n",block);

	memzero(TpBuf,PG_SIZE);
	memset(oob,0x00,6);
	oob[0]=AID_BAD;

	icx1087_nvp_nand_erase_block(0,b2p(block));
	icx1087_nvp_nand_write_page(0,b2p(block),TpBuf,oob);

	rv=icx1087_nvp_set_block_id(block,AID_BAD);

	return(rv);
}

static int icx1087_nvp_set_block_id(int block, byte id)
{
	if(block<PartitionStartBlock
	|| block>=PartitionStartBlock+PartitionBlockCount)
		return(-1);

	dbgprint("block %d id=%02X\n",block,id);

	BlockStatus[block-PartitionStartBlock]=id;

	return(0);
}

/***********************/
/*@ NAND BIOS Routines */
/***********************/

static int icx1087_nvp_nand_init()
{
	struct icx1087_nand_dev *nand;

	memset( &Mtd, 0, sizeof( Mtd ) );
	memset( &Nand, 0, sizeof( Nand ) );

	/* get nand bios function pointer. */
	nand=icx1087_nand_bios_get();
	if(nand==NULL)
		return(-EIO);

	/* copy function pointer not to change original. */
	Nand = *nand;

	/* report nand function pointer to mtd. */
	Mtd.priv  = &Nand;

	return(0);
}

#if 0
static int icx1087_nvp_nand_erase_write_block(
	int chip,   /* almost case is 0.  */
	int page,   /* head page of block */
	byte *buf,  /* 256KByte buffer    */
	byte *oob   /* oob of all pages   */
)
{
	const byte tmp[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
	int rv;
	int i;

	/* exclusive control */
	Nand.get_device(&Mtd,FL_WRITING);

	Nand.select_chip(&Mtd,chip);

	rv=Nand.erase_block(&Mtd,page);
	if(rv==0){

		for(i=0;i<128;i++){
			if(oob==NULL)
				rv=Nand.write_page(&Mtd,page+i,buf+2048*i,tmp);
			else
				rv=Nand.write_page(&Mtd,page+i,buf+2048*i,oob);

			if(rv!=0)
				break;
		}
	}

	/* exclusive control */
	Nand.release_device(&Mtd);

	if(rv!=0)
		return(-EIO);

	return(0);
}
#endif

static int icx1087_nvp_nand_write_page(
	int chip,   /* almost case is 0.    */
	int page,   /* absolute page number */
	byte *buf,  /* 2KByte buffer        */
	byte *oob   /* oob data             */
)
{
	const byte tmp[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
	int rv;

	/* exclusive control */
	Nand.get_device(&Mtd,FL_WRITING);

	Nand.select_chip(&Mtd,chip);

	if(oob==NULL)
		rv=Nand.write_page(&Mtd,page,buf,(byte *)tmp);
	else
		rv=Nand.write_page(&Mtd,page,buf,oob);

	/* exclusive control */
	Nand.release_device(&Mtd);

	dbgprint("    ===> W %d:%d (%d)\n",p2b(page),(page&0x7F),rv);

	if(rv!=0)
		return(-EIO);

	return(0);
}

#if 0
static int icx1087_nvp_nand_read_block(
	int chip,   /* almost case is 0.  */
	int page,   /* head page of block */
	byte *buf,  /* 256KByte buffer    */
	byte *oob   /* oob of head page   */
)
{
	byte tmp[6];
	int rv;
	int i;

	/* exclusive control */
	Nand.get_device(&Mtd,FL_READING);

	Nand.select_chip(&Mtd,chip);

	for(i=0;i<128;i++){
		if(oob==NULL || i!=0)
			rv=Nand.read_page(&Mtd,page+i,buf+2048*i,tmp);
		else
			rv=Nand.read_page(&Mtd,page+i,buf+2048*i,oob);

		if(rv!=0)
			break;
	}

	/* exclusive control */
	Nand.release_device( &Mtd );

	if(rv!=0)
		return(-EIO);

	return(0);
}
#endif

static int icx1087_nvp_nand_read_page(
	int chip,   /* almost case is 0.    */
	int page,   /* absolute page number */
	byte *buf,  /* 2KByte buffer        */
	byte *oob   /* oob data             */
)
{
	byte tmp[6];
	int rv;
	int i;

	/* exclusive control */
	Nand.get_device(&Mtd,FL_READING);

	Nand.select_chip(&Mtd,chip);

	for(i=0;i<READ_RETRY;i++){
		if(oob==NULL)
			rv=Nand.read_page(&Mtd,page,buf,tmp);
		else
			rv=Nand.read_page(&Mtd,page,buf,oob);

		if(rv==0)
			break;
	}

	/* exclusive control */
	Nand.release_device( &Mtd );

	if(oob==NULL){
		dbgprint(
			"    ===> R %d:%d (%d)\n",
			p2b(page),
			(page&0x7F),
			rv
		);
	}
	else{
		dbgprint(
			"    ===> R %d:%d (%d) %02X %02X %02X %02X %02X %02X\n",
			p2b(page),
			(page&0x7F),
			rv,
			oob[0],
			oob[1],
			oob[2],
			oob[3],
			oob[4],
			oob[5]
		);
	}

	if(rv!=0)
		return(-EIO);

	return(0);
}

static int icx1087_nvp_nand_erase_block(
	int chip,  /* almost case is 0.  */
	int page   /* head page of block */
)
{
	int rv;

	/* exclusive control */
	Nand.get_device(&Mtd,FL_ERASING);

	Nand.select_chip(&Mtd,chip);

	rv=Nand.erase_block(&Mtd,page);

	/* exclusive control */
	Nand.release_device(&Mtd);

	dbgprint("    ===> E %d (%d)\n",p2b(page),rv);

	if(rv!=0)
		return(-EIO);

	return(0);
}

module_init(icx1087_nvp_init);
module_exit(icx1087_nvp_exit);
EXPORT_SYMBOL(icx1087_nvp_read_data);
EXPORT_SYMBOL(icx1087_nvp_write_data);
MODULE_AUTHOR("Sony Corporation");
MODULE_DESCRIPTION("Sony ICX1087 NVP device driver");
MODULE_LICENSE("GPL");
