/*++
 
 Copyright (c) 2012-2022 ChipOne Technology (Beijing) Co., Ltd. All Rights Reserved.
 This PROPRIETARY SOFTWARE is the property of ChipOne Technology (Beijing) Co., Ltd. 
 and may contains trade secrets and/or other confidential information of ChipOne 
 Technology (Beijing) Co., Ltd. This file shall not be disclosed to any third party,
 in whole or in part, without prior written consent of ChipOne.  
 THIS PROPRIETARY SOFTWARE & ANY RELATED DOCUMENTATION ARE PROVIDED AS IS, 
 WITH ALL FAULTS, & WITHOUT WARRANTY OF ANY KIND. CHIPONE DISCLAIMS ALL EXPRESS OR 
 IMPLIED WARRANTIES.  
 
 File Name:    flash.c
 Abstract:
               flash operation, read write etc.
 Author:       Zhimin Tian
 Date :        10 30,2012
 Version:      0.1[.revision]
 History :
     Change logs.  
 --*/
#include "icn83xx.h"

struct file  *fp; 
int g_status = R_OK;
static char fw_mode = 0;
static int fw_size = 0;
static unsigned char *fw_buf;

void icn83xx_rawdatadump(short *mem, int size, char br)
{
    int i;
    for(i=0;i<size; i++)
    {
        if((i!=0)&&(i%br == 0))
            printk("\n");
        printk(" %5d", mem[i]);
    }
    printk("\n"); 
} 

void icn83xx_memdump(char *mem, int size)
{
    int i;
    for(i=0;i<size; i++)
    {
        if(i%16 == 0)
            printk("\n");
        printk(" 0x%2x", mem[i]);
    }
    printk("\n"); 
} 

int  icn83xx_checksum(int sum, char *buf, unsigned int size)
{
    int i;
    for(i=0; i<size; i++)
    {
        sum = sum + buf[i];
    }
    return sum;
}


int icn83xx_update_status(int status)
{
//  flash_info("icn83xx_update_status: %d\n", status);
    g_status = status;
    return 0;
}

int icn83xx_get_status(void)
{
    return  g_status;
}

void icn83xx_set_fw(int size, unsigned char *buf)
{
    fw_size = size;
    fw_buf = buf;
    
}

/***********************************************************************************************
Name    :   icn83xx_writeInfo 
Input   :   addr, value
Output  :   
function    :   write Flash Info
***********************************************************************************************/

int icn83xx_writeInfo(unsigned short addr, char value)
{
    int ret = -1;
    char temp_buf[3];
    
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(230, temp_buf, 2);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);
    temp_buf[0] = value;
    ret = icn83xx_i2c_txdata(232, temp_buf, 1);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(5);
    return 0;   
}
/***********************************************************************************************
Name    :   icn83xx_readInfo 
Input   :   
Output  :   
function    :   read Flash info
***********************************************************************************************/

int icn83xx_readInfo(unsigned short addr, char *value)
{
    int ret = -1;
    char temp_buf[3];
    
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(230, temp_buf, 2);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);
    ret = icn83xx_i2c_rxdata(232, value, 1); 
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);
    return 0;   
}

/***********************************************************************************************
Name    :   icn83xx_writeReg 
Input   :   addr, value
Output  :   
function    :   write MCU xdata and reg
***********************************************************************************************/

int icn83xx_writeReg(unsigned short addr, char value)
{
    int ret = -1;
    char temp_buf[3];

    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(224, temp_buf, 2);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);
    temp_buf[0] = value;
    ret = icn83xx_i2c_txdata(226, temp_buf, 1);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(5);
    return 0;   
}
/***********************************************************************************************
Name    :   icn83xx_readReg 
Input   :   
Output  :   
function    :   read MCU xdata and reg
***********************************************************************************************/

int icn83xx_readReg(unsigned short addr, char *value)
{
    int ret = -1;
    char temp_buf[3];
    
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(224, temp_buf, 2);
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);

    ret = icn83xx_i2c_rxdata(226, value, 1); 
    if (ret < 0) {
        op_error("%s failed! ret: %d\n", __func__, ret);
        return -1;
    }
    mdelay(2);
    return 0;   
}

