// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2018-2020 Oplus. All rights reserved.
 */

#include <linux/crc32.h>
#include <linux/firmware.h>
#include "synaptics_tcm_core.h"

#define STARTUP_REFLASH

#define FORCE_REFLASH false

#define DISPLAY_REFLASH true

#define ENABLE_SYSFS_INTERFACE true

#define SYSFS_DIR_NAME "reflash"

#define CUSTOM_DIR_NAME "custom"

#define FW_IMAGE_NAME "synaptics/firmware.img"

#define BOOT_CONFIG_ID "BOOT_CONFIG"

#define APP_CODE_ID "APP_CODE"

#define PROD_TEST_ID "APP_PROD_TEST"

#define APP_CONFIG_ID "APP_CONFIG"

#define DISP_CONFIG_ID "DISPLAY"

#define FB_READY_COUNT 2

#define FB_READY_WAIT_MS 100

#define FB_READY_TIMEOUT_S 30

#define IMAGE_FILE_MAGIC_VALUE 0x4818472b

#define FLASH_AREA_MAGIC_VALUE 0x7c05e516

#define BOOT_CONFIG_SIZE 8

#define BOOT_CONFIG_SLOTS 16

#define IMAGE_BUF_SIZE (512 * 1024)

#define ERASE_FLASH_DELAY_MS 500

#define WRITE_FLASH_DELAY_MS 20

#define REFLASH (1 << 0)

#define FORCE_UPDATE (1 << 1)

#define APP_CFG_UPDATE (1 << 2)

#define DISP_CFG_UPDATE (1 << 3)

#define BOOT_CFG_UPDATE (1 << 4)

#define BOOT_CFG_LOCKDOWN (1 << 5)

#define reflash_write(p_name) \
static int reflash_write_##p_name(void) \
{ \
	int retval; \
	unsigned int size; \
	unsigned int flash_addr; \
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
	const unsigned char *data; \
\
	data = reflash_hcd->image_info.p_name.data; \
	size = reflash_hcd->image_info.p_name.size; \
	flash_addr = reflash_hcd->image_info.p_name.flash_addr; \
\
	retval = reflash_write_flash(flash_addr, data, size); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to write to flash\n"); \
		return retval; \
	} \
\
	return 0; \
}

#define reflash_erase(p_name) \
static int reflash_erase_##p_name(void) \
{ \
	int retval; \
	unsigned int size; \
	unsigned int flash_addr; \
	unsigned int page_start; \
	unsigned int page_count; \
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
\
	flash_addr = reflash_hcd->image_info.p_name.flash_addr; \
\
	page_start = flash_addr / reflash_hcd->page_size; \
\
	size = reflash_hcd->image_info.p_name.size; \
	page_count = ceil_div(size, reflash_hcd->page_size); \
\
	LOGD(tcm_hcd->pdev->dev.parent, \
			"Page start = %d\n", \
			page_start); \
\
	LOGD(tcm_hcd->pdev->dev.parent, \
			"Page count = %d\n", \
			page_count); \
\
	retval = reflash_erase_flash(page_start, page_count); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to erase flash pages\n"); \
		return retval; \
	} \
\
	return 0; \
}

#define reflash_update(p_name) \
static int reflash_update_##p_name(bool reset) \
{ \
	int retval; \
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd; \
\
	retval = reflash_set_up_flash_access(); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to set up flash access\n"); \
		return retval; \
	} \
\
	tcm_hcd->update_watchdog(tcm_hcd, false); \
\
	retval = reflash_check_##p_name(); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed "#p_name" partition check\n"); \
		reset = true; \
		goto reset; \
	} \
\
	retval = reflash_erase_##p_name(); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to erase "#p_name" partition\n"); \
		reset = true; \
		goto reset; \
	} \
\
	LOGN(tcm_hcd->pdev->dev.parent, \
			"Partition erased ("#p_name")\n"); \
\
	retval = reflash_write_##p_name(); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to write "#p_name" partition\n"); \
		reset = true; \
		goto reset; \
	} \
\
	LOGN(tcm_hcd->pdev->dev.parent, \
			"Partition written ("#p_name")\n"); \
\
	retval = 0; \
\
reset: \
	if (!reset) \
		goto exit; \
\
	if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
				"Failed to do reset\n"); \
	} \
\
exit: \
	tcm_hcd->update_watchdog(tcm_hcd, true); \
\
	return retval; \
}

#define reflash_show_data() \
{ \
	LOCK_BUFFER(reflash_hcd->read); \
\
	readlen = MIN(count, reflash_hcd->read.data_length - pos); \
\
	retval = secure_memcpy(buf, \
			count, \
			&reflash_hcd->read.buf[pos], \
			reflash_hcd->read.buf_size - pos, \
			readlen); \
	if (retval < 0) { \
		LOGE(tcm_hcd->pdev->dev.parent, \
			"Failed to copy read data\n"); \
	} else { \
		retval = readlen; \
	} \
\
	UNLOCK_BUFFER(reflash_hcd->read); \
}

enum update_area {
	NONE = 0,
	FIRMWARE_CONFIG,
	CONFIG_ONLY,
};

struct app_config_header {
	unsigned short magic_value[4];
	unsigned char checksum[4];
	unsigned char length[2];
	unsigned char build_id[4];
	unsigned char customer_config_id[16];
};

struct area_descriptor {
	unsigned char magic_value[4];
	unsigned char id_string[16];
	unsigned char flags[4];
	unsigned char flash_addr_words[4];
	unsigned char length[4];
	unsigned char checksum[4];
};

struct block_data {
	const unsigned char *data;
	unsigned int size;
	unsigned int flash_addr;
};

struct image_info {
	struct block_data boot_config;
	struct block_data app_firmware;
	struct block_data prod_test_firmware;
	struct block_data app_config;
	struct block_data disp_config;
};

struct image_header {
	unsigned char magic_value[4];
	unsigned char num_of_areas[4];
};

struct boot_config {
	union {
		unsigned char i2c_address;
		struct {
			unsigned char cpha:1;
			unsigned char cpol:1;
			unsigned char word0_b2__7:6;
		} __packed;
	};
	unsigned char attn_polarity:1;
	unsigned char attn_drive:2;
	unsigned char attn_pullup:1;
	unsigned char word0_b12__14:3;
	unsigned char used:1;
	unsigned short customer_part_id;
	unsigned short boot_timeout;
	unsigned short continue_on_reset:1;
	unsigned short word3_b1__15:15;
} __packed;

