// SPDX-License-Identifier: GPL-2.0-only
/*
 * FTS Capacitive touch screen controller (FingerTipS)
 *
 * Copyright (C) 2016-2019, STMicroelectronics Limited.
 * Authors: AMG(Analog Mems Group) <marco.cali@st.com>
 *
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This 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, see <http://www.gnu.org/licenses/>.
 */

/**
 *
 **************************************************************************
 **                        STMicroelectronics                            **
 **************************************************************************
 **                        marco.cali@st.com                             **
 **************************************************************************
 *                                                                        *
 *                        FTS API for Flashing the IC                     *
 *                                                                        *
 **************************************************************************
 **************************************************************************
 *
 */

#include <linux/init.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <stdarg.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/serio.h>
#include <linux/time.h>
#include <linux/pm.h>
#include <linux/delay.h>
#include <linux/ctype.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/power_supply.h>
#include <linux/firmware.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>

#include "ftsCrossCompile.h"
#include "ftsCompensation.h"
#include "ftsError.h"
#include "ftsFlash.h"
#include "ftsFrame.h"
#include "ftsIO.h"
#include "ftsSoftware.h"
#include "ftsTest.h"
#include "ftsTime.h"
#include "ftsTool.h"
#include "../fts.h"//needed for including the define FW_H_FILE

#ifdef FW_H_FILE
#include <../fts_fw.h>
#define LOAD_FW_FROM 1
#else
#define LOAD_FW_FROM 0
#endif

#define FTS_LATEST_VERSION 0x1101

static char tag[8] = "[ FTS ]\0";

int getFirmwareVersion(u16 *fw_vers, u16 *config_id)
{
	u8 fwvers[DCHIP_FW_VER_BYTE];
	u8 confid[CONFIG_ID_BYTE];
	int res;

	res = readCmdU16(FTS_CMD_HW_REG_R, DCHIP_FW_VER_ADDR,
			fwvers, DCHIP_FW_VER_BYTE, DUMMY_HW_REG);
	if (res < OK) {
		logError(1,
		"%s %s:unable to read fw_version ERROR %02X\n",
		tag, __func__, ERROR_FW_VER_READ);
		return (res | ERROR_FW_VER_READ);
	}

	u8ToU16(fwvers, fw_vers); //fw version use big endian
	if (*fw_vers != 0) {
	// if fw_version is 00 00 means that there is
	//no firmware running in the chip therefore will be
	//impossible find the config_id
		res = readB2(CONFIG_ID_ADDR, confid, CONFIG_ID_BYTE);
		if (res < OK) {
			logError(1, "%s %s:unable to read config_id ",
				tag, __func__);
			logError(1, "ERROR %02X\n", ERROR_FW_VER_READ);
			return (res | ERROR_FW_VER_READ);
		}
		u8ToU16(confid, config_id); //config id use little endian
	} else {
		*config_id = 0x0000;
	}

	logError(0, "%s FW VERS = %04X\n", tag, *fw_vers);
	logError(0, "%s CONFIG ID = %04X\n", tag, *config_id);
	return OK;
}

int getFWdata_nocheck(const char *pathToFile, u8 **data, int *size, int from)
{
	const struct firmware *fw = NULL;
	struct device *dev = getDev();
	int res;

	if (dev == NULL)
		return ERROR_OP_NOT_ALLOW;

	logError(0, "%s Read FW from BIN file!\n", tag);

	res = firmware_request_nowarn(&fw, pathToFile, dev);
	if (res) {
		logError(1, "%s %s:No File found! ERROR %08X\n",
			tag, __func__, ERROR_FILE_NOT_FOUND);
		return ERROR_FILE_NOT_FOUND;
	}

	*size = fw->size;
	*data = (u8 *)kmalloc_array((*size), sizeof(u8), GFP_KERNEL);
	if (*data == NULL) {
		logError(1, "%s %s:Impossible to allocate! %08X\n", __func__);
		release_firmware(fw);
		return ERROR_ALLOC;
	}
	memcpy(*data, (u8 *)fw->data, (*size));
	release_firmware(fw);

	logError(0, "%s %s:Finshed!\n", tag, __func__);
	return OK;
}