/***********************************************************************************************
Name    :   icn83xx_open_fw 
Input   :   *fw
            
Output  :   file size
function    :   open the fw file, and return total size
***********************************************************************************************/
int  icn83xx_open_fw( char *fw)
{
    int file_size;
    mm_segment_t fs; 
    struct inode *inode = NULL; 
    if(strcmp(fw, "icn83xx_firmware") == 0)
    {
        fw_mode = 1;  //use inner array
        return fw_size;
    }
    else
    {
        fw_mode = 0; //use file in file system
    }
    
    fp = filp_open(fw, O_RDONLY, 0); 
    if (IS_ERR(fp)) { 
        flash_error("read fw file error\n"); 
        return -1; 
    } 
    else
        flash_info("open fw file ok\n"); 
        
    inode = fp->f_dentry->d_inode;
    file_size = inode->i_size;  
    flash_info("file size: %d\n", file_size); 

    fs = get_fs(); 
    set_fs(KERNEL_DS); 
    
    return  file_size;
    
}

/***********************************************************************************************
Name    :   icn83xx_read_fw 
Input   :   offset
            length, read length
            buf, return buffer
Output  :   
function    :   read data to buffer
***********************************************************************************************/
int  icn83xx_read_fw(int offset, int length, char *buf)
{
    loff_t  pos = offset;               
    if(fw_mode == 1)
    {
        memcpy(buf, fw_buf+offset, length);
    }
    else
    {                   
        vfs_read(fp, buf, length, &pos); 
    }
//  icn83xx_memdump(buf, length);
    return 0;       
}


/***********************************************************************************************
Name    :   icn83xx_close_fw 
Input   :   
Output  :   
function    :   close file
***********************************************************************************************/
int  icn83xx_close_fw(void)
{   
    if(fw_mode == 0)
    {
        filp_close(fp, NULL); 
    }
    
    return 0;
}
/***********************************************************************************************
Name    :   icn83xx_readVersion
Input   :   void
Output  :   
function    :   return version
***********************************************************************************************/
int icn83xx_readVersion(void)
{
    int err = 0;
    char tmp[2];    
    short CurVersion;
    err = icn83xx_i2c_rxdata(12, tmp, 2);
    if (err < 0) {
        calib_error("%s failed: %d\n", __func__, err); 
        return err;
    }       
    CurVersion = (tmp[0]<<8) | tmp[1];
    return CurVersion;  
}

/***********************************************************************************************
Name    :   icn83xx_changemode 
Input   :   normal/factory/config
Output  :   
function    :   change work mode
***********************************************************************************************/
int icn83xx_changemode(char mode)
{
    char value = 0x0;
    icn83xx_write_reg(0, mode); 
    mdelay(1);
    icn83xx_read_reg(1, &value);
    while(value != 0)
    {
        mdelay(1);
        icn83xx_read_reg(1, &value);
    }   
//  calib_info("icn83xx_changemode ok\n");
    return 0;   
}


/***********************************************************************************************
Name    :   icn83xx_readrawdata 
Input   :   rownum and length
Output  :   
function    :   read one row rawdata
***********************************************************************************************/

int icn83xx_readrawdata(char *buffer, char row, char length)
{
    int err = 0;
    int i;
//  calib_info("readrawdata: %d, length: %d\n", row, length);
    icn83xx_write_reg(3, row);
    mdelay(1);
    err = icn83xx_i2c_rxdata(160, buffer, length);
    if (err < 0) {
        calib_error("%s failed: %d\n", __func__, err); 
        return err;
    }   

    for(i=0; i<length; i=i+2)
    {
        swap_ab(buffer[i], buffer[i+1]);
    }   
    return err; 
}

/***********************************************************************************************
Name    :   icn83xx_scanTP 
Input   :   
Output  :   
function    :   scan one frame rawdata
***********************************************************************************************/

int icn83xx_scanTP(void)
{
    char value = 0;
    icn83xx_write_reg(2, 0x0); 
    mdelay(1);  
    icn83xx_read_reg(2, &value);
    while(value != 1)
    {
        mdelay(1);
        icn83xx_read_reg(2, &value);
    }
//  calib_info("icn83xx_scanTP ok\n");    
    return 0;
}

/***********************************************************************************************
Name    :   icn83xx_readTP 
Input   :   rownum and columnnum
Output  :   
function    :   read one frame rawdata
***********************************************************************************************/

int icn83xx_readTP(char row_num, char column_num, char *buffer)
{
    int err = 0;
    int i;
//  calib_info("icn83xx_readTP\n");
    icn83xx_changemode(1);  
    icn83xx_scanTP();
    for(i=0; i<row_num; i++)
    {
        icn83xx_readrawdata(&buffer[i*16*2], i, column_num*2);
    }
    icn83xx_changemode(0);  
    return err; 
}