struct reflash_hcd {
	bool force_update;
	bool disp_cfg_update;
	const unsigned char *image;
	unsigned char *image_buf;
	unsigned int image_size;
	unsigned int page_size;
	unsigned int write_block_size;
	unsigned int max_write_payload_size;
	const struct firmware *fw_entry;
	struct mutex reflash_mutex;
	struct kobject *sysfs_dir;
	struct kobject *custom_dir;
	struct work_struct work;
	struct workqueue_struct *workqueue;
	struct image_info image_info;
	struct syna_tcm_buffer out;
	struct syna_tcm_buffer resp;
	struct syna_tcm_buffer read;
	struct syna_tcm_hcd *tcm_hcd;
};

DECLARE_COMPLETION(reflash_remove_complete);

static struct reflash_hcd *reflash_hcd;

static int reflash_get_fw_image(void);

static int reflash_read_data(enum flash_area area, bool run_app_firmware,
		struct syna_tcm_buffer *output);

static int reflash_update_custom_otp(const unsigned char *data,
		unsigned int offset, unsigned int datalen);

static int reflash_update_custom_lcm(const unsigned char *data,
		unsigned int offset, unsigned int datalen);

static int reflash_update_custom_oem(const unsigned char *data,
		unsigned int offset, unsigned int datalen);

static int reflash_update_boot_config(bool lock);

static int reflash_update_app_config(bool reset);

static int reflash_update_disp_config(bool reset);

static int reflash_do_reflash(void);

STORE_PROTOTYPE(reflash, reflash)

static struct device_attribute *attrs[] = {
	ATTRIFY(reflash),
};

static ssize_t reflash_sysfs_image_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_lockdown_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_lockdown_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_lcm_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_lcm_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_oem_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static ssize_t reflash_sysfs_oem_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count);

static struct bin_attribute bin_attrs[] = {
	{
		.attr = {
			.name = "image",
			.mode = (S_IWUSR | S_IWGRP),
		},
		.size = 0,
		.write = reflash_sysfs_image_store,
	},
	{
		.attr = {
			.name = "lockdown",
			.mode = (S_IRUGO | S_IWUSR | S_IWGRP),
		},
		.size = 0,
		.read = reflash_sysfs_lockdown_show,
		.write = reflash_sysfs_lockdown_store,
	},
	{
		.attr = {
			.name = "lcm",
			.mode = (S_IRUGO | S_IWUSR | S_IWGRP),
		},
		.size = 0,
		.read = reflash_sysfs_lcm_show,
		.write = reflash_sysfs_lcm_store,
	},
	{
		.attr = {
			.name = "oem",
			.mode = (S_IRUGO | S_IWUSR | S_IWGRP),
		},
		.size = 0,
		.read = reflash_sysfs_oem_show,
		.write = reflash_sysfs_oem_store,
	},
};

static ssize_t reflash_sysfs_reflash_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int retval;
	unsigned int input;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (sscanf(buf, "%u", &input) != 1)
		return -EINVAL;

	mutex_lock(&tcm_hcd->extif_mutex);

	pm_stay_awake(&tcm_hcd->pdev->dev);

	mutex_lock(&reflash_hcd->reflash_mutex);

	if (reflash_hcd->image_size != 0)
		reflash_hcd->image = reflash_hcd->image_buf;

	reflash_hcd->force_update = input & FORCE_UPDATE ? true : false;

	if (input & REFLASH || input & FORCE_UPDATE) {
		retval = reflash_do_reflash();
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to do reflash\n");
			goto exit;
		}
	}

	if ((input & ~(REFLASH | FORCE_UPDATE)) == 0) {
		retval = count;
		goto exit;
	}

	retval = reflash_get_fw_image();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to get firmware image\n");
		goto exit;
	}

	if (input & BOOT_CFG_LOCKDOWN) {
		retval = reflash_update_boot_config(true);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to lockdown boot config\n");
			goto exit;
		}
	} else if (input & BOOT_CFG_UPDATE) {
		retval = reflash_update_boot_config(false);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to update boot config\n");
			goto exit;
		}
	}

	if (input & REFLASH || input & FORCE_UPDATE) {
		retval = count;
		goto exit;
	}

	if (input & DISP_CFG_UPDATE) {
		if (input & APP_CFG_UPDATE)
			retval = reflash_update_disp_config(false);
		else
			retval = reflash_update_disp_config(true);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to reflash display config\n");
			goto exit;
		}
	}

	if (input & APP_CFG_UPDATE) {
		retval = reflash_update_app_config(true);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to reflash application config\n");
			goto exit;
		}
	}

	retval = count;

exit:
	if (reflash_hcd->fw_entry) {
		release_firmware(reflash_hcd->fw_entry);
		reflash_hcd->fw_entry = NULL;
	}

	reflash_hcd->image = NULL;
	reflash_hcd->image_size = 0;
	reflash_hcd->force_update = FORCE_REFLASH;

	mutex_unlock(&reflash_hcd->reflash_mutex);

	pm_relax(&tcm_hcd->pdev->dev);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_image_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	retval = secure_memcpy(&reflash_hcd->image_buf[pos],
			IMAGE_BUF_SIZE - pos,
			buf,
			count,
			count);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to copy firmware image data\n");
		reflash_hcd->image_size = 0;
		goto exit;
	}

	reflash_hcd->image_size = pos + count;

	retval = count;

