blob: 5e8295477cdb52e299777f46960fd817c1294ea5 [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/gpio.h>
#include <linux/crc32.h>
#include <linux/firmware.h>
#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 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
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 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);
static struct zeroflash_hcd *zeroflash_hcd;
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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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 {
LOGE(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) {
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,
F35_APP_CODE_ID,
strlen(F35_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,
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;
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 (!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;
}
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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for out.buf\n");
goto unlock_out;
}
switch (zeroflash_hcd->fw_status.hdl_version) {
case 0:
zeroflash_hcd->out.buf[0] = 1;
break;
case 1:
zeroflash_hcd->out.buf[0] = 2;
break;
default:
retval = -EINVAL;
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for out.buf\n");
goto unlock_out;
}
switch (zeroflash_hcd->fw_status.hdl_version) {
case 0:
zeroflash_hcd->out.buf[0] = 1;
break;
case 1:
zeroflash_hcd->out.buf[0] = 2;
break;
default:
retval = -EINVAL;
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for 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) {
LOGE(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);
msleep(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to read F$35 data\n");
goto exit;
}
if (data.error_code != REQUESTING_FIRMWARE) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to disable interrupt\n");
}
} else {
retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to enable interrupt\n");
}
}
}
static int zeroflash_init(struct syna_tcm_hcd *tcm_hcd)
{
zeroflash_hcd = kzalloc(sizeof(*zeroflash_hcd), GFP_KERNEL);
if (!zeroflash_hcd) {
LOGE(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);
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) {
LOGE(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) {
LOGE(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");