int getFWdata(const char *pathToFile, u8 **data, int *size, int from)
{
	const struct firmware *fw = NULL;
	struct device *dev = NULL;
	int res;

	logError(0, "%s %s starting...\n", tag, __func__);
	switch (from) {
#ifdef FW_H_FILE
	case 1:
		logError(1, "%s Read FW from .h file!\n", tag);
		*size = FW_SIZE_NAME;
		*data = (u8 *)kmalloc_array((*size), sizeof(u8), GFP_KERNEL);
		if (*data == NULL) {
			logError(1, "%s %s:Impossible to allocate memory! ",
				tag, __func__);
			logError(1, "ERROR %08X\n", ERROR_ALLOC);

			return ERROR_ALLOC;
		}
		memcpy(*data, (u8 *)FW_ARRAY_NAME, (*size));
		break;
#endif
	default:
		logError(0, "%s Read FW from BIN file!\n", tag);

		if (ftsInfo.u16_fwVer >= FTS_LATEST_VERSION)
			return ERROR_FW_NO_UPDATE;

		dev = getDev();

		if (dev != NULL) {
			res = firmware_request_nowarn(&fw, pathToFile, dev);
			if (res == 0) {
				*size = fw->size;
				*data = (u8 *)kmalloc_array((*size), sizeof(u8),
					GFP_KERNEL);
				if (*data == NULL) {
					logError(1, "%s %s:Impossible to ",
						tag, __func__);
					logError(1, "%allocate! %08X\n",
						ERROR_ALLOC);
					release_firmware(fw);
					return ERROR_ALLOC;
				}
				memcpy(*data, (u8 *)fw->data, (*size));
				release_firmware(fw);
			} else {
				logError(0, "%s %s:No File found! ERROR %08X\n",
					tag, __func__, ERROR_FILE_NOT_FOUND);
				return ERROR_FILE_NOT_FOUND;
			}
		} else {
			logError(1, "%s %s:No device found! ERROR %08X\n",
			tag, __func__, ERROR_OP_NOT_ALLOW);
			return ERROR_OP_NOT_ALLOW;
		}
		/* break; */
	}

	logError(0, "%s %s:Finshed!\n", tag, __func__);
	return OK;
}

int readFwFile(const char *path, struct Firmware *fw, int keep_cx)
{
	int res;
	int orig_size;
	u8 *orig_data = NULL;


	res = getFWdata(path, &orig_data, &orig_size, LOAD_FW_FROM);
	if (res < OK) {
		logError(0, "%s %s:impossible retrieve FW... ERROR %08X\n",
			tag, __func__, ERROR_MEMH_READ);

		return (res | ERROR_MEMH_READ);
	}
	res = parseBinFile(orig_data, orig_size, fw, keep_cx);

	if (res < OK) {
		logError(1, "%s %s:impossible parse ERROR %08X\n",
			tag, __func__, ERROR_MEMH_READ);
		return (res | ERROR_MEMH_READ);
	}

	return OK;
}

int flashProcedure(const char *path, int force, int keep_cx)
{
	struct Firmware fw;
	int res;

	fw.data = NULL;
	logError(0, "%s Reading Fw file...\n", tag);
	res = readFwFile(path, &fw, keep_cx);
	if (res < OK) {
		logError(0, "%s %s: ERROR %02X\n",
			tag, __func__, (res | ERROR_FLASH_PROCEDURE));
		kfree(fw.data);
		return (res | ERROR_FLASH_PROCEDURE);
	}
	logError(0, "%s Fw file read COMPLETED!\n", tag);

	logError(0, "%s Starting flashing procedure...\n", tag);
	res = flash_burn(&fw, force, keep_cx);
	if (res < OK && res != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) {
		logError(1, "%s %s: ERROR %02X\n",
			tag, __func__, ERROR_FLASH_PROCEDURE);
		kfree(fw.data);
		return (res | ERROR_FLASH_PROCEDURE);
	}
	logError(0, "%s flashing procedure Finished!\n", tag);
	kfree(fw.data);

	return res;
}

#ifdef FTM3_CHIP
int flash_status(void)
{
	u8 cmd[2] = {FLASH_CMD_READSTATUS, 0x00};
	u8 readData = 0;

	logError(0, "%s %s:Reading ...\n", tag, __func__);
	if (fts_readCmd(cmd, 2, &readData, FLASH_STATUS_BYTES) < 0) {
		logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_R);
		return ERROR_I2C_R;
	}

	readData &= 0x01;
	logError(0, "%s %s = %d\n", tag, __func__, readData);
	return (int) readData;
}

int flash_status_ready(void)
{

	int status = flash_status();

	if (status == ERROR_I2C_R) {
		logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_R);
		return ERROR_I2C_R;
	}

	if (status != FLASH_READY) {
		//logError(1,
		//"%s %s:flash busy or unknown STATUS = % 02X\n",
		//tag, status);
		return ERROR_FLASH_UNKNOWN;
	}

	return FLASH_READY;
}

int wait_for_flash_ready(void)
{
	int status;
	int (*code)(void);

	code = flash_status_ready;

	logError(0, "%s Waiting for flash ready...\n", tag);
	status = attempt_function(code, FLASH_WAIT_BEFORE_RETRY,
			FLASH_RETRY_COUNT);

	if (status != FLASH_READY) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_FLASH_NOT_READY);
		return (status | ERROR_FLASH_NOT_READY);
	}

	logError(0, "%s Flash ready!\n", tag);
	return OK;
}

