6db4831e98
Android 14
1127 lines
25 KiB
C
1127 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/crc32.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/spi/spi.h>
|
|
#include "synaptics_tcm_core.h"
|
|
|
|
#define SYSFS_DIR_NAME "romboot"
|
|
|
|
#define BOOT_CONFIG_ID "BOOT_CONFIG"
|
|
|
|
#define ROMBOOT_APP_CODE_ID "ROMBOOT_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 BINARY_FILE_MAGIC_VALUE 0xaa55
|
|
|
|
#define FW_IMAGE_NAME "synaptics/hdl_firmware.img"
|
|
|
|
#define IMAGE_BUF_SIZE (512 * 1024)
|
|
|
|
#define IHEX_BUF_SIZE (2048 * 1024)
|
|
|
|
#define DATA_BUF_SIZE (512 * 256)
|
|
|
|
#define IHEX_RECORD_SIZE 14
|
|
|
|
#define ENABLE_SYSFS_INTERFACE true
|
|
|
|
#define RESERVED_BYTES 14
|
|
|
|
#define FLASH_PAGE_SIZE 256
|
|
|
|
#define STATUS_CHECK_US_MIN 5000
|
|
|
|
#define STATUS_CHECK_US_MAX 10000
|
|
|
|
#define STATUS_CHECK_RETRY 50
|
|
|
|
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 romboot_hcd {
|
|
bool has_rom_flash;
|
|
bool force_update;
|
|
const unsigned char *image;
|
|
struct image_info image_info;
|
|
unsigned char *image_buf;
|
|
unsigned char *ihex_buf;
|
|
unsigned char *data_buf;
|
|
unsigned long int image_size;
|
|
unsigned long int ihex_size;
|
|
unsigned int ihex_records;
|
|
unsigned int data_entries;
|
|
unsigned int page_size;
|
|
unsigned int write_block_size;
|
|
unsigned int max_write_payload_size;
|
|
const struct firmware *fw_entry;
|
|
struct work_struct romboot_work;
|
|
struct mutex romboot_mutex;
|
|
struct kobject *sysfs_dir;
|
|
struct workqueue_struct *workqueue;
|
|
struct syna_tcm_buffer out;
|
|
struct syna_tcm_buffer resp;
|
|
struct syna_tcm_hcd *tcm_hcd;
|
|
};
|
|
|
|
enum flash_command {
|
|
JEDEC_PAGE_PROGRAM = 0x02,
|
|
JEDEC_READ_STATUS = 0x05,
|
|
JEDEC_WRITE_ENABLE = 0x06,
|
|
JEDEC_CHIP_ERASE = 0xc7,
|
|
};
|
|
|
|
struct flash_param {
|
|
unsigned char spi_param;
|
|
unsigned char clk_div;
|
|
unsigned char mode;
|
|
unsigned short read_size;
|
|
unsigned char jedec_cmd;
|
|
} __packed;
|
|
|
|
DECLARE_COMPLETION(romboot_remove_complete);
|
|
|
|
static struct romboot_hcd *romboot_hcd;
|
|
|
|
static int romboot_get_fw_image(void);
|
|
|
|
STORE_PROTOTYPE(romboot, download)
|
|
|
|
STORE_PROTOTYPE(romboot, program)
|
|
|
|
static struct device_attribute *attrs[] = {
|
|
ATTRIFY(download),
|
|
ATTRIFY(program),
|
|
};
|
|
|
|
static void romboot_do_download(struct syna_tcm_hcd *tcm_hcd);
|
|
|
|
static void romboot_do_program(struct syna_tcm_hcd *tcm_hcd);
|
|
|
|
static ssize_t romboot_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 romboot_sysfs_ihex_store(struct file *data_file,
|
|
struct kobject *kobj, struct bin_attribute *attributes,
|
|
char *buf, loff_t pos, size_t count);
|
|
|
|
static struct bin_attribute bin_attrs[] = {
|
|
{
|
|
.attr = {
|
|
.name = "img",
|
|
.mode = 0220,
|
|
},
|
|
.size = 0,
|
|
.write = romboot_sysfs_image_store,
|
|
},
|
|
{
|
|
.attr = {
|
|
.name = "ihex",
|
|
.mode = 0220,
|
|
},
|
|
.size = 0,
|
|
.write = romboot_sysfs_ihex_store,
|
|
},
|
|
};
|
|
|
|
static ssize_t romboot_sysfs_download_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 = romboot_hcd->tcm_hcd;
|
|
|
|
if (kstrtouint(buf, 0, &input) != 0)
|
|
return -EINVAL;
|
|
|
|
if (input)
|
|
romboot_do_download(tcm_hcd);
|
|
|
|
if (romboot_hcd->fw_entry) {
|
|
release_firmware(romboot_hcd->fw_entry);
|
|
romboot_hcd->fw_entry = NULL;
|
|
}
|
|
|
|
romboot_hcd->image = NULL;
|
|
romboot_hcd->image_size = 0;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static ssize_t romboot_sysfs_program_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 = romboot_hcd->tcm_hcd;
|
|
|
|
if (kstrtouint(buf, 0, &input) != 0)
|
|
return -EINVAL;
|
|
|
|
if (input)
|
|
romboot_do_program(tcm_hcd);
|
|
|
|
if (romboot_hcd->fw_entry) {
|
|
release_firmware(romboot_hcd->fw_entry);
|
|
romboot_hcd->fw_entry = NULL;
|
|
}
|
|
|
|
romboot_hcd->image = NULL;
|
|
romboot_hcd->image_size = 0;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static ssize_t romboot_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 = romboot_hcd->tcm_hcd;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
retval = secure_memcpy(&romboot_hcd->image_buf[pos],
|
|
IMAGE_BUF_SIZE - pos,
|
|
buf,
|
|
count,
|
|
count);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy firmware image data\n");
|
|
romboot_hcd->image_size = 0;
|
|
goto exit;
|
|
}
|
|
|
|
romboot_hcd->image_size = pos + count;
|
|
|
|
retval = count;
|
|
|
|
exit:
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
static ssize_t romboot_sysfs_ihex_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 = romboot_hcd->tcm_hcd;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
retval = secure_memcpy(&romboot_hcd->ihex_buf[pos],
|
|
IHEX_BUF_SIZE - pos,
|
|
buf,
|
|
count,
|
|
count);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy ihex data\n");
|
|
romboot_hcd->ihex_size = 0;
|
|
goto exit;
|
|
}
|
|
|
|
romboot_hcd->ihex_size = pos + count;
|
|
|
|
retval = count;
|
|
|
|
exit:
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int romboot_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 = romboot_hcd->tcm_hcd;
|
|
const unsigned char *image;
|
|
const unsigned char *content;
|
|
|
|
image = romboot_hcd->image;
|
|
image_info = &romboot_hcd->image_info;
|
|
header = (struct image_header *)image;
|
|
|
|
magic_value = le4_to_uint(header->magic_value);
|
|
if ((magic_value & 0xffff) == BINARY_FILE_MAGIC_VALUE) {
|
|
LOGN(tcm_hcd->pdev->dev.parent,
|
|
"use binary file\n");
|
|
image_info->app_firmware.size = romboot_hcd->image_size;
|
|
image_info->app_firmware.data = romboot_hcd->image;
|
|
LOGN(tcm_hcd->pdev->dev.parent,
|
|
"Application firmware size = %ld\n",
|
|
romboot_hcd->image_size);
|
|
return 0;
|
|
} else 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 (strncmp((char *)descriptor->id_string,
|
|
BOOT_CONFIG_ID,
|
|
strlen(BOOT_CONFIG_ID)) == 0) {
|
|
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 (strncmp((char *)descriptor->id_string,
|
|
ROMBOOT_APP_CODE_ID,
|
|
strlen(ROMBOOT_APP_CODE_ID)) == 0) {
|
|
if (checksum != (crc32(~0, content, length) ^ ~0)) {
|
|
LOGE(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)) {
|
|
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 (strncmp((char *)descriptor->id_string,
|
|
DISP_CONFIG_ID,
|
|
strlen(DISP_CONFIG_ID)) == 0) {
|
|
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 romboot_get_fw_image(void)
|
|
{
|
|
int retval;
|
|
struct syna_tcm_hcd *tcm_hcd = romboot_hcd->tcm_hcd;
|
|
|
|
if (romboot_hcd->image == NULL) {
|
|
retval = request_firmware(&romboot_hcd->fw_entry,
|
|
FW_IMAGE_NAME,
|
|
tcm_hcd->pdev->dev.parent);
|
|
if (retval < 0) {
|
|
LOGD(tcm_hcd->pdev->dev.parent,
|
|
"Failed to request %s\n",
|
|
FW_IMAGE_NAME);
|
|
return retval;
|
|
}
|
|
|
|
LOGD(tcm_hcd->pdev->dev.parent,
|
|
"Firmware image size = %d\n",
|
|
(unsigned int)romboot_hcd->fw_entry->size);
|
|
|
|
romboot_hcd->image = romboot_hcd->fw_entry->data;
|
|
}
|
|
retval = romboot_parse_fw_image();
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to parse firmware image\n");
|
|
release_firmware(romboot_hcd->fw_entry);
|
|
romboot_hcd->fw_entry = NULL;
|
|
romboot_hcd->image = NULL;
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void romboot_do_download(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
unsigned char *out_buf = NULL;
|
|
unsigned char *resp_buf = NULL;
|
|
unsigned int resp_buf_size;
|
|
unsigned int resp_length;
|
|
unsigned int data_size_blocks;
|
|
unsigned int image_size;
|
|
|
|
LOGN(tcm_hcd->pdev->dev.parent,
|
|
"%s\n", __func__);
|
|
|
|
resp_buf = NULL;
|
|
resp_buf_size = 0;
|
|
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
pm_stay_awake(&tcm_hcd->pdev->dev);
|
|
|
|
mutex_lock(&romboot_hcd->romboot_mutex);
|
|
|
|
|
|
if (tcm_hcd->id_info.mode != MODE_ROMBOOTLOADER) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Not in romboot\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (romboot_hcd->image_size != 0)
|
|
romboot_hcd->image = romboot_hcd->image_buf;
|
|
|
|
retval = romboot_get_fw_image();
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to request romboot.img\n");
|
|
goto exit;
|
|
}
|
|
|
|
image_size = (unsigned int)romboot_hcd->image_info.app_firmware.size;
|
|
|
|
LOGD(tcm_hcd->pdev->dev.parent,
|
|
"image_size = %d\n",
|
|
image_size);
|
|
|
|
data_size_blocks = image_size / 16;
|
|
|
|
out_buf = kzalloc(image_size + RESERVED_BYTES,
|
|
GFP_KERNEL);
|
|
|
|
memset(out_buf, 0x00, RESERVED_BYTES);
|
|
|
|
out_buf[0] = romboot_hcd->image_info.app_firmware.size >> 16;
|
|
|
|
retval = secure_memcpy(&out_buf[RESERVED_BYTES],
|
|
romboot_hcd->image_info.app_firmware.size,
|
|
romboot_hcd->image_info.app_firmware.data,
|
|
romboot_hcd->image_info.app_firmware.size,
|
|
romboot_hcd->image_info.app_firmware.size);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy payload\n");
|
|
goto exit;
|
|
}
|
|
|
|
LOGD(tcm_hcd->pdev->dev.parent,
|
|
"data_size_blocks: %d\n",
|
|
data_size_blocks);
|
|
|
|
retval = tcm_hcd->write_message(tcm_hcd,
|
|
CMD_ROMBOOT_DOWNLOAD,
|
|
out_buf,
|
|
image_size + RESERVED_BYTES,
|
|
&resp_buf,
|
|
&resp_buf_size,
|
|
&resp_length,
|
|
NULL,
|
|
20);
|
|
if (retval < 0) {
|
|
if (retval == -ETIME) {
|
|
retval = tcm_hcd->read_message(tcm_hcd,
|
|
NULL,
|
|
0);
|
|
if (retval < 0 || tcm_hcd->in.buf[1] != STATUS_OK) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to wait for HOSTDOWNLOAD response");
|
|
goto exit;
|
|
} else {
|
|
LOGN(tcm_hcd->pdev->dev.parent,
|
|
"written blocks = %d",
|
|
tcm_hcd->in.buf[4] |
|
|
tcm_hcd->in.buf[5] << 8);
|
|
}
|
|
} else {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write command HOSTDOWNLOAD");
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to switch to bootloader");
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&romboot_hcd->romboot_mutex);
|
|
|
|
pm_relax(&tcm_hcd->pdev->dev);
|
|
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
kfree(out_buf);
|
|
}
|
|
|
|
static int romboot_add_data_entry(unsigned char data)
|
|
{
|
|
struct syna_tcm_hcd *tcm_hcd = romboot_hcd->tcm_hcd;
|
|
|
|
if (romboot_hcd->data_entries >= DATA_BUF_SIZE) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Reached data buffer size limit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
romboot_hcd->data_buf[romboot_hcd->data_entries++] = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int romboot_parse_ihex(void)
|
|
{
|
|
int retval;
|
|
unsigned char colon;
|
|
unsigned char *buf;
|
|
unsigned int addr;
|
|
unsigned int type;
|
|
unsigned int addrl;
|
|
unsigned int addrh;
|
|
unsigned int data0;
|
|
unsigned int data1;
|
|
unsigned int count;
|
|
unsigned int words;
|
|
unsigned int offset;
|
|
unsigned int record;
|
|
struct syna_tcm_hcd *tcm_hcd = romboot_hcd->tcm_hcd;
|
|
struct image_info *image_info = &romboot_hcd->image_info;
|
|
|
|
words = 0;
|
|
|
|
offset = 0;
|
|
|
|
buf = romboot_hcd->ihex_buf;
|
|
|
|
romboot_hcd->data_entries = 0;
|
|
|
|
romboot_hcd->ihex_records = romboot_hcd->ihex_size / IHEX_RECORD_SIZE;
|
|
|
|
memset(romboot_hcd->data_buf, 0xff, DATA_BUF_SIZE);
|
|
|
|
for (record = 0; record < romboot_hcd->ihex_records; record++) {
|
|
buf[(record + 1) * IHEX_RECORD_SIZE - 1] = 0x00;
|
|
retval = sscanf(&buf[record * IHEX_RECORD_SIZE],
|
|
"%c%02x%02x%02x%02x%02x%02x",
|
|
&colon,
|
|
&count,
|
|
&addrh,
|
|
&addrl,
|
|
&type,
|
|
&data0,
|
|
&data1);
|
|
if (retval != 7) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to read ihex record\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (type == 0x00) {
|
|
addr = (addrh << 8) + addrl;
|
|
addr += offset;
|
|
|
|
romboot_hcd->data_entries = addr;
|
|
|
|
retval = romboot_add_data_entry(data0);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to add data entry\n");
|
|
return retval;
|
|
}
|
|
|
|
retval = romboot_add_data_entry(data1);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to add data entry\n");
|
|
return retval;
|
|
}
|
|
|
|
words++;
|
|
} else if (type == 0x02) {
|
|
offset = (data0 << 8) + data1;
|
|
offset <<= 4;
|
|
|
|
romboot_hcd->data_entries = offset;
|
|
}
|
|
}
|
|
|
|
image_info->app_firmware.size = DATA_BUF_SIZE;
|
|
image_info->app_firmware.data = romboot_hcd->data_buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int romboot_flash_command(struct syna_tcm_hcd *tcm_hcd,
|
|
unsigned char flash_command, unsigned char *out,
|
|
unsigned int out_size, unsigned char *in,
|
|
unsigned int in_size)
|
|
{
|
|
int retval;
|
|
unsigned char *payld_buf = NULL;
|
|
unsigned char *resp_buf = NULL;
|
|
unsigned int resp_buf_size;
|
|
unsigned int resp_length;
|
|
struct flash_param flash_param = {1, 0x19, 0, 0, 0};
|
|
|
|
flash_param.read_size = in_size;
|
|
|
|
flash_param.jedec_cmd = flash_command;
|
|
|
|
resp_buf = NULL;
|
|
resp_buf_size = 0;
|
|
|
|
payld_buf = kzalloc(sizeof(flash_param) + out_size,
|
|
GFP_KERNEL);
|
|
|
|
memcpy(payld_buf, &flash_param, sizeof(flash_param));
|
|
|
|
memcpy(payld_buf + sizeof(flash_param), out, out_size);
|
|
|
|
retval = tcm_hcd->write_message(tcm_hcd,
|
|
CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED,
|
|
payld_buf,
|
|
sizeof(flash_param) + out_size,
|
|
&resp_buf,
|
|
&resp_buf_size,
|
|
&resp_length,
|
|
NULL,
|
|
20);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write command CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED");
|
|
goto exit;
|
|
}
|
|
|
|
if (in_size && (in_size <= resp_length))
|
|
memcpy(in, resp_buf, in_size);
|
|
|
|
exit:
|
|
kfree(payld_buf);
|
|
kfree(resp_buf);
|
|
return retval;
|
|
}
|
|
|
|
static int romboot_flash_status(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
int idx;
|
|
unsigned char status;
|
|
|
|
for (idx = 0; idx < STATUS_CHECK_RETRY; idx++) {
|
|
retval = romboot_flash_command(tcm_hcd, JEDEC_READ_STATUS,
|
|
NULL, 0, &status, sizeof(status));
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write JEDEC_READ_STATUS");
|
|
return retval;
|
|
}
|
|
|
|
usleep_range(STATUS_CHECK_US_MIN, STATUS_CHECK_US_MAX);
|
|
|
|
if (!status)
|
|
break;
|
|
}
|
|
|
|
if (status)
|
|
retval = -EIO;
|
|
else
|
|
retval = status;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int romboot_flash_erase(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"%s", __func__);
|
|
|
|
retval = romboot_flash_command(tcm_hcd, JEDEC_WRITE_ENABLE,
|
|
NULL, 0, NULL, 0);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write JEDEC_WRITE_ENABLE");
|
|
return retval;
|
|
}
|
|
|
|
retval = romboot_flash_command(tcm_hcd, JEDEC_CHIP_ERASE,
|
|
NULL, 0, NULL, 0);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write JEDEC_CHIP_ERASE");
|
|
return retval;
|
|
}
|
|
|
|
retval = romboot_flash_status(tcm_hcd);
|
|
if (retval < 0)
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to get correct status: %d", retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int romboot_flash_program(struct syna_tcm_hcd *tcm_hcd,
|
|
unsigned char *image_buf, unsigned int image_size)
|
|
{
|
|
int retval;
|
|
int idx;
|
|
unsigned short img_header;
|
|
unsigned int pages;
|
|
unsigned char buf[FLASH_PAGE_SIZE + 3];
|
|
|
|
img_header = image_buf[0] | image_buf[1] << 8;
|
|
if ((image_size % FLASH_PAGE_SIZE) ||
|
|
(img_header != BINARY_FILE_MAGIC_VALUE)) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Wrong image file");
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"image_size = %d, img_header = 0x%04x",
|
|
image_size, img_header);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pages = image_size / FLASH_PAGE_SIZE;
|
|
|
|
for (idx = 0; idx < pages; idx++) {
|
|
retval = romboot_flash_command(tcm_hcd, JEDEC_WRITE_ENABLE,
|
|
NULL, 0, NULL, 0);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write JEDEC_WRITE_ENABLE");
|
|
return retval;
|
|
}
|
|
|
|
buf[0] = FLASH_PAGE_SIZE * idx >> 16;
|
|
buf[1] = FLASH_PAGE_SIZE * idx >> 8;
|
|
buf[2] = FLASH_PAGE_SIZE * idx;
|
|
|
|
memcpy(buf + 3, image_buf + FLASH_PAGE_SIZE * idx,
|
|
FLASH_PAGE_SIZE);
|
|
|
|
retval = romboot_flash_command(tcm_hcd, JEDEC_PAGE_PROGRAM,
|
|
buf, sizeof(buf), NULL, 0);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write JEDEC_READ_STATUS");
|
|
return retval;
|
|
}
|
|
|
|
retval = romboot_flash_status(tcm_hcd);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to get correct status: %d",
|
|
retval);
|
|
return retval;
|
|
}
|
|
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void romboot_do_program(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
unsigned int image_size;
|
|
unsigned char *out_buf = NULL;
|
|
|
|
LOGN(tcm_hcd->pdev->dev.parent,
|
|
"%s\n", __func__);
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
pm_stay_awake(&tcm_hcd->pdev->dev);
|
|
|
|
mutex_lock(&romboot_hcd->romboot_mutex);
|
|
|
|
if (tcm_hcd->id_info.mode != MODE_ROMBOOTLOADER) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Not in romboot\n");
|
|
goto do_program_exit;
|
|
}
|
|
|
|
retval = romboot_parse_ihex();
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to parse ihex data\n");
|
|
goto do_program_exit;
|
|
}
|
|
|
|
image_size = (unsigned int)romboot_hcd->image_info.app_firmware.size;
|
|
|
|
out_buf = kzalloc(image_size, GFP_KERNEL);
|
|
|
|
retval = secure_memcpy(out_buf,
|
|
romboot_hcd->image_info.app_firmware.size,
|
|
romboot_hcd->image_info.app_firmware.data,
|
|
romboot_hcd->image_info.app_firmware.size,
|
|
romboot_hcd->image_info.app_firmware.size);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy payload\n");
|
|
goto do_program_exit;
|
|
}
|
|
|
|
LOGD(tcm_hcd->pdev->dev.parent,
|
|
"image_size = %d\n",
|
|
image_size);
|
|
|
|
retval = romboot_flash_erase(tcm_hcd);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to erase chip");
|
|
goto do_program_exit;
|
|
}
|
|
|
|
retval = romboot_flash_program(tcm_hcd, out_buf, image_size);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to program chip");
|
|
goto do_program_exit;
|
|
}
|
|
|
|
retval = tcm_hcd->reset(tcm_hcd, true, true);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to reset");
|
|
}
|
|
|
|
do_program_exit:
|
|
|
|
mutex_unlock(&romboot_hcd->romboot_mutex);
|
|
|
|
pm_relax(&tcm_hcd->pdev->dev);
|
|
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
kfree(out_buf);
|
|
}
|
|
|
|
static void romboot_download_firmware(void)
|
|
{
|
|
queue_work(romboot_hcd->workqueue, &romboot_hcd->romboot_work);
|
|
}
|
|
|
|
static void romboot_download_work(struct work_struct *work)
|
|
{
|
|
struct syna_tcm_hcd *tcm_hcd = romboot_hcd->tcm_hcd;
|
|
|
|
atomic_set(&tcm_hcd->host_downloading, 1);
|
|
romboot_do_download(tcm_hcd);
|
|
}
|
|
|
|
static int romboot_init(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
int idx;
|
|
|
|
romboot_hcd = kzalloc(sizeof(*romboot_hcd), GFP_KERNEL);
|
|
if (!romboot_hcd) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for romboot_hcd\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
romboot_hcd->image_buf = kzalloc(IMAGE_BUF_SIZE, GFP_KERNEL);
|
|
if (!romboot_hcd->image_buf) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for romboot_hcd->image_buf\n");
|
|
goto err_allocate_memory;
|
|
}
|
|
|
|
romboot_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL);
|
|
if (!romboot_hcd->ihex_buf) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for romboot_hcd->ihex_buf\n");
|
|
goto err_allocate_ihex_buf;
|
|
}
|
|
|
|
romboot_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL);
|
|
if (!romboot_hcd->data_buf) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for romboot_hcd->data_buf\n");
|
|
goto err_allocate_data_buf;
|
|
}
|
|
|
|
romboot_hcd->tcm_hcd = tcm_hcd;
|
|
|
|
mutex_init(&romboot_hcd->romboot_mutex);
|
|
|
|
INIT_BUFFER(romboot_hcd->out, false);
|
|
INIT_BUFFER(romboot_hcd->resp, false);
|
|
|
|
romboot_hcd->workqueue =
|
|
create_singlethread_workqueue("syna_tcm_romboot");
|
|
INIT_WORK(&romboot_hcd->romboot_work,
|
|
romboot_download_work);
|
|
|
|
romboot_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
|
|
tcm_hcd->sysfs_dir);
|
|
if (!romboot_hcd->sysfs_dir) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create sysfs directory\n");
|
|
retval = -EINVAL;
|
|
goto err_sysfs_create_dir;
|
|
}
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
|
|
retval = sysfs_create_file(romboot_hcd->sysfs_dir,
|
|
&(*attrs[idx]).attr);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create sysfs file\n");
|
|
goto err_sysfs_create_file;
|
|
}
|
|
}
|
|
|
|
retval = sysfs_create_bin_file(romboot_hcd->sysfs_dir, &bin_attrs[0]);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create sysfs bin file\n");
|
|
goto err_sysfs_create_bin_file;
|
|
}
|
|
|
|
retval = sysfs_create_bin_file(romboot_hcd->sysfs_dir, &bin_attrs[1]);
|
|
if (retval < 0) {
|
|
LOGE(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create sysfs hex file\n");
|
|
goto err_sysfs_create_hex_file;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_sysfs_create_hex_file:
|
|
sysfs_remove_bin_file(romboot_hcd->sysfs_dir, &bin_attrs[0]);
|
|
|
|
err_sysfs_create_bin_file:
|
|
idx = ARRAY_SIZE(attrs);
|
|
for (idx--; idx >= 0; idx--)
|
|
sysfs_remove_file(romboot_hcd->sysfs_dir, &(*attrs[idx]).attr);
|
|
|
|
err_sysfs_create_file:
|
|
kobject_put(romboot_hcd->sysfs_dir);
|
|
|
|
err_sysfs_create_dir:
|
|
kfree(romboot_hcd->data_buf);
|
|
|
|
err_allocate_data_buf:
|
|
kfree(romboot_hcd->ihex_buf);
|
|
|
|
err_allocate_ihex_buf:
|
|
kfree(romboot_hcd->image_buf);
|
|
|
|
err_allocate_memory:
|
|
RELEASE_BUFFER(romboot_hcd->resp);
|
|
RELEASE_BUFFER(romboot_hcd->out);
|
|
|
|
kfree(romboot_hcd);
|
|
romboot_hcd = NULL;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
static int romboot_remove(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int idx;
|
|
|
|
if (!romboot_hcd)
|
|
goto exit;
|
|
|
|
if (romboot_hcd->fw_entry)
|
|
release_firmware(romboot_hcd->fw_entry);
|
|
|
|
if (true == ENABLE_SYSFS_INTERFACE) {
|
|
sysfs_remove_bin_file(romboot_hcd->sysfs_dir, &bin_attrs[1]);
|
|
sysfs_remove_bin_file(romboot_hcd->sysfs_dir, &bin_attrs[0]);
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
|
|
sysfs_remove_file(romboot_hcd->sysfs_dir,
|
|
&(*attrs[idx]).attr);
|
|
}
|
|
|
|
kobject_put(romboot_hcd->sysfs_dir);
|
|
}
|
|
|
|
cancel_work_sync(&romboot_hcd->romboot_work);
|
|
flush_workqueue(romboot_hcd->workqueue);
|
|
destroy_workqueue(romboot_hcd->workqueue);
|
|
|
|
RELEASE_BUFFER(romboot_hcd->resp);
|
|
RELEASE_BUFFER(romboot_hcd->out);
|
|
|
|
kfree(romboot_hcd->image_buf);
|
|
kfree(romboot_hcd->data_buf);
|
|
kfree(romboot_hcd->ihex_buf);
|
|
kfree(romboot_hcd);
|
|
romboot_hcd = NULL;
|
|
|
|
exit:
|
|
complete(&romboot_remove_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int romboot_syncbox(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
if (!romboot_hcd)
|
|
return 0;
|
|
|
|
switch (tcm_hcd->report.id) {
|
|
case REPORT_ROMBOOT:
|
|
romboot_download_firmware();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int romboot_reset(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
|
|
if (!romboot_hcd) {
|
|
retval = romboot_init(tcm_hcd);
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct syna_tcm_module_cb romboot_module = {
|
|
.type = TCM_ROMBOOT,
|
|
.init = romboot_init,
|
|
.remove = romboot_remove,
|
|
.syncbox = romboot_syncbox,
|
|
.asyncbox = NULL,
|
|
.reset = romboot_reset,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
.early_suspend = NULL,
|
|
};
|
|
|
|
static int __init romboot_module_init(void)
|
|
{
|
|
return syna_tcm_add_module(&romboot_module, true);
|
|
}
|
|
|
|
static void __exit romboot_module_exit(void)
|
|
{
|
|
syna_tcm_add_module(&romboot_module, false);
|
|
|
|
wait_for_completion(&romboot_remove_complete);
|
|
}
|
|
|
|
module_init(romboot_module_init);
|
|
module_exit(romboot_module_exit);
|
|
|
|
MODULE_AUTHOR("Synaptics, Inc.");
|
|
MODULE_DESCRIPTION("Synaptics TCM Romboot Module");
|
|
MODULE_LICENSE("GPL v2");
|