/***********************************************************************************************
Name    :   icn83xx_goto_progmode 
Input   :   
Output  :   
function    :   change MCU to progmod
***********************************************************************************************/
int icn83xx_goto_progmode(void)
{
    int ret = -1;
//    char value[64];
    char regValue = 0;
    
    flash_info("icn83xx_goto_progmode\n");
    
    ret = icn83xx_readReg(0x009, &regValue);
    if(ret != 0)
        return ret; 
    flash_info("[0x009]: 0x%x\n", regValue);  
        
// open clock
    if(regValue != 0xDF)
    {
        icn83xx_changemode(2);
        ret = icn83xx_writeReg(0x002, 0x00);
        if(ret != 0)
            return ret;         
        ret = icn83xx_writeReg(0x009, 0xDF);
        if(ret != 0)
            return ret; 
        ret = icn83xx_writeReg(0x010, 0x00);
        if(ret != 0)
            return ret;     
        
    }
    
/*  
    addr = 0x0;
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(230, temp_buf, 2);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }

    temp_buf[0] = 0xff;
    ret = icn83xx_i2c_txdata(232, temp_buf, 1);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }   
*/
    ret = icn83xx_writeInfo(0x0, 0xff);
    if(ret != 0)
        return ret;

/*    
    addr = 0x1;
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(230, temp_buf, 2);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }

    temp_buf[0] = 0xff;
    ret = icn83xx_i2c_txdata(232, temp_buf, 1);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }       
*/
    ret = icn83xx_writeInfo(0x1, 0xff);
    if(ret != 0)
        return ret;   
        
    ret = icn83xx_writeInfo(0x10, 0xff);
    if(ret != 0)
        return ret;   
        
    ret = icn83xx_writeInfo(0x11, 0xff);
    if(ret != 0)
        return ret;                 
/*    
    addr = 0xf00;
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);            
    ret = icn83xx_i2c_txdata(224, temp_buf, 2);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }
    temp_buf[0] = 0x1;
    ret = icn83xx_i2c_txdata(226, temp_buf, 1);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }       
*/
    ret = icn83xx_writeReg(0xf00, 1);
    if(ret != 0)
        return ret;
    icn83xx_ts_reset();     
    //mdelay(100);        
    msleep(100);        
    return 0;   
}

/***********************************************************************************************
Name    :   icn83xx_check_progmod 
Input   :   
Output  :   
function    :   check if MCU at progmode or not
***********************************************************************************************/
int icn83xx_check_progmod(void)
{
    int ret;
    unsigned char ucTemp = 0x0;
    ret = icn83xx_prog_i2c_rxdata(0x0, &ucTemp, 1);
    flash_info("icn83xx_check_progmod: 0x%x\n", ucTemp);
    if(ret < 0)
    {
        flash_error("icn83xx_check_progmod error, ret: %d\n", ret);
        return ret;
    }

    return 0;
}


/***********************************************************************************************
Name    :   icn83xx_uu 
Input   :   
Output  :   
function    :   unlock flash
***********************************************************************************************/
int icn83xx_uu(void)
{
    unsigned char ucTemp = 0x0;
    ucTemp = 0x1e;                      
    icn83xx_prog_i2c_txdata(0x050a, &ucTemp, 1);
    ucTemp = 0x10;                      
    icn83xx_prog_i2c_txdata(0x050b, &ucTemp, 1);
    return 0;   
}
/***********************************************************************************************
Name    :   icn83xx_ll 
Input   :   
Output  :   
function    :   lock flash
***********************************************************************************************/
void icn83xx_ll(void)
{
    unsigned char ucTemp = 0x0;
    ucTemp = 0xcc;                      
    icn83xx_prog_i2c_txdata(0x050a, &ucTemp, 1);
    ucTemp = 0xcc;                      
    icn83xx_prog_i2c_txdata(0x050b, &ucTemp, 1);        
}

/***********************************************************************************************
Name    :   icn83xx_op1 
Input   :   
Output  :   
function    :   erase flash
***********************************************************************************************/