int flash_unlock(void)
{
	int status;
	//write the command to perform the unlock
	u8 cmd[3] = {FLASH_CMD_UNLOCK, FLASH_UNLOCK_CODE0,
		FLASH_UNLOCK_CODE1};

	logError(0, "%s Try to unlock flash...\n", tag);
	status = wait_for_flash_ready();

	if (status != OK) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_FLASH_NOT_READY);
		//Flash not ready within the chosen time, better exit!
		return (status | ERROR_FLASH_NOT_READY);
	}

	logError(0, "%s Command unlock ...\n", tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_I2C_W);
		return ERROR_I2C_W;
	}

	status = wait_for_flash_ready();

	if (status != OK) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_FLASH_NOT_READY);
		//Flash not ready within the chosen time,
		//better exit!
		return (status | ERROR_FLASH_NOT_READY);
	}
	logError(0, "%s Unlock flash DONE!\n", tag);

	return OK;
}

int parseBinFile(u8 *fw_data, int fw_size, Firmware *fwData, int keep_cx)
{
	int dimension;

	if (keep_cx) {
		dimension = FW_SIZE - FW_CX_SIZE;
		logError(1, "%s %s: Selected 124k Configuration!\n",
			tag, __func__);
	} else {
		dimension = FW_SIZE;
		logError(1, "%s %s: Selected 128k Configuration!\n",
			tag, __func__);
	}

	if (fw_size - FW_HEADER_SIZE != FW_SIZE || fw_data == NULL) {
		logError(1, "%s %s:Read only %d instead of %d... ERROR %02X\n",
			tag, __func__,
			fw_size - FW_HEADER_SIZE,
			FW_SIZE, ERROR_FILE_PARSE);
		kfree(fw_data);
		return ERROR_FILE_PARSE;
	}

	fwData->data = (u8 *)kmalloc_array(dimension, sizeof(u8), GFP_KERNEL);
	if (fwData->data == NULL) {
		logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC);

		kfree(fw_data);
		return ERROR_ALLOC;
	}

	memcpy(fwData->data, ((u8 *)(fw_data) + FW_HEADER_SIZE),
				dimension);
	fwData->data_size = dimension;

	fwData->fw_ver = (u16)(((fwData->data[FW_VER_MEMH_BYTE1] & 0x00FF) << 8)
				+ (fwData->data[FW_VER_MEMH_BYTE0] & 0x00FF));

	fwData->config_id = (u16)(((fwData->data[(FW_CODE_SIZE)
				+ FW_OFF_CONFID_MEMH_BYTE1] & 0x00FF) << 8)
			+ (fwData->data[(FW_CODE_SIZE) +
				FW_OFF_CONFID_MEMH_BYTE0] & 0x00FF));

	logError(0, "%s %s: FW VERS File = %04X\n",
			tag, __func__, fwData->fw_ver);
	logError(0, "%s %s: CONFIG ID File = %04X\n",
			tag, __func__, fwData->config_id);

	logError(0, "%s READ FW DONE %d bytes!\n", tag, fwData->data_size);

	kfree(fw_data);
		return OK;
}

int fillMemory(u32 address, u8 *data, int size)
{
	int remaining = size;
	int toWrite = 0;
	int delta;

	u8 *buff = (u8 *)kmalloc_array((MEMORY_CHUNK + 3), sizeof(u8),
				GFP_KERNEL);
	if (buff == NULL) {
		logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC);
		return ERROR_ALLOC;
	}

	while (remaining > 0) {
		if (remaining >= MEMORY_CHUNK) {
			if ((address + MEMORY_CHUNK) < FLASH_ADDR_SWITCH_CMD) {
				buff[0] = FLASH_CMD_WRITE_LOWER_64;
				toWrite = MEMORY_CHUNK;
				remaining -= MEMORY_CHUNK;
			} else {
				if (address < FLASH_ADDR_SWITCH_CMD) {
					delta = FLASH_ADDR_SWITCH_CMD - address;

					buff[0] = FLASH_CMD_WRITE_LOWER_64;
					toWrite = delta;
					remaining -= delta;
				} else {
					buff[0] = FLASH_CMD_WRITE_UPPER_64;
					toWrite = MEMORY_CHUNK;
					remaining -= MEMORY_CHUNK;
				}
			}
		} else {
			if ((address + remaining) < FLASH_ADDR_SWITCH_CMD) {
				buff[0] = FLASH_CMD_WRITE_LOWER_64;
				toWrite = remaining;
				remaining = 0;
			} else {
				if (address < FLASH_ADDR_SWITCH_CMD) {
					delta = FLASH_ADDR_SWITCH_CMD - address;

					buff[0] = FLASH_CMD_WRITE_LOWER_64;
					toWrite = delta;
					remaining -= delta;
				} else {
					buff[0] = FLASH_CMD_WRITE_UPPER_64;
					toWrite = remaining;
					remaining = 0;
				}
			}
		}

		buff[1] = (u8) ((address & 0x0000FF00) >> 8);
		buff[2] = (u8) (address & 0x000000FF);
		memcpy(buff + 3, data, toWrite);
		//logError(0,
		//"%s Command = %02X , address = %02X %02X, bytes = %d\n",
		//tag, buff[0], buff[1], buff[2], toWrite);
		if (fts_writeCmd(buff, 3 + toWrite) < 0) {
			logError(1, "%s %s: ERROR %02X\n",
				tag, __func__, ERROR_I2C_W);
			kfree(buff);
			return ERROR_I2C_W;
		}
		address += toWrite;
		data += toWrite;
	}
	kfree(buff);
	return OK;
}

