/*
 *
 * Copyright 2004 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * 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
 */

#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <asm/arch/clock.h>
#include <asm/io.h>
#include "mxc_nd.h"

#ifdef CONFIG_ARCH_SCMA11BB
/* Define default values set for 64MB NAND chip */
#define DEF_PAGE_SIZE   512
#define DEF_OOB_SIZE    16
#define DEF_ERASE_SIZE  16384
#define DEF_MAX_BLOCKS  4096
#endif

/* 
 * Define delays in microsec for NAND device operations
 */
#define TRST_US_DELAY   500
#define TPROG_US_DELAY  500
#define TROP_US_DELAY   100

struct nand_info {
        bool bSpareOnly;
        bool bStatusRequest;
        u16 colAddr;
};

static struct nand_info g_nandfc_info;
    
#if CONFIG_MTD_NAND_MXC_SWECC
static int hardware_ecc = 0;
#else
static int hardware_ecc = 1;
#endif

#ifdef CONFIG_MTD_NAND_MXC_16BIT
static int busw = NAND_BUSWIDTH_16;
#else
static int busw = 0;
#endif

static bool nand_debug = false;

/* 
 * OOB placement block for use with hardware ecc generation
 */
static struct nand_oobinfo nand_hw_eccoob = {
    .useecc = MTD_NANDECC_AUTOPLACE,
    .eccbytes = 5,
    .eccpos = { 6, 7, 8, 9, 10 },
    .oobfree = { {0, 5} , {11, 5} }
};

#define STATIC static

/*!
 * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors
 */

/*!
 * @file mxc_nd.c
 * 
 * @brief This file contains the hardware specific layer for NAND Flash on 
 * MXC processor
 * 
 * @ingroup NAND_MTD
 */

/*!
 * MTD structure for NAND MTD.
 */
static struct mtd_info *mxc_mtd = NULL;

#ifdef CONFIG_ARCH_SCMA11BB
/*!
 * This array is used by parse_mtd_partitions() for dynamic partition parsing.
 * The proper partition parsing functions will be called in the order as they
 * appear in this array. For the BB port, the Redboot partition parsing
 * function is called first and then the command line parsing function. If a
 * valid partition table is found by any of these dynamic partition parsing
 * functions, the static partition table won't be used.
 */
extern const char *probes[];
extern struct mtd_partition *parsed_parts;
#endif  /* CONFIG_ARCH_SCMA11BB */

/*!
 * This table contains the static partition information for the NAND flash. 
 * It is only used by add_mtd_partitions() for static partition registration to 
 * the upper MTD driver. See include/linux/mtd/partitions.h for definition of 
 * the mtd_partition structure.
 */
static struct mtd_partition partition_info[] = {
        { 
                .name = "IPL-SPL",
                .offset = 0,
                .size = 0x4000 
        },
        { 
                .name = "Kernel",
                .offset = 0x4000,
                .size = 4*1024*1024 
        },
        { 
                .name = "ROOTFS.CRAMFS",
                .offset = MTDPART_OFS_APPEND,
                .size = 13*1024*1024 
        },
        { 
                .name = "JFFS2",
                .offset =  MTDPART_OFS_APPEND,
				.size = 13*1024*1024
	},
#ifdef CONFIG_ARCH_SCMA11BB
	{
		.name = "YAFFS",
		.offset = MTDPART_OFS_APPEND,
		.size = 0x1000000
	},
	{
		.name = "Other",
		.offset = MTDPART_OFS_APPEND,
                .size =    MTDPART_SIZ_FULL
        }
#endif		
};

#if 1
/*!
 * This function polls the NANDFC to wait for the basic operation to complete by
 * checking the INT bit of config2 register.
 * 
 * @param       maxRetries     number of retry attempts (separated by 1 us)
 * @param       param          parameter for debug
 */ 