int  icn83xx_op1(char info, unsigned short offset, unsigned int size)
{
    int count = 0;
    unsigned char ucTemp = 0x0;
    unsigned short uiAddress = 0x0; 
    int i;
    
    icn83xx_uu();
    for(i=0; i<size; )
    {       
        uiAddress = offset + i;
//      flash_info("uiAddress: 0x%x\n", uiAddress);
        ucTemp = U16LOBYTE(uiAddress);                        
        icn83xx_prog_i2c_txdata(0x0502, &ucTemp, 1);
        ucTemp = U16HIBYTE(uiAddress);                       
        icn83xx_prog_i2c_txdata(0x0503, &ucTemp, 1);
        
        ucTemp = 0x02;                                      
        icn83xx_prog_i2c_txdata(0x0500, &ucTemp, 1);
        ucTemp = 0x01;
        count = 0;
        while(ucTemp)                                       
        {
            icn83xx_prog_i2c_rxdata(0x0501, &ucTemp, 1);
            count++;
            if(count > 5000)
            {
                flash_error("op1 ucTemp: 0x%x\n", ucTemp);
                return 1;
            }
        }
        i = i+1024;
    }
    icn83xx_ll();
    return 0;
}

/***********************************************************************************************
Name    :   icn83xx_op2 
Input   :   
Output  :   
function    :   progm flash
***********************************************************************************************/
int  icn83xx_op2(char info, unsigned short offset, unsigned char * buffer, unsigned int size)
{
    int count = 0;
    unsigned int flash_size;
    unsigned char ucTemp;
    unsigned short uiAddress;
    ucTemp = 0x00;
    uiAddress = 0x1000; 
    
    icn83xx_prog_i2c_txdata(uiAddress, buffer, size);
    
    icn83xx_uu();
    
    ucTemp = U16LOBYTE(offset);                           
    icn83xx_prog_i2c_txdata(0x0502, &ucTemp, 1);
    ucTemp = U16HIBYTE(offset); 
    icn83xx_prog_i2c_txdata(0x0503, &ucTemp, 1);
    
    icn83xx_prog_i2c_txdata(0x0504, (char *)&uiAddress, 2);


//ensure size is even
    if(size%2 != 0)
    {
        flash_info("write op size: %d\n", size);
        flash_size = size+1;
    }
    else
        flash_size = size;
    
    ucTemp = U16LOBYTE(flash_size);                               
    icn83xx_prog_i2c_txdata(0x0506, &ucTemp, 1);
    ucTemp = U16HIBYTE(flash_size);                               
    icn83xx_prog_i2c_txdata(0x0507, &ucTemp, 1);
    ucTemp = 0x01;

    if(info > 0)
       ucTemp = 0x01 | (1<<3);                          

    icn83xx_prog_i2c_txdata(0x0500, &ucTemp, 1);    //
    while(ucTemp)                                       
    {
        icn83xx_prog_i2c_rxdata(0x0501, &ucTemp, 1);
        count++;
        if(count > 5000)
        {
            flash_error("op2 ucTemp: 0x%x\n", ucTemp);
            return 1;
        }       
        
    }
    icn83xx_ll();
    return 0;   
}

/***********************************************************************************************
Name    :   icn83xx_op3 
Input   :   
Output  :   
function    :   read flash
***********************************************************************************************/
int  icn83xx_op3(char info, unsigned short offset, unsigned char * buffer, unsigned int size)
{
    int count = 0;
    unsigned int flash_size;
    unsigned char ucTemp;
    unsigned short uiAddress;
    ucTemp = 0x00;
    uiAddress = 0x1000; 
    icn83xx_uu();
    ucTemp = U16LOBYTE(offset);                      
    icn83xx_prog_i2c_txdata(0x0502, &ucTemp, 1);
    ucTemp = U16HIBYTE(offset);                       
    icn83xx_prog_i2c_txdata(0x0503, &ucTemp, 1);

    icn83xx_prog_i2c_txdata(0x0504, (unsigned char*)&uiAddress, 2);

//ensure size is even
    if(size%2 != 0)
    {
        flash_info("read op size: %d\n", size);
        flash_size = size+1;
    }
    else
        flash_size = size;
    
    ucTemp = U16LOBYTE(flash_size);                           
    icn83xx_prog_i2c_txdata(0x0506, &ucTemp, 1);
    
    ucTemp = U16HIBYTE(flash_size);                           
    icn83xx_prog_i2c_txdata(0x0507, &ucTemp, 1);
    ucTemp = 0x40;

    if(info > 0)
        ucTemp = 0x40 | (1<<3);                   

    icn83xx_prog_i2c_txdata(0x0500, &ucTemp, 1);
    ucTemp = 0x01;
    while(ucTemp)                                   
    {
        icn83xx_prog_i2c_rxdata(0x0501, &ucTemp, 1);
        count++;
        if(count > 5000)
        {
            flash_error("op3 ucTemp: 0x%x\n", ucTemp);
            return 1;
        }       
                
    }
    icn83xx_ll();
    icn83xx_prog_i2c_rxdata(uiAddress, buffer, size);
    return 0;   
}