int flash_burn(Firmware *fw, int force_burn, int keep_cx)
{
	u8 cmd;
	int res;

	if (!force_burn && (ftsInfo.u16_fwVer >= fw->fw_ver)
		&& (ftsInfo.u16_cfgId >= fw->config_id)) {
		logError(0, "Firmware in the chip newer");
		logError(0, " or equal to the one to burn! ");
		logError(0, "%s %s:NO UPDATE ERROR %02X\n",
			tag, __func__, ERROR_FW_NO_UPDATE);
		return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED);
	}

	//programming procedure start

	logError(0, "%s Programming Procedure for flashing started:\n", tag);

	logError(0, "%s 1) SYSTEM RESET:\n", tag);
	res = fts_system_reset();
	if (res < 0) {
		logError(1, "%s system reset FAILED!\n", tag);
		//if there is no firmware i will not
		//get the controller ready event and
		//there will be a timeout but i can
		//keep going, but if there is
		//an I2C error i have to exit
		if (res != (ERROR_SYSTEM_RESET_FAIL | ERROR_TIMEOUT))
			return (res | ERROR_FLASH_BURN_FAILED);
	} else
		logError(0, "%s system reset COMPLETED!\n\n", tag);

	logError(0, "%s 2) FLASH UNLOCK:\n", tag);
	res = flash_unlock();
	if (res < 0) {
		logError(1, "%s flash unlock FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s flash unlock COMPLETED!\n\n", tag);


	//Write the lower part of the Program RAM
	logError(0, "%s 3) PREPARING DATA FOR FLASH BURN:\n", tag);

	res = fillMemory(FLASH_ADDR_CODE, fw->data, fw->data_size);
	if (res < 0) {
		logError(1, "%s Error During filling the memory!%02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s Data copy COMPLETED!\n\n", tag);

	logError(0, "%s 4) ERASE FLASH:\n", tag);
	res = wait_for_flash_ready();
	if (res < 0) {
		logError(1, "%s Flash not ready! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}

	logError(0, "%s Command erase ...\n", tag);
	cmd = FLASH_CMD_ERASE;
	if (fts_writeCmd(&cmd, 1) < 0) {
		logError(1, "%s Error during erasing flash! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED);
	}

	res = wait_for_flash_ready();
	if (res < 0) {
		logError(1, "%s Flash not ready 2! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}

	logError(0, "%s Flash erase COMPLETED!\n\n", tag);

	logError(0, "%s 5) BURN FLASH:\n", tag);
	logError(0, "%s Command burn ...\n", tag);
	cmd = FLASH_CMD_BURN;
	if (fts_writeCmd(&cmd, 1) < 0) {
		logError(1, "%s Error during burning data! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED);
	}

	res = wait_for_flash_ready();
	if (res < 0) {
		logError(1, "%s Flash not ready! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}

	logError(0, "%s Flash burn COMPLETED!\n\n", tag);

	logError(0, "%s 6) SYSTEM RESET:\n", tag);
	res = fts_system_reset();
	if (res < 0) {
		logError(1, "%s system reset FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s system reset COMPLETED!\n\n", tag);


	logError(0, "%s 7) FINAL CHECK:\n", tag);
	res = readChipInfo(0);
	if (res < 0) {
		logError(1, "%s %s:Unable to retrieve Chip INFO!%02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}

	if ((ftsInfo.u16_fwVer != fw->fw_ver)
		&& (ftsInfo.u16_cfgId != fw->config_id)) {
		logError(1, "Firmware in the chip different");
		logError(1, " from the one that was burn!");
		logError(1, "%s fw: %x != %x , conf: %x != %x\n",
			tag, ftsInfo.u16_fwVer,
			fw->fw_ver,
			ftsInfo.u16_cfgId,
			fw->config_id);
		return ERROR_FLASH_BURN_FAILED;
	}

	logError(0, "%s Final check OK! fw: %02X, conf: %02X\n",
		tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId);

	return OK;
}

#else

int wait_for_flash_ready(u8 type)
{
	u8 cmd[2] = {FLASH_CMD_READ_REGISTER, type};
	u8 readData = 0;
	int i, res = -1;

	logError(0, "%s Waiting for flash ready ...\n", tag);
	for (i = 0; i < FLASH_RETRY_COUNT && res != 0; i++) {
		if (fts_readCmd(cmd, sizeof(cmd), &readData, 1) < 0) {
			logError(1, "%s %s: ERROR % 02X\n",
				tag, __func__, ERROR_I2C_R);
		} else {
			res = readData & 0x80;
			//logError(0, "%s flash status = %d\n", tag, res);
		}
		msleep(FLASH_WAIT_BEFORE_RETRY);
	}

	if (i == FLASH_RETRY_COUNT && res != 0) {
		logError(1, "%s Wait for flash TIMEOUT! ERROR %02X\n",
			tag, ERROR_TIMEOUT);
		return ERROR_TIMEOUT;
	}

	logError(0, "%s Flash READY!\n", tag);
	return OK;
}

int fts_warm_boot(void)
{
	//write the command to perform the warm boot
	u8 cmd[4] = {FTS_CMD_HW_REG_W, 0x00, 0x00, WARM_BOOT_VALUE};

	u16ToU8_be(ADDR_WARM_BOOT, &cmd[1]);

	logError(0, "%s Command warm boot ...\n", tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_I2C_W);
		return ERROR_I2C_W;
	}

	logError(0, "%s Warm boot DONE!\n", tag);

	return OK;
}

int parseBinFile(u8 *data, int fw_size,
		struct Firmware *fwData, int keep_cx)
{
	int dimension, index = 0;
	u32 temp;
	int res, i;

	//the file should contain at least the header plus the content_crc
	if (fw_size < FW_HEADER_SIZE+FW_BYTES_ALIGN || data == NULL) {
		logError(1, "%s %s:Read only %d instead of %d...ERROR %02X\n",
			tag, __func__, fw_size,
			FW_HEADER_SIZE + FW_BYTES_ALIGN,
			ERROR_FILE_PARSE);
		res = ERROR_FILE_PARSE;
		goto END;
	} else {
		//start parsing of bytes
		u8ToU32(&data[index], &temp);
		if (temp != FW_HEADER_SIGNATURE) {
			logError(1, "%s %s:Wrong Signature %08X...ERROR %02X\n",
				tag, __func__, temp, ERROR_FILE_PARSE);
			res = ERROR_FILE_PARSE;
			goto END;
		}

		logError(0, "%s %s: Fw Signature OK!\n", tag, __func__);
		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		if (temp != FW_FTB_VER) {
			logError(1, "%s %s:Wrong ftb_version %08X.ERROR %02X\n",
			tag, __func__, temp, ERROR_FILE_PARSE);
			res = ERROR_FILE_PARSE;
			goto END;
		}
		logError(0, "%s %s:ftb_version OK!\n", __func__, tag);
		index += FW_BYTES_ALIGN;
		if (data[index] != DCHIP_ID_0 || data[index+1] != DCHIP_ID_1) {
			logError(1, "%s %s:Wrong target %02X != %02X ",
				tag, __func__, data[index]);
			logError(1, "%%02X != %02X:%08X\n",
				DCHIP_ID_0, data[index+1],
				DCHIP_ID_1, ERROR_FILE_PARSE);
			res = ERROR_FILE_PARSE;
			goto END;
		}
		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		logError(0, "%s %s: Fw ID = %08X\n", tag, __func__, temp);
		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->fw_ver = temp;
		logError(0, "%s %s:FILE Fw Version = %04X\n",
			tag, __func__, fwData->fw_ver);

		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->config_id = temp;
		logError(0, "%s %s:FILE Config ID = %04X\n",
			tag, __func__, fwData->config_id);

		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		logError(0, "%s %s:Config Version = %08X\n",
			tag, __func__, temp);
		//skip reserved data
		index += FW_BYTES_ALIGN * 2;
		index += FW_BYTES_ALIGN;
		logError(0, "%s %s:File External Release =  ",
			tag, __func__);
		for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) {
			fwData->externalRelease[i] = data[index++];
			logError(0, "%02X", fwData->externalRelease[i]);
		}
		logError(0, "\n");

		//index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->sec0_size = temp;
		logError(0, "%s %s:sec0_size = %08X (%d bytes)\n",
			tag, __func__, fwData->sec0_size, fwData->sec0_size);

		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->sec1_size = temp;
		logError(0, "%s %s:sec1_size = %08X (%d bytes)\n",
			tag, __func__, fwData->sec1_size, fwData->sec1_size);

		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->sec2_size = temp;
		logError(0, "%s %s:sec2_size = %08X (%d bytes)\n",
			tag, __func__, fwData->sec2_size, fwData->sec2_size);

		index += FW_BYTES_ALIGN;
		u8ToU32(&data[index], &temp);
		fwData->sec3_size = temp;
		logError(0, "%s %s:sec3_size = %08X (%d bytes)\n",
			tag, __func__, fwData->sec3_size, fwData->sec3_size);

		//skip header crc
		index += FW_BYTES_ALIGN;
		if (!keep_cx) {
			dimension = fwData->sec0_size + fwData->sec1_size
				+ fwData->sec2_size + fwData->sec3_size;
			temp = fw_size;
		} else {
			//sec2 may contain cx data (future implementation)
			//sec3 atm not used
			dimension = fwData->sec0_size + fwData->sec1_size;
			temp = fw_size - fwData->sec2_size - fwData->sec3_size;
			fwData->sec2_size = 0;
			fwData->sec3_size = 0;
		}
		if (dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN != temp) {
			logError(1, "%s %s:Read only %d instead of %d...",
				tag, __func__, fw_size,
				dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN);
			logError(1, "ERROR %02X\n", ERROR_FILE_PARSE);
			res = ERROR_FILE_PARSE;
			goto END;
		}
		fwData->data = (u8 *)kmalloc_array(dimension, sizeof(u8),
					GFP_KERNEL);
		if (fwData->data == NULL) {
			logError(1, "%s %s: ERROR %02X\n",
				tag, __func__, ERROR_ALLOC);
			res = ERROR_ALLOC;
			goto END;
		}
		index += FW_BYTES_ALIGN;
		memcpy(fwData->data, &data[index], dimension);
		fwData->data_size = dimension;
		logError(0, "%s READ FW DONE %d bytes!\n",
			tag, fwData->data_size);
		res = OK;
		goto END;
	}
END:
	kfree(data);
	return res;
}

int flash_unlock(void)
{
	//write the command to perform the unlock
	u8 cmd[3] = {FLASH_CMD_UNLOCK,
		FLASH_UNLOCK_CODE0, FLASH_UNLOCK_CODE1};
	logError(0, "%s Command unlock ...\n", tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s %s: ERROR % 02X\n", tag, __func__, ERROR_I2C_W);
		return ERROR_I2C_W;
	}
	//mdelay(FLASH_WAIT_TIME);
	logError(0, "%s Unlock flash DONE!\n", tag);

	return OK;
}

int flash_erase_unlock(void)
{
	//write the command to perform
	//the unlock for erasing the flash
	u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_UNLOCK_CODE0,
		FLASH_ERASE_UNLOCK_CODE1};

	logError(0, "%s Try to erase unlock flash...\n", tag);

	logError(0, "%s Command erase unlock ...\n", tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s %s:ERROR % 02X\n", tag, __func__, ERROR_I2C_W);
		return ERROR_I2C_W;
	}

	logError(0, "%s Erase Unlock flash DONE!\n", tag);

	return OK;
}

int flash_full_erase(void)
{
	int status;
	//write the command to erase the flash
	u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_CODE0,
			FLASH_ERASE_CODE1};

	logError(0, "%s Command full erase sent...\n",
		tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s %s:ERROR % 02X\n", tag, __func__, ERROR_I2C_W);
		return ERROR_I2C_W;
	}

	status = wait_for_flash_ready(FLASH_ERASE_CODE0);

	if (status != OK) {
		logError(1, "%s %s:ERROR % 02X\n",
			tag, __func__, ERROR_FLASH_NOT_READY);
		//Flash not ready within the chosen time,
		//better exit!
		return (status | ERROR_FLASH_NOT_READY);
	}

	logError(0, "%s Full Erase flash DONE!\n", tag);

	return OK;
}

int flash_erase_page_by_page(int keep_cx)
{
	u8 status, i = 0;
	//write the command to erase the flash
	u8 cmd[4] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_CODE0, 0x00, 0x00};

	for (i = 0; i < FLASH_NUM_PAGE; i++) {
		if (i >= FLASH_CX_PAGE_START && i <= FLASH_CX_PAGE_END
			&& keep_cx == 1) {
			logError(0, "%s Skipping erase page %d!\n", tag, i);
			continue;
		}
		cmd[2] = (0x3F & i) | FLASH_ERASE_START;
		logError(0, "Command erase page %d sent", i);
		logError(0, "%s:%02X %02X %02X %02X\n",
			tag, i, cmd[0], cmd[1], cmd[2], cmd[3]);
		if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
			logError(1,
			"%s %s:ERROR % 08X\n",
			tag, __func__, ERROR_I2C_W);
			return ERROR_I2C_W;
		}

		status = wait_for_flash_ready(FLASH_ERASE_CODE0);
		if (status != OK) {
			logError(1, "%s %s:ERROR % 08X\n",
				tag, __func__, ERROR_FLASH_NOT_READY);
			//Flash not ready within the chosen time,
			//better exit!
			return (status | ERROR_FLASH_NOT_READY);
		}
	}

	logError(0, "%s Erase flash page by page DONE!\n", tag);

	return OK;
}

int start_flash_dma(void)
{
	int status;
	//write the command to erase the flash
	u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_DMA_CODE0,
			FLASH_DMA_CODE1};

	logError(0, "%s Command flash DMA ...\n", tag);
	if (fts_writeCmd(cmd, sizeof(cmd)) < 0) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_I2C_W);
		return ERROR_I2C_W;
	}

	status = wait_for_flash_ready(FLASH_DMA_CODE0);

	if (status != OK) {
		logError(1, "%s %s: ERROR % 02X\n",
			tag, __func__, ERROR_FLASH_NOT_READY);
		//Flash not ready within the chosen time, better exit!
		return (status | ERROR_FLASH_NOT_READY);
	}

	logError(0, "%s flash DMA DONE!\n", tag);

	return OK;
}

int fillFlash(u32 address, u8 *data, int size)
{
	int remaining = size;
	int toWrite = 0;
	int byteBlock = 0;
	int wheel = 0;
	u32 addr = 0;
	int res;
	int delta;
	u8 *buff = NULL;
	u8 buff2[9] = {0};


	buff = (u8 *)kmalloc_array((DMA_CHUNK + 3), sizeof(u8), GFP_KERNEL);
	if (buff == NULL) {
		logError(1, "%s %s: ERROR %02X\n", tag, __func__, ERROR_ALLOC);
		return ERROR_ALLOC;
	}

	while (remaining > 0) {
		byteBlock = 0;
		addr = 0;
		while (byteBlock < FLASH_CHUNK && remaining > 0) {
			buff[0] = FLASH_CMD_WRITE_64K;
			if (remaining >= DMA_CHUNK) {
				if ((byteBlock + DMA_CHUNK) <= FLASH_CHUNK) {
					//logError(1, "%s fillFlash:1\n", tag);
					toWrite = DMA_CHUNK;
					remaining -= DMA_CHUNK;
					byteBlock += DMA_CHUNK;
				} else {
					//logError(1, "%s fillFlash:2\n", tag);
					delta = FLASH_CHUNK - byteBlock;
					toWrite = delta;
					remaining -= delta;
					byteBlock += delta;
				}
			} else {
				if ((byteBlock + remaining) <= FLASH_CHUNK) {
					//logError(1, "%s fillFlash:3\n", tag);
					toWrite = remaining;
					byteBlock += remaining;
					remaining = 0;
				} else {
					//logError(1, "%s fillFlash:4\n", tag);
					delta = FLASH_CHUNK - byteBlock;
					toWrite = delta;
					remaining -= delta;
					byteBlock += delta;
				}
			}

			buff[1] = (u8) ((addr & 0x0000FF00) >> 8);
			buff[2] = (u8) (addr & 0x000000FF);
			memcpy(&buff[3], data, toWrite);
			//logError(0,
			//"%s Command = %02X, address = %02X %02X,
			//bytes = %d, data = %02X %02X, %02X %02X\n",
			//tag, buff[0], buff[1], buff[2], toWrite,
			//buff[3], buff[4], buff[3 + toWrite-2],
			//buff[3 + toWrite-1]);
			if (fts_writeCmd(buff, 3 + toWrite) < 0) {
				logError(1, "%s %s: ERROR %02X\n",
					tag, __func__, ERROR_I2C_W);
				kfree(buff);
				return ERROR_I2C_W;
			}

			addr += toWrite;
			data += toWrite;
		}
		//configuring the DMA
		byteBlock = byteBlock / 4 - 1;

		buff2[0] = FLASH_CMD_WRITE_REGISTER;
		buff2[1] = FLASH_DMA_CONFIG;
		buff2[2] = 0x00;
		buff2[3] = 0x00;

		addr = address + ((wheel * FLASH_CHUNK)/4);
		buff2[4] = (u8) ((addr & 0x000000FF));
		buff2[5] = (u8) ((addr & 0x0000FF00) >> 8);
		buff2[6] = (u8) (byteBlock & 0x000000FF);
		buff2[7] = (u8) ((byteBlock & 0x0000FF00) >> 8);
		buff2[8] = 0x00;

		logError(0, "%s:Command:%02X, address:%02X %02X, ",
			tag, buff2[0], buff2[5], buff2[4]);
		logError(0, "words:%02X %02X\n", buff2[7], buff2[6]);
		if (fts_writeCmd(buff2, 9) < OK) {
			logError(1, "%s Error during filling Flash!:%02X\n",
				tag, ERROR_I2C_W);
			kfree(buff);
			return ERROR_I2C_W;
		}
		//mdelay(FLASH_WAIT_TIME);
		res = start_flash_dma();
		if (res < OK) {
			logError(1, "%s Error during flashing DMA!:%02X\n",
				tag, res);
			kfree(buff);
			return res;
		}
		wheel++;
	}
	kfree(buff);
	return OK;
}

int flash_burn(struct Firmware *fw, int force_burn, int keep_cx)
{
	int res;

	if (!force_burn && (ftsInfo.u16_fwVer >= fw->fw_ver)
		&& (ftsInfo.u16_cfgId >= fw->config_id)) {
		for (res = EXTERNAL_RELEASE_INFO_SIZE-1; res >= 0; res--) {
			if (fw->externalRelease[res] >
				ftsInfo.u8_extReleaseInfo[res])
				goto start;
		}

		logError(0, "Firmware in the chip newer or ");
		logError(0, "equal to the one to burn!");
		logError(0, "%s %s:NO UPDATE ERROR %02X\n",
			tag, __func__, ERROR_FW_NO_UPDATE);
		return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED);
	}

	//programming procedure start
start:
	logError(0, "%s Programming Procedure for flashing started:\n\n", tag);

	logError(0, "%s 1) SYSTEM RESET:\n", tag);

	logError(0, "%s 2) WARM BOOT:\n", tag);
	res = fts_warm_boot();
	if (res < OK) {
		logError(1, "%s warm boot FAILED!\n", tag);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s warm boot COMPLETED!\n\n", tag);

	//mdelay(FLASH_WAIT_TIME);
	logError(0, "%s 3) FLASH UNLOCK:\n", tag);
	res = flash_unlock();
	if (res < OK) {
		logError(1, "%s flash unlock FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s flash unlock COMPLETED!\n\n", tag);

	//mdelay(200);
	logError(0, "%s 4) FLASH ERASE UNLOCK:\n", tag);
	res = flash_erase_unlock();
	if (res < 0) {
		logError(1, "%s flash unlock FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s flash unlock COMPLETED!\n\n", tag);

	//mdelay(FLASH_WAIT_TIME);
	logError(0, "%s 5) FLASH ERASE:\n", tag);
	if (keep_cx == 1)
		res = flash_erase_page_by_page(keep_cx);
	else
		res = flash_full_erase();
	if (res < 0) {
		logError(1, "%s flash erase FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s flash erase COMPLETED!\n\n", tag);


	//mdelay(FLASH_WAIT_TIME);
	logError(0, "%s 6) LOAD PROGRAM:\n", tag);
	res = fillFlash(FLASH_ADDR_CODE, (u8 *)(&fw->data[0]),
					fw->sec0_size);
	if (res < OK) {
		logError(1, "%s   load program ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s   load program DONE!\n", tag);
	logError(0, "%s 7) LOAD CONFIG:\n", tag);
	res = fillFlash(FLASH_ADDR_CONFIG,
		&(fw->data[fw->sec0_size]), fw->sec1_size);
	if (res < OK) {
		logError(1, "%s   load config ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s   load config DONE!\n", tag);

	logError(0, "%s   Flash burn COMPLETED!\n\n", tag);

	logError(0, "%s 8) SYSTEM RESET:\n", tag);
	res = fts_system_reset();
	if (res < 0) {
		logError(1, "%s system reset FAILED! ERROR %02X\n",
			tag, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}
	logError(0, "%s   system reset COMPLETED!\n\n", tag);


	logError(0, "%s 9) FINAL CHECK:\n", tag);
	res = readChipInfo(0);
	if (res < 0) {
		logError(1, "%s %s:Unable to retrieve Chip INFO!:%02X\n",
			tag, __func__, ERROR_FLASH_BURN_FAILED);
		return (res | ERROR_FLASH_BURN_FAILED);
	}

	if ((ftsInfo.u16_fwVer != fw->fw_ver)
		&& (ftsInfo.u16_cfgId != fw->config_id)) {
		pr_err("Firmware is different from the old!\n");
		logError(1, "%s fw: %x != %x, conf: %x != %x\n",
			tag, ftsInfo.u16_fwVer, fw->fw_ver,
			ftsInfo.u16_cfgId, fw->config_id);
		return ERROR_FLASH_BURN_FAILED;
	}

	logError(0, "%s Final check OK! fw: %02X , conf: %02X\n",
		tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId);

	return OK;
}

#endif