blob: bb6bef6f1df55f55b4ec4ca730c1f9f9a2ea3b81 [file] [log] [blame]
/*
* Synaptics TCM touchscreen driver
*
* Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
*
* Copyright (C) 2017-2019 Scott Lin <scott.lin@tw.synaptics.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
* DOLLARS.
*/
#include <linux/crc32.h>
#include <linux/firmware.h>
#include "synaptics_tcm_core.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 "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 80
#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 = 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,
},
};
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, 10, &input))
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) {
LOGD(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 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) {
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 (!memcmp((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 (!memcmp((char *)descriptor->id_string,
APP_CODE_ID,
strlen(APP_CODE_ID))) {
if (checksum != (crc32(~0, content, length) ^ ~0)) {
LOGE(tcm_hcd->pdev->dev.parent,
"APP 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 (!memcmp((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 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 flash address = 0x%08x\n",
flash_addr);
} else if (!memcmp((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 (!memcmp((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;
}
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) {
retval = request_firmware(&reflash_hcd->fw_entry, FW_IMAGE_NAME,
tcm_hcd->pdev->dev.parent);
if (retval < 0) {
LOGD(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's 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's ID\n");
update_area = NONE;
goto exit;
}
}
update_area = NONE;
exit:
if (update_area == NONE)
LOGD(tcm_hcd->pdev->dev.parent, "No need to do reflash\n");
else
LOGD(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 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 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) {
LOGD(tcm_hcd->pdev->dev.parent,
"Failed to get firmware image\n");
goto exit;
}
LOGD(tcm_hcd->pdev->dev.parent,
"Start of reflash\n");
atomic_set(&tcm_hcd->firmware_flashing, 1);
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\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;
}
LOGD(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;
#if defined(CONFIG_DRM) || defined(CONFIG_FB)
unsigned int timeout;
#endif
struct syna_tcm_hcd *tcm_hcd = reflash_hcd->tcm_hcd;
#if defined(CONFIG_DRM) || defined(CONFIG_FB)
timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS;
while (tcm_hcd->fb_ready != FB_READY_COUNT - 1) {
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);
}
#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 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 (!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) {
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) {
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");