871 lines
18 KiB
C
871 lines
18 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include "synaptics_tcm_core.h"
|
||
|
|
||
|
#define SET_UP_RECOVERY_MODE true
|
||
|
|
||
|
#define ENABLE_SYSFS_INTERFACE true
|
||
|
|
||
|
#define SYSFS_DIR_NAME "recovery"
|
||
|
|
||
|
#define IHEX_BUF_SIZE (2048 * 1024)
|
||
|
|
||
|
#define DATA_BUF_SIZE (512 * 1024)
|
||
|
|
||
|
#define IHEX_RECORD_SIZE 14
|
||
|
|
||
|
#define PDT_START_ADDR 0x00e9
|
||
|
|
||
|
#define UBL_FN_NUMBER 0x35
|
||
|
|
||
|
#define F35_CHUNK_SIZE 16
|
||
|
|
||
|
#define F35_CHUNK_SIZE_WORDS 8
|
||
|
|
||
|
#define F35_ERASE_ALL_WAIT_MS 5000
|
||
|
|
||
|
#define F35_ERASE_ALL_POLL_MS 100
|
||
|
|
||
|
#define F35_DATA5_OFFSET 5
|
||
|
|
||
|
#define F35_CTRL3_OFFSET 18
|
||
|
|
||
|
#define F35_RESET_COMMAND 16
|
||
|
|
||
|
#define F35_ERASE_ALL_COMMAND 3
|
||
|
|
||
|
#define F35_WRITE_CHUNK_COMMAND 2
|
||
|
|
||
|
#define F35_READ_FLASH_STATUS_COMMAND 1
|
||
|
|
||
|
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 recovery_hcd {
|
||
|
bool set_up_recovery_mode;
|
||
|
unsigned char chunk_buf[F35_CHUNK_SIZE + 3];
|
||
|
unsigned char out_buf[3];
|
||
|
unsigned char *ihex_buf;
|
||
|
unsigned char *data_buf;
|
||
|
unsigned int ihex_size;
|
||
|
unsigned int ihex_records;
|
||
|
unsigned int data_entries;
|
||
|
struct kobject *sysfs_dir;
|
||
|
struct rmi_addr f35_addr;
|
||
|
struct syna_tcm_hcd *tcm_hcd;
|
||
|
};
|
||
|
|
||
|
DECLARE_COMPLETION(recovery_remove_complete);
|
||
|
|
||
|
static struct recovery_hcd *recovery_hcd;
|
||
|
|
||
|
static int recovery_do_recovery(void);
|
||
|
|
||
|
STORE_PROTOTYPE(recovery, recovery)
|
||
|
|
||
|
static struct device_attribute *attrs[] = {
|
||
|
ATTRIFY(recovery),
|
||
|
};
|
||
|
|
||
|
static ssize_t recovery_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_attr = {
|
||
|
.attr = {
|
||
|
.name = "ihex",
|
||
|
.mode = 0220,
|
||
|
},
|
||
|
.size = 0,
|
||
|
.write = recovery_sysfs_ihex_store,
|
||
|
};
|
||
|
|
||
|
static ssize_t recovery_sysfs_recovery_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 = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
if (kstrtouint(buf, 0, &input) != 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (input == 1)
|
||
|
recovery_hcd->set_up_recovery_mode = true;
|
||
|
else if (input == 2)
|
||
|
recovery_hcd->set_up_recovery_mode = false;
|
||
|
else
|
||
|
return -EINVAL;
|
||
|
|
||
|
mutex_lock(&tcm_hcd->extif_mutex);
|
||
|
|
||
|
if (recovery_hcd->ihex_size == 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get ihex data\n");
|
||
|
retval = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Invalid ihex data\n");
|
||
|
retval = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->ihex_records = recovery_hcd->ihex_size / IHEX_RECORD_SIZE;
|
||
|
|
||
|
retval = recovery_do_recovery();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to do recovery\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
retval = count;
|
||
|
|
||
|
exit:
|
||
|
recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE;
|
||
|
|
||
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static ssize_t recovery_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 = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
mutex_lock(&tcm_hcd->extif_mutex);
|
||
|
|
||
|
retval = secure_memcpy(&recovery_hcd->ihex_buf[pos],
|
||
|
IHEX_BUF_SIZE - pos,
|
||
|
buf,
|
||
|
count,
|
||
|
count);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to copy ihex data\n");
|
||
|
recovery_hcd->ihex_size = 0;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->ihex_size = pos + count;
|
||
|
|
||
|
retval = count;
|
||
|
|
||
|
exit:
|
||
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static int recovery_device_reset(void)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned char command;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
|
||
|
|
||
|
command = F35_RESET_COMMAND;
|
||
|
|
||
|
retval = syna_tcm_rmi_write(tcm_hcd,
|
||
|
recovery_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");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
msleep(bdata->reset_delay_ms);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_add_data_entry(unsigned char data)
|
||
|
{
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
if (recovery_hcd->data_entries >= DATA_BUF_SIZE) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Reached data buffer size limit\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->data_buf[recovery_hcd->data_entries++] = data;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_add_padding(unsigned int *words)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned int padding;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
padding = (F35_CHUNK_SIZE_WORDS - *words % F35_CHUNK_SIZE_WORDS);
|
||
|
padding %= F35_CHUNK_SIZE_WORDS;
|
||
|
|
||
|
while (padding) {
|
||
|
retval = recovery_add_data_entry(0xff);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = recovery_add_data_entry(0xff);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
(*words)++;
|
||
|
padding--;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_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 = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
words = 0;
|
||
|
|
||
|
offset = 0;
|
||
|
|
||
|
buf = recovery_hcd->ihex_buf;
|
||
|
|
||
|
recovery_hcd->data_entries = 0;
|
||
|
|
||
|
for (record = 0; record < recovery_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) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to read ihex record\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (type == 0x00) {
|
||
|
if ((words % F35_CHUNK_SIZE_WORDS) == 0) {
|
||
|
addr = (addrh << 8) + addrl;
|
||
|
addr += offset;
|
||
|
addr >>= 4;
|
||
|
|
||
|
retval = recovery_add_data_entry(addr);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = recovery_add_data_entry(addr >> 8);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
retval = recovery_add_data_entry(data0);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = recovery_add_data_entry(data1);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add data entry\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
words++;
|
||
|
} else if (type == 0x02) {
|
||
|
retval = recovery_add_padding(&words);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add padding\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
offset = (data0 << 8) + data1;
|
||
|
offset <<= 4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
retval = recovery_add_padding(&words);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to add padding\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_check_status(void)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned char status;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
retval = syna_tcm_rmi_read(tcm_hcd,
|
||
|
recovery_hcd->f35_addr.data_base,
|
||
|
&status,
|
||
|
sizeof(status));
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to read status\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
status = status & 0x1f;
|
||
|
|
||
|
if (status != 0x00) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Recovery mode status = 0x%02x\n",
|
||
|
status);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_write_flash(void)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned char *data_ptr;
|
||
|
unsigned int chunk_buf_size;
|
||
|
unsigned int chunk_data_size;
|
||
|
unsigned int entries_written;
|
||
|
unsigned int entries_to_write;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
entries_written = 0;
|
||
|
|
||
|
data_ptr = recovery_hcd->data_buf;
|
||
|
|
||
|
chunk_buf_size = sizeof(recovery_hcd->chunk_buf);
|
||
|
|
||
|
chunk_data_size = chunk_buf_size - 1;
|
||
|
|
||
|
recovery_hcd->chunk_buf[chunk_buf_size - 1] = F35_WRITE_CHUNK_COMMAND;
|
||
|
|
||
|
while (entries_written < recovery_hcd->data_entries) {
|
||
|
entries_to_write = F35_CHUNK_SIZE + 2;
|
||
|
|
||
|
retval = secure_memcpy(recovery_hcd->chunk_buf,
|
||
|
chunk_buf_size - 1,
|
||
|
data_ptr,
|
||
|
recovery_hcd->data_entries - entries_written,
|
||
|
entries_to_write);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to copy chunk data\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
retval = syna_tcm_rmi_write(tcm_hcd,
|
||
|
recovery_hcd->f35_addr.control_base,
|
||
|
recovery_hcd->chunk_buf,
|
||
|
chunk_buf_size);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to write chunk data\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
data_ptr += entries_to_write;
|
||
|
entries_written += entries_to_write;
|
||
|
}
|
||
|
|
||
|
retval = recovery_check_status();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get no error recovery mode status\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_poll_erase_completion(void)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned char status;
|
||
|
unsigned char command;
|
||
|
unsigned char data_base;
|
||
|
unsigned int timeout;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
timeout = F35_ERASE_ALL_WAIT_MS;
|
||
|
|
||
|
data_base = recovery_hcd->f35_addr.data_base;
|
||
|
|
||
|
do {
|
||
|
command = F35_READ_FLASH_STATUS_COMMAND;
|
||
|
|
||
|
retval = syna_tcm_rmi_write(tcm_hcd,
|
||
|
recovery_hcd->f35_addr.command_base,
|
||
|
&command,
|
||
|
sizeof(command));
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to write F$35 command\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
do {
|
||
|
retval = syna_tcm_rmi_read(tcm_hcd,
|
||
|
recovery_hcd->f35_addr.command_base,
|
||
|
&command,
|
||
|
sizeof(command));
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to read command status\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if (command == 0x00)
|
||
|
break;
|
||
|
|
||
|
if (timeout == 0)
|
||
|
break;
|
||
|
|
||
|
msleep(F35_ERASE_ALL_POLL_MS);
|
||
|
timeout -= F35_ERASE_ALL_POLL_MS;
|
||
|
} while (true);
|
||
|
|
||
|
if (command != 0 && timeout == 0) {
|
||
|
retval = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
retval = syna_tcm_rmi_read(tcm_hcd,
|
||
|
data_base + F35_DATA5_OFFSET,
|
||
|
&status,
|
||
|
sizeof(status));
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to read flash status\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if ((status & 0x01) == 0x00)
|
||
|
break;
|
||
|
|
||
|
if (timeout == 0) {
|
||
|
retval = -EINVAL;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
msleep(F35_ERASE_ALL_POLL_MS);
|
||
|
timeout -= F35_ERASE_ALL_POLL_MS;
|
||
|
} while (true);
|
||
|
|
||
|
retval = 0;
|
||
|
|
||
|
exit:
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get erase completion\n");
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static int recovery_erase_flash(void)
|
||
|
{
|
||
|
int retval;
|
||
|
unsigned char command;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
command = F35_ERASE_ALL_COMMAND;
|
||
|
|
||
|
retval = syna_tcm_rmi_write(tcm_hcd,
|
||
|
recovery_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");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if (recovery_hcd->f35_addr.command_base) {
|
||
|
retval = recovery_poll_erase_completion();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to wait for erase completion\n");
|
||
|
return retval;
|
||
|
}
|
||
|
} else {
|
||
|
msleep(F35_ERASE_ALL_WAIT_MS);
|
||
|
}
|
||
|
|
||
|
retval = recovery_check_status();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to get no error recovery mode status\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_set_up_recovery_mode(void)
|
||
|
{
|
||
|
int retval;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
retval = tcm_hcd->write_message(tcm_hcd,
|
||
|
recovery_hcd->out_buf[0],
|
||
|
&recovery_hcd->out_buf[1],
|
||
|
2,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
0);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to write command %s\n",
|
||
|
STR(CMD_REBOOT_TO_ROM_BOOTLOADER));
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
msleep(bdata->reset_delay_ms);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_do_recovery(void)
|
||
|
{
|
||
|
int retval;
|
||
|
struct rmi_pdt_entry p_entry;
|
||
|
struct syna_tcm_hcd *tcm_hcd = recovery_hcd->tcm_hcd;
|
||
|
|
||
|
retval = recovery_parse_ihex();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to parse ihex data\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
if (recovery_hcd->set_up_recovery_mode) {
|
||
|
retval = recovery_set_up_recovery_mode();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to set up recovery mode\n");
|
||
|
return retval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tcm_hcd->update_watchdog(tcm_hcd, false);
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
if (p_entry.fn_number != UBL_FN_NUMBER) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to find F$35\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->f35_addr.query_base = p_entry.query_base_addr;
|
||
|
recovery_hcd->f35_addr.command_base = p_entry.command_base_addr;
|
||
|
recovery_hcd->f35_addr.control_base = p_entry.control_base_addr;
|
||
|
recovery_hcd->f35_addr.data_base = p_entry.data_base_addr;
|
||
|
|
||
|
LOGN(tcm_hcd->pdev->dev.parent,
|
||
|
"Start of recovery\n");
|
||
|
|
||
|
retval = recovery_erase_flash();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to erase flash\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
LOGN(tcm_hcd->pdev->dev.parent,
|
||
|
"Flash erased\n");
|
||
|
|
||
|
retval = recovery_write_flash();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to write to flash\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
LOGN(tcm_hcd->pdev->dev.parent,
|
||
|
"Flash written\n");
|
||
|
|
||
|
retval = recovery_device_reset();
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to do reset\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
LOGN(tcm_hcd->pdev->dev.parent,
|
||
|
"End of recovery\n");
|
||
|
|
||
|
if (recovery_hcd->set_up_recovery_mode)
|
||
|
return 0;
|
||
|
|
||
|
tcm_hcd->update_watchdog(tcm_hcd, true);
|
||
|
|
||
|
retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to enable interrupt\n");
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
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_APPLICATION);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to run application firmware\n");
|
||
|
return retval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_init(struct syna_tcm_hcd *tcm_hcd)
|
||
|
{
|
||
|
int retval;
|
||
|
int idx;
|
||
|
|
||
|
recovery_hcd = kzalloc(sizeof(*recovery_hcd), GFP_KERNEL);
|
||
|
if (!recovery_hcd) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to allocate memory for recovery_hcd\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->ihex_buf = kzalloc(IHEX_BUF_SIZE, GFP_KERNEL);
|
||
|
if (!recovery_hcd->ihex_buf) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to allocate memory for recovery_hcd->ihex_buf\n");
|
||
|
goto err_allocate_ihex_buf;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL);
|
||
|
if (!recovery_hcd->data_buf) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to allocate memory for recovery_hcd->data_buf\n");
|
||
|
goto err_allocate_data_buf;
|
||
|
}
|
||
|
|
||
|
recovery_hcd->tcm_hcd = tcm_hcd;
|
||
|
|
||
|
recovery_hcd->set_up_recovery_mode = SET_UP_RECOVERY_MODE;
|
||
|
|
||
|
recovery_hcd->out_buf[0] = CMD_REBOOT_TO_ROM_BOOTLOADER;
|
||
|
recovery_hcd->out_buf[1] = 0;
|
||
|
recovery_hcd->out_buf[2] = 0;
|
||
|
|
||
|
if (false == ENABLE_SYSFS_INTERFACE)
|
||
|
return 0;
|
||
|
|
||
|
recovery_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
|
||
|
tcm_hcd->sysfs_dir);
|
||
|
if (!recovery_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(recovery_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(recovery_hcd->sysfs_dir, &bin_attr);
|
||
|
if (retval < 0) {
|
||
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
||
|
"Failed to create sysfs bin file\n");
|
||
|
goto err_sysfs_create_bin_file;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_sysfs_create_bin_file:
|
||
|
err_sysfs_create_file:
|
||
|
for (idx--; idx >= 0; idx--)
|
||
|
sysfs_remove_file(recovery_hcd->sysfs_dir, &(*attrs[idx]).attr);
|
||
|
|
||
|
kobject_put(recovery_hcd->sysfs_dir);
|
||
|
|
||
|
err_sysfs_create_dir:
|
||
|
kfree(recovery_hcd->data_buf);
|
||
|
err_allocate_data_buf:
|
||
|
kfree(recovery_hcd->ihex_buf);
|
||
|
err_allocate_ihex_buf:
|
||
|
kfree(recovery_hcd);
|
||
|
recovery_hcd = NULL;
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static int recovery_remove(struct syna_tcm_hcd *tcm_hcd)
|
||
|
{
|
||
|
int idx;
|
||
|
|
||
|
if (!recovery_hcd)
|
||
|
goto exit;
|
||
|
|
||
|
if (true == ENABLE_SYSFS_INTERFACE) {
|
||
|
sysfs_remove_bin_file(recovery_hcd->sysfs_dir, &bin_attr);
|
||
|
|
||
|
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
|
||
|
sysfs_remove_file(recovery_hcd->sysfs_dir,
|
||
|
&(*attrs[idx]).attr);
|
||
|
}
|
||
|
|
||
|
kobject_put(recovery_hcd->sysfs_dir);
|
||
|
}
|
||
|
|
||
|
kfree(recovery_hcd->data_buf);
|
||
|
kfree(recovery_hcd->ihex_buf);
|
||
|
kfree(recovery_hcd);
|
||
|
recovery_hcd = NULL;
|
||
|
|
||
|
exit:
|
||
|
complete(&recovery_remove_complete);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int recovery_reset(struct syna_tcm_hcd *tcm_hcd)
|
||
|
{
|
||
|
int retval;
|
||
|
|
||
|
if (!recovery_hcd) {
|
||
|
retval = recovery_init(tcm_hcd);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct syna_tcm_module_cb recovery_module = {
|
||
|
.type = TCM_RECOVERY,
|
||
|
.init = recovery_init,
|
||
|
.remove = recovery_remove,
|
||
|
.syncbox = NULL,
|
||
|
.asyncbox = NULL,
|
||
|
.reset = recovery_reset,
|
||
|
.suspend = NULL,
|
||
|
.resume = NULL,
|
||
|
.early_suspend = NULL,
|
||
|
};
|
||
|
|
||
|
static int __init recovery_module_init(void)
|
||
|
{
|
||
|
return syna_tcm_add_module(&recovery_module, true);
|
||
|
}
|
||
|
|
||
|
static void __exit recovery_module_exit(void)
|
||
|
{
|
||
|
syna_tcm_add_module(&recovery_module, false);
|
||
|
|
||
|
wait_for_completion(&recovery_remove_complete);
|
||
|
}
|
||
|
|
||
|
module_init(recovery_module_init);
|
||
|
module_exit(recovery_module_exit);
|
||
|
|
||
|
MODULE_AUTHOR("Synaptics, Inc.");
|
||
|
MODULE_DESCRIPTION("Synaptics TCM Recovery Module");
|
||
|
MODULE_LICENSE("GPL v2");
|