exit:
	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_lockdown_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	unsigned int readlen;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_read_data(CUSTOM_OTP, true, NULL);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read lockdown data\n");
		goto exit;
	}

	reflash_show_data();

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_lockdown_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	pm_stay_awake(&tcm_hcd->pdev->dev);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_update_custom_otp(buf, pos, count);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to update custom OTP data\n");
		goto exit;
	}

	retval = count;

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	pm_relax(&tcm_hcd->pdev->dev);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_lcm_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	unsigned int readlen;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_read_data(CUSTOM_LCM, true, NULL);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read LCM data\n");
		goto exit;
	}

	reflash_show_data();

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_lcm_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	pm_stay_awake(&tcm_hcd->pdev->dev);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_update_custom_lcm(buf, pos, count);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to update custom LCM data\n");
		goto exit;
	}

	retval = count;

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	pm_relax(&tcm_hcd->pdev->dev);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_oem_show(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	unsigned int readlen;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_read_data(CUSTOM_OEM, true, NULL);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read OEM data\n");
		goto exit;
	}

	reflash_show_data();

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static ssize_t reflash_sysfs_oem_store(struct file *data_file,
		struct kobject *kobj, struct bin_attribute *attributes,
		char *buf, loff_t pos, size_t count)
{
	int retval;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	mutex_lock(&tcm_hcd->extif_mutex);

	pm_stay_awake(&tcm_hcd->pdev->dev);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_update_custom_oem(buf, pos, count);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to update custom OEM data\n");
		goto exit;
	}

	retval = count;

exit:
	mutex_unlock(&reflash_hcd->reflash_mutex);

	pm_relax(&tcm_hcd->pdev->dev);

	mutex_unlock(&tcm_hcd->extif_mutex);

	return retval;
}

static int reflash_set_up_flash_access(void)
{
	int retval;
	unsigned int temp;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = tcm_hcd->identify(tcm_hcd, true);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to do identification\n");
		return retval;
	}

	if (tcm_hcd->id_info.mode == MODE_APPLICATION) {
		retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to enter bootloader mode\n");
			return retval;
		}
	}

	temp = tcm_hcd->boot_info.write_block_size_words;
	reflash_hcd->write_block_size = temp * 2;

	temp = le2_to_uint(tcm_hcd->boot_info.erase_page_size_words);
	reflash_hcd->page_size = temp * 2;

	temp = le2_to_uint(tcm_hcd->boot_info.max_write_payload_size);
	reflash_hcd->max_write_payload_size = temp;

	LOGD(tcm_hcd->pdev->dev.parent,
			"Write block size = %d\n",
			reflash_hcd->write_block_size);

	LOGD(tcm_hcd->pdev->dev.parent,
			"Page size = %d\n",
			reflash_hcd->page_size);

	LOGD(tcm_hcd->pdev->dev.parent,
			"Max write payload size = %d\n",
			reflash_hcd->max_write_payload_size);

	if (reflash_hcd->write_block_size > (tcm_hcd->wr_chunk_size - 5)) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Write block size greater than available chunk space\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_parse_fw_image(void)
{
	unsigned int idx;
	unsigned int addr;
	unsigned int offset;
	unsigned int length;
	unsigned int checksum;
	unsigned int flash_addr;
	unsigned int magic_value;
	unsigned int num_of_areas;
	struct image_header *header;
	struct image_info *image_info;
	struct area_descriptor *descriptor;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
	const unsigned char *image;
	const unsigned char *content;

	image = reflash_hcd->image;
	image_info = &reflash_hcd->image_info;
	header = (struct image_header *)image;

	magic_value = le4_to_uint(header->magic_value);
	if (magic_value != IMAGE_FILE_MAGIC_VALUE) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid image file magic value\n");
		return -EINVAL;
	}

	memset(image_info, 0x00, sizeof(*image_info));

	offset = sizeof(*header);
	num_of_areas = le4_to_uint(header->num_of_areas);

	for (idx = 0; idx < num_of_areas; idx++) {
		addr = le4_to_uint(image + offset);
		descriptor = (struct area_descriptor *)(image + addr);
		offset += 4;

		magic_value = le4_to_uint(descriptor->magic_value);
		if (magic_value != FLASH_AREA_MAGIC_VALUE)
			continue;

		length = le4_to_uint(descriptor->length);
		content = (unsigned char *)descriptor + sizeof(*descriptor);
		flash_addr = le4_to_uint(descriptor->flash_addr_words) * 2;
		checksum = le4_to_uint(descriptor->checksum);

		if (0 == strncmp((char *)descriptor->id_string,
				BOOT_CONFIG_ID,
				strlen(BOOT_CONFIG_ID))) {
			if (checksum != (crc32(~0, content, length) ^ ~0)) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Boot config checksum error\n");
				return -EINVAL;
			}
			image_info->boot_config.size = length;
			image_info->boot_config.data = content;
			image_info->boot_config.flash_addr = flash_addr;
			LOGD(tcm_hcd->pdev->dev.parent,
					"Boot config size = %d\n",
					length);
			LOGD(tcm_hcd->pdev->dev.parent,
					"Boot config flash address = 0x%08x\n",
					flash_addr);
		} else if (0 == strncmp((char *)descriptor->id_string,
				APP_CODE_ID,
				strlen(APP_CODE_ID))) {
			if (checksum != (crc32(~0, content, length) ^ ~0)) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Application firmware checksum error\n");
				return -EINVAL;
			}
			image_info->app_firmware.size = length;
			image_info->app_firmware.data = content;
			image_info->app_firmware.flash_addr = flash_addr;
			LOGD(tcm_hcd->pdev->dev.parent,
					"Application firmware size = %d\n",
					length);
			LOGD(tcm_hcd->pdev->dev.parent,
					"Application firmware flash address = 0x%08x\n",
					flash_addr);
		} else if (0 == strncmp((char *)descriptor->id_string,
				PROD_TEST_ID,
				strlen(PROD_TEST_ID))) {
			if (checksum != (crc32(~0, content, length) ^ ~0)) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Production test firmware checksum error\n");
				return -EINVAL;
			}
			image_info->prod_test_firmware.size = length;
			image_info->prod_test_firmware.data = content;
			image_info->prod_test_firmware.flash_addr = flash_addr;
			LOGD(tcm_hcd->pdev->dev.parent,
					"Production test firmware size = %d\n",
					length);
			LOGD(tcm_hcd->pdev->dev.parent,
					"Production test firmware flash address = 0x%08x\n",
					flash_addr);
		} else if (0 == strncmp((char *)descriptor->id_string,
				APP_CONFIG_ID,
				strlen(APP_CONFIG_ID))) {
			if (checksum != (crc32(~0, content, length) ^ ~0)) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Application config checksum error\n");
				return -EINVAL;
			}
			image_info->app_config.size = length;
			image_info->app_config.data = content;
			image_info->app_config.flash_addr = flash_addr;
			LOGD(tcm_hcd->pdev->dev.parent,
					"Application config size = %d\n",
					length);
			LOGD(tcm_hcd->pdev->dev.parent,
					"Application config flash address = 0x%08x\n",
					flash_addr);
		} else if (0 == strncmp((char *)descriptor->id_string,
				DISP_CONFIG_ID,
				strlen(DISP_CONFIG_ID))) {
			if (checksum != (crc32(~0, content, length) ^ ~0)) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Display config checksum error\n");
				return -EINVAL;
			}
			image_info->disp_config.size = length;
			image_info->disp_config.data = content;
			image_info->disp_config.flash_addr = flash_addr;
			LOGD(tcm_hcd->pdev->dev.parent,
					"Display config size = %d\n",
					length);
			LOGD(tcm_hcd->pdev->dev.parent,
					"Display config flash address = 0x%08x\n",
					flash_addr);
		}
	}

	return 0;
}