STATIC void wait_op_done(u32 maxRetries, u16 param)
{
        while (maxRetries-- > 0) {
                if (NFC_CONFIG2 & NFC_INT) {
                        NFC_CONFIG2 &= ~NFC_INT;
                        break;
                }
                udelay(1);  
        }
        if ( maxRetries <=0)
        {
                DEBUG (MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", __FUNCTION__, param);

        }
}
#else   /* Use macro while debugging */
#define wait_op_done(_count, _param)                    \
{                                                       \
        u32 maxRetries = _count;                        \
        while (maxRetries-- > 0)                        \
        {                                               \
                if (NFC_CONFIG2 & NFC_INT) {            \
                        NFC_CONFIG2 &= ~NFC_INT;        \
                        break;                          \
                }                                       \
                udelay(1);                              \
        }                                               \
        if (maxRetries <=0 )                            \
        {                                               \
                DEBUG (MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", __FUNCTION__, _param);\
        }                                               \
}
#endif

/*!
 * This function issues the specified command to the NAND device and
 * waits for completion.
 * 
 * @param       cmd     command for NAND Flash  
 */ 
STATIC void send_cmd(u16 cmd)
{        
        if (nand_debug) 
                DEBUG (MTD_DEBUG_LEVEL3, "send_cmd(0x%x)\n", cmd);

        /* Delay required for send_cmd to succeed. */
        udelay(100);

        NFC_FLASH_CMD = (u16) cmd;
        NFC_CONFIG2 = NFC_CMD;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, cmd);
}

/*!
 * This function sends an address (or partial address) to the 
 * NAND device.  The address is used to select the source/destination for 
 * a NAND command.
 * 
 * @param       addr    address to be written to NFC.  
 */  
STATIC void send_addr(u16 addr)
{
        if (nand_debug) 
                DEBUG (MTD_DEBUG_LEVEL3, "send_addr(0x%x)\n", addr);

        NFC_FLASH_ADDR = addr;
        NFC_CONFIG2 = NFC_ADDR;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, addr);
}

/*!
 * This function requests the NANDFC to initate the transfer
 * of data currently in the NANDFC RAM buffer to the NAND device.
 * 
 * @param       bSpareOnly    set true if only the spare area is transferred
 */  
STATIC void send_prog_page(bool bSpareOnly)
{        
        if (nand_debug) 
                DEBUG (MTD_DEBUG_LEVEL3, "send_prog_page (%d)\n", bSpareOnly);
        
        /* NANDFC buffer 0 is used for page read/write */
        NFC_BUF_ADDR = 0x0;

        /* Configure spare or page+spare access */                
        if (bSpareOnly) {
            NFC_CONFIG1 |= NFC_SP_EN;
        }
        else {
            NFC_CONFIG1 &= (~(NFC_SP_EN));
        }
                    
        NFC_CONFIG2 = NFC_INPUT;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, bSpareOnly);
}

/*!
 * This function requests the NANDFC to initated the transfer
 * of data from the NAND device into in the NANDFC ram buffer.
 * 
 * @param       bSpareOnly    set true if only the spare area is transferred
 */  
STATIC void send_read_page(bool bSpareOnly)
{
        if (nand_debug) 
               DEBUG (MTD_DEBUG_LEVEL3, "send_read_page (%d)\n", bSpareOnly);
        
        /* NANDFC buffer 0 is used for page read/write */        
        NFC_BUF_ADDR = 0x0; 

        /* Configure spare or page+spare access */                
        if (bSpareOnly) {
            NFC_CONFIG1 |= NFC_SP_EN;
        }

        else {
            NFC_CONFIG1 &= (~(NFC_SP_EN));
        }
                        
        NFC_CONFIG2 = NFC_OUTPUT;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, bSpareOnly);
}
    
/*!
 * This function requests the NANDFC to perform a read of the
 * NAND device ID.
 */  
STATIC void send_read_id(void)
{
        /* NANDFC buffer 0 is used for device ID output */        
        NFC_BUF_ADDR = 0x0; 

        /* Read ID into main buffer */
        NFC_CONFIG1 &= (~(NFC_SP_EN));
        NFC_CONFIG2 = NFC_ID;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, 0);
}

/*!
 * This function requests the NANDFC to perform a read of the
 * NAND device status.
 */  
STATIC void send_read_status(void)
{        
        /* 
         * NANDFC buffer 1 is used for device status to prevent 
         * corruption of read/write buffer on status requests.
         */        
        NFC_BUF_ADDR = 1;

        /* Send the Read status command before reading status */
        send_cmd(NAND_CMD_STATUS);

        /* Read status into main buffer */
        NFC_CONFIG1 &= (~(NFC_SP_EN));
        NFC_CONFIG2 = NFC_STATUS;

        /* Wait for operation to complete */
        wait_op_done(TROP_US_DELAY, 0);
}

/*!
 * This function requests the NANDFC to perform a read of the
 * NAND device status and returns the current status.
 *
 * @return  device status
 */  
STATIC u16 get_dev_status(void)
{
        volatile u16 *mainBuf = MAIN_AREA1;
        
        /* Issue status request to NAND device */
        send_read_status();

        /* Status is placed in first word of main buffer */
        return mainBuf[0];
}

