// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #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");