static int reflash_get_fw_image(void)
{
	int retval;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image == NULL) {
		retval = request_firmware(&reflash_hcd->fw_entry, FW_IMAGE_NAME,
				tcm_hcd->pdev->dev.parent);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to request %s\n",
					FW_IMAGE_NAME);
			return retval;
		}

		LOGD(tcm_hcd->pdev->dev.parent,
				"Firmware image size = %d\n",
				(unsigned int)reflash_hcd->fw_entry->size);

		reflash_hcd->image = reflash_hcd->fw_entry->data;
		reflash_hcd->image_size = reflash_hcd->fw_entry->size;
	}

	retval = reflash_parse_fw_image();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to parse firmware image\n");
		return retval;
	}

	return 0;
}

static enum update_area reflash_compare_id_info(void)
{
	enum update_area update_area;
	unsigned int idx;
	unsigned int image_fw_id;
	unsigned int device_fw_id;
	unsigned char *image_config_id;
	unsigned char *device_config_id;
	struct app_config_header *header;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
	const unsigned char *app_config_data;

	update_area = NONE;

	if (reflash_hcd->image_info.app_config.size < sizeof(*header)) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid application config in image file\n");
		goto exit;
	}

	app_config_data = reflash_hcd->image_info.app_config.data;
	header = (struct app_config_header *)app_config_data;

	if (reflash_hcd->force_update) {
		update_area = FIRMWARE_CONFIG;
		goto exit;
	}

	if (tcm_hcd->id_info.mode != MODE_APPLICATION) {
		update_area = FIRMWARE_CONFIG;
		goto exit;
	}

	image_fw_id = le4_to_uint(header->build_id);
	device_fw_id = tcm_hcd->packrat_number;

	if (image_fw_id > device_fw_id) {
		LOGN(tcm_hcd->pdev->dev.parent,
				"Image firmware ID newer than device firmware ID\n");
		update_area = FIRMWARE_CONFIG;
		goto exit;
	} else if (image_fw_id < device_fw_id) {
		LOGN(tcm_hcd->pdev->dev.parent,
				"Image firmware ID older than device firmware ID\n");
		update_area = NONE;
		goto exit;
	}

	image_config_id = header->customer_config_id;
	device_config_id = tcm_hcd->app_info.customer_config_id;

	for (idx = 0; idx < 16; idx++) {
		if (image_config_id[idx] > device_config_id[idx]) {
			LOGN(tcm_hcd->pdev->dev.parent,
					"Image config ID newer than device config ID\n");
			update_area = CONFIG_ONLY;
			goto exit;
		} else if (image_config_id[idx] < device_config_id[idx]) {
			LOGN(tcm_hcd->pdev->dev.parent,
					"Image config ID older than device config ID\n");
			update_area = NONE;
			goto exit;
		}
	}

	update_area = NONE;

exit:
	if (update_area == NONE) {
		LOGN(tcm_hcd->pdev->dev.parent,
				"No need to do reflash\n");
	} else {
		LOGN(tcm_hcd->pdev->dev.parent,
				"Updating %s\n",
				update_area == FIRMWARE_CONFIG ?
				"firmware and config" :
				"config only");
	}

	return update_area;
}

static int reflash_read_flash(unsigned int address, unsigned char *data,
		unsigned int datalen)
{
	int retval;
	unsigned int length_words;
	unsigned int flash_addr_words;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	LOCK_BUFFER(reflash_hcd->out);

	retval = syna_tcm_alloc_mem(tcm_hcd,
			&reflash_hcd->out,
			6);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to allocate memory for reflash_hcd->out.buf\n");
		UNLOCK_BUFFER(reflash_hcd->out);
		return retval;
	}

	length_words = datalen / 2;
	flash_addr_words = address / 2;

	reflash_hcd->out.buf[0] = (unsigned char)flash_addr_words;
	reflash_hcd->out.buf[1] = (unsigned char)(flash_addr_words >> 8);
	reflash_hcd->out.buf[2] = (unsigned char)(flash_addr_words >> 16);
	reflash_hcd->out.buf[3] = (unsigned char)(flash_addr_words >> 24);
	reflash_hcd->out.buf[4] = (unsigned char)length_words;
	reflash_hcd->out.buf[5] = (unsigned char)(length_words >> 8);

	LOCK_BUFFER(reflash_hcd->resp);

	retval = tcm_hcd->write_message(tcm_hcd,
			CMD_READ_FLASH,
			reflash_hcd->out.buf,
			6,
			&reflash_hcd->resp.buf,
			&reflash_hcd->resp.buf_size,
			&reflash_hcd->resp.data_length,
			NULL,
			0);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write command %s\n",
				STR(CMD_READ_FLASH));
		UNLOCK_BUFFER(reflash_hcd->resp);
		UNLOCK_BUFFER(reflash_hcd->out);
		return retval;
	}

	UNLOCK_BUFFER(reflash_hcd->out);

	if (reflash_hcd->resp.data_length != datalen) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read requested length\n");
		UNLOCK_BUFFER(reflash_hcd->resp);
		return -EIO;
	}

	retval = secure_memcpy(data,
			datalen,
			reflash_hcd->resp.buf,
			reflash_hcd->resp.buf_size,
			datalen);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to copy read data\n");
		UNLOCK_BUFFER(reflash_hcd->resp);
		return retval;
	}

	UNLOCK_BUFFER(reflash_hcd->resp);

	return 0;
}