/*!
 * This functions is used by upper layer to checks if device is ready
 * 
 * @param       mtd     MTD structure for the NAND Flash 
 * 
 * @return  0 if device is busy else 1
 */  
STATIC int mxc_nand_dev_ready(struct mtd_info *mtd)
{
        /* Check if NAND device is ready */
        if (get_dev_status() & NAND_STATUS_READY) {
                return 1;
        }
        return 0;
}

STATIC void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode)
{
        /* 
         * If HW ECC is enabled, we turn it on during init.  There is
         * no need to enable again here.
         */
}

STATIC int mxc_nand_correct_data(struct mtd_info * mtd, u_char * dat, u_char * read_ecc, u_char * calc_ecc)
{
        /* 
         * 1-Bit errors are automatically corrected in HW.  No need for 
         * additional correction.  2-Bit errors cannot be corrected by
         * HW ECC, so we need to return failure
         */
        u16 ecc_status = NFC_ECC_STATUS_RESULT;

        if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) {
            DEBUG (MTD_DEBUG_LEVEL0, 
                "MXC_NAND: HWECC uncorrectable 2-bit ECC error\n");
            return -1;
        }

        return 0;
}

STATIC int mxc_nand_calculate_ecc(struct mtd_info * mtd, const u_char * dat, u_char * ecc_code)
{
        /* 
         * Just return success.  HW ECC does not read/write the NFC spare
         * buffer.  Only the FLASH spare area contains the calcuated ECC.
         */
         return 0;
}


/*!
 * This function reads byte from the NAND Flash
 *   
 * @param       mtd     MTD structure for the NAND Flash
 *
 * @return    data read from the NAND Flash
 */  
STATIC u_char mxc_nand_read_byte(struct mtd_info *mtd)
{
        u_char retVal = 0;
        u16 col, rdWord;
        volatile u16 *mainBuf = MAIN_AREA0;
        volatile u16 *spareBuf = SPARE_AREA0;

        /* Check for status request */
        if (g_nandfc_info.bStatusRequest) {
            /* Without the below delay write fails on SCM. */
            { volatile int i = 100000; while (i--); }
            return (get_dev_status() & 0xFF);
        }
            
        /* Get column for 16-bit access */
        col = g_nandfc_info.colAddr >> 1;
        
        /* If we are accessing the spare region */
        if (g_nandfc_info.bSpareOnly) {
                rdWord = spareBuf[col];                                        
        } 
        else {
                rdWord = mainBuf[col];
        }
                
        /* Pick upper/lower byte of word from RAM buffer */
        if (g_nandfc_info.colAddr & 0x1) {
            retVal = (rdWord >> 8) & 0xFF;
        }
        else {
            retVal = rdWord & 0xFF;
        }            

        /* Update saved column address */
        g_nandfc_info.colAddr++;
                
        return retVal;
}

/*!
 * This function writes the data byte to the NAND Flash
 *   
 * @param       mtd     MTD structure for the NAND Flash
 * @param       byte    data to be written to NAND Flash 
 */  
STATIC void mxc_nand_write_byte(struct mtd_info *mtd, u_char byte)
{
        DEBUG (MTD_DEBUG_LEVEL0, "ERROR:  mxc_nand_write_byte not supported.\n");
}

/*!
 * This function writes data of length \b len to buffer \b buf. The data to be 
 * written on NAND Flash is first copied to RAMbuffer. After the Data Input  
 * Operation by the NFC, the data is written to NAND Flash
 *  
 * @param       mtd     MTD structure for the NAND Flash
 * @param       buf     data to be written to NAND Flash 
 * @param       len     number of bytes to be written 
 */ 
