| /* |
| * 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 "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, 10, &input)) |
| 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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to get ihex data\n"); |
| retval = -EINVAL; |
| goto exit; |
| } |
| |
| if (recovery_hcd->ihex_size % IHEX_RECORD_SIZE) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to add data entry\n"); |
| return retval; |
| } |
| |
| retval = recovery_add_data_entry(0xff); |
| if (retval < 0) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to add data entry\n"); |
| return retval; |
| } |
| |
| retval = recovery_add_data_entry(addr >> 8); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to add data entry\n"); |
| return retval; |
| } |
| } |
| |
| retval = recovery_add_data_entry(data0); |
| if (retval < 0) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to add data entry\n"); |
| return retval; |
| } |
| |
| retval = recovery_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) { |
| retval = recovery_add_padding(&words); |
| if (retval < 0) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to read status\n"); |
| return retval; |
| } |
| |
| status = status & 0x1f; |
| |
| if (status != 0x00) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to read PDT entry\n"); |
| return retval; |
| } |
| |
| if (p_entry.fn_number != UBL_FN_NUMBER) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to enable interrupt\n"); |
| return retval; |
| } |
| |
| retval = tcm_hcd->identify(tcm_hcd, true); |
| if (retval < 0) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(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) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for ihex_buf\n"); |
| goto err_allocate_ihex_buf; |
| } |
| |
| recovery_hcd->data_buf = kzalloc(DATA_BUF_SIZE, GFP_KERNEL); |
| if (!recovery_hcd->data_buf) { |
| LOGE(tcm_hcd->pdev->dev.parent, |
| "Failed to allocate memory for 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 (!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) { |
| 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(recovery_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(recovery_hcd->sysfs_dir, &bin_attr); |
| if (retval < 0) { |
| LOGE(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 (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"); |