static int reflash_read_data(enum flash_area area, bool run_app_firmware,
		struct syna_tcm_buffer *output)
{
	int retval;
	unsigned int temp;
	unsigned int addr;
	unsigned int length;
	struct syna_tcm_app_info *app_info;
	struct syna_tcm_boot_info *boot_info;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	switch (area) {
	case CUSTOM_LCM:
	case CUSTOM_OEM:
	case PPDT:
		retval = tcm_hcd->get_data_location(tcm_hcd,
				area,
				&addr,
				&length);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to get data location\n");
			return retval;
		}
		break;
	default:
		break;
	}

	retval = reflash_set_up_flash_access();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to set up flash access\n");
		return retval;
	}

	app_info = &tcm_hcd->app_info;
	boot_info = &tcm_hcd->boot_info;

	switch (area) {
	case BOOT_CONFIG:
		temp = le2_to_uint(boot_info->boot_config_start_block);
		addr = temp * reflash_hcd->write_block_size;
		length = BOOT_CONFIG_SIZE * BOOT_CONFIG_SLOTS;
		break;
	case APP_CONFIG:
		temp = le2_to_uint(app_info->app_config_start_write_block);
		addr = temp * reflash_hcd->write_block_size;
		length = le2_to_uint(app_info->app_config_size);
		break;
	case DISP_CONFIG:
		temp = le4_to_uint(boot_info->display_config_start_block);
		addr = temp * reflash_hcd->write_block_size;
		temp = le2_to_uint(boot_info->display_config_length_blocks);
		length = temp * reflash_hcd->write_block_size;
		break;
	case CUSTOM_OTP:
		temp = le2_to_uint(boot_info->custom_otp_start_block);
		addr = temp * reflash_hcd->write_block_size;
		temp = le2_to_uint(boot_info->custom_otp_length_blocks);
		length = temp * reflash_hcd->write_block_size;
		break;
	case CUSTOM_LCM:
	case CUSTOM_OEM:
	case PPDT:
		addr *= reflash_hcd->write_block_size;
		length *= reflash_hcd->write_block_size;
		break;
	default:
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid data area\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (addr == 0 || length == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Data area unavailable\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	LOCK_BUFFER(reflash_hcd->read);

	retval = syna_tcm_alloc_mem(tcm_hcd,
			&reflash_hcd->read,
			length);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to allocate memory for reflash_hcd->read.buf\n");
		UNLOCK_BUFFER(reflash_hcd->read);
		goto run_app_firmware;
	}

	retval = reflash_read_flash(addr, reflash_hcd->read.buf, length);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read from flash\n");
		UNLOCK_BUFFER(reflash_hcd->read);
		goto run_app_firmware;
	}

	reflash_hcd->read.data_length = length;

	if (output != NULL) {
		retval = syna_tcm_alloc_mem(tcm_hcd,
				output,
				length);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to allocate memory for output->buf\n");
			UNLOCK_BUFFER(reflash_hcd->read);
			goto run_app_firmware;
		}

		retval = secure_memcpy(output->buf,
				output->buf_size,
				reflash_hcd->read.buf,
				reflash_hcd->read.buf_size,
				length);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to copy read data\n");
			UNLOCK_BUFFER(reflash_hcd->read);
			goto run_app_firmware;
		}

		output->data_length = length;
	}

	UNLOCK_BUFFER(reflash_hcd->read);

	retval = 0;

run_app_firmware:
	if (!run_app_firmware)
		goto exit;

	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to run application firmware\n");
	}

exit:
	return retval;
}