STATIC void mxc_nand_write_buf(struct mtd_info *mtd, 
                                                const u_char *buf, int len)
{
        int i, j, maxCol = (mtd->oobblock >> 2); 
        u32 *wrtBuf, wrtLen, col;
        volatile u32 *mainBuf = (u32 *)MAIN_AREA0;
        volatile u32 *spareBuf = (u32 *)SPARE_AREA0;
                
        if (nand_debug) 
              DEBUG (MTD_DEBUG_LEVEL3, "mxc_nand_write_buf(col = %d, len = %d)\n", 
                      g_nandfc_info.colAddr, len);
         
        /* Get write length for 32-bit access */
        wrtLen = len >> 2; 

        /* Point to write buffer for 32-bit access */
        wrtBuf = (u32 *)buf; 
        
        /* Adjust saved column address for 32-bit access */
        col = g_nandfc_info.colAddr >> 2; 
            
        /* If we are accessing the spare region */
        if (g_nandfc_info.bSpareOnly) {

                /* Copy data to spare buffer */
                for (i = 0; i < wrtLen; i++, col++) {
                        spareBuf[col] = wrtBuf[i];
                }

        
                /* Write for odd length */
                if (len & 0x1) spareBuf[col] |= buf[len - 1];
        }
        else {
                if (len & 0x1) {
                    DEBUG (MTD_DEBUG_LEVEL0, 
                            "WARNING:  Unaligned length in mxc_nand_write_buf\n");
                }
                
                /* Copy data from main buffer */
                for (i = 0; i < wrtLen && col < maxCol; i++, col++) {
                        mainBuf[col]= wrtBuf[i];
                }
                        
                /* Copy data from spare buffer */
                for (j = 0; i < wrtLen; i++, j++, col++) {
                        spareBuf[j] = wrtBuf[i];
                }
        }

        wait_op_done(TROP_US_DELAY, 0);
                
        /* Update saved column address */
        g_nandfc_info.colAddr += len;
}

/*!
 * This function id is used to read the data buffer from the NAND Flash. To 
 * read the data from NAND Flash first the data output cycle is initiated by 
 * the NFC, which copies the data to RAMbuffer. This data of length \b len is 
 * then copied to buffer \b buf.
 *
 * @param       mtd     MTD structure for the NAND Flash
 * @param       buf     data to be read from NAND Flash 
 * @param       len     number of bytes to be read  
 */
STATIC void mxc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
{
        int i, j, maxCol = (mtd->oobblock >> 2); 
        u32 *rdBuf, rdLen, col;
        volatile u32 *mainBuf =(u32 *) MAIN_AREA0;
        volatile u32 *spareBuf = (u32 *)SPARE_AREA0;
                        
        if (nand_debug) 
        {
               DEBUG (MTD_DEBUG_LEVEL3, 
                      "mxc_nand_read_buf(col = %d, len = %d)\n", 
                     g_nandfc_info.colAddr, len);
        }

        /* Delay required for the read to succeed */
        udelay(50);
        
        /* Get read length for 32-bit access */
        rdLen = len >> 2; 

        /* Point to read buffer for 32-bit access */
        rdBuf = (u32 *)buf; 
        
        /* Adjust saved column address for 32-bit access */
        col = g_nandfc_info.colAddr >> 2; 
            
        /* If we are accessing the spare region */
        if (g_nandfc_info.bSpareOnly) {

                /* Copy data from spare buffer */
                for (i = 0; i < rdLen; i++, col++) {
                        rdBuf[i] = spareBuf[col];
                }

                /* Read for odd length */
                if (len & 0x1) buf[len - 1] = spareBuf[col] & 0xFF;
                    
        } 
        else {
                if (len & 0x1)
                {
                    DEBUG (MTD_DEBUG_LEVEL0, 
                        "WARNING:  Unaligned length in mxc_nand_read_buf\n");
                }
                        
                /* Copy data from main buffer */
                for (i = 0; i < rdLen &&  col < maxCol; i++, col++) {
                            rdBuf[i] = mainBuf[col];
                }
                
                /* Copy data from spare buffer */
                for (j = 0; i < rdLen; i++, j++, col++) {
                        rdBuf[i] = spareBuf[j];
                }
                                
        }

        /* Update saved column address */
        g_nandfc_info.colAddr += len;
}

/*!
 * This function is used by the upper layer to verify the data in NAND Flash 
 * with the data in the \b buf.
 * 
 * @param       mtd     MTD structure for the NAND Flash
 * @param       buf     data to be verified 
 * @param       len     length of the data to be verified  
 *   
 * @return      -EFAULT if error else 0     
 *
 */  
STATIC int 
mxc_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
{
        return -EFAULT;
}

/*!
 * This function is used by upper layer for select and deselect of the NAND 
 * chip
 *
 * @param       mtd     MTD structure for the NAND Flash
 * @param       chip    val indicating select or deselect
 */  
STATIC void mxc_nand_select_chip(struct mtd_info *mtd, int chip)
{
#ifdef CONFIG_MTD_NAND_MXC_FORCE_CE
        if (chip > 0) {
                DEBUG (MTD_DEBUG_LEVEL0, 
                        "ERROR:  Illegal chip select (chip = %d)\n", chip);
                return;
        }

        if (chip == -1) {
                NFC_CONFIG1 &= (~(NFC_CE));
                return;
        }

        NFC_CONFIG1 |= NFC_CE;        
#endif        
}