/***********************************************************************************************
Name    :   icn83xx_goto_nomalmode 
Input   :   
Output  :   
function    :   when prog flash ok, change flash info flag
***********************************************************************************************/
int icn83xx_goto_nomalmode(void)
{
    int ret = -1;
    //unsigned short addr = 0;
    char temp_buf[3];

    flash_info("icn83xx_goto_nomalmode\n");
    temp_buf[0] = 0x03; 
    icn83xx_prog_i2c_txdata(0x0f00, temp_buf, 1);
    
    msleep(100);
/*  
    addr = 0;
    temp_buf[0] = U16HIBYTE(addr);
    temp_buf[1] = U16LOBYTE(addr);    
    temp_buf[2] = 0;        
    ret = icn83xx_i2c_txdata(230, temp_buf, 2);
    if (ret < 0) {
        pr_err("write reg failed! ret: %d\n", ret);
        return -1;
    }
    
    icn83xx_i2c_rxdata(232, &temp_buf[2], 1);   
    flash_info("temp_buf[2]: 0x%x\n", temp_buf[2]);
*/
    ret = icn83xx_readInfo(0, &temp_buf[2]);
    if(ret != 0)
        return ret;
    flash_info("temp_buf[2]: 0x%x\n", temp_buf[2]);
    if(temp_buf[2] == 0xff)
    {
/*      
        addr = 0;
        temp_buf[0] = U16HIBYTE(addr);
        temp_buf[1] = U16LOBYTE(addr);    
        ret = icn83xx_i2c_txdata(230, temp_buf, 2);
        if (ret < 0) {
            pr_err("write reg failed! ret: %d\n", ret);
            return -1;
        }           
        temp_buf[0] = 0x11;
        ret = icn83xx_i2c_txdata(232, temp_buf, 1);
        if (ret < 0) {
            pr_err("write reg failed! ret: %d\n", ret);
            return -1;
        }
*/      
        ret = icn83xx_writeInfo(0, 0x11);
        if(ret != 0)
            return ret;     
            
    }   
    return 0;
}

/***********************************************************************************************
Name    :   icn83xx_read_fw_Ver 
Input   :   fw
Output  :   
function    :   read fw version
***********************************************************************************************/

short  icn83xx_read_fw_Ver(char *fw)
{
    short FWversion;
    char tmp[2];
    int file_size;
    file_size = icn83xx_open_fw(fw);
    if(file_size < 0)
    {
        return -1;  
    }   
    icn83xx_read_fw(0x4000, 2, &tmp[0]);
    
    icn83xx_close_fw();
    FWversion = (tmp[0]<<8)|tmp[1];
//  flash_info("FWversion: 0x%x\n", FWversion);
    return FWversion;
}




/***********************************************************************************************
Name    :   icn83xx_fw_update 
Input   :   fw
Output  :   
function    :   upgrade fw
***********************************************************************************************/