static int reflash_check_boot_config(void)
{
	unsigned int temp;
	unsigned int image_addr;
	unsigned int device_addr;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image_info.boot_config.size < BOOT_CONFIG_SIZE) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No valid boot config in image file\n");
		return -EINVAL;
	}

	image_addr = reflash_hcd->image_info.boot_config.flash_addr;

	temp = le2_to_uint(tcm_hcd->boot_info.boot_config_start_block);
	device_addr = temp * reflash_hcd->write_block_size;

	if (image_addr != device_addr) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Flash address mismatch\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_check_app_config(void)
{
	unsigned int temp;
	unsigned int image_addr;
	unsigned int image_size;
	unsigned int device_addr;
	unsigned int device_size;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image_info.app_config.size == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No application config in image file\n");
		return -EINVAL;
	}

	image_addr = reflash_hcd->image_info.app_config.flash_addr;
	image_size = reflash_hcd->image_info.app_config.size;

	temp = le2_to_uint(tcm_hcd->app_info.app_config_start_write_block);
	device_addr = temp * reflash_hcd->write_block_size;
	device_size = le2_to_uint(tcm_hcd->app_info.app_config_size);

	if (device_addr == 0 && device_size == 0)
		return 0;

	if (image_addr != device_addr) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Flash address mismatch\n");
		return -EINVAL;
	}

	if (image_size != device_size) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Config size mismatch\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_check_disp_config(void)
{
	unsigned int temp;
	unsigned int image_addr;
	unsigned int image_size;
	unsigned int device_addr;
	unsigned int device_size;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image_info.disp_config.size == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No display config in image file\n");
		return -EINVAL;
	}

	image_addr = reflash_hcd->image_info.disp_config.flash_addr;
	image_size = reflash_hcd->image_info.disp_config.size;

	temp = le4_to_uint(tcm_hcd->boot_info.display_config_start_block);
	device_addr = temp * reflash_hcd->write_block_size;

	temp = le2_to_uint(tcm_hcd->boot_info.display_config_length_blocks);
	device_size = temp * reflash_hcd->write_block_size;

	if (image_addr != device_addr) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Flash address mismatch\n");
		return -EINVAL;
	}

	if (image_size != device_size) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Config size mismatch\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_check_prod_test_firmware(void)
{
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image_info.prod_test_firmware.size == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No production test firmware in image file\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_check_app_firmware(void)
{
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	if (reflash_hcd->image_info.app_firmware.size == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No application firmware in image file\n");
		return -EINVAL;
	}

	return 0;
}

static int reflash_write_flash(unsigned int address, const unsigned char *data,
		unsigned int datalen)
{
	int retval;
	unsigned int offset;
	unsigned int w_length;
	unsigned int xfer_length;
	unsigned int remaining_length;
	unsigned int flash_address;
	unsigned int block_address;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	w_length = tcm_hcd->wr_chunk_size - 5;

	w_length = w_length - (w_length % reflash_hcd->write_block_size);

	w_length = MIN(w_length, reflash_hcd->max_write_payload_size);

	offset = 0;

	remaining_length = datalen;

	LOCK_BUFFER(reflash_hcd->out);
	LOCK_BUFFER(reflash_hcd->resp);

	while (remaining_length) {
		if (remaining_length > w_length)
			xfer_length = w_length;
		else
			xfer_length = remaining_length;

		retval = syna_tcm_alloc_mem(tcm_hcd,
				&reflash_hcd->out,
				xfer_length + 2);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to allocate memory for reflash_hcd->out.buf\n");
			UNLOCK_BUFFER(reflash_hcd->resp);
			UNLOCK_BUFFER(reflash_hcd->out);
			return retval;
		}

		flash_address = address + offset;
		block_address = flash_address / reflash_hcd->write_block_size;
		reflash_hcd->out.buf[0] = (unsigned char)block_address;
		reflash_hcd->out.buf[1] = (unsigned char)(block_address >> 8);

		retval = secure_memcpy(&reflash_hcd->out.buf[2],
				reflash_hcd->out.buf_size - 2,
				&data[offset],
				datalen - offset,
				xfer_length);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to copy write data\n");
			UNLOCK_BUFFER(reflash_hcd->resp);
			UNLOCK_BUFFER(reflash_hcd->out);
			return retval;
		}

		retval = tcm_hcd->write_message(tcm_hcd,
				CMD_WRITE_FLASH,
				reflash_hcd->out.buf,
				xfer_length + 2,
				&reflash_hcd->resp.buf,
				&reflash_hcd->resp.buf_size,
				&reflash_hcd->resp.data_length,
				NULL,
				WRITE_FLASH_DELAY_MS);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to write command %s\n",
					STR(CMD_WRITE_FLASH));
			LOGE(tcm_hcd->pdev->dev.parent,
					"Flash address = 0x%08x\n",
					flash_address);
			LOGE(tcm_hcd->pdev->dev.parent,
					"Data length = %d\n",
					xfer_length);
			UNLOCK_BUFFER(reflash_hcd->resp);
			UNLOCK_BUFFER(reflash_hcd->out);
			return retval;
		}

		offset += xfer_length;
		remaining_length -= xfer_length;
	}

	UNLOCK_BUFFER(reflash_hcd->resp);
	UNLOCK_BUFFER(reflash_hcd->out);

	return 0;
}

reflash_write(app_config)

reflash_write(disp_config)

reflash_write(prod_test_firmware)

reflash_write(app_firmware)

static int reflash_erase_flash(unsigned int page_start, unsigned int page_count)
{
	int retval;
	unsigned char out_buf[2];
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	out_buf[0] = (unsigned char)page_start;
	out_buf[1] = (unsigned char)page_count;

	LOCK_BUFFER(reflash_hcd->resp);

	retval = tcm_hcd->write_message(tcm_hcd,
			CMD_ERASE_FLASH,
			out_buf,
			sizeof(out_buf),
			&reflash_hcd->resp.buf,
			&reflash_hcd->resp.buf_size,
			&reflash_hcd->resp.data_length,
			NULL,
			ERASE_FLASH_DELAY_MS);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write command %s\n",
				STR(CMD_ERASE_FLASH));
		UNLOCK_BUFFER(reflash_hcd->resp);
		return retval;
	}

	UNLOCK_BUFFER(reflash_hcd->resp);

	return 0;
}

reflash_erase(app_config)

reflash_erase(disp_config)

reflash_erase(prod_test_firmware)

reflash_erase(app_firmware)

static int reflash_update_custom_otp(const unsigned char *data,
		unsigned int offset, unsigned int datalen)
{
	int retval;
	unsigned int temp;
	unsigned int addr;
	unsigned int length;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = reflash_set_up_flash_access();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to set up flash access\n");
		return retval;
	}

	tcm_hcd->update_watchdog(tcm_hcd, false);

	temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_start_block);
	addr = temp * reflash_hcd->write_block_size;

	temp = le2_to_uint(tcm_hcd->boot_info.custom_otp_length_blocks);
	length = temp * reflash_hcd->write_block_size;

	if (addr == 0 || length == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Data area unavailable\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (datalen + offset > length) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid data length\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	retval = reflash_write_flash(addr + offset,
			data,
			datalen);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write to flash\n");
		goto run_app_firmware;
	}

	retval = 0;

run_app_firmware:
	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to run application firmware\n");
	}

	tcm_hcd->update_watchdog(tcm_hcd, true);

	return retval;
}

static int reflash_update_custom_lcm(const unsigned char *data,
		unsigned int offset, unsigned int datalen)
{
	int retval;
	unsigned int addr;
	unsigned int length;
	unsigned int page_start;
	unsigned int page_count;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = tcm_hcd->get_data_location(tcm_hcd,
			CUSTOM_LCM,
			&addr,
			&length);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to get data location\n");
		return retval;
	}

	retval = reflash_set_up_flash_access();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to set up flash access\n");
		return retval;
	}

	tcm_hcd->update_watchdog(tcm_hcd, false);

	addr *= reflash_hcd->write_block_size;
	length *= reflash_hcd->write_block_size;

	if (addr == 0 || length == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Data area unavailable\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (datalen + offset > length) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid data length\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (offset == 0) {
		page_start = addr / reflash_hcd->page_size;

		page_count = ceil_div(length, reflash_hcd->page_size);

		retval = reflash_erase_flash(page_start, page_count);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to erase flash pages\n");
			goto run_app_firmware;
		}
	}

	retval = reflash_write_flash(addr + offset,
			data,
			datalen);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write to flash\n");
		goto run_app_firmware;
	}

	retval = 0;