/*!
 * This function is used by the upper layer to write command to NAND Flash for 
 * different operations to be carried out on NAND Flash
 *    
 * @param       mtd             MTD structure for the NAND Flash
 * @param       command         command for NAND Flash 
 * @param       column          column offset for the page read
 * @param       page_addr       page to be read from NAND Flash
 */  
STATIC void mxc_nand_command (struct mtd_info *mtd, unsigned command, 
                int column, int page_addr)
{
        if (nand_debug)
               DEBUG (MTD_DEBUG_LEVEL3, 
                      "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", 
                      command, column, page_addr);

        /*
         * Reset command state information
         */
        g_nandfc_info.bStatusRequest = false;
            
        /*
         * Command pre-processing step
         */
        switch(command) {
            
        case NAND_CMD_STATUS:
            g_nandfc_info.colAddr = 0;
            g_nandfc_info.bStatusRequest = true;
            break;
    
        case NAND_CMD_READ0:
            g_nandfc_info.colAddr = column;
            g_nandfc_info.bSpareOnly = false;
            break;
            
        case NAND_CMD_READOOB:
            g_nandfc_info.colAddr = column;
            g_nandfc_info.bSpareOnly = true;
            break;

        case NAND_CMD_SEQIN:
            udelay(100);
            if (column >= mtd->oobblock) {
                g_nandfc_info.colAddr = column - mtd->oobblock;
                g_nandfc_info.bSpareOnly = true;
                /* Set program pointer to spare region */
                send_cmd(NAND_CMD_READOOB);
            }
            else {
                g_nandfc_info.bSpareOnly = false;
                g_nandfc_info.colAddr = column;
                /* Set program pointer to page start */
                send_cmd(NAND_CMD_READ0);
            }
            break;
            
        case NAND_CMD_PAGEPROG:
            send_prog_page(g_nandfc_info.bSpareOnly);
            udelay(100);
            break;

        case NAND_CMD_ERASE1:
            /* 
             * The below delay is put here so that there is a delay
             * between subsequent erase commands.
             * Without the below delay erase fails on SCM.
             */
            mdelay(3);
            break;
        }            
        
        /*
         * Write out the command to the device.
         */
        send_cmd(command);

        /*
         * Write out column address, if necessary
         */
        if (column != -1) {
            /*
             * MXC NANDFC can only perform full page+spare or
             * spare-only read/write.  When the upper layers
             * layers perform a read/write buf operation,
             * we will used the saved column adress to index into
             * the full page.
             */
            send_addr(0);
        }

        /*
         * Write out page address, if necessary
         */
        if (page_addr != -1) {
                send_addr(page_addr & 0xff);
                send_addr((page_addr >> 8) & 0xff);

                /* One more address cycle for higher density devices */
                if (mtd->size >= 0x4000000)
                {
                    send_addr((page_addr >> 16) & 0xff);
                }
        }

        /*
         * Command post-processing step 
         */
        switch(command) {
            
        case NAND_CMD_RESET:
                /* Reset requires the below delay, otherwise it may hang */
                udelay(TRST_US_DELAY);  
                break;
            
        case NAND_CMD_READOOB:
        case NAND_CMD_READ0:
                send_read_page(g_nandfc_info.bSpareOnly);
                break;

        case NAND_CMD_READID:
                send_read_id();
                break;

        case NAND_CMD_PAGEPROG:
                udelay(TPROG_US_DELAY);
                break;

        case NAND_CMD_STATUS:
                break;

        case NAND_CMD_ERASE2:
            /* 
             * The below delay is put here so that there is a delay
             * between subsequent erase commands.
             * Without the below delay erase fails on SCM.
             */
            mdelay(3);
            break;
        }
}

/*
 * Main initialization routine
 */
