2226 lines
52 KiB
C
2226 lines
52 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include <linux/crc32.h>
|
||
|
#include <linux/firmware.h>
|
||
|
#include "synaptics_tcm_core.h"
|
||
|
#include "mtk_boot_common.h"
|
||
|
|
||
|
#define STARTUP_REFLASH
|
||
|
|
||
|
#define FORCE_REFLASH false
|
||
|
|
||
|
#define ENABLE_SYSFS_INTERFACE true
|
||
|
|
||
|
#define SYSFS_DIR_NAME "reflash"
|
||
|
|
||
|
#define CUSTOM_DIR_NAME "custom"
|
||
|
|
||
|
#define FW_IMAGE_NAME "firmware.img"
|
||
|
|
||
|
#define FW_IMAGE_NAME_MANUAL "synaptics/firmware_manual.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 1
|
||
|
|
||
|
#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) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent, \
|
||
|
"Failed "#p_name" partition check\n"); \
|
||
|
reset = true; \
|
||
|
goto reset; \
|
||
|
} \
|
||
|
\
|
||
|
retval = reflash_erase_##p_name(); \
|
||
|
if (retval < 0) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(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) { \
|
||
|
LOG_ERR(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;
|
||
|
bool reflash_by_manual;
|
||
|
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 ssize_t reflash_sysfs_cs_show(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 = 0220,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.write = reflash_sysfs_image_store,
|
||
|
},
|
||
|
{
|
||
|
.attr = {
|
||
|
.name = "lockdown",
|
||
|
.mode = 0664,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.read = reflash_sysfs_lockdown_show,
|
||
|
.write = reflash_sysfs_lockdown_store,
|
||
|
},
|
||
|
{
|
||
|
.attr = {
|
||
|
.name = "lcm",
|
||
|
.mode = 0664,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.read = reflash_sysfs_lcm_show,
|
||
|
.write = reflash_sysfs_lcm_store,
|
||
|
},
|
||
|
{
|
||
|
.attr = {
|
||
|
.name = "oem",
|
||
|
.mode = 0664,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.read = reflash_sysfs_oem_show,
|
||
|
.write = reflash_sysfs_oem_store,
|
||
|
},
|
||
|
{
|
||
|
.attr = {
|
||
|
.name = "customer_serialization",
|
||
|
.mode = 0444,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.read = reflash_sysfs_cs_show,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
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 (kstrtouint(buf, 0, &input) != 0)
|
||
|
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;
|
||
|
|
||
|
reflash_hcd->reflash_by_manual = true;
|
||
|
if (input & REFLASH || input & FORCE_UPDATE) {
|
||
|
retval = reflash_do_reflash();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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->reflash_by_manual = false;
|
||
|
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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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 ssize_t reflash_sysfs_cs_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(BOOT_CONFIG, true, NULL);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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 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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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)) {
|
||
|
LOG_ERR(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;
|
||
|
|
||
|
reflash_hcd->disp_cfg_update = false;
|
||
|
|
||
|
magic_value = le4_to_uint(header->magic_value);
|
||
|
if (magic_value != IMAGE_FILE_MAGIC_VALUE) {
|
||
|
LOG_ERR(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 (strncmp((char *)descriptor->id_string,
|
||
|
BOOT_CONFIG_ID,
|
||
|
strlen(BOOT_CONFIG_ID)) == 0) {
|
||
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
||
|
LOG_ERR(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 (strncmp((char *)descriptor->id_string,
|
||
|
APP_CODE_ID,
|
||
|
strlen(APP_CODE_ID)) == 0) {
|
||
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
||
|
LOG_ERR(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 (strncmp((char *)descriptor->id_string,
|
||
|
PROD_TEST_ID,
|
||
|
strlen(PROD_TEST_ID)) == 0) {
|
||
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
||
|
LOG_ERR(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 (strncmp((char *)descriptor->id_string,
|
||
|
APP_CONFIG_ID,
|
||
|
strlen(APP_CONFIG_ID)) == 0) {
|
||
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
||
|
LOG_ERR(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 (strncmp((char *)descriptor->id_string,
|
||
|
DISP_CONFIG_ID,
|
||
|
strlen(DISP_CONFIG_ID)) == 0) {
|
||
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Display config checksum error\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
reflash_hcd->disp_cfg_update = true;
|
||
|
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) {
|
||
|
if (reflash_hcd->reflash_by_manual == false) {
|
||
|
retval = request_firmware(&reflash_hcd->fw_entry,
|
||
|
FW_IMAGE_NAME,
|
||
|
tcm_hcd->pdev->dev.parent);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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;
|
||
|
} else {
|
||
|
retval = request_firmware(&reflash_hcd->fw_entry,
|
||
|
FW_IMAGE_NAME_MANUAL,
|
||
|
tcm_hcd->pdev->dev.parent);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to request %s\n",
|
||
|
FW_IMAGE_NAME_MANUAL);
|
||
|
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) {
|
||
|
LOG_ERR(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)) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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:
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Invalid data area\n");
|
||
|
retval = -EINVAL;
|
||
|
goto run_app_firmware;
|
||
|
}
|
||
|
|
||
|
if (addr == 0 || length == 0) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Flash address mismatch\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (image_size != device_size) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Flash address mismatch\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (image_size != device_size) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to write command %s\n",
|
||
|
STR(CMD_WRITE_FLASH));
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Flash address = 0x%08x\n",
|
||
|
flash_address);
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Data area unavailable\n");
|
||
|
retval = -EINVAL;
|
||
|
goto run_app_firmware;
|
||
|
}
|
||
|
|
||
|
if (datalen + offset > length) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get data location\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = reflash_set_up_flash_access();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Data area unavailable\n");
|
||
|
retval = -EINVAL;
|
||
|
goto run_app_firmware;
|
||
|
}
|
||
|
|
||
|
if (datalen + offset > length) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get data location\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = reflash_set_up_flash_access();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Data area unavailable\n");
|
||
|
retval = -EINVAL;
|
||
|
goto run_app_firmware;
|
||
|
}
|
||
|
|
||
|
if (datalen + offset > length) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed boot_config partition check\n");
|
||
|
goto reset;
|
||
|
}
|
||
|
|
||
|
retval = reflash_read_data(BOOT_CONFIG, false, NULL);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to reflash display config\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
retval = reflash_update_app_config(true);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(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;
|
||
|
}
|
||
|
|
||
|
atomic_set(&tcm_hcd->firmware_flashing, 0);
|
||
|
wake_up_interruptible(&tcm_hcd->reflash_wq);
|
||
|
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;
|
||
|
/* Bypass fb_ready judge in other boot mode, eg. factory mode */
|
||
|
while ((tcm_hcd->fb_ready != FB_READY_COUNT) &&
|
||
|
(get_boot_mode == NORMAL_BOOT)) {
|
||
|
if (timeout == 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Timed out waiting for FB ready: %d\n",
|
||
|
tcm_hcd->fb_ready);
|
||
|
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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to do reflash\n");
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&reflash_hcd->reflash_mutex);
|
||
|
|
||
|
pm_relax(&tcm_hcd->pdev->dev);
|
||
|
|
||
|
}
|
||
|
#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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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;
|
||
|
|
||
|
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 (false == ENABLE_SYSFS_INTERFACE)
|
||
|
return 0;
|
||
|
|
||
|
reflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
|
||
|
tcm_hcd->sysfs_dir);
|
||
|
if (!reflash_hcd->sysfs_dir) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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) {
|
||
|
LOG_ERR(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 (true == ENABLE_SYSFS_INTERFACE) {
|
||
|
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)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
module_init(reflash_module_init);
|
||
|
module_exit(reflash_module_exit);
|
||
|
|
||
|
MODULE_AUTHOR("Synaptics, Inc.");
|
||
|
MODULE_DESCRIPTION("Synaptics TCM Reflash Module");
|
||
|
MODULE_LICENSE("GPL v2");
|