E_UPGRADE_ERR_TYPE  icn83xx_fw_update(char *fw)
{
    int file_size, last_length;
    int j, num;
    int checksum_bak = 0;
    int checksum = 0;
    char temp_buf[B_SIZE];
#ifdef ENABLE_BYTE_CHECK    
    char temp_buf1[B_SIZE];
#endif  

    file_size = icn83xx_open_fw(fw);
    if(file_size < 0)
    {
        icn83xx_update_status(R_FILE_ERR);
        return R_FILE_ERR;  
    }
    
    if(icn83xx_goto_progmode() != 0)
    {
        if(icn83xx_check_progmod() < 0)
        {
            icn83xx_update_status(R_STATE_ERR);
            icn83xx_close_fw();
            return R_STATE_ERR;
        }   
    }
//  msleep(50);

    if(icn83xx_op1(0, 0, file_size) != 0)
    {
        flash_error("icn83xx_op1 error\n");
        icn83xx_update_status(R_ERASE_ERR);
        icn83xx_close_fw();
        return R_ERASE_ERR;
    }
    icn83xx_update_status(5);
    
    num = file_size/B_SIZE;
    for(j=0; j < num; j++)
    {
        icn83xx_read_fw(j*B_SIZE, B_SIZE, temp_buf);
        
//      icn83xx_op3(0, j*B_SIZE, temp_buf1, B_SIZE);
//      icn83xx_memdump(temp_buf1, B_SIZE);
        
        if(icn83xx_op2(0, j*B_SIZE, temp_buf, B_SIZE) != 0)
        {
            icn83xx_update_status(R_PROGRAM_ERR);
            icn83xx_close_fw();
            return R_PROGRAM_ERR;
        }
        checksum_bak = icn83xx_checksum(checksum_bak, temp_buf, B_SIZE);
        
        icn83xx_update_status(5+(int)(60*j/num));
    }
    last_length = file_size - B_SIZE*j;
    if(last_length > 0)
    {
        icn83xx_read_fw(j*B_SIZE, last_length, temp_buf);
        
//      icn83xx_op3(0, j*B_SIZE, temp_buf1, B_SIZE);
//      icn83xx_memdump(temp_buf1, B_SIZE);     
        
        if(icn83xx_op2(0, j*B_SIZE, temp_buf, last_length) != 0)
        {
            icn83xx_update_status(R_PROGRAM_ERR);
            icn83xx_close_fw();
            return R_PROGRAM_ERR;
        }
        checksum_bak = icn83xx_checksum(checksum_bak, temp_buf, last_length);
    }
    
    icn83xx_close_fw(); 
    icn83xx_update_status(65);
    
#ifdef ENABLE_BYTE_CHECK
    file_size = icn83xx_open_fw(fw);
    num = file_size/B_SIZE;
#endif
    
    for(j=0; j < num; j++)
    {

#ifdef ENABLE_BYTE_CHECK        
        icn83xx_read_fw(j*B_SIZE, B_SIZE, temp_buf1);
#endif      
        icn83xx_op3(0, j*B_SIZE, temp_buf, B_SIZE);
        checksum = icn83xx_checksum(checksum, temp_buf, B_SIZE);

#ifdef ENABLE_BYTE_CHECK        
        if(memcmp(temp_buf1, temp_buf, B_SIZE) != 0)
        {
            flash_error("cmp error, %d\n", j);
            icn83xx_memdump(temp_buf1, B_SIZE);
            icn83xx_memdump(temp_buf, B_SIZE);  
            icn83xx_update_status(R_VERIFY_ERR);
#ifdef ENABLE_BYTE_CHECK            
            icn83xx_close_fw();
#endif          
            return R_VERIFY_ERR;                
            //while(1);
        }
#endif      
        icn83xx_update_status(65+(int)(30*j/num));
    }

#ifdef ENABLE_BYTE_CHECK    
    last_length = file_size - B_SIZE*j;
#endif  
    if(last_length > 0)
    {
#ifdef ENABLE_BYTE_CHECK        
        icn83xx_read_fw(j*B_SIZE, last_length, temp_buf1);
#endif      
        icn83xx_op3(0, j*B_SIZE, temp_buf, last_length);
        checksum = icn83xx_checksum(checksum, temp_buf, last_length);

#ifdef ENABLE_BYTE_CHECK
        if(memcmp(temp_buf1, temp_buf, last_length) != 0)
        {
            flash_error("cmp error, %d\n", j);
            icn83xx_memdump(temp_buf1, last_length);    
            icn83xx_memdump(temp_buf, last_length); 
            icn83xx_update_status(R_VERIFY_ERR);
#ifdef ENABLE_BYTE_CHECK            
            icn83xx_close_fw();
#endif              
            return R_VERIFY_ERR;                            
            //while(1);
        }
#endif      

    }

#ifdef ENABLE_BYTE_CHECK    
    icn83xx_close_fw();
#endif      
    
    flash_info("checksum_bak: 0x%x, checksum: 0x%x\n", checksum_bak, checksum);
    if(checksum_bak != checksum)
    {
        flash_error("upgrade checksum error\n");
        icn83xx_update_status(R_VERIFY_ERR);
        return R_VERIFY_ERR;
    }

    if(icn83xx_goto_nomalmode() != 0)
    {
        flash_error("icn83xx_goto_nomalmode error\n");
        icn83xx_update_status(R_STATE_ERR);
        return R_STATE_ERR;
    }
    
    icn83xx_update_status(R_OK);
    flash_info("upgrade ok\n");
    return R_OK;
}