int __init mxc_nd_init (void)
{
        struct nand_chip *this;
#ifdef CONFIG_ARCH_SCMA11BB
	int nr_parsed_parts;
#endif

        /* Without the below delay wait_op_done fails to see INT bit set during scan */
        udelay(10);

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

        /* Get pointer to private data */
        this = (struct nand_chip *) (&mxc_mtd[1]);
        
        /* Initialize structures */
        memset((char*) mxc_mtd, 0, sizeof(struct mtd_info));
        memset((char*) this, 0, sizeof(struct nand_chip));
        memset((char*) &g_nandfc_info, 0, sizeof(g_nandfc_info));
        
        /* Link the private data with the MTD structure */
        mxc_mtd->priv = this;

        /* 50 us command delay time */
        this->chip_delay = 50;

        this->dev_ready = mxc_nand_dev_ready;
        this->cmdfunc = mxc_nand_command;
        this->select_chip = mxc_nand_select_chip;
        this->write_byte = mxc_nand_write_byte;
        this->read_byte = mxc_nand_read_byte;
        this->write_buf = mxc_nand_write_buf;
        this->read_buf = mxc_nand_read_buf;
        this->verify_buf = mxc_nand_verify_buf;

        NFC_CONFIG1 = NFC_INT_MSK;
        
        if (hardware_ecc) {
                this->correct_data  = mxc_nand_correct_data;
                this->enable_hwecc  = mxc_nand_enable_hwecc;
                this->calculate_ecc = mxc_nand_calculate_ecc;
                this->eccmode       = NAND_ECC_HW3_512;
                this->autooob       = &nand_hw_eccoob;
                NFC_CONFIG1 |= NFC_ECC_EN;
        } else {
                this->eccmode       = NAND_ECC_SOFT;
        }

        /* Reset NAND */
        this->cmdfunc(mxc_mtd, NAND_CMD_RESET, -1, -1);
        
        /* preset operation */
        /* Unlock the internal RAM Buffer */
        NFC_CONFIG = 0x2;

        /* Blocks to be unlocked */
        NFC_UNLOCKSTART_BLKADDR = 0x0;
#ifdef CONFIG_ARCH_SCMA11BB
	NFC_UNLOCKEND_BLKADDR = 0x1000;
	this->options |= NAND_USE_FLASH_BBT;
#else
        NFC_UNLOCKEND_BLKADDR = 0x800;
#endif

        /* Unlock Block Command for given address range */
        NFC_WRPROT = 0x4;
        
        /* NAND bus width determines access funtions used by upper layer */
        this->options |= busw;

        /* Scan to find existence of the device */
        if (nand_scan (mxc_mtd, 1)) 
        {
                DEBUG (MTD_DEBUG_LEVEL0, "MXC_ND: Unable to find any NAND device.\n");
                kfree (mxc_mtd);
                mxc_mtd = NULL;
                return -ENXIO;
        }

        /* Allocate memory for internal data buffer */
        this->data_buf = kmalloc (sizeof(u_char) * (mxc_mtd->oobblock + 
                                mxc_mtd->oobsize), GFP_KERNEL);
        if (!this->data_buf) 
        {
                DEBUG (MTD_DEBUG_LEVEL0, 
                        "MXC_ND: Unable to allocate NAND data buffer.\n");
                kfree (mxc_mtd);
                mxc_mtd = NULL;
                return -ENOMEM;
        }
        /* 
         * Enable nand_debug only after nand_scan(), otherwise you might be
         * flooded with debug messages.
         */
        nand_debug = true;

        /* Register the partitions */
#ifdef CONFIG_ARCH_SCMA11BB
        strcpy(mxc_mtd->name, "nand0");
	nr_parsed_parts = parse_mtd_partitions(mxc_mtd, probes, &parsed_parts, 0);
	if (nr_parsed_parts > 0) {
		printk(KERN_NOTICE "Using dynamic partition definition\n");
		add_mtd_partitions(mxc_mtd, parsed_parts, nr_parsed_parts);
	} else {
		printk(KERN_NOTICE "Using static partition definition\n");
		add_mtd_partitions(mxc_mtd, partition_info, NUM_PARTITIONS);
	}
        add_mtd_partitions(mxc_mtd, partition_info, NUM_PARTITIONS);
#else   
        add_mtd_partitions(mxc_mtd, partition_info, NUM_PARTITIONS);
#endif  /* CONFIG_ARCH_SCMA11BB */

        return 0;
}

/*
 * Clean up routine
 */
STATIC void __exit mxc_nd_cleanup (void)
{
        struct nand_chip *this = (struct nand_chip *) &mxc_mtd[1];

        /* Unregister partitions */
        del_mtd_partitions(mxc_mtd);

        /* Unregister the device */
        del_mtd_device(mxc_mtd);

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

        /* Free the MTD device structure */
        kfree(mxc_mtd);
        mxc_mtd = NULL;
}

module_init(mxc_nd_init);
module_exit(mxc_nd_cleanup);

MODULE_AUTHOR("Freescale");
MODULE_DESCRIPTION("MXC NAND MTD driver");
MODULE_LICENSE("GPL");
