// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include "synaptics_tcm_core.h" #define FW_IMAGE_NAME "synaptics/hdl_firmware.img" #define BOOT_CONFIG_ID "BOOT_CONFIG" #define F35_APP_CODE_ID "F35_APP_CODE" #define APP_CONFIG_ID "APP_CONFIG" #define DISP_CONFIG_ID "DISPLAY" #define SYSFS_DIR_NAME "zeroflash" #define IMAGE_FILE_MAGIC_VALUE 0x4818472b #define FLASH_AREA_MAGIC_VALUE 0x7c05e516 #define PDT_START_ADDR 0x00e9 #define PDT_END_ADDR 0x00ee #define UBL_FN_NUMBER 0x35 #define F35_CTRL3_OFFSET 18 #define F35_CTRL7_OFFSET 22 #define F35_WRITE_FW_TO_PMEM_COMMAND 4 #define RESET_TO_HDL_DELAY_MS 12 #define DOWNLOAD_RETRY_COUNT 10 #define ZEROFLASH_HOST_DOWNLOAD_WAIT_MS 300 #define ZEROFLASH_HOST_DOWNLOAD_TIMEOUT_MS 1000 enum f35_error_code { SUCCESS = 0, UNKNOWN_FLASH_PRESENT, MAGIC_NUMBER_NOT_PRESENT, INVALID_BLOCK_NUMBER, BLOCK_NOT_ERASED, NO_FLASH_PRESENT, CHECKSUM_FAILURE, WRITE_FAILURE, INVALID_COMMAND, IN_DEBUG_MODE, INVALID_HEADER, REQUESTING_FIRMWARE, INVALID_CONFIGURATION, DISABLE_BLOCK_PROTECT_FAILURE, }; enum config_download { HDL_INVALID = 0, HDL_TOUCH_CONFIG, HDL_DISPLAY_CONFIG, HDL_DISPLAY_CONFIG_TO_RAM, }; 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 { unsigned int packrat_number; struct block_data boot_config; struct block_data app_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 rmi_f35_query { unsigned char version:4; unsigned char has_debug_mode:1; unsigned char has_data5:1; unsigned char has_query1:1; unsigned char has_query2:1; unsigned char chunk_size; unsigned char has_ctrl7:1; unsigned char has_host_download:1; unsigned char has_spi_master:1; unsigned char advanced_recovery_mode:1; unsigned char reserved:4; } __packed; struct rmi_f35_data { unsigned char error_code:5; unsigned char recovery_mode_forced:1; unsigned char nvm_programmed:1; unsigned char in_recovery:1; } __packed; struct rmi_pdt_entry { unsigned char query_base_addr; unsigned char command_base_addr; unsigned char control_base_addr; unsigned char data_base_addr; unsigned char intr_src_count:3; unsigned char reserved_1:2; unsigned char fn_version:2; unsigned char reserved_2:1; unsigned char fn_number; } __packed; struct rmi_addr { unsigned short query_base; unsigned short command_base; unsigned short control_base; unsigned short data_base; }; struct firmware_status { unsigned short invalid_static_config:1; unsigned short need_disp_config:1; unsigned short need_app_config:1; unsigned short hdl_version:4; unsigned short reserved:9; } __packed; struct zeroflash_hcd { bool has_hdl; bool f35_ready; const unsigned char *image; unsigned char *buf; const struct firmware *fw_entry; struct work_struct config_work; struct work_struct firmware_work; struct workqueue_struct *workqueue; struct kobject *sysfs_dir; struct rmi_addr f35_addr; struct image_info image_info; struct firmware_status fw_status; struct syna_tcm_buffer out; struct syna_tcm_buffer resp; struct syna_tcm_hcd *tcm_hcd; }; DECLARE_COMPLETION(zeroflash_remove_complete); STORE_PROTOTYPE(zeroflash, hdl) static struct device_attribute *attrs[] = { ATTRIFY(hdl), }; static struct zeroflash_hcd *zeroflash_hcd; static void zeroflash_download_firmware(void); static int zeroflash_wait_hdl(struct syna_tcm_hcd *tcm_hcd) { int retval; msleep(ZEROFLASH_HOST_DOWNLOAD_WAIT_MS); if (!atomic_read(&tcm_hcd->host_downloading)) return 0; retval = wait_event_interruptible_timeout(tcm_hcd->hdl_wq, !atomic_read(&tcm_hcd->host_downloading), msecs_to_jiffies(ZEROFLASH_HOST_DOWNLOAD_TIMEOUT_MS)); if (retval == 0) { LOGE(tcm_hcd->pdev->dev.parent, "Timed out waiting for completion of host download\n"); atomic_set(&tcm_hcd->host_downloading, 0); retval = -EIO; } else { retval = 0; } return retval; } static ssize_t zeroflash_sysfs_hdl_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int retval = 0; unsigned int input; unsigned int times; unsigned int fcounts; const struct syna_tcm_board_data *bdata = zeroflash_hcd->tcm_hcd->hw_if->bdata; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; if (kstrtouint(buf, 0, &input) != 0) return -EINVAL; times = input; fcounts = 0; if (tcm_hcd->host_download_mode) { while (input) { LOGD(tcm_hcd->pdev->dev.parent, "HDL cycles : %u\n", ((times+1) - input)); gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); msleep(bdata->reset_active_ms); gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); msleep(bdata->reset_active_ms); retval = zeroflash_wait_hdl(tcm_hcd); if (retval < 0) { LOGI(tcm_hcd->pdev->dev.parent, "Failed to wait for completion\n" "of host download\n"); fcounts++; } input--; } if (fcounts != 0) { LOGI(tcm_hcd->pdev->dev.parent, "Failed %u times for host download in %u\n" "test cycles\n", fcounts, times); } else { LOGI(tcm_hcd->pdev->dev.parent, "SUCCESS %u times HDL cycles\n", times); } } else { LOGI(tcm_hcd->pdev->dev.parent, "Sensor is not in HDL, this test will not perform\n"); } return count; } static int zeroflash_check_uboot(void) { int retval; unsigned char fn_number; struct rmi_f35_query query; struct rmi_pdt_entry p_entry; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; retval = syna_tcm_rmi_read(tcm_hcd, PDT_END_ADDR, &fn_number, sizeof(fn_number)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to read RMI function number\n"); return retval; } LOGD(tcm_hcd->pdev->dev.parent, "Found F$%02x\n", fn_number); if (fn_number != UBL_FN_NUMBER) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to find F$35\n"); return -ENODEV; } if (zeroflash_hcd->f35_ready) return 0; retval = syna_tcm_rmi_read(tcm_hcd, PDT_START_ADDR, (unsigned char *)&p_entry, sizeof(p_entry)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to read PDT entry\n"); return retval; } zeroflash_hcd->f35_addr.query_base = p_entry.query_base_addr; zeroflash_hcd->f35_addr.command_base = p_entry.command_base_addr; zeroflash_hcd->f35_addr.control_base = p_entry.control_base_addr; zeroflash_hcd->f35_addr.data_base = p_entry.data_base_addr; retval = syna_tcm_rmi_read(tcm_hcd, zeroflash_hcd->f35_addr.query_base, (unsigned char *)&query, sizeof(query)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to read F$35 query\n"); return retval; } zeroflash_hcd->f35_ready = true; if (query.has_query2 && query.has_ctrl7 && query.has_host_download) { zeroflash_hcd->has_hdl = true; } else { LOG_ERR(tcm_hcd->pdev->dev.parent, "Host download not supported\n"); zeroflash_hcd->has_hdl = false; return -ENODEV; } return 0; } static int zeroflash_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 = zeroflash_hcd->tcm_hcd; const unsigned char *image; const unsigned char *content; image = zeroflash_hcd->image; image_info = &zeroflash_hcd->image_info; header = (struct image_header *)image; 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, F35_APP_CODE_ID, strlen(F35_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, 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; image_info->packrat_number = le4_to_uint(&content[14]); 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; } 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 zeroflash_get_fw_image(void) { int retval; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; if (zeroflash_hcd->fw_entry != NULL) return 0; do { retval = request_firmware(&zeroflash_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); msleep(100); } else { break; } } while (1); LOGD(tcm_hcd->pdev->dev.parent, "Firmware image size = %d\n", (unsigned int)zeroflash_hcd->fw_entry->size); zeroflash_hcd->image = zeroflash_hcd->fw_entry->data; retval = zeroflash_parse_fw_image(); if (retval < 0) { LOGE(tcm_hcd->pdev->dev.parent, "Failed to parse firmware image\n"); release_firmware(zeroflash_hcd->fw_entry); zeroflash_hcd->fw_entry = NULL; zeroflash_hcd->image = NULL; return retval; } return 0; } static void zeroflash_download_config(void) { struct firmware_status *fw_status; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; fw_status = &zeroflash_hcd->fw_status; if (!fw_status->need_app_config && !fw_status->need_disp_config) { if (atomic_read(&tcm_hcd->helper.task) == HELP_NONE) { atomic_set(&tcm_hcd->helper.task, HELP_SEND_RESET_NOTIFICATION); queue_work(tcm_hcd->helper.workqueue, &tcm_hcd->helper.work); } atomic_set(&tcm_hcd->host_downloading, 0); return; } queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->config_work); } static void zeroflash_download_firmware(void) { queue_work(zeroflash_hcd->workqueue, &zeroflash_hcd->firmware_work); } static int zeroflash_download_disp_config(void) { int retval; unsigned char response_code; struct image_info *image_info; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; static unsigned int retry_count; LOGN(tcm_hcd->pdev->dev.parent, "Downloading display config\n"); image_info = &zeroflash_hcd->image_info; if (image_info->disp_config.size == 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "No display config in image file\n"); return -EINVAL; } LOCK_BUFFER(zeroflash_hcd->out); retval = syna_tcm_alloc_mem(tcm_hcd, &zeroflash_hcd->out, image_info->disp_config.size + 2); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for zeroflash_hcd->out.buf\n"); goto unlock_out; } switch (zeroflash_hcd->fw_status.hdl_version) { case 0: case 1: zeroflash_hcd->out.buf[0] = 1; break; case 2: zeroflash_hcd->out.buf[0] = 2; break; default: retval = -EINVAL; LOG_ERR(tcm_hcd->pdev->dev.parent, "Invalid HDL version (%d)\n", zeroflash_hcd->fw_status.hdl_version); goto unlock_out; } zeroflash_hcd->out.buf[1] = HDL_DISPLAY_CONFIG; retval = secure_memcpy(&zeroflash_hcd->out.buf[2], zeroflash_hcd->out.buf_size - 2, image_info->disp_config.data, image_info->disp_config.size, image_info->disp_config.size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy display config data\n"); goto unlock_out; } zeroflash_hcd->out.data_length = image_info->disp_config.size + 2; LOCK_BUFFER(zeroflash_hcd->resp); retval = tcm_hcd->write_message(tcm_hcd, CMD_DOWNLOAD_CONFIG, zeroflash_hcd->out.buf, zeroflash_hcd->out.data_length, &zeroflash_hcd->resp.buf, &zeroflash_hcd->resp.buf_size, &zeroflash_hcd->resp.data_length, &response_code, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write command %s\n", STR(CMD_DOWNLOAD_CONFIG)); if (response_code != STATUS_ERROR) goto unlock_resp; retry_count++; if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) goto unlock_resp; } else { retry_count = 0; } retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, sizeof(zeroflash_hcd->fw_status), zeroflash_hcd->resp.buf, zeroflash_hcd->resp.buf_size, sizeof(zeroflash_hcd->fw_status)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy firmware status\n"); goto unlock_resp; } LOGN(tcm_hcd->pdev->dev.parent, "Display config downloaded\n"); retval = 0; unlock_resp: UNLOCK_BUFFER(zeroflash_hcd->resp); unlock_out: UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } static int zeroflash_download_app_config(void) { int retval; unsigned char padding; unsigned char response_code; struct image_info *image_info; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; static unsigned int retry_count; LOGN(tcm_hcd->pdev->dev.parent, "Downloading application config\n"); image_info = &zeroflash_hcd->image_info; if (image_info->app_config.size == 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "No application config in image file\n"); return -EINVAL; } padding = image_info->app_config.size % 8; if (padding) padding = 8 - padding; LOCK_BUFFER(zeroflash_hcd->out); retval = syna_tcm_alloc_mem(tcm_hcd, &zeroflash_hcd->out, image_info->app_config.size + 2 + padding); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for zeroflash_hcd->out.buf\n"); goto unlock_out; } switch (zeroflash_hcd->fw_status.hdl_version) { case 0: case 1: zeroflash_hcd->out.buf[0] = 1; break; case 2: zeroflash_hcd->out.buf[0] = 2; break; default: retval = -EINVAL; LOG_ERR(tcm_hcd->pdev->dev.parent, "Invalid HDL version (%d)\n", zeroflash_hcd->fw_status.hdl_version); goto unlock_out; } zeroflash_hcd->out.buf[1] = HDL_TOUCH_CONFIG; retval = secure_memcpy(&zeroflash_hcd->out.buf[2], zeroflash_hcd->out.buf_size - 2, image_info->app_config.data, image_info->app_config.size, image_info->app_config.size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy application config data\n"); goto unlock_out; } zeroflash_hcd->out.data_length = image_info->app_config.size + 2; zeroflash_hcd->out.data_length += padding; LOCK_BUFFER(zeroflash_hcd->resp); retval = tcm_hcd->write_message(tcm_hcd, CMD_DOWNLOAD_CONFIG, zeroflash_hcd->out.buf, zeroflash_hcd->out.data_length, &zeroflash_hcd->resp.buf, &zeroflash_hcd->resp.buf_size, &zeroflash_hcd->resp.data_length, &response_code, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write command %s\n", STR(CMD_DOWNLOAD_CONFIG)); if (response_code != STATUS_ERROR) goto unlock_resp; retry_count++; if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) goto unlock_resp; } else { retry_count = 0; } retval = secure_memcpy((unsigned char *)&zeroflash_hcd->fw_status, sizeof(zeroflash_hcd->fw_status), zeroflash_hcd->resp.buf, zeroflash_hcd->resp.buf_size, sizeof(zeroflash_hcd->fw_status)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy firmware status\n"); goto unlock_resp; } LOGN(tcm_hcd->pdev->dev.parent, "Application config downloaded\n"); retval = 0; unlock_resp: UNLOCK_BUFFER(zeroflash_hcd->resp); unlock_out: UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } static void zeroflash_download_config_work(struct work_struct *work) { int retval; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; retval = zeroflash_get_fw_image(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to get firmware image\n"); return; } LOGN(tcm_hcd->pdev->dev.parent, "Start of config download\n"); if (zeroflash_hcd->fw_status.need_app_config) { retval = zeroflash_download_app_config(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to download application config\n"); return; } goto exit; } if (zeroflash_hcd->fw_status.need_disp_config) { retval = zeroflash_download_disp_config(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to download display config\n"); return; } goto exit; } exit: LOGN(tcm_hcd->pdev->dev.parent, "End of config download\n"); zeroflash_download_config(); } static int zeroflash_download_app_fw(void) { int retval; unsigned char command; struct image_info *image_info; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; #if RESET_TO_HDL_DELAY_MS const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; #endif LOGN(tcm_hcd->pdev->dev.parent, "Downloading application firmware\n"); image_info = &zeroflash_hcd->image_info; if (image_info->app_firmware.size == 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "No application firmware in image file\n"); return -EINVAL; } LOCK_BUFFER(zeroflash_hcd->out); retval = syna_tcm_alloc_mem(tcm_hcd, &zeroflash_hcd->out, image_info->app_firmware.size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for zeroflash_hcd->out.buf\n"); UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } retval = secure_memcpy(zeroflash_hcd->out.buf, zeroflash_hcd->out.buf_size, image_info->app_firmware.data, image_info->app_firmware.size, image_info->app_firmware.size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy application firmware data\n"); UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } zeroflash_hcd->out.data_length = image_info->app_firmware.size; command = F35_WRITE_FW_TO_PMEM_COMMAND; #if RESET_TO_HDL_DELAY_MS gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); msleep(bdata->reset_active_ms); gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); mdelay(RESET_TO_HDL_DELAY_MS); #endif retval = syna_tcm_rmi_write(tcm_hcd, zeroflash_hcd->f35_addr.control_base + F35_CTRL3_OFFSET, &command, sizeof(command)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write F$35 command\n"); UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } retval = syna_tcm_rmi_write(tcm_hcd, zeroflash_hcd->f35_addr.control_base + F35_CTRL7_OFFSET, zeroflash_hcd->out.buf, zeroflash_hcd->out.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write application firmware data\n"); UNLOCK_BUFFER(zeroflash_hcd->out); return retval; } UNLOCK_BUFFER(zeroflash_hcd->out); LOGN(tcm_hcd->pdev->dev.parent, "Application firmware downloaded\n"); return 0; } static void zeroflash_download_firmware_work(struct work_struct *work) { int retval; struct rmi_f35_data data; struct syna_tcm_hcd *tcm_hcd = zeroflash_hcd->tcm_hcd; static unsigned int retry_count; retval = zeroflash_check_uboot(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Microbootloader support unavailable\n"); goto exit; } atomic_set(&tcm_hcd->host_downloading, 1); retval = syna_tcm_rmi_read(tcm_hcd, zeroflash_hcd->f35_addr.data_base, (unsigned char *)&data, sizeof(data)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to read F$35 data\n"); goto exit; } if (data.error_code != REQUESTING_FIRMWARE) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Microbootloader error code = 0x%02x\n", data.error_code); if (data.error_code != CHECKSUM_FAILURE) { retval = -EIO; goto exit; } else { retry_count++; } } else { retry_count = 0; } retval = zeroflash_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 firmware download\n"); retval = zeroflash_download_app_fw(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to download application firmware\n"); goto exit; } LOGN(tcm_hcd->pdev->dev.parent, "End of firmware download\n"); exit: if (retval < 0) retry_count++; if (DOWNLOAD_RETRY_COUNT && retry_count > DOWNLOAD_RETRY_COUNT) { retval = tcm_hcd->enable_irq(tcm_hcd, false, true); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to disable interrupt\n"); } } else { retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to enable interrupt\n"); } } } static int zeroflash_init(struct syna_tcm_hcd *tcm_hcd) { int retval = 0; int idx; zeroflash_hcd = kzalloc(sizeof(*zeroflash_hcd), GFP_KERNEL); if (!zeroflash_hcd) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for zeroflash_hcd\n"); return -ENOMEM; } zeroflash_hcd->tcm_hcd = tcm_hcd; INIT_BUFFER(zeroflash_hcd->out, false); INIT_BUFFER(zeroflash_hcd->resp, false); zeroflash_hcd->workqueue = create_singlethread_workqueue("syna_tcm_zeroflash"); INIT_WORK(&zeroflash_hcd->config_work, zeroflash_download_config_work); INIT_WORK(&zeroflash_hcd->firmware_work, zeroflash_download_firmware_work); zeroflash_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, tcm_hcd->sysfs_dir); if (!zeroflash_hcd->sysfs_dir) { LOGE(tcm_hcd->pdev->dev.parent, "Failed to create sysfs directory\n"); return -EINVAL; } for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { retval = sysfs_create_file(zeroflash_hcd->sysfs_dir, &(*attrs[idx]).attr); if (retval < 0) { LOGE(tcm_hcd->pdev->dev.parent, "Failed to create sysfs file\n"); } } if (tcm_hcd->init_okay == false && tcm_hcd->hw_if->bus_io->type == BUS_SPI) zeroflash_download_firmware(); return 0; } static int zeroflash_remove(struct syna_tcm_hcd *tcm_hcd) { if (!zeroflash_hcd) goto exit; if (zeroflash_hcd->fw_entry) release_firmware(zeroflash_hcd->fw_entry); cancel_work_sync(&zeroflash_hcd->config_work); cancel_work_sync(&zeroflash_hcd->firmware_work); flush_workqueue(zeroflash_hcd->workqueue); destroy_workqueue(zeroflash_hcd->workqueue); RELEASE_BUFFER(zeroflash_hcd->resp); RELEASE_BUFFER(zeroflash_hcd->out); kfree(zeroflash_hcd); zeroflash_hcd = NULL; exit: complete(&zeroflash_remove_complete); return 0; } static int zeroflash_syncbox(struct syna_tcm_hcd *tcm_hcd) { int retval; unsigned char *fw_status; if (!zeroflash_hcd) return 0; switch (tcm_hcd->report.id) { case REPORT_STATUS: fw_status = (unsigned char *)&zeroflash_hcd->fw_status; retval = secure_memcpy(fw_status, sizeof(zeroflash_hcd->fw_status), tcm_hcd->report.buffer.buf, tcm_hcd->report.buffer.buf_size, sizeof(zeroflash_hcd->fw_status)); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy firmware status\n"); return retval; } zeroflash_download_config(); break; case REPORT_HDL: retval = tcm_hcd->enable_irq(tcm_hcd, false, true); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to disable interrupt\n"); return retval; } zeroflash_download_firmware(); break; default: break; } return 0; } static int zeroflash_reset(struct syna_tcm_hcd *tcm_hcd) { int retval; if (!zeroflash_hcd) { retval = zeroflash_init(tcm_hcd); return retval; } return 0; } static struct syna_tcm_module_cb zeroflash_module = { .type = TCM_ZEROFLASH, .init = zeroflash_init, .remove = zeroflash_remove, .syncbox = zeroflash_syncbox, .asyncbox = NULL, .reset = zeroflash_reset, .suspend = NULL, .resume = NULL, .early_suspend = NULL, }; static int __init zeroflash_module_init(void) { return syna_tcm_add_module(&zeroflash_module, true); } static void __exit zeroflash_module_exit(void) { syna_tcm_add_module(&zeroflash_module, false); wait_for_completion(&zeroflash_remove_complete); } module_init(zeroflash_module_init); module_exit(zeroflash_module_exit); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics TCM Zeroflash Module"); MODULE_LICENSE("GPL v2");