run_app_firmware:
	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to run application firmware\n");
	}

	tcm_hcd->update_watchdog(tcm_hcd, true);

	return retval;
}

static int reflash_update_custom_oem(const unsigned char *data,
		unsigned int offset, unsigned int datalen)
{
	int retval;
	unsigned int addr;
	unsigned int length;
	unsigned int page_start;
	unsigned int page_count;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = tcm_hcd->get_data_location(tcm_hcd,
			CUSTOM_OEM,
			&addr,
			&length);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to get data location\n");
		return retval;
	}

	retval = reflash_set_up_flash_access();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to set up flash access\n");
		return retval;
	}

	tcm_hcd->update_watchdog(tcm_hcd, false);

	addr *= reflash_hcd->write_block_size;
	length *= reflash_hcd->write_block_size;

	if (addr == 0 || length == 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Data area unavailable\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (datalen + offset > length) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Invalid data length\n");
		retval = -EINVAL;
		goto run_app_firmware;
	}

	if (offset == 0) {
		page_start = addr / reflash_hcd->page_size;

		page_count = ceil_div(length, reflash_hcd->page_size);

		retval = reflash_erase_flash(page_start, page_count);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to erase flash pages\n");
			goto run_app_firmware;
		}
	}

	retval = reflash_write_flash(addr + offset,
			data,
			datalen);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write to flash\n");
		goto run_app_firmware;
	}

	retval = 0;

run_app_firmware:
	if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to run application firmware\n");
	}

	tcm_hcd->update_watchdog(tcm_hcd, true);

	return retval;
}

static int reflash_update_boot_config(bool lock)
{
	int retval;
	unsigned char slot_used;
	unsigned int idx;
	unsigned int addr;
	struct boot_config *data;
	struct boot_config *last_slot;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = reflash_set_up_flash_access();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to set up flash access\n");
		return retval;
	}

	tcm_hcd->update_watchdog(tcm_hcd, false);

	retval = reflash_check_boot_config();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed boot_config partition check\n");
		goto reset;
	}

	retval = reflash_read_data(BOOT_CONFIG, false, NULL);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to read boot config\n");
		goto reset;
	}

	LOCK_BUFFER(reflash_hcd->read);

	data = (struct boot_config *)reflash_hcd->read.buf;
	last_slot = data + (BOOT_CONFIG_SLOTS - 1);
	slot_used = tcm_hcd->id_info.mode == MODE_TDDI_BOOTLOADER ? 0 : 1;

	if (last_slot->used == slot_used) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Boot config already locked down\n");
		UNLOCK_BUFFER(reflash_hcd->read);
		goto reset;
	}

	if (lock) {
		idx = BOOT_CONFIG_SLOTS - 1;
	} else {
		for (idx = 0; idx < BOOT_CONFIG_SLOTS; idx++) {
			if (data->used == slot_used) {
				data++;
				continue;
			} else {
				break;
			}
		}
	}

	UNLOCK_BUFFER(reflash_hcd->read);

	if (idx == BOOT_CONFIG_SLOTS) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"No free boot config slot available\n");
		goto reset;
	}

	addr += idx * BOOT_CONFIG_SIZE;

	retval = reflash_write_flash(addr,
			reflash_hcd->image_info.boot_config.data,
			BOOT_CONFIG_SIZE);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to write to flash\n");
		goto reset;
	}

	LOGN(tcm_hcd->pdev->dev.parent,
			"Slot %d updated with new boot config\n",
			idx);

	retval = 0;

reset:
	if (tcm_hcd->reset(tcm_hcd, false, true) < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to do reset\n");
	}

	tcm_hcd->update_watchdog(tcm_hcd, true);

	return retval;
}

reflash_update(app_config)

reflash_update(disp_config)

reflash_update(prod_test_firmware)

reflash_update(app_firmware)

static int reflash_do_reflash(void)
{
	int retval;
	enum update_area update_area;
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

	retval = reflash_get_fw_image();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to get firmware image\n");
		goto exit;
	}

	LOGN(tcm_hcd->pdev->dev.parent,
			"Start of reflash\n");

	update_area = reflash_compare_id_info();

	switch (update_area) {
	case FIRMWARE_CONFIG:
		retval = reflash_update_app_firmware(false);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to reflash application firmware\n");
			goto exit;
		}
		memset(&tcm_hcd->app_info, 0x00, sizeof(tcm_hcd->app_info));
		if (tcm_hcd->features.dual_firmware) {
			retval = reflash_update_prod_test_firmware(false);
			if (retval < 0) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Failed to reflash production test firmware\n");
				goto exit;
			}
		}
	case CONFIG_ONLY:
		if (reflash_hcd->disp_cfg_update) {
			retval = reflash_update_disp_config(false);
			if (retval < 0) {
				LOGE(tcm_hcd->pdev->dev.parent,
						"Failed to reflash display config\n");
				goto exit;
			}
		}
		retval = reflash_update_app_config(true);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to reflash application config\n");
			goto exit;
		}
		break;
	case NONE:
	default:
		break;
	}

	LOGN(tcm_hcd->pdev->dev.parent,
			"End of reflash\n");

	retval = 0;

exit:
	if (reflash_hcd->fw_entry) {
		release_firmware(reflash_hcd->fw_entry);
		reflash_hcd->fw_entry = NULL;
		reflash_hcd->image = NULL;
		reflash_hcd->image_size = 0;
	}

	return retval;
}

#ifdef STARTUP_REFLASH
static void reflash_startup_work(struct work_struct *work)
{
	int retval;
#ifdef CONFIG_FB
	unsigned int timeout;
#endif
	struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;

#ifdef CONFIG_FB
	timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS;

	while (tcm_hcd->fb_ready != FB_READY_COUNT) {
		if (timeout == 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Timed out waiting for FB ready\n");
			return;
		}
		msleep(FB_READY_WAIT_MS);
		timeout--;
	}
#endif

	pm_stay_awake(&tcm_hcd->pdev->dev);

	mutex_lock(&reflash_hcd->reflash_mutex);

	retval = reflash_do_reflash();
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to do reflash\n");
	}

	mutex_unlock(&reflash_hcd->reflash_mutex);

	pm_relax(&tcm_hcd->pdev->dev);

	return;
}
#endif

static int reflash_init(struct syna_tcm_hcd *tcm_hcd)
{
	int retval;
	int idx;

	reflash_hcd = kzalloc(sizeof(*reflash_hcd), GFP_KERNEL);
	if (!reflash_hcd) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to allocate memory for reflash_hcd\n");
		return -ENOMEM;
	}

	reflash_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL);
	if (!reflash_hcd->image_buf) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to allocate memory for reflash_hcd->image_buf\n");
		goto err_allocate_memory;
	}

	reflash_hcd->tcm_hcd = tcm_hcd;

	reflash_hcd->force_update = FORCE_REFLASH;

	reflash_hcd->disp_cfg_update = DISPLAY_REFLASH;

	mutex_init(&reflash_hcd->reflash_mutex);

	INIT_BUFFER(reflash_hcd->out, false);
	INIT_BUFFER(reflash_hcd->resp, false);
	INIT_BUFFER(reflash_hcd->read, false);

#ifdef STARTUP_REFLASH
	reflash_hcd->workqueue =
			create_singlethread_workqueue("syna_tcm_reflash");
	INIT_WORK(&reflash_hcd->work, reflash_startup_work);
	queue_work(reflash_hcd->workqueue, &reflash_hcd->work);
#endif

	if (ENABLE_SYSFS_INTERFACE == false)
		return 0;

	reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
			tcm_hcd->sysfs_dir);
	if (!reflash_hcd->sysfs_dir) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to create sysfs directory\n");
		retval = -EINVAL;
		goto err_sysfs_create_dir;
	}

	for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
		retval = sysfs_create_file(reflash_hcd->sysfs_dir,
				&(*attrs[idx]).attr);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to create sysfs file\n");
			goto err_sysfs_create_file;
		}
	}

	retval = sysfs_create_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);
	if (retval < 0) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to create sysfs bin file\n");
		goto err_sysfs_create_bin_file;
	}

	reflash_hcd->custom_dir = kobject_create_and_add(CUSTOM_DIR_NAME,
			reflash_hcd->sysfs_dir);
	if (!reflash_hcd->custom_dir) {
		LOGE(tcm_hcd->pdev->dev.parent,
				"Failed to create custom sysfs directory\n");
		retval = -EINVAL;
		goto err_custom_sysfs_create_dir;
	}

	for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) {
		retval = sysfs_create_bin_file(reflash_hcd->custom_dir,
				&bin_attrs[idx]);
		if (retval < 0) {
			LOGE(tcm_hcd->pdev->dev.parent,
					"Failed to create sysfs bin file\n");
			goto err_custom_sysfs_create_bin_file;
		}
	}

	tcm_hcd->read_flash_data = reflash_read_data;

	return 0;

err_custom_sysfs_create_bin_file:
	for (idx--; idx > 0; idx--)
		sysfs_remove_bin_file(reflash_hcd->custom_dir, &bin_attrs[idx]);

	kobject_put(reflash_hcd->custom_dir);

	idx = ARRAY_SIZE(attrs);

err_custom_sysfs_create_dir:
	sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);

err_sysfs_create_bin_file:
err_sysfs_create_file:
	for (idx--; idx >= 0; idx--)
		sysfs_remove_file(reflash_hcd->sysfs_dir, &(*attrs[idx]).attr);

	kobject_put(reflash_hcd->sysfs_dir);

err_sysfs_create_dir:
err_allocate_memory:
	kfree(reflash_hcd->image_buf);

	RELEASE_BUFFER(reflash_hcd->read);
	RELEASE_BUFFER(reflash_hcd->resp);
	RELEASE_BUFFER(reflash_hcd->out);

	kfree(reflash_hcd);
	reflash_hcd = NULL;

	return retval;
}

static int reflash_remove(struct syna_tcm_hcd *tcm_hcd)
{
	int idx;

	if (!reflash_hcd)
		goto exit;

	tcm_hcd->read_flash_data = NULL;

	if (ENABLE_SYSFS_INTERFACE == true) {
		for (idx = 1; idx < ARRAY_SIZE(bin_attrs); idx++) {
			sysfs_remove_bin_file(reflash_hcd->custom_dir,
					&bin_attrs[idx]);
		}

		kobject_put(reflash_hcd->custom_dir);

		sysfs_remove_bin_file(reflash_hcd->sysfs_dir, &bin_attrs[0]);

		for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
			sysfs_remove_file(reflash_hcd->sysfs_dir,
					&(*attrs[idx]).attr);
		}

		kobject_put(reflash_hcd->sysfs_dir);
	}

#ifdef STARTUP_REFLASH
	cancel_work_sync(&reflash_hcd->work);
	flush_workqueue(reflash_hcd->workqueue);
	destroy_workqueue(reflash_hcd->workqueue);
#endif

	kfree(reflash_hcd->image_buf);

	RELEASE_BUFFER(reflash_hcd->read);
	RELEASE_BUFFER(reflash_hcd->resp);
	RELEASE_BUFFER(reflash_hcd->out);

	kfree(reflash_hcd);
	reflash_hcd = NULL;

exit:
	complete(&reflash_remove_complete);

	return 0;
}

static int reflash_reset(struct syna_tcm_hcd *tcm_hcd)
{
	int retval;

	if (!reflash_hcd) {
		retval = reflash_init(tcm_hcd);
		return retval;
	}

	return 0;
}

static struct syna_tcm_module_cb reflash_module = {
	.type = TCM_REFLASH,
	.init = reflash_init,
	.remove = reflash_remove,
	.syncbox = NULL,
	.asyncbox = NULL,
	.reset = reflash_reset,
	.suspend = NULL,
	.resume = NULL,
	.early_suspend = NULL,
};

static int __init reflash_module_init(void)
{
    if (!tp_judge_ic_match("td4320_nf")) {
        return -1;
    }
	return syna_tcm_add_module(&reflash_module, true);
}

static void __exit reflash_module_exit(void)
{
	syna_tcm_add_module(&reflash_module, false);

	wait_for_completion(&reflash_remove_complete);

	return;
}

module_init(reflash_module_init);
module_exit(reflash_module_exit);

MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics TCM Reflash Module");
MODULE_LICENSE("GPL v2");
