blob: 4db9da1486420d4e966f7b19f42c138b96ba2112 [file] [log] [blame]
/*
* Synaptics DSX touchscreen driver
*
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
*
* Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
* Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com>
* Copyright (C) 2018 The Linux Foundation. All rights reserved.
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/firmware.h>
#include <linux/platform_device.h>
#include <linux/input/synaptics_dsx_v2_6.h>
#include "synaptics_dsx_core.h"
#define FW_IMAGE_NAME "synaptics/startup_fw_update.img"
#define DO_STARTUP_FW_UPDATE
#ifdef DO_STARTUP_FW_UPDATE
#ifdef CONFIG_FB
#define WAIT_FOR_FB_READY
#define FB_READY_WAIT_MS 100
#define FB_READY_TIMEOUT_S 30
#endif
#endif
#define FORCE_UPDATE false
#define DO_LOCKDOWN false
#define MAX_IMAGE_NAME_LEN 256
#define MAX_FIRMWARE_ID_LEN 10
#define IMAGE_HEADER_VERSION_05 0x05
#define IMAGE_HEADER_VERSION_06 0x06
#define IMAGE_HEADER_VERSION_10 0x10
#define IMAGE_AREA_OFFSET 0x100
#define LOCKDOWN_SIZE 0x50
#define V5V6_BOOTLOADER_ID_OFFSET 0
#define V5V6_CONFIG_ID_SIZE 4
#define V5_PROPERTIES_OFFSET 2
#define V5_BLOCK_SIZE_OFFSET 3
#define V5_BLOCK_COUNT_OFFSET 5
#define V5_BLOCK_NUMBER_OFFSET 0
#define V5_BLOCK_DATA_OFFSET 2
#define V6_PROPERTIES_OFFSET 1
#define V6_BLOCK_SIZE_OFFSET 2
#define V6_BLOCK_COUNT_OFFSET 3
#define V6_PROPERTIES_2_OFFSET 4
#define V6_GUEST_CODE_BLOCK_COUNT_OFFSET 5
#define V6_BLOCK_NUMBER_OFFSET 0
#define V6_BLOCK_DATA_OFFSET 1
#define V6_FLASH_COMMAND_OFFSET 2
#define V6_FLASH_STATUS_OFFSET 3
#define V7_CONFIG_ID_SIZE 32
#define V7_FLASH_STATUS_OFFSET 0
#define V7_PARTITION_ID_OFFSET 1
#define V7_BLOCK_NUMBER_OFFSET 2
#define V7_TRANSFER_LENGTH_OFFSET 3
#define V7_COMMAND_OFFSET 4
#define V7_PAYLOAD_OFFSET 5
#define V7_PARTITION_SUPPORT_BYTES 4
#define F35_ERROR_CODE_OFFSET 0
#define F35_CHUNK_NUM_LSB_OFFSET 0
#define F35_CHUNK_NUM_MSB_OFFSET 1
#define F35_CHUNK_DATA_OFFSET 2
#define F35_CHUNK_COMMAND_OFFSET 18
#define F35_CHUNK_SIZE 16
#define F35_ERASE_ALL_WAIT_MS 3000
#define F35_RESET_WAIT_MS 250
#define SLEEP_MODE_NORMAL (0x00)
#define SLEEP_MODE_SENSOR_SLEEP (0x01)
#define SLEEP_MODE_RESERVED0 (0x02)
#define SLEEP_MODE_RESERVED1 (0x03)
#define ENABLE_WAIT_MS (1 * 1000)
#define WRITE_WAIT_MS (3 * 1000)
#define ERASE_WAIT_MS (5 * 1000)
#define MIN_SLEEP_TIME_US 50
#define MAX_SLEEP_TIME_US 100
#define INT_DISABLE_WAIT_MS 20
#define ENTER_FLASH_PROG_WAIT_MS 20
static int fwu_do_reflash(void);
static int fwu_recovery_check_status(void);
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static ssize_t fwu_sysfs_show_image(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t fwu_sysfs_store_image(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t fwu_sysfs_do_recovery_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_do_reflash_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_write_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_read_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_config_area_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_image_name_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_image_size_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
static ssize_t fwu_sysfs_block_size_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
#endif
enum f34_version {
F34_V0 = 0,
F34_V1,
F34_V2,
};
enum bl_version {
BL_V5 = 5,
BL_V6 = 6,
BL_V7 = 7,
BL_V8 = 8,
};
enum flash_area {
NONE = 0,
UI_FIRMWARE,
UI_CONFIG,
};
enum update_mode {
NORMAL = 1,
FORCE = 2,
LOCKDOWN = 8,
};
enum config_area {
UI_CONFIG_AREA = 0,
PM_CONFIG_AREA,
BL_CONFIG_AREA,
DP_CONFIG_AREA,
FLASH_CONFIG_AREA,
};
enum v7_status {
SUCCESS = 0x00,
DEVICE_NOT_IN_BOOTLOADER_MODE,
INVALID_PARTITION,
INVALID_COMMAND,
INVALID_BLOCK_OFFSET,
INVALID_TRANSFER,
NOT_ERASED,
FLASH_PROGRAMMING_KEY_INCORRECT,
BAD_PARTITION_TABLE,
CHECKSUM_FAILED,
FLASH_HARDWARE_FAILURE = 0x1f,
};
enum v7_partition_id {
BOOTLOADER_PARTITION = 0x01,
DEVICE_CONFIG_PARTITION,
FLASH_CONFIG_PARTITION,
MANUFACTURING_BLOCK_PARTITION,
GUEST_SERIALIZATION_PARTITION,
GLOBAL_PARAMETERS_PARTITION,
CORE_CODE_PARTITION,
CORE_CONFIG_PARTITION,
GUEST_CODE_PARTITION,
DISPLAY_CONFIG_PARTITION,
};
enum v7_flash_command {
CMD_V7_IDLE = 0x00,
CMD_V7_ENTER_BL,
CMD_V7_READ,
CMD_V7_WRITE,
CMD_V7_ERASE,
CMD_V7_ERASE_AP,
CMD_V7_SENSOR_ID,
};
enum v5v6_flash_command {
CMD_V5V6_IDLE = 0x0,
CMD_V5V6_WRITE_FW = 0x2,
CMD_V5V6_ERASE_ALL = 0x3,
CMD_V5V6_WRITE_LOCKDOWN = 0x4,
CMD_V5V6_READ_CONFIG = 0x5,
CMD_V5V6_WRITE_CONFIG = 0x6,
CMD_V5V6_ERASE_UI_CONFIG = 0x7,
CMD_V5V6_ERASE_BL_CONFIG = 0x9,
CMD_V5V6_ERASE_DISP_CONFIG = 0xa,
CMD_V5V6_ERASE_GUEST_CODE = 0xb,
CMD_V5V6_WRITE_GUEST_CODE = 0xc,
CMD_V5V6_ENABLE_FLASH_PROG = 0xf,
};
enum flash_command {
CMD_IDLE = 0,
CMD_WRITE_FW,
CMD_WRITE_CONFIG,
CMD_WRITE_LOCKDOWN,
CMD_WRITE_GUEST_CODE,
CMD_READ_CONFIG,
CMD_ERASE_ALL,
CMD_ERASE_UI_FIRMWARE,
CMD_ERASE_UI_CONFIG,
CMD_ERASE_BL_CONFIG,
CMD_ERASE_DISP_CONFIG,
CMD_ERASE_FLASH_CONFIG,
CMD_ERASE_GUEST_CODE,
CMD_ENABLE_FLASH_PROG,
};
enum f35_flash_command {
CMD_F35_IDLE = 0x0,
CMD_F35_RESERVED = 0x1,
CMD_F35_WRITE_CHUNK = 0x2,
CMD_F35_ERASE_ALL = 0x3,
CMD_F35_RESET = 0x10,
};
enum container_id {
TOP_LEVEL_CONTAINER = 0,
UI_CONTAINER,
UI_CONFIG_CONTAINER,
BL_CONTAINER,
BL_IMAGE_CONTAINER,
BL_CONFIG_CONTAINER,
BL_LOCKDOWN_INFO_CONTAINER,
PERMANENT_CONFIG_CONTAINER,
GUEST_CODE_CONTAINER,
BL_PROTOCOL_DESCRIPTOR_CONTAINER,
UI_PROTOCOL_DESCRIPTOR_CONTAINER,
RMI_SELF_DISCOVERY_CONTAINER,
RMI_PAGE_CONTENT_CONTAINER,
GENERAL_INFORMATION_CONTAINER,
DEVICE_CONFIG_CONTAINER,
FLASH_CONFIG_CONTAINER,
GUEST_SERIALIZATION_CONTAINER,
GLOBAL_PARAMETERS_CONTAINER,
CORE_CODE_CONTAINER,
CORE_CONFIG_CONTAINER,
DISPLAY_CONFIG_CONTAINER,
};
struct pdt_properties {
union {
struct {
unsigned char reserved_1:6;
unsigned char has_bsr:1;
unsigned char reserved_2:1;
} __packed;
unsigned char data[1];
};
};
struct partition_table {
unsigned char partition_id:5;
unsigned char byte_0_reserved:3;
unsigned char byte_1_reserved;
unsigned char partition_length_7_0;
unsigned char partition_length_15_8;
unsigned char start_physical_address_7_0;
unsigned char start_physical_address_15_8;
unsigned char partition_properties_7_0;
unsigned char partition_properties_15_8;
} __packed;
struct f01_device_control {
union {
struct {
unsigned char sleep_mode:2;
unsigned char nosleep:1;
unsigned char reserved:2;
unsigned char charger_connected:1;
unsigned char report_rate:1;
unsigned char configured:1;
} __packed;
unsigned char data[1];
};
};
struct f34_v7_query_0 {
union {
struct {
unsigned char subpacket_1_size:3;
unsigned char has_config_id:1;
unsigned char f34_query0_b4:1;
unsigned char has_thqa:1;
unsigned char f34_query0_b6__7:2;
} __packed;
unsigned char data[1];
};
};
struct f34_v7_query_1_7 {
union {
struct {
/* query 1 */
unsigned char bl_minor_revision;
unsigned char bl_major_revision;
/* query 2 */
unsigned char bl_fw_id_7_0;
unsigned char bl_fw_id_15_8;
unsigned char bl_fw_id_23_16;
unsigned char bl_fw_id_31_24;
/* query 3 */
unsigned char minimum_write_size;
unsigned char block_size_7_0;
unsigned char block_size_15_8;
unsigned char flash_page_size_7_0;
unsigned char flash_page_size_15_8;
/* query 4 */
unsigned char adjustable_partition_area_size_7_0;
unsigned char adjustable_partition_area_size_15_8;
/* query 5 */
unsigned char flash_config_length_7_0;
unsigned char flash_config_length_15_8;
/* query 6 */
unsigned char payload_length_7_0;
unsigned char payload_length_15_8;
/* query 7 */
unsigned char f34_query7_b0:1;
unsigned char has_bootloader:1;
unsigned char has_device_config:1;
unsigned char has_flash_config:1;
unsigned char has_manufacturing_block:1;
unsigned char has_guest_serialization:1;
unsigned char has_global_parameters:1;
unsigned char has_core_code:1;
unsigned char has_core_config:1;
unsigned char has_guest_code:1;
unsigned char has_display_config:1;
unsigned char f34_query7_b11__15:5;
unsigned char f34_query7_b16__23;
unsigned char f34_query7_b24__31;
} __packed;
unsigned char data[21];
};
};
struct f34_v7_data0 {
union {
struct {
unsigned char operation_status:5;
unsigned char device_cfg_status:2;
unsigned char bl_mode:1;
} __packed;
unsigned char data[1];
};
};
struct f34_v7_data_1_5 {
union {
struct {
unsigned char partition_id:5;
unsigned char f34_data1_b5__7:3;
unsigned char block_offset_7_0;
unsigned char block_offset_15_8;
unsigned char transfer_length_7_0;
unsigned char transfer_length_15_8;
unsigned char command;
unsigned char payload_0;
unsigned char payload_1;
} __packed;
unsigned char data[8];
};
};
struct f34_v5v6_flash_properties {
union {
struct {
unsigned char reg_map:1;
unsigned char unlocked:1;
unsigned char has_config_id:1;
unsigned char has_pm_config:1;
unsigned char has_bl_config:1;
unsigned char has_disp_config:1;
unsigned char has_ctrl1:1;
unsigned char has_query4:1;
} __packed;
unsigned char data[1];
};
};
struct f34_v5v6_flash_properties_2 {
union {
struct {
unsigned char has_guest_code:1;
unsigned char reserved:7;
} __packed;
unsigned char data[1];
};
};
struct register_offset {
unsigned char properties;
unsigned char properties_2;
unsigned char block_size;
unsigned char block_count;
unsigned char gc_block_count;
unsigned char flash_status;
unsigned char partition_id;
unsigned char block_number;
unsigned char transfer_length;
unsigned char flash_cmd;
unsigned char payload;
};
struct block_count {
unsigned short ui_firmware;
unsigned short ui_config;
unsigned short dp_config;
unsigned short pm_config;
unsigned short fl_config;
unsigned short bl_image;
unsigned short bl_config;
unsigned short lockdown;
unsigned short guest_code;
unsigned short total_count;
};
struct physical_address {
unsigned short ui_firmware;
unsigned short ui_config;
unsigned short dp_config;
unsigned short fl_config;
unsigned short guest_code;
};
struct container_descriptor {
unsigned char content_checksum[4];
unsigned char container_id[2];
unsigned char minor_version;
unsigned char major_version;
unsigned char reserved_08;
unsigned char reserved_09;
unsigned char reserved_0a;
unsigned char reserved_0b;
unsigned char container_option_flags[4];
unsigned char content_options_length[4];
unsigned char content_options_address[4];
unsigned char content_length[4];
unsigned char content_address[4];
};
struct image_header_10 {
unsigned char checksum[4];
unsigned char reserved_04;
unsigned char reserved_05;
unsigned char minor_header_version;
unsigned char major_header_version;
unsigned char reserved_08;
unsigned char reserved_09;
unsigned char reserved_0a;
unsigned char reserved_0b;
unsigned char top_level_container_start_addr[4];
};
struct image_header_05_06 {
/* 0x00 - 0x0f */
unsigned char checksum[4];
unsigned char reserved_04;
unsigned char reserved_05;
unsigned char options_firmware_id:1;
unsigned char options_bootloader:1;
unsigned char options_guest_code:1;
unsigned char options_tddi:1;
unsigned char options_reserved:4;
unsigned char header_version;
unsigned char firmware_size[4];
unsigned char config_size[4];
/* 0x10 - 0x1f */
unsigned char product_id[PRODUCT_ID_SIZE];
unsigned char package_id[2];
unsigned char package_id_revision[2];
unsigned char product_info[PRODUCT_INFO_SIZE];
/* 0x20 - 0x2f */
unsigned char bootloader_addr[4];
unsigned char bootloader_size[4];
unsigned char ui_addr[4];
unsigned char ui_size[4];
/* 0x30 - 0x3f */
unsigned char ds_id[16];
/* 0x40 - 0x4f */
union {
struct {
unsigned char cstmr_product_id[PRODUCT_ID_SIZE];
unsigned char reserved_4a_4f[6];
};
struct {
unsigned char dsp_cfg_addr[4];
unsigned char dsp_cfg_size[4];
unsigned char reserved_48_4f[8];
};
};
/* 0x50 - 0x53 */
unsigned char firmware_id[4];
};
struct block_data {
unsigned int size;
const unsigned char *data;
};
struct image_metadata {
bool contains_firmware_id;
bool contains_bootloader;
bool contains_guest_code;
bool contains_disp_config;
bool contains_perm_config;
bool contains_flash_config;
unsigned int firmware_id;
unsigned int checksum;
unsigned int bootloader_size;
unsigned int disp_config_offset;
unsigned char bl_version;
unsigned char product_id[PRODUCT_ID_SIZE + 1];
unsigned char cstmr_product_id[PRODUCT_ID_SIZE + 1];
struct block_data bootloader;
struct block_data ui_firmware;
struct block_data ui_config;
struct block_data dp_config;
struct block_data pm_config;
struct block_data fl_config;
struct block_data bl_image;
struct block_data bl_config;
struct block_data lockdown;
struct block_data guest_code;
struct block_count blkcount;
struct physical_address phyaddr;
};
struct synaptics_rmi4_fwu_handle {
enum bl_version bl_version;
bool initialized;
bool in_bl_mode;
bool in_ub_mode;
bool force_update;
bool do_lockdown;
bool has_guest_code;
bool new_partition_table;
unsigned int data_pos;
unsigned char *ext_data_source;
unsigned char *read_config_buf;
unsigned char intr_mask;
unsigned char command;
unsigned char bootloader_id[2];
unsigned char config_id[32];
unsigned char flash_status;
unsigned char partitions;
unsigned short block_size;
unsigned short config_size;
unsigned short config_area;
unsigned short config_block_count;
unsigned short flash_config_length;
unsigned short payload_length;
unsigned short partition_table_bytes;
unsigned short read_config_buf_size;
const unsigned char *config_data;
const unsigned char *image;
unsigned char *image_name;
unsigned int image_size;
struct image_metadata img;
struct register_offset off;
struct block_count blkcount;
struct physical_address phyaddr;
struct f34_v5v6_flash_properties flash_properties;
struct synaptics_rmi4_fn_desc f34_fd;
struct synaptics_rmi4_fn_desc f35_fd;
struct synaptics_rmi4_data *rmi4_data;
struct workqueue_struct *fwu_workqueue;
struct work_struct fwu_work;
};
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static struct bin_attribute dev_attr_data = {
.attr = {
.name = "data",
.mode = (S_IRUGO | S_IWUGO),
},
.size = 0,
.read = fwu_sysfs_show_image,
.write = fwu_sysfs_store_image,
};
#endif
static struct device_attribute attrs[] = {
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
__ATTR(dorecovery, 0220,
NULL,
fwu_sysfs_do_recovery_store),
__ATTR(doreflash, 0220,
NULL,
fwu_sysfs_do_reflash_store),
__ATTR(writeconfig, 0220,
NULL,
fwu_sysfs_write_config_store),
__ATTR(readconfig, 0220,
NULL,
fwu_sysfs_read_config_store),
__ATTR(configarea, 0220,
NULL,
fwu_sysfs_config_area_store),
__ATTR(imagename, 0220,
NULL,
fwu_sysfs_image_name_store),
__ATTR(imagesize, 0220,
NULL,
fwu_sysfs_image_size_store),
__ATTR(blocksize, 0444,
fwu_sysfs_block_size_show,
NULL),
__ATTR(fwblockcount, 0444,
fwu_sysfs_firmware_block_count_show,
NULL),
__ATTR(configblockcount, 0444,
fwu_sysfs_configuration_block_count_show,
NULL),
__ATTR(dispconfigblockcount, 0444,
fwu_sysfs_disp_config_block_count_show,
NULL),
__ATTR(permconfigblockcount, 0444,
fwu_sysfs_perm_config_block_count_show,
NULL),
__ATTR(blconfigblockcount, 0444,
fwu_sysfs_bl_config_block_count_show,
NULL),
__ATTR(guestcodeblockcount, 0444,
fwu_sysfs_guest_code_block_count_show,
NULL),
__ATTR(writeguestcode, 0220,
NULL,
fwu_sysfs_write_guest_code_store),
#endif
};
static struct synaptics_rmi4_fwu_handle *fwu;
DECLARE_COMPLETION(fwu_remove_complete);
static unsigned int le_to_uint(const unsigned char *ptr)
{
return (unsigned int)ptr[0] +
(unsigned int)ptr[1] * 0x100 +
(unsigned int)ptr[2] * 0x10000 +
(unsigned int)ptr[3] * 0x1000000;
}
static int fwu_allocate_read_config_buf(unsigned int count)
{
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (count > fwu->read_config_buf_size) {
kfree(fwu->read_config_buf);
fwu->read_config_buf = kzalloc(count, GFP_KERNEL);
if (!fwu->read_config_buf) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for fwu->read_config_buf\n",
__func__);
fwu->read_config_buf_size = 0;
return -ENOMEM;
}
fwu->read_config_buf_size = count;
}
return 0;
}
static void fwu_compare_partition_tables(void)
{
if (fwu->phyaddr.ui_firmware != fwu->img.phyaddr.ui_firmware) {
fwu->new_partition_table = true;
return;
}
if (fwu->phyaddr.ui_config != fwu->img.phyaddr.ui_config) {
fwu->new_partition_table = true;
return;
}
if (fwu->flash_properties.has_disp_config) {
if (fwu->phyaddr.dp_config != fwu->img.phyaddr.dp_config) {
fwu->new_partition_table = true;
return;
}
}
if (fwu->has_guest_code) {
if (fwu->phyaddr.guest_code != fwu->img.phyaddr.guest_code) {
fwu->new_partition_table = true;
return;
}
}
fwu->new_partition_table = false;
return;
}
static void fwu_parse_partition_table(const unsigned char *partition_table,
struct block_count *blkcount, struct physical_address *phyaddr)
{
unsigned char ii;
unsigned char index;
unsigned char offset;
unsigned short partition_length;
unsigned short physical_address;
struct partition_table *ptable;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
for (ii = 0; ii < fwu->partitions; ii++) {
index = ii * 8 + 2;
ptable = (struct partition_table *)&partition_table[index];
partition_length = ptable->partition_length_15_8 << 8 |
ptable->partition_length_7_0;
physical_address = ptable->start_physical_address_15_8 << 8 |
ptable->start_physical_address_7_0;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Partition entry %d:\n",
__func__, ii);
for (offset = 0; offset < 8; offset++) {
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: 0x%02x\n",
__func__,
partition_table[index + offset]);
}
switch (ptable->partition_id) {
case CORE_CODE_PARTITION:
blkcount->ui_firmware = partition_length;
phyaddr->ui_firmware = physical_address;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Core code block count: %d\n",
__func__, blkcount->ui_firmware);
blkcount->total_count += partition_length;
break;
case CORE_CONFIG_PARTITION:
blkcount->ui_config = partition_length;
phyaddr->ui_config = physical_address;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Core config block count: %d\n",
__func__, blkcount->ui_config);
blkcount->total_count += partition_length;
break;
case BOOTLOADER_PARTITION:
blkcount->bl_image = partition_length;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Core config block count: %d\n",
__func__, blkcount->ui_config);
blkcount->total_count += partition_length;
break;
case DISPLAY_CONFIG_PARTITION:
blkcount->dp_config = partition_length;
phyaddr->dp_config = physical_address;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Display config block count: %d\n",
__func__, blkcount->dp_config);
blkcount->total_count += partition_length;
break;
case FLASH_CONFIG_PARTITION:
blkcount->fl_config = partition_length;
phyaddr->fl_config = physical_address;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Flash config block count: %d\n",
__func__, blkcount->fl_config);
blkcount->total_count += partition_length;
break;
case GUEST_CODE_PARTITION:
blkcount->guest_code = partition_length;
phyaddr->guest_code = physical_address;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Guest code block count: %d\n",
__func__, blkcount->guest_code);
blkcount->total_count += partition_length;
break;
case GUEST_SERIALIZATION_PARTITION:
blkcount->pm_config = partition_length;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Guest serialization block count: %d\n",
__func__, blkcount->pm_config);
blkcount->total_count += partition_length;
break;
case GLOBAL_PARAMETERS_PARTITION:
blkcount->bl_config = partition_length;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Global parameters block count: %d\n",
__func__, blkcount->bl_config);
blkcount->total_count += partition_length;
break;
case DEVICE_CONFIG_PARTITION:
blkcount->lockdown = partition_length;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Device config block count: %d\n",
__func__, blkcount->lockdown);
blkcount->total_count += partition_length;
break;
};
}
return;
}
static void fwu_parse_image_header_10_bl_container(const unsigned char *image)
{
unsigned char ii;
unsigned char num_of_containers;
unsigned int addr;
unsigned int container_id;
unsigned int length;
const unsigned char *content;
struct container_descriptor *descriptor;
num_of_containers = (fwu->img.bootloader.size - 4) / 4;
for (ii = 1; ii <= num_of_containers; ii++) {
addr = le_to_uint(fwu->img.bootloader.data + (ii * 4));
descriptor = (struct container_descriptor *)(image + addr);
container_id = descriptor->container_id[0] |
descriptor->container_id[1] << 8;
content = image + le_to_uint(descriptor->content_address);
length = le_to_uint(descriptor->content_length);
switch (container_id) {
case BL_IMAGE_CONTAINER:
fwu->img.bl_image.data = content;
fwu->img.bl_image.size = length;
break;
case BL_CONFIG_CONTAINER:
case GLOBAL_PARAMETERS_CONTAINER:
fwu->img.bl_config.data = content;
fwu->img.bl_config.size = length;
break;
case BL_LOCKDOWN_INFO_CONTAINER:
case DEVICE_CONFIG_CONTAINER:
fwu->img.lockdown.data = content;
fwu->img.lockdown.size = length;
break;
default:
break;
};
}
return;
}
static void fwu_parse_image_header_10(void)
{
unsigned char ii;
unsigned char num_of_containers;
unsigned int addr;
unsigned int offset;
unsigned int container_id;
unsigned int length;
const unsigned char *image;
const unsigned char *content;
struct container_descriptor *descriptor;
struct image_header_10 *header;
image = fwu->image;
header = (struct image_header_10 *)image;
fwu->img.checksum = le_to_uint(header->checksum);
/* address of top level container */
offset = le_to_uint(header->top_level_container_start_addr);
descriptor = (struct container_descriptor *)(image + offset);
/* address of top level container content */
offset = le_to_uint(descriptor->content_address);
num_of_containers = le_to_uint(descriptor->content_length) / 4;
for (ii = 0; ii < num_of_containers; ii++) {
addr = le_to_uint(image + offset);
offset += 4;
descriptor = (struct container_descriptor *)(image + addr);
container_id = descriptor->container_id[0] |
descriptor->container_id[1] << 8;
content = image + le_to_uint(descriptor->content_address);
length = le_to_uint(descriptor->content_length);
switch (container_id) {
case UI_CONTAINER:
case CORE_CODE_CONTAINER:
fwu->img.ui_firmware.data = content;
fwu->img.ui_firmware.size = length;
break;
case UI_CONFIG_CONTAINER:
case CORE_CONFIG_CONTAINER:
fwu->img.ui_config.data = content;
fwu->img.ui_config.size = length;
break;
case BL_CONTAINER:
fwu->img.bl_version = *content;
fwu->img.bootloader.data = content;
fwu->img.bootloader.size = length;
fwu_parse_image_header_10_bl_container(image);
break;
case GUEST_CODE_CONTAINER:
fwu->img.contains_guest_code = true;
fwu->img.guest_code.data = content;
fwu->img.guest_code.size = length;
break;
case DISPLAY_CONFIG_CONTAINER:
fwu->img.contains_disp_config = true;
fwu->img.dp_config.data = content;
fwu->img.dp_config.size = length;
break;
case PERMANENT_CONFIG_CONTAINER:
case GUEST_SERIALIZATION_CONTAINER:
fwu->img.contains_perm_config = true;
fwu->img.pm_config.data = content;
fwu->img.pm_config.size = length;
break;
case FLASH_CONFIG_CONTAINER:
fwu->img.contains_flash_config = true;
fwu->img.fl_config.data = content;
fwu->img.fl_config.size = length;
break;
case GENERAL_INFORMATION_CONTAINER:
fwu->img.contains_firmware_id = true;
fwu->img.firmware_id = le_to_uint(content + 4);
break;
default:
break;
}
}
return;
}
static void fwu_parse_image_header_05_06(void)
{
int retval;
const unsigned char *image;
struct image_header_05_06 *header;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
image = fwu->image;
header = (struct image_header_05_06 *)image;
fwu->img.checksum = le_to_uint(header->checksum);
fwu->img.bl_version = header->header_version;
fwu->img.contains_bootloader = header->options_bootloader;
if (fwu->img.contains_bootloader)
fwu->img.bootloader_size = le_to_uint(header->bootloader_size);
fwu->img.ui_firmware.size = le_to_uint(header->firmware_size);
if (fwu->img.ui_firmware.size) {
fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET;
if (fwu->img.contains_bootloader)
fwu->img.ui_firmware.data += fwu->img.bootloader_size;
}
if ((fwu->img.bl_version == BL_V6) && header->options_tddi)
fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET;
fwu->img.ui_config.size = le_to_uint(header->config_size);
if (fwu->img.ui_config.size) {
fwu->img.ui_config.data = fwu->img.ui_firmware.data +
fwu->img.ui_firmware.size;
}
if ((fwu->img.bl_version == BL_V5 && fwu->img.contains_bootloader) ||
(fwu->img.bl_version == BL_V6 && header->options_tddi))
fwu->img.contains_disp_config = true;
else
fwu->img.contains_disp_config = false;
if (fwu->img.contains_disp_config) {
fwu->img.disp_config_offset = le_to_uint(header->dsp_cfg_addr);
fwu->img.dp_config.size = le_to_uint(header->dsp_cfg_size);
fwu->img.dp_config.data = image + fwu->img.disp_config_offset;
} else {
retval = secure_memcpy(fwu->img.cstmr_product_id,
sizeof(fwu->img.cstmr_product_id),
header->cstmr_product_id,
sizeof(header->cstmr_product_id),
PRODUCT_ID_SIZE);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy custom product ID string\n",
__func__);
}
fwu->img.cstmr_product_id[PRODUCT_ID_SIZE] = 0;
}
fwu->img.contains_firmware_id = header->options_firmware_id;
if (fwu->img.contains_firmware_id)
fwu->img.firmware_id = le_to_uint(header->firmware_id);
retval = secure_memcpy(fwu->img.product_id,
sizeof(fwu->img.product_id),
header->product_id,
sizeof(header->product_id),
PRODUCT_ID_SIZE);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy product ID string\n",
__func__);
}
fwu->img.product_id[PRODUCT_ID_SIZE] = 0;
fwu->img.lockdown.size = LOCKDOWN_SIZE;
fwu->img.lockdown.data = image + IMAGE_AREA_OFFSET - LOCKDOWN_SIZE;
return;
}
static int fwu_parse_image_info(void)
{
struct image_header_10 *header;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
header = (struct image_header_10 *)fwu->image;
memset(&fwu->img, 0x00, sizeof(fwu->img));
switch (header->major_header_version) {
case IMAGE_HEADER_VERSION_10:
fwu_parse_image_header_10();
break;
case IMAGE_HEADER_VERSION_05:
case IMAGE_HEADER_VERSION_06:
fwu_parse_image_header_05_06();
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Unsupported image file format (0x%02x)\n",
__func__, header->major_header_version);
return -EINVAL;
}
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) {
if (!fwu->img.contains_flash_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: No flash config found in firmware image\n",
__func__);
return -EINVAL;
}
fwu_parse_partition_table(fwu->img.fl_config.data,
&fwu->img.blkcount, &fwu->img.phyaddr);
fwu_compare_partition_tables();
} else {
fwu->new_partition_table = false;
}
return 0;
}
static int fwu_read_flash_status(void)
{
int retval;
unsigned char status;
unsigned char command;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = synaptics_rmi4_reg_read(rmi4_data,
fwu->f34_fd.data_base_addr + fwu->off.flash_status,
&status,
sizeof(status));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash status\n",
__func__);
return retval;
}
fwu->in_bl_mode = status >> 7;
if (fwu->bl_version == BL_V5)
fwu->flash_status = (status >> 4) & MASK_3BIT;
else if (fwu->bl_version == BL_V6)
fwu->flash_status = status & MASK_3BIT;
else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
fwu->flash_status = status & MASK_5BIT;
if (fwu->flash_status != 0x00) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Flash status = %d, command = 0x%02x\n",
__func__, fwu->flash_status, fwu->command);
}
retval = synaptics_rmi4_reg_read(rmi4_data,
fwu->f34_fd.data_base_addr + fwu->off.flash_cmd,
&command,
sizeof(command));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash command\n",
__func__);
return retval;
}
if (fwu->bl_version == BL_V5)
fwu->command = command & MASK_4BIT;
else if (fwu->bl_version == BL_V6)
fwu->command = command & MASK_6BIT;
else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
fwu->command = command;
return 0;
}
static int fwu_wait_for_idle(int timeout_ms, bool poll)
{
int count = 0;
int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
do {
usleep_range(MIN_SLEEP_TIME_US, MAX_SLEEP_TIME_US);
count++;
if (poll || (count == timeout_count))
fwu_read_flash_status();
if ((fwu->command == CMD_IDLE) && (fwu->flash_status == 0x00))
return 0;
} while (count < timeout_count);
dev_err(rmi4_data->pdev->dev.parent,
"%s: Timed out waiting for idle status\n",
__func__);
return -ETIMEDOUT;
}
static int fwu_write_f34_v7_command_single_transaction(unsigned char cmd)
{
int retval;
unsigned char base;
struct f34_v7_data_1_5 data_1_5;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
memset(data_1_5.data, 0x00, sizeof(data_1_5.data));
switch (cmd) {
case CMD_ERASE_ALL:
data_1_5.partition_id = CORE_CODE_PARTITION;
data_1_5.command = CMD_V7_ERASE_AP;
break;
case CMD_ERASE_UI_FIRMWARE:
data_1_5.partition_id = CORE_CODE_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ERASE_BL_CONFIG:
data_1_5.partition_id = GLOBAL_PARAMETERS_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ERASE_UI_CONFIG:
data_1_5.partition_id = CORE_CONFIG_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ERASE_DISP_CONFIG:
data_1_5.partition_id = DISPLAY_CONFIG_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ERASE_FLASH_CONFIG:
data_1_5.partition_id = FLASH_CONFIG_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ERASE_GUEST_CODE:
data_1_5.partition_id = GUEST_CODE_PARTITION;
data_1_5.command = CMD_V7_ERASE;
break;
case CMD_ENABLE_FLASH_PROG:
data_1_5.partition_id = BOOTLOADER_PARTITION;
data_1_5.command = CMD_V7_ENTER_BL;
break;
};
data_1_5.payload_0 = fwu->bootloader_id[0];
data_1_5.payload_1 = fwu->bootloader_id[1];
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.partition_id,
data_1_5.data,
sizeof(data_1_5.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write single transaction command\n",
__func__);
return retval;
}
return 0;
}
static int fwu_write_f34_v7_command(unsigned char cmd)
{
int retval;
unsigned char base;
unsigned char command;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
switch (cmd) {
case CMD_WRITE_FW:
case CMD_WRITE_CONFIG:
case CMD_WRITE_LOCKDOWN:
case CMD_WRITE_GUEST_CODE:
command = CMD_V7_WRITE;
break;
case CMD_READ_CONFIG:
command = CMD_V7_READ;
break;
case CMD_ERASE_ALL:
command = CMD_V7_ERASE_AP;
break;
case CMD_ERASE_UI_FIRMWARE:
case CMD_ERASE_BL_CONFIG:
case CMD_ERASE_UI_CONFIG:
case CMD_ERASE_DISP_CONFIG:
case CMD_ERASE_FLASH_CONFIG:
case CMD_ERASE_GUEST_CODE:
command = CMD_V7_ERASE;
break;
case CMD_ENABLE_FLASH_PROG:
command = CMD_V7_ENTER_BL;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Invalid command 0x%02x\n",
__func__, cmd);
return -EINVAL;
};
fwu->command = command;
switch (cmd) {
case CMD_ERASE_ALL:
case CMD_ERASE_UI_FIRMWARE:
case CMD_ERASE_BL_CONFIG:
case CMD_ERASE_UI_CONFIG:
case CMD_ERASE_DISP_CONFIG:
case CMD_ERASE_FLASH_CONFIG:
case CMD_ERASE_GUEST_CODE:
case CMD_ENABLE_FLASH_PROG:
retval = fwu_write_f34_v7_command_single_transaction(cmd);
if (retval < 0)
return retval;
else
return 0;
default:
break;
};
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.flash_cmd,
&command,
sizeof(command));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write flash command\n",
__func__);
return retval;
}
return 0;
}
static int fwu_write_f34_v5v6_command(unsigned char cmd)
{
int retval;
unsigned char base;
unsigned char command;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
switch (cmd) {
case CMD_IDLE:
command = CMD_V5V6_IDLE;
break;
case CMD_WRITE_FW:
command = CMD_V5V6_WRITE_FW;
break;
case CMD_WRITE_CONFIG:
command = CMD_V5V6_WRITE_CONFIG;
break;
case CMD_WRITE_LOCKDOWN:
command = CMD_V5V6_WRITE_LOCKDOWN;
break;
case CMD_WRITE_GUEST_CODE:
command = CMD_V5V6_WRITE_GUEST_CODE;
break;
case CMD_READ_CONFIG:
command = CMD_V5V6_READ_CONFIG;
break;
case CMD_ERASE_ALL:
command = CMD_V5V6_ERASE_ALL;
break;
case CMD_ERASE_UI_CONFIG:
command = CMD_V5V6_ERASE_UI_CONFIG;
break;
case CMD_ERASE_DISP_CONFIG:
command = CMD_V5V6_ERASE_DISP_CONFIG;
break;
case CMD_ERASE_GUEST_CODE:
command = CMD_V5V6_ERASE_GUEST_CODE;
break;
case CMD_ENABLE_FLASH_PROG:
command = CMD_V5V6_ENABLE_FLASH_PROG;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Invalid command 0x%02x\n",
__func__, cmd);
return -EINVAL;
}
switch (cmd) {
case CMD_ERASE_ALL:
case CMD_ERASE_UI_CONFIG:
case CMD_ERASE_DISP_CONFIG:
case CMD_ERASE_GUEST_CODE:
case CMD_ENABLE_FLASH_PROG:
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.payload,
fwu->bootloader_id,
sizeof(fwu->bootloader_id));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write bootloader ID\n",
__func__);
return retval;
}
break;
default:
break;
};
fwu->command = command;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.flash_cmd,
&command,
sizeof(command));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write command 0x%02x\n",
__func__, command);
return retval;
}
return 0;
}
static int fwu_write_f34_command(unsigned char cmd)
{
int retval;
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
retval = fwu_write_f34_v7_command(cmd);
else
retval = fwu_write_f34_v5v6_command(cmd);
return retval;
}
static int fwu_write_f34_v7_partition_id(unsigned char cmd)
{
int retval;
unsigned char base;
unsigned char partition;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
switch (cmd) {
case CMD_WRITE_FW:
partition = CORE_CODE_PARTITION;
break;
case CMD_WRITE_CONFIG:
case CMD_READ_CONFIG:
if (fwu->config_area == UI_CONFIG_AREA)
partition = CORE_CONFIG_PARTITION;
else if (fwu->config_area == DP_CONFIG_AREA)
partition = DISPLAY_CONFIG_PARTITION;
else if (fwu->config_area == PM_CONFIG_AREA)
partition = GUEST_SERIALIZATION_PARTITION;
else if (fwu->config_area == BL_CONFIG_AREA)
partition = GLOBAL_PARAMETERS_PARTITION;
else if (fwu->config_area == FLASH_CONFIG_AREA)
partition = FLASH_CONFIG_PARTITION;
break;
case CMD_WRITE_LOCKDOWN:
partition = DEVICE_CONFIG_PARTITION;
break;
case CMD_WRITE_GUEST_CODE:
partition = GUEST_CODE_PARTITION;
break;
case CMD_ERASE_ALL:
partition = CORE_CODE_PARTITION;
break;
case CMD_ERASE_BL_CONFIG:
partition = GLOBAL_PARAMETERS_PARTITION;
break;
case CMD_ERASE_UI_CONFIG:
partition = CORE_CONFIG_PARTITION;
break;
case CMD_ERASE_DISP_CONFIG:
partition = DISPLAY_CONFIG_PARTITION;
break;
case CMD_ERASE_FLASH_CONFIG:
partition = FLASH_CONFIG_PARTITION;
break;
case CMD_ERASE_GUEST_CODE:
partition = GUEST_CODE_PARTITION;
break;
case CMD_ENABLE_FLASH_PROG:
partition = BOOTLOADER_PARTITION;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Invalid command 0x%02x\n",
__func__, cmd);
return -EINVAL;
};
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.partition_id,
&partition,
sizeof(partition));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write partition ID\n",
__func__);
return retval;
}
return 0;
}
static int fwu_write_f34_partition_id(unsigned char cmd)
{
int retval;
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
retval = fwu_write_f34_v7_partition_id(cmd);
else
retval = 0;
return retval;
}
static int fwu_read_f34_v7_partition_table(unsigned char *partition_table)
{
int retval;
unsigned char base;
unsigned char length[2];
unsigned short block_number = 0;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
fwu->config_area = FLASH_CONFIG_AREA;
retval = fwu_write_f34_partition_id(CMD_READ_CONFIG);
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.block_number,
(unsigned char *)&block_number,
sizeof(block_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block number\n",
__func__);
return retval;
}
length[0] = (unsigned char)(fwu->flash_config_length & MASK_8BIT);
length[1] = (unsigned char)(fwu->flash_config_length >> 8);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.transfer_length,
length,
sizeof(length));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write transfer length\n",
__func__);
return retval;
}
retval = fwu_write_f34_command(CMD_READ_CONFIG);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write command\n",
__func__);
return retval;
}
retval = fwu_wait_for_idle(WRITE_WAIT_MS, true);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to wait for idle status\n",
__func__);
return retval;
}
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.payload,
partition_table,
fwu->partition_table_bytes);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read block data\n",
__func__);
return retval;
}
return 0;
}
static int fwu_read_f34_v7_queries(void)
{
int retval;
unsigned char ii;
unsigned char base;
unsigned char index;
unsigned char offset;
unsigned char *ptable;
struct f34_v7_query_0 query_0;
struct f34_v7_query_1_7 query_1_7;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.query_base_addr;
retval = synaptics_rmi4_reg_read(rmi4_data,
base,
query_0.data,
sizeof(query_0.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read query 0\n",
__func__);
return retval;
}
offset = query_0.subpacket_1_size + 1;
retval = synaptics_rmi4_reg_read(rmi4_data,
base + offset,
query_1_7.data,
sizeof(query_1_7.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read queries 1 to 7\n",
__func__);
return retval;
}
fwu->bootloader_id[0] = query_1_7.bl_minor_revision;
fwu->bootloader_id[1] = query_1_7.bl_major_revision;
if (fwu->bootloader_id[1] == BL_V8)
fwu->bl_version = BL_V8;
fwu->block_size = query_1_7.block_size_15_8 << 8 |
query_1_7.block_size_7_0;
fwu->flash_config_length = query_1_7.flash_config_length_15_8 << 8 |
query_1_7.flash_config_length_7_0;
fwu->payload_length = query_1_7.payload_length_15_8 << 8 |
query_1_7.payload_length_7_0;
fwu->off.flash_status = V7_FLASH_STATUS_OFFSET;
fwu->off.partition_id = V7_PARTITION_ID_OFFSET;
fwu->off.block_number = V7_BLOCK_NUMBER_OFFSET;
fwu->off.transfer_length = V7_TRANSFER_LENGTH_OFFSET;
fwu->off.flash_cmd = V7_COMMAND_OFFSET;
fwu->off.payload = V7_PAYLOAD_OFFSET;
index = sizeof(query_1_7.data) - V7_PARTITION_SUPPORT_BYTES;
fwu->partitions = 0;
for (offset = 0; offset < V7_PARTITION_SUPPORT_BYTES; offset++) {
for (ii = 0; ii < 8; ii++) {
if (query_1_7.data[index + offset] & (1 << ii))
fwu->partitions++;
}
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Supported partitions: 0x%02x\n",
__func__, query_1_7.data[index + offset]);
}
fwu->partition_table_bytes = fwu->partitions * 8 + 2;
ptable = kzalloc(fwu->partition_table_bytes, GFP_KERNEL);
if (!ptable) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for partition table\n",
__func__);
return -ENOMEM;
}
retval = fwu_read_f34_v7_partition_table(ptable);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read partition table\n",
__func__);
kfree(ptable);
return retval;
}
fwu_parse_partition_table(ptable, &fwu->blkcount, &fwu->phyaddr);
if (fwu->blkcount.dp_config)
fwu->flash_properties.has_disp_config = 1;
else
fwu->flash_properties.has_disp_config = 0;
if (fwu->blkcount.pm_config)
fwu->flash_properties.has_pm_config = 1;
else
fwu->flash_properties.has_pm_config = 0;
if (fwu->blkcount.bl_config)
fwu->flash_properties.has_bl_config = 1;
else
fwu->flash_properties.has_bl_config = 0;
if (fwu->blkcount.guest_code)
fwu->has_guest_code = 1;
else
fwu->has_guest_code = 0;
kfree(ptable);
return 0;
}
static int fwu_read_f34_v5v6_queries(void)
{
int retval;
unsigned char count;
unsigned char base;
unsigned char buf[10];
struct f34_v5v6_flash_properties_2 properties_2;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.query_base_addr;
retval = synaptics_rmi4_reg_read(rmi4_data,
base + V5V6_BOOTLOADER_ID_OFFSET,
fwu->bootloader_id,
sizeof(fwu->bootloader_id));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read bootloader ID\n",
__func__);
return retval;
}
if (fwu->bl_version == BL_V5) {
fwu->off.properties = V5_PROPERTIES_OFFSET;
fwu->off.block_size = V5_BLOCK_SIZE_OFFSET;
fwu->off.block_count = V5_BLOCK_COUNT_OFFSET;
fwu->off.block_number = V5_BLOCK_NUMBER_OFFSET;
fwu->off.payload = V5_BLOCK_DATA_OFFSET;
} else if (fwu->bl_version == BL_V6) {
fwu->off.properties = V6_PROPERTIES_OFFSET;
fwu->off.properties_2 = V6_PROPERTIES_2_OFFSET;
fwu->off.block_size = V6_BLOCK_SIZE_OFFSET;
fwu->off.block_count = V6_BLOCK_COUNT_OFFSET;
fwu->off.gc_block_count = V6_GUEST_CODE_BLOCK_COUNT_OFFSET;
fwu->off.block_number = V6_BLOCK_NUMBER_OFFSET;
fwu->off.payload = V6_BLOCK_DATA_OFFSET;
}
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.block_size,
buf,
2);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read block size info\n",
__func__);
return retval;
}
batohs(&fwu->block_size, &(buf[0]));
if (fwu->bl_version == BL_V5) {
fwu->off.flash_cmd = fwu->off.payload + fwu->block_size;
fwu->off.flash_status = fwu->off.flash_cmd;
} else if (fwu->bl_version == BL_V6) {
fwu->off.flash_cmd = V6_FLASH_COMMAND_OFFSET;
fwu->off.flash_status = V6_FLASH_STATUS_OFFSET;
}
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.properties,
fwu->flash_properties.data,
sizeof(fwu->flash_properties.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash properties\n",
__func__);
return retval;
}
count = 4;
if (fwu->flash_properties.has_pm_config)
count += 2;
if (fwu->flash_properties.has_bl_config)
count += 2;
if (fwu->flash_properties.has_disp_config)
count += 2;
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.block_count,
buf,
count);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read block count info\n",
__func__);
return retval;
}
batohs(&fwu->blkcount.ui_firmware, &(buf[0]));
batohs(&fwu->blkcount.ui_config, &(buf[2]));
count = 4;
if (fwu->flash_properties.has_pm_config) {
batohs(&fwu->blkcount.pm_config, &(buf[count]));
count += 2;
}
if (fwu->flash_properties.has_bl_config) {
batohs(&fwu->blkcount.bl_config, &(buf[count]));
count += 2;
}
if (fwu->flash_properties.has_disp_config)
batohs(&fwu->blkcount.dp_config, &(buf[count]));
fwu->has_guest_code = false;
if (fwu->flash_properties.has_query4) {
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.properties_2,
properties_2.data,
sizeof(properties_2.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash properties 2\n",
__func__);
return retval;
}
if (properties_2.has_guest_code) {
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.gc_block_count,
buf,
2);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read guest code block count\n",
__func__);
return retval;
}
batohs(&fwu->blkcount.guest_code, &(buf[0]));
fwu->has_guest_code = true;
}
}
return 0;
}
static int fwu_read_f34_queries(void)
{
int retval;
memset(&fwu->blkcount, 0x00, sizeof(fwu->blkcount));
memset(&fwu->phyaddr, 0x00, sizeof(fwu->phyaddr));
if (fwu->bl_version == BL_V7)
retval = fwu_read_f34_v7_queries();
else
retval = fwu_read_f34_v5v6_queries();
return retval;
}
static int fwu_write_f34_v7_blocks(unsigned char *block_ptr,
unsigned short block_cnt, unsigned char command)
{
int retval;
unsigned char base;
unsigned char length[2];
unsigned short transfer;
unsigned short max_transfer;
unsigned short remaining = block_cnt;
unsigned short block_number = 0;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
retval = fwu_write_f34_partition_id(command);
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.block_number,
(unsigned char *)&block_number,
sizeof(block_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block number\n",
__func__);
return retval;
}
if (fwu->payload_length > (PAGE_SIZE / fwu->block_size))
max_transfer = PAGE_SIZE / fwu->block_size;
else
max_transfer = fwu->payload_length;
do {
if (remaining / max_transfer)
transfer = max_transfer;
else
transfer = remaining;
length[0] = (unsigned char)(transfer & MASK_8BIT);
length[1] = (unsigned char)(transfer >> 8);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.transfer_length,
length,
sizeof(length));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write transfer length (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = fwu_write_f34_command(command);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write command (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.payload,
block_ptr,
transfer * fwu->block_size);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block data (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = fwu_wait_for_idle(WRITE_WAIT_MS, true);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to wait for idle status (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
block_ptr += (transfer * fwu->block_size);
remaining -= transfer;
dev_dbg(rmi4_data->pdev->dev.parent, "%s: remaining %d\n",
__func__, remaining);
} while (remaining);
return 0;
}
static int fwu_write_f34_v5v6_blocks(unsigned char *block_ptr,
unsigned short block_cnt, unsigned char command)
{
int retval;
unsigned char base;
unsigned char block_number[] = {0, 0};
unsigned short blk;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
block_number[1] |= (fwu->config_area << 5);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.block_number,
block_number,
sizeof(block_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block number\n",
__func__);
return retval;
}
for (blk = 0; blk < block_cnt; blk++) {
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.payload,
block_ptr,
fwu->block_size);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block data (block %d)\n",
__func__, blk);
return retval;
}
retval = fwu_write_f34_command(command);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write command for block %d\n",
__func__, blk);
return retval;
}
retval = fwu_wait_for_idle(WRITE_WAIT_MS, true);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to wait for idle status (block %d)\n",
__func__, blk);
return retval;
}
block_ptr += fwu->block_size;
dev_dbg(rmi4_data->pdev->dev.parent, "%s: remaining %d\n",
__func__, block_cnt - blk);
}
return 0;
}
static int fwu_write_f34_blocks(unsigned char *block_ptr,
unsigned short block_cnt, unsigned char cmd)
{
int retval;
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
retval = fwu_write_f34_v7_blocks(block_ptr, block_cnt, cmd);
else
retval = fwu_write_f34_v5v6_blocks(block_ptr, block_cnt, cmd);
return retval;
}
static int fwu_read_f34_v7_blocks(unsigned short block_cnt,
unsigned char command)
{
int retval;
unsigned char base;
unsigned char length[2];
unsigned short transfer;
unsigned short max_transfer;
unsigned short remaining = block_cnt;
unsigned short block_number = 0;
unsigned short index = 0;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
retval = fwu_write_f34_partition_id(command);
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.block_number,
(unsigned char *)&block_number,
sizeof(block_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block number\n",
__func__);
return retval;
}
if (fwu->payload_length > (PAGE_SIZE / fwu->block_size))
max_transfer = PAGE_SIZE / fwu->block_size;
else
max_transfer = fwu->payload_length;
do {
if (remaining / max_transfer)
transfer = max_transfer;
else
transfer = remaining;
length[0] = (unsigned char)(transfer & MASK_8BIT);
length[1] = (unsigned char)(transfer >> 8);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.transfer_length,
length,
sizeof(length));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write transfer length (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = fwu_write_f34_command(command);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write command (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = fwu_wait_for_idle(WRITE_WAIT_MS, false);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to wait for idle status (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.payload,
&fwu->read_config_buf[index],
transfer * fwu->block_size);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read block data (%d blocks remaining)\n",
__func__, remaining);
return retval;
}
index += (transfer * fwu->block_size);
remaining -= transfer;
} while (remaining);
return 0;
}
static int fwu_read_f34_v5v6_blocks(unsigned short block_cnt,
unsigned char command)
{
int retval;
unsigned char base;
unsigned char block_number[] = {0, 0};
unsigned short blk;
unsigned short index = 0;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f34_fd.data_base_addr;
block_number[1] |= (fwu->config_area << 5);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + fwu->off.block_number,
block_number,
sizeof(block_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write block number\n",
__func__);
return retval;
}
for (blk = 0; blk < block_cnt; blk++) {
retval = fwu_write_f34_command(command);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write read config command\n",
__func__);
return retval;
}
retval = fwu_wait_for_idle(WRITE_WAIT_MS, false);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to wait for idle status\n",
__func__);
return retval;
}
retval = synaptics_rmi4_reg_read(rmi4_data,
base + fwu->off.payload,
&fwu->read_config_buf[index],
fwu->block_size);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read block data (block %d)\n",
__func__, blk);
return retval;
}
index += fwu->block_size;
}
return 0;
}
static int fwu_read_f34_blocks(unsigned short block_cnt, unsigned char cmd)
{
int retval;
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
retval = fwu_read_f34_v7_blocks(block_cnt, cmd);
else
retval = fwu_read_f34_v5v6_blocks(block_cnt, cmd);
return retval;
}
static int fwu_get_image_firmware_id(unsigned int *fw_id)
{
int retval;
unsigned char index = 0;
char *strptr;
char *firmware_id;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (fwu->img.contains_firmware_id) {
*fw_id = fwu->img.firmware_id;
} else {
strptr = strnstr(fwu->image_name, "PR", MAX_IMAGE_NAME_LEN);
if (!strptr) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: No valid PR number (PRxxxxxxx) found in image file name (%s)\n",
__func__, fwu->image_name);
return -EINVAL;
}
strptr += 2;
firmware_id = kzalloc(MAX_FIRMWARE_ID_LEN, GFP_KERNEL);
if (!firmware_id) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for firmware_id\n",
__func__);
return -ENOMEM;
}
while ((index < MAX_FIRMWARE_ID_LEN - 1) && strptr[index] >= '0'
&& strptr[index] <= '9') {
firmware_id[index] = strptr[index];
index++;
}
firmware_id[index] = '\0';
retval = sstrtoul(firmware_id, 10, (unsigned long *)fw_id);
kfree(firmware_id);
if (retval) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to obtain image firmware ID\n",
__func__);
return -EINVAL;
}
}
return 0;
}
static int fwu_get_device_config_id(void)
{
int retval;
unsigned char config_id_size;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
config_id_size = V7_CONFIG_ID_SIZE;
else
config_id_size = V5V6_CONFIG_ID_SIZE;
retval = synaptics_rmi4_reg_read(rmi4_data,
fwu->f34_fd.ctrl_base_addr,
fwu->config_id,
config_id_size);
if (retval < 0)
return retval;
return 0;
}
static enum flash_area fwu_go_nogo(void)
{
int retval;
enum flash_area flash_area = NONE;
unsigned char ii;
unsigned char config_id_size;
unsigned int device_fw_id;
unsigned int image_fw_id;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (fwu->force_update) {
flash_area = UI_FIRMWARE;
goto exit;
}
/* Update both UI and config if device is in bootloader mode */
if (fwu->in_bl_mode) {
flash_area = UI_FIRMWARE;
goto exit;
}
/* Get device firmware ID */
device_fw_id = rmi4_data->firmware_id;
dev_info(rmi4_data->pdev->dev.parent,
"%s: Device firmware ID = %d\n",
__func__, device_fw_id);
/* Get image firmware ID */
retval = fwu_get_image_firmware_id(&image_fw_id);
if (retval < 0) {
flash_area = NONE;
goto exit;
}
dev_info(rmi4_data->pdev->dev.parent,
"%s: Image firmware ID = %d\n",
__func__, image_fw_id);
if (image_fw_id > device_fw_id) {
flash_area = UI_FIRMWARE;
goto exit;
} else if (image_fw_id < device_fw_id) {
dev_info(rmi4_data->pdev->dev.parent,
"%s: Image firmware ID older than device firmware ID\n",
__func__);
flash_area = NONE;
goto exit;
}
/* Get device config ID */
retval = fwu_get_device_config_id();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read device config ID\n",
__func__);
flash_area = NONE;
goto exit;
}
if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8)
config_id_size = V7_CONFIG_ID_SIZE;
else
config_id_size = V5V6_CONFIG_ID_SIZE;
for (ii = 0; ii < config_id_size; ii++) {
if (fwu->img.ui_config.data[ii] > fwu->config_id[ii]) {
flash_area = UI_CONFIG;
goto exit;
} else if (fwu->img.ui_config.data[ii] < fwu->config_id[ii]) {
flash_area = NONE;
goto exit;
}
}
flash_area = NONE;
exit:
if (flash_area == NONE) {
dev_info(rmi4_data->pdev->dev.parent,
"%s: No need to do reflash\n",
__func__);
} else {
dev_info(rmi4_data->pdev->dev.parent,
"%s: Updating %s\n",
__func__,
flash_area == UI_FIRMWARE ?
"UI firmware and config" :
"UI config only");
}
return flash_area;
}
static int fwu_scan_pdt(void)
{
int retval;
unsigned char ii;
unsigned char intr_count = 0;
unsigned char intr_off;
unsigned char intr_src;
unsigned short addr;
bool f01found = false;
bool f34found = false;
bool f35found = false;
struct synaptics_rmi4_fn_desc rmi_fd;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
fwu->in_ub_mode = false;
for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) {
retval = synaptics_rmi4_reg_read(rmi4_data,
addr,
(unsigned char *)&rmi_fd,
sizeof(rmi_fd));
if (retval < 0)
return retval;
if (rmi_fd.fn_number) {
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Found F%02x\n",
__func__, rmi_fd.fn_number);
switch (rmi_fd.fn_number) {
case SYNAPTICS_RMI4_F01:
f01found = true;
rmi4_data->f01_query_base_addr =
rmi_fd.query_base_addr;
rmi4_data->f01_ctrl_base_addr =
rmi_fd.ctrl_base_addr;
rmi4_data->f01_data_base_addr =
rmi_fd.data_base_addr;
rmi4_data->f01_cmd_base_addr =
rmi_fd.cmd_base_addr;
break;
case SYNAPTICS_RMI4_F34:
f34found = true;
fwu->f34_fd.query_base_addr =
rmi_fd.query_base_addr;
fwu->f34_fd.ctrl_base_addr =
rmi_fd.ctrl_base_addr;
fwu->f34_fd.data_base_addr =
rmi_fd.data_base_addr;
switch (rmi_fd.fn_version) {
case F34_V0:
fwu->bl_version = BL_V5;
break;
case F34_V1:
fwu->bl_version = BL_V6;
break;
case F34_V2:
fwu->bl_version = BL_V7;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Unrecognized F34 version\n",
__func__);
return -EINVAL;
}
fwu->intr_mask = 0;
intr_src = rmi_fd.intr_src_count;
intr_off = intr_count % 8;
for (ii = intr_off;
ii < (intr_src + intr_off);
ii++) {
fwu->intr_mask |= 1 << ii;
}
break;
case SYNAPTICS_RMI4_F35:
f35found = true;
fwu->f35_fd.query_base_addr =
rmi_fd.query_base_addr;
fwu->f35_fd.ctrl_base_addr =
rmi_fd.ctrl_base_addr;
fwu->f35_fd.data_base_addr =
rmi_fd.data_base_addr;
break;
}
} else {
break;
}
intr_count += rmi_fd.intr_src_count;
}
if (!f01found || !f34found) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to find both F01 and F34\n",
__func__);
if (!f35found) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to find F35\n",
__func__);
return -EINVAL;
} else {
fwu->in_ub_mode = true;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: In microbootloader mode\n",
__func__);
fwu_recovery_check_status();
return 0;
}
}
rmi4_data->intr_mask[0] |= fwu->intr_mask;
addr = rmi4_data->f01_ctrl_base_addr + 1;
retval = synaptics_rmi4_reg_write(rmi4_data,
addr,
&(rmi4_data->intr_mask[0]),
sizeof(rmi4_data->intr_mask[0]));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to set interrupt enable bit\n",
__func__);
return retval;
}
return 0;
}
static int fwu_enter_flash_prog(void)
{
int retval;
struct f01_device_control f01_device_control;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_read_flash_status();
if (retval < 0)
return retval;
if (fwu->in_bl_mode)
return 0;
retval = rmi4_data->irq_enable(rmi4_data, false, true);
if (retval < 0)
return retval;
msleep(INT_DISABLE_WAIT_MS);
retval = fwu_write_f34_command(CMD_ENABLE_FLASH_PROG);
if (retval < 0)
return retval;
retval = fwu_wait_for_idle(ENABLE_WAIT_MS, false);
if (retval < 0)
return retval;
if (!fwu->in_bl_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: BL mode not entered\n",
__func__);
return -EINVAL;
}
if (rmi4_data->hw_if->bl_hw_init) {
retval = rmi4_data->hw_if->bl_hw_init(rmi4_data);
if (retval < 0)
return retval;
}
retval = fwu_scan_pdt();
if (retval < 0)
return retval;
retval = fwu_read_f34_queries();
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_read(rmi4_data,
rmi4_data->f01_ctrl_base_addr,
f01_device_control.data,
sizeof(f01_device_control.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read F01 device control\n",
__func__);
return retval;
}
f01_device_control.nosleep = true;
f01_device_control.sleep_mode = SLEEP_MODE_NORMAL;
retval = synaptics_rmi4_reg_write(rmi4_data,
rmi4_data->f01_ctrl_base_addr,
f01_device_control.data,
sizeof(f01_device_control.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write F01 device control\n",
__func__);
return retval;
}
msleep(ENTER_FLASH_PROG_WAIT_MS);
return retval;
}
static int fwu_check_ui_firmware_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.ui_firmware.size / fwu->block_size;
if (block_count != fwu->blkcount.ui_firmware) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: UI firmware size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
static int fwu_check_ui_configuration_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.ui_config.size / fwu->block_size;
if (block_count != fwu->blkcount.ui_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: UI configuration size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
static int fwu_check_dp_configuration_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.dp_config.size / fwu->block_size;
if (block_count != fwu->blkcount.dp_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Display configuration size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int fwu_check_pm_configuration_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.pm_config.size / fwu->block_size;
if (block_count != fwu->blkcount.pm_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Permanent configuration size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
#endif
static int fwu_check_bl_configuration_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.bl_config.size / fwu->block_size;
if (block_count != fwu->blkcount.bl_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Bootloader configuration size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
static int fwu_check_guest_code_size(void)
{
unsigned short block_count;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
block_count = fwu->img.guest_code.size / fwu->block_size;
if (block_count != fwu->blkcount.guest_code) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Guest code size mismatch\n",
__func__);
return -EINVAL;
}
return 0;
}
static int fwu_write_firmware(void)
{
unsigned short firmware_block_count;
firmware_block_count = fwu->img.ui_firmware.size / fwu->block_size;
return fwu_write_f34_blocks((unsigned char *)fwu->img.ui_firmware.data,
firmware_block_count, CMD_WRITE_FW);
}
static int fwu_erase_configuration(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
switch (fwu->config_area) {
case UI_CONFIG_AREA:
retval = fwu_write_f34_command(CMD_ERASE_UI_CONFIG);
if (retval < 0)
return retval;
break;
case DP_CONFIG_AREA:
retval = fwu_write_f34_command(CMD_ERASE_DISP_CONFIG);
if (retval < 0)
return retval;
break;
case BL_CONFIG_AREA:
retval = fwu_write_f34_command(CMD_ERASE_BL_CONFIG);
if (retval < 0)
return retval;
break;
}
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Erase command written\n",
__func__);
retval = fwu_wait_for_idle(ERASE_WAIT_MS, false);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Idle status detected\n",
__func__);
return retval;
}
static int fwu_erase_guest_code(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_write_f34_command(CMD_ERASE_GUEST_CODE);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Erase command written\n",
__func__);
retval = fwu_wait_for_idle(ERASE_WAIT_MS, false);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Idle status detected\n",
__func__);
return 0;
}
static int fwu_erase_all(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (fwu->bl_version == BL_V7) {
retval = fwu_write_f34_command(CMD_ERASE_UI_FIRMWARE);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Erase command written\n",
__func__);
retval = fwu_wait_for_idle(ERASE_WAIT_MS, false);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Idle status detected\n",
__func__);
fwu->config_area = UI_CONFIG_AREA;
retval = fwu_erase_configuration();
if (retval < 0)
return retval;
} else {
retval = fwu_write_f34_command(CMD_ERASE_ALL);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Erase all command written\n",
__func__);
retval = fwu_wait_for_idle(ERASE_WAIT_MS, false);
if (!(fwu->bl_version == BL_V8 &&
fwu->flash_status == BAD_PARTITION_TABLE)) {
if (retval < 0)
return retval;
}
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Idle status detected\n",
__func__);
if (fwu->bl_version == BL_V8)
return 0;
}
if (fwu->flash_properties.has_disp_config &&
fwu->img.contains_disp_config) {
fwu->config_area = DP_CONFIG_AREA;
retval = fwu_erase_configuration();
if (retval < 0)
return retval;
}
if (fwu->has_guest_code && fwu->img.contains_guest_code) {
retval = fwu_erase_guest_code();
if (retval < 0)
return retval;
}
return 0;
}
static int fwu_write_configuration(void)
{
return fwu_write_f34_blocks((unsigned char *)fwu->config_data,
fwu->config_block_count, CMD_WRITE_CONFIG);
}
static int fwu_write_ui_configuration(void)
{
fwu->config_area = UI_CONFIG_AREA;
fwu->config_data = fwu->img.ui_config.data;
fwu->config_size = fwu->img.ui_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
return fwu_write_configuration();
}
static int fwu_write_dp_configuration(void)
{
fwu->config_area = DP_CONFIG_AREA;
fwu->config_data = fwu->img.dp_config.data;
fwu->config_size = fwu->img.dp_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
return fwu_write_configuration();
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int fwu_write_pm_configuration(void)
{
fwu->config_area = PM_CONFIG_AREA;
fwu->config_data = fwu->img.pm_config.data;
fwu->config_size = fwu->img.pm_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
return fwu_write_configuration();
}
#endif
static int fwu_write_flash_configuration(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
fwu->config_area = FLASH_CONFIG_AREA;
fwu->config_data = fwu->img.fl_config.data;
fwu->config_size = fwu->img.fl_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
if (fwu->config_block_count != fwu->blkcount.fl_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Flash configuration size mismatch\n",
__func__);
return -EINVAL;
}
retval = fwu_write_f34_command(CMD_ERASE_FLASH_CONFIG);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Erase flash configuration command written\n",
__func__);
retval = fwu_wait_for_idle(ERASE_WAIT_MS, false);
if (retval < 0)
return retval;
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Idle status detected\n",
__func__);
retval = fwu_write_configuration();
if (retval < 0)
return retval;
rmi4_data->reset_device(rmi4_data, false);
return 0;
}
static int fwu_write_guest_code(void)
{
int retval;
unsigned short guest_code_block_count;
guest_code_block_count = fwu->img.guest_code.size / fwu->block_size;
retval = fwu_write_f34_blocks((unsigned char *)fwu->img.guest_code.data,
guest_code_block_count, CMD_WRITE_GUEST_CODE);
if (retval < 0)
return retval;
return 0;
}
static int fwu_write_lockdown(void)
{
unsigned short lockdown_block_count;
lockdown_block_count = fwu->img.lockdown.size / fwu->block_size;
return fwu_write_f34_blocks((unsigned char *)fwu->img.lockdown.data,
lockdown_block_count, CMD_WRITE_LOCKDOWN);
}
static int fwu_write_partition_table_v8(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
fwu->config_area = FLASH_CONFIG_AREA;
fwu->config_data = fwu->img.fl_config.data;
fwu->config_size = fwu->img.fl_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
if (fwu->config_block_count != fwu->blkcount.fl_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Flash configuration size mismatch\n",
__func__);
return -EINVAL;
}
retval = fwu_write_configuration();
if (retval < 0)
return retval;
rmi4_data->reset_device(rmi4_data, false);
return 0;
}
static int fwu_write_partition_table_v7(void)
{
int retval;
unsigned short block_count;
block_count = fwu->blkcount.bl_config;
fwu->config_area = BL_CONFIG_AREA;
fwu->config_size = fwu->block_size * block_count;
retval = fwu_allocate_read_config_buf(fwu->config_size);
if (retval < 0)
return retval;
retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG);
if (retval < 0)
return retval;
retval = fwu_erase_configuration();
if (retval < 0)
return retval;
retval = fwu_write_flash_configuration();
if (retval < 0)
return retval;
fwu->config_area = BL_CONFIG_AREA;
fwu->config_data = fwu->read_config_buf;
fwu->config_size = fwu->img.bl_config.size;
fwu->config_block_count = fwu->config_size / fwu->block_size;
retval = fwu_write_configuration();
if (retval < 0)
return retval;
return 0;
}
static int fwu_do_reflash(void)
{
int retval;
if (!fwu->new_partition_table) {
retval = fwu_check_ui_firmware_size();
if (retval < 0)
return retval;
retval = fwu_check_ui_configuration_size();
if (retval < 0)
return retval;
if (fwu->flash_properties.has_disp_config &&
fwu->img.contains_disp_config) {
retval = fwu_check_dp_configuration_size();
if (retval < 0)
return retval;
}
if (fwu->has_guest_code && fwu->img.contains_guest_code) {
retval = fwu_check_guest_code_size();
if (retval < 0)
return retval;
}
} else if (fwu->bl_version == BL_V7) {
retval = fwu_check_bl_configuration_size();
if (retval < 0)
return retval;
}
retval = fwu_erase_all();
if (retval < 0)
return retval;
if (fwu->bl_version == BL_V7 && fwu->new_partition_table) {
retval = fwu_write_partition_table_v7();
if (retval < 0)
return retval;
pr_notice("%s: Partition table programmed\n", __func__);
} else if (fwu->bl_version == BL_V8) {
retval = fwu_write_partition_table_v8();
if (retval < 0)
return retval;
pr_notice("%s: Partition table programmed\n", __func__);
}
retval = fwu_write_firmware();
if (retval < 0)
return retval;
pr_notice("%s: Firmware programmed\n", __func__);
fwu->config_area = UI_CONFIG_AREA;
retval = fwu_write_ui_configuration();
if (retval < 0)
return retval;
pr_notice("%s: Configuration programmed\n", __func__);
if (fwu->flash_properties.has_disp_config &&
fwu->img.contains_disp_config) {
retval = fwu_write_dp_configuration();
if (retval < 0)
return retval;
pr_notice("%s: Display configuration programmed\n", __func__);
}
if (fwu->has_guest_code && fwu->img.contains_guest_code) {
retval = fwu_write_guest_code();
if (retval < 0)
return retval;
pr_notice("%s: Guest code programmed\n", __func__);
}
return retval;
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int fwu_do_read_config(void)
{
int retval;
unsigned short block_count;
unsigned short config_area;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
switch (fwu->config_area) {
case UI_CONFIG_AREA:
block_count = fwu->blkcount.ui_config;
break;
case DP_CONFIG_AREA:
if (!fwu->flash_properties.has_disp_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Display configuration not supported\n",
__func__);
return -EINVAL;
}
block_count = fwu->blkcount.dp_config;
break;
case PM_CONFIG_AREA:
if (!fwu->flash_properties.has_pm_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Permanent configuration not supported\n",
__func__);
return -EINVAL;
}
block_count = fwu->blkcount.pm_config;
break;
case BL_CONFIG_AREA:
if (!fwu->flash_properties.has_bl_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Bootloader configuration not supported\n",
__func__);
return -EINVAL;
}
block_count = fwu->blkcount.bl_config;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Invalid config area\n",
__func__);
return -EINVAL;
}
if (block_count == 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Invalid block count\n",
__func__);
return -EINVAL;
}
mutex_lock(&rmi4_data->rmi4_exp_init_mutex);
config_area = fwu->config_area;
retval = fwu_enter_flash_prog();
if (retval < 0)
goto exit;
fwu->config_area = config_area;
fwu->config_size = fwu->block_size * block_count;
retval = fwu_allocate_read_config_buf(fwu->config_size);
if (retval < 0)
goto exit;
retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG);
exit:
rmi4_data->reset_device(rmi4_data, false);
mutex_unlock(&rmi4_data->rmi4_exp_init_mutex);
return retval;
}
#endif
static int fwu_do_lockdown_v7(void)
{
int retval;
struct f34_v7_data0 status;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_enter_flash_prog();
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_read(rmi4_data,
fwu->f34_fd.data_base_addr + fwu->off.flash_status,
status.data,
sizeof(status.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash status\n",
__func__);
return retval;
}
if (status.device_cfg_status == 2) {
dev_info(rmi4_data->pdev->dev.parent,
"%s: Device already locked down\n",
__func__);
return 0;
}
retval = fwu_write_lockdown();
if (retval < 0)
return retval;
pr_notice("%s: Lockdown programmed\n", __func__);
return retval;
}
static int fwu_do_lockdown_v5v6(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_enter_flash_prog();
if (retval < 0)
return retval;
retval = synaptics_rmi4_reg_read(rmi4_data,
fwu->f34_fd.query_base_addr + fwu->off.properties,
fwu->flash_properties.data,
sizeof(fwu->flash_properties.data));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read flash properties\n",
__func__);
return retval;
}
if (fwu->flash_properties.unlocked == 0) {
dev_info(rmi4_data->pdev->dev.parent,
"%s: Device already locked down\n",
__func__);
return 0;
}
retval = fwu_write_lockdown();
if (retval < 0)
return retval;
pr_notice("%s: Lockdown programmed\n", __func__);
return retval;
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int fwu_start_write_guest_code(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_parse_image_info();
if (retval < 0)
return -EINVAL;
if (!fwu->has_guest_code) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Guest code not supported\n",
__func__);
return -EINVAL;
}
if (!fwu->img.contains_guest_code) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: No guest code in firmware image\n",
__func__);
return -EINVAL;
}
if (rmi4_data->sensor_sleep) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Sensor sleeping\n",
__func__);
return -ENODEV;
}
rmi4_data->stay_awake = true;
mutex_lock(&rmi4_data->rmi4_exp_init_mutex);
pr_notice("%s: Start of write guest code process\n", __func__);
retval = fwu_enter_flash_prog();
if (retval < 0)
goto exit;
retval = fwu_check_guest_code_size();
if (retval < 0)
goto exit;
retval = fwu_erase_guest_code();
if (retval < 0)
goto exit;
retval = fwu_write_guest_code();
if (retval < 0)
goto exit;
pr_notice("%s: Guest code programmed\n", __func__);
exit:
rmi4_data->reset_device(rmi4_data, false);
pr_notice("%s: End of write guest code process\n", __func__);
mutex_unlock(&rmi4_data->rmi4_exp_init_mutex);
rmi4_data->stay_awake = false;
return retval;
}
static int fwu_start_write_config(void)
{
int retval;
unsigned short config_area;
unsigned int device_fw_id;
unsigned int image_fw_id;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = fwu_parse_image_info();
if (retval < 0)
return -EINVAL;
switch (fwu->config_area) {
case UI_CONFIG_AREA:
device_fw_id = rmi4_data->firmware_id;
retval = fwu_get_image_firmware_id(&image_fw_id);
if (retval < 0)
return retval;
if (device_fw_id != image_fw_id) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Device and image firmware IDs don't match\n",
__func__);
return -EINVAL;
}
retval = fwu_check_ui_configuration_size();
if (retval < 0)
return retval;
break;
case DP_CONFIG_AREA:
if (!fwu->flash_properties.has_disp_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Display configuration not supported\n",
__func__);
return -EINVAL;
}
if (!fwu->img.contains_disp_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: No display configuration in firmware image\n",
__func__);
return -EINVAL;
}
retval = fwu_check_dp_configuration_size();
if (retval < 0)
return retval;
break;
case PM_CONFIG_AREA:
if (!fwu->flash_properties.has_pm_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Permanent configuration not supported\n",
__func__);
return -EINVAL;
}
if (!fwu->img.contains_perm_config) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: No permanent configuration in firmware image\n",
__func__);
return -EINVAL;
}
retval = fwu_check_pm_configuration_size();
if (retval < 0)
return retval;
break;
default:
dev_err(rmi4_data->pdev->dev.parent,
"%s: Configuration not supported\n",
__func__);
return -EINVAL;
}
if (rmi4_data->sensor_sleep) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Sensor sleeping\n",
__func__);
return -ENODEV;
}
rmi4_data->stay_awake = true;
mutex_lock(&rmi4_data->rmi4_exp_init_mutex);
pr_notice("%s: Start of write config process\n", __func__);
config_area = fwu->config_area;
retval = fwu_enter_flash_prog();
if (retval < 0)
goto exit;
fwu->config_area = config_area;
if (fwu->config_area != PM_CONFIG_AREA) {
retval = fwu_erase_configuration();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to erase config\n",
__func__);
goto exit;
}
}
switch (fwu->config_area) {
case UI_CONFIG_AREA:
retval = fwu_write_ui_configuration();
if (retval < 0)
goto exit;
break;
case DP_CONFIG_AREA:
retval = fwu_write_dp_configuration();
if (retval < 0)
goto exit;
break;
case PM_CONFIG_AREA:
retval = fwu_write_pm_configuration();
if (retval < 0)
goto exit;
break;
}
pr_notice("%s: Config written\n", __func__);
exit:
switch (fwu->config_area) {
case UI_CONFIG_AREA:
rmi4_data->reset_device(rmi4_data, true);
break;
case DP_CONFIG_AREA:
case PM_CONFIG_AREA:
rmi4_data->reset_device(rmi4_data, false);
break;
}
pr_notice("%s: End of write config process\n", __func__);
mutex_unlock(&rmi4_data->rmi4_exp_init_mutex);
rmi4_data->stay_awake = false;
return retval;
}
#endif
static int fwu_start_reflash(void)
{
int retval = 0;
enum flash_area flash_area;
const struct firmware *fw_entry = NULL;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
const unsigned char *image_name;
if (rmi4_data->sensor_sleep) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Sensor sleeping\n",
__func__);
return -ENODEV;
}
rmi4_data->stay_awake = true;
mutex_lock(&rmi4_data->rmi4_exp_init_mutex);
pr_notice("%s: Start of reflash process\n", __func__);
if (fwu->image == NULL) {
if (rmi4_data->hw_if->board_data->fw_name)
image_name = rmi4_data->hw_if->board_data->fw_name;
else
image_name = FW_IMAGE_NAME;
retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN,
image_name, strlen(image_name),
strlen(image_name));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy image file name\n",
__func__);
goto exit;
}
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Requesting firmware image %s\n",
__func__, fwu->image_name);
retval = request_firmware(&fw_entry, fwu->image_name,
rmi4_data->pdev->dev.parent);
if (retval != 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Firmware image %s not available\n",
__func__, fwu->image_name);
retval = -EINVAL;
goto exit;
}
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Firmware image size = %d\n",
__func__, (unsigned int)fw_entry->size);
fwu->image = fw_entry->data;
}
retval = fwu_parse_image_info();
if (retval < 0)
goto exit;
if (fwu->blkcount.total_count != fwu->img.blkcount.total_count) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Flash size mismatch\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (fwu->bl_version != fwu->img.bl_version) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Bootloader version mismatch\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (!fwu->force_update && fwu->new_partition_table) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Partition table mismatch\n",
__func__);
retval = -EINVAL;
goto exit;
}
retval = fwu_read_flash_status();
if (retval < 0)
goto exit;
if (fwu->in_bl_mode) {
dev_info(rmi4_data->pdev->dev.parent,
"%s: Device in bootloader mode\n",
__func__);
}
flash_area = fwu_go_nogo();
if (flash_area != NONE) {
retval = fwu_enter_flash_prog();
if (retval < 0) {
rmi4_data->reset_device(rmi4_data, false);
goto exit;
}
}
switch (flash_area) {
case UI_FIRMWARE:
retval = fwu_do_reflash();
rmi4_data->reset_device(rmi4_data, true);
break;
case UI_CONFIG:
retval = fwu_check_ui_configuration_size();
if (retval < 0)
break;
fwu->config_area = UI_CONFIG_AREA;
retval = fwu_erase_configuration();
if (retval < 0)
break;
retval = fwu_write_ui_configuration();
rmi4_data->reset_device(rmi4_data, true);
break;
case NONE:
default:
rmi4_data->reset_device(rmi4_data, false);
break;
}
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do reflash\n",
__func__);
goto exit;
}
if (fwu->do_lockdown && (fwu->img.lockdown.data != NULL)) {
switch (fwu->bl_version) {
case BL_V5:
case BL_V6:
retval = fwu_do_lockdown_v5v6();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do lockdown\n",
__func__);
}
rmi4_data->reset_device(rmi4_data, false);
break;
case BL_V7:
case BL_V8:
retval = fwu_do_lockdown_v7();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do lockdown\n",
__func__);
}
rmi4_data->reset_device(rmi4_data, false);
break;
default:
break;
}
}
exit:
if (fw_entry)
release_firmware(fw_entry);
pr_notice("%s: End of reflash process\n", __func__);
mutex_unlock(&rmi4_data->rmi4_exp_init_mutex);
rmi4_data->stay_awake = false;
return retval;
}
static int fwu_recovery_check_status(void)
{
int retval;
unsigned char base;
unsigned char status;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f35_fd.data_base_addr;
retval = synaptics_rmi4_reg_read(rmi4_data,
base + F35_ERROR_CODE_OFFSET,
&status,
1);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read status\n",
__func__);
return retval;
}
status = status & MASK_7BIT;
if (status != 0x00) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Recovery mode status = %d\n",
__func__, status);
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int fwu_recovery_erase_all(void)
{
int retval;
unsigned char base;
unsigned char command = CMD_F35_ERASE_ALL;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f35_fd.ctrl_base_addr;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + F35_CHUNK_COMMAND_OFFSET,
&command,
sizeof(command));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to issue erase all command\n",
__func__);
return retval;
}
msleep(F35_ERASE_ALL_WAIT_MS);
retval = fwu_recovery_check_status();
if (retval < 0)
return retval;
return 0;
}
static int fwu_recovery_write_chunk(void)
{
int retval;
unsigned char base;
unsigned char chunk_number[] = {0, 0};
unsigned char chunk_spare;
unsigned char chunk_size;
unsigned char buf[F35_CHUNK_SIZE + 1];
unsigned short chunk;
unsigned short chunk_total;
unsigned short bytes_written = 0;
unsigned char *chunk_ptr = (unsigned char *)fwu->image;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f35_fd.ctrl_base_addr;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + F35_CHUNK_NUM_LSB_OFFSET,
chunk_number,
sizeof(chunk_number));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write chunk number\n",
__func__);
return retval;
}
buf[sizeof(buf) - 1] = CMD_F35_WRITE_CHUNK;
chunk_total = fwu->image_size / F35_CHUNK_SIZE;
chunk_spare = fwu->image_size % F35_CHUNK_SIZE;
if (chunk_spare)
chunk_total++;
for (chunk = 0; chunk < chunk_total; chunk++) {
if (chunk_spare && chunk == chunk_total - 1)
chunk_size = chunk_spare;
else
chunk_size = F35_CHUNK_SIZE;
memset(buf, 0x00, F35_CHUNK_SIZE);
secure_memcpy(buf, sizeof(buf), chunk_ptr,
fwu->image_size - bytes_written,
chunk_size);
retval = synaptics_rmi4_reg_write(rmi4_data,
base + F35_CHUNK_DATA_OFFSET,
buf,
sizeof(buf));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write chunk data (chunk %d)\n",
__func__, chunk);
return retval;
}
chunk_ptr += chunk_size;
bytes_written += chunk_size;
}
retval = fwu_recovery_check_status();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write chunk data\n",
__func__);
return retval;
}
return 0;
}
static int fwu_recovery_reset(void)
{
int retval;
unsigned char base;
unsigned char command = CMD_F35_RESET;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
base = fwu->f35_fd.ctrl_base_addr;
retval = synaptics_rmi4_reg_write(rmi4_data,
base + F35_CHUNK_COMMAND_OFFSET,
&command,
sizeof(command));
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to issue reset command\n",
__func__);
return retval;
}
msleep(F35_RESET_WAIT_MS);
return 0;
}
static int fwu_start_recovery(void)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (rmi4_data->sensor_sleep) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Sensor sleeping\n",
__func__);
return -ENODEV;
}
rmi4_data->stay_awake = true;
mutex_lock(&rmi4_data->rmi4_exp_init_mutex);
pr_notice("%s: Start of recovery process\n", __func__);
retval = rmi4_data->irq_enable(rmi4_data, false, false);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to disable interrupt\n",
__func__);
goto exit;
}
retval = fwu_recovery_erase_all();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do erase all in recovery mode\n",
__func__);
goto exit;
}
pr_notice("%s: External flash erased\n", __func__);
retval = fwu_recovery_write_chunk();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write chunk data in recovery mode\n",
__func__);
goto exit;
}
pr_notice("%s: Chunk data programmed\n", __func__);
retval = fwu_recovery_reset();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to reset device in recovery mode\n",
__func__);
goto exit;
}
pr_notice("%s: Recovery mode reset issued\n", __func__);
rmi4_data->reset_device(rmi4_data, true);
retval = 0;
exit:
pr_notice("%s: End of recovery process\n", __func__);
mutex_unlock(&rmi4_data->rmi4_exp_init_mutex);
rmi4_data->stay_awake = false;
return retval;
}
#endif
int synaptics_fw_updater(const unsigned char *fw_data)
{
int retval;
if (!fwu)
return -ENODEV;
if (!fwu->initialized)
return -ENODEV;
if (fwu->in_ub_mode)
return -ENODEV;
fwu->image = fw_data;
retval = fwu_start_reflash();
fwu->image = NULL;
return retval;
}
EXPORT_SYMBOL(synaptics_fw_updater);
#ifdef DO_STARTUP_FW_UPDATE
static void fwu_startup_fw_update_work(struct work_struct *work)
{
static unsigned char do_once = 1;
#ifdef WAIT_FOR_FB_READY
unsigned int timeout;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
#endif
if (!do_once)
return;
do_once = 0;
#ifdef WAIT_FOR_FB_READY
timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS + 1;
while (!rmi4_data->fb_ready) {
msleep(FB_READY_WAIT_MS);
timeout--;
if (timeout == 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Timed out waiting for FB ready\n",
__func__);
return;
}
}
#endif
synaptics_fw_updater(NULL);
return;
}
#endif
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static ssize_t fwu_sysfs_show_image(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (count < fwu->config_size) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Not enough space (%d bytes) in buffer\n",
__func__, (unsigned int)count);
return -EINVAL;
}
retval = secure_memcpy(buf, count, fwu->read_config_buf,
fwu->read_config_buf_size, fwu->config_size);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy config data\n",
__func__);
return retval;
}
return fwu->config_size;
}
static ssize_t fwu_sysfs_store_image(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = secure_memcpy(&fwu->ext_data_source[fwu->data_pos],
fwu->image_size - fwu->data_pos, buf, count, count);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy image data\n",
__func__);
return retval;
}
fwu->data_pos += count;
return count;
}
static ssize_t fwu_sysfs_do_recovery_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (sscanf(buf, "%u", &input) != 1) {
retval = -EINVAL;
goto exit;
}
if (!fwu->in_ub_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Not in microbootloader mode\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (!fwu->ext_data_source)
return -EINVAL;
else
fwu->image = fwu->ext_data_source;
retval = fwu_start_recovery();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do recovery\n",
__func__);
goto exit;
}
retval = count;
exit:
kfree(fwu->ext_data_source);
fwu->ext_data_source = NULL;
fwu->image = NULL;
return retval;
}
static ssize_t fwu_sysfs_do_reflash_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (sscanf(buf, "%u", &input) != 1) {
retval = -EINVAL;
goto exit;
}
if (fwu->in_ub_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: In microbootloader mode\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (!fwu->ext_data_source)
return -EINVAL;
else
fwu->image = fwu->ext_data_source;
if (input & LOCKDOWN) {
fwu->do_lockdown = true;
input &= ~LOCKDOWN;
}
if ((input != NORMAL) && (input != FORCE)) {
retval = -EINVAL;
goto exit;
}
if (input == FORCE)
fwu->force_update = true;
retval = synaptics_fw_updater(fwu->image);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to do reflash\n",
__func__);
goto exit;
}
retval = count;
exit:
kfree(fwu->ext_data_source);
fwu->ext_data_source = NULL;
fwu->image = NULL;
fwu->force_update = FORCE_UPDATE;
fwu->do_lockdown = DO_LOCKDOWN;
return retval;
}
static ssize_t fwu_sysfs_write_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (sscanf(buf, "%u", &input) != 1) {
retval = -EINVAL;
goto exit;
}
if (input != 1) {
retval = -EINVAL;
goto exit;
}
if (fwu->in_ub_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: In microbootloader mode\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (!fwu->ext_data_source)
return -EINVAL;
else
fwu->image = fwu->ext_data_source;
retval = fwu_start_write_config();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write config\n",
__func__);
goto exit;
}
retval = count;
exit:
kfree(fwu->ext_data_source);
fwu->ext_data_source = NULL;
fwu->image = NULL;
return retval;
}
static ssize_t fwu_sysfs_read_config_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (sscanf(buf, "%u", &input) != 1)
return -EINVAL;
if (input != 1)
return -EINVAL;
if (fwu->in_ub_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: In microbootloader mode\n",
__func__);
return -EINVAL;
}
retval = fwu_do_read_config();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read config\n",
__func__);
return retval;
}
return count;
}
static ssize_t fwu_sysfs_config_area_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned long config_area;
retval = sstrtoul(buf, 10, &config_area);
if (retval)
return retval;
fwu->config_area = config_area;
return count;
}
static ssize_t fwu_sysfs_image_name_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN,
buf, count, count);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to copy image file name\n",
__func__);
return retval;
}
return count;
}
static ssize_t fwu_sysfs_image_size_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned long size;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
retval = sstrtoul(buf, 10, &size);
if (retval)
return retval;
fwu->image_size = size;
fwu->data_pos = 0;
kfree(fwu->ext_data_source);
fwu->ext_data_source = kzalloc(fwu->image_size, GFP_KERNEL);
if (!fwu->ext_data_source) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for image data\n",
__func__);
return -ENOMEM;
}
return count;
}
static ssize_t fwu_sysfs_block_size_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->block_size);
}
static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.ui_firmware);
}
static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.ui_config);
}
static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.dp_config);
}
static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.pm_config);
}
static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.bl_config);
}
static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.guest_code);
}
static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data;
if (sscanf(buf, "%u", &input) != 1) {
retval = -EINVAL;
goto exit;
}
if (input != 1) {
retval = -EINVAL;
goto exit;
}
if (fwu->in_ub_mode) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: In microbootloader mode\n",
__func__);
retval = -EINVAL;
goto exit;
}
if (!fwu->ext_data_source)
return -EINVAL;
else
fwu->image = fwu->ext_data_source;
retval = fwu_start_write_guest_code();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to write guest code\n",
__func__);
goto exit;
}
retval = count;
exit:
kfree(fwu->ext_data_source);
fwu->ext_data_source = NULL;
fwu->image = NULL;
return retval;
}
#endif
static void synaptics_rmi4_fwu_attn(struct synaptics_rmi4_data *rmi4_data,
unsigned char intr_mask)
{
if (!fwu)
return;
if (fwu->intr_mask & intr_mask)
fwu_read_flash_status();
return;
}
#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS
static int synaptics_create_fwu_bin_file(struct synaptics_rmi4_data *rmi4_data)
{
return sysfs_create_bin_file(&rmi4_data->input_dev->dev.kobj,
&dev_attr_data);
}
static void synaptics_remove_fwu_bin_file(struct synaptics_rmi4_data *rmi4_data)
{
sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data);
}
#else
static int synaptics_create_fwu_bin_file(struct synaptics_rmi4_data *rmi4_data)
{
return 0;
}
static void synaptics_remove_fwu_bin_file(struct synaptics_rmi4_data *rmi4_data)
{
}
#endif
static int synaptics_rmi4_fwu_init(struct synaptics_rmi4_data *rmi4_data)
{
int retval;
unsigned char attr_count;
struct pdt_properties pdt_props;
if (fwu) {
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Handle already exists\n",
__func__);
return 0;
}
fwu = kzalloc(sizeof(*fwu), GFP_KERNEL);
if (!fwu) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for fwu\n",
__func__);
retval = -ENOMEM;
goto exit;
}
fwu->image_name = kzalloc(MAX_IMAGE_NAME_LEN, GFP_KERNEL);
if (!fwu->image_name) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to alloc mem for image name\n",
__func__);
retval = -ENOMEM;
goto exit_free_fwu;
}
fwu->rmi4_data = rmi4_data;
retval = synaptics_rmi4_reg_read(rmi4_data,
PDT_PROPS,
pdt_props.data,
sizeof(pdt_props.data));
if (retval < 0) {
dev_dbg(rmi4_data->pdev->dev.parent,
"%s: Failed to read PDT properties, assuming 0x00\n",
__func__);
} else if (pdt_props.has_bsr) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Reflash for LTS not currently supported\n",
__func__);
retval = -ENODEV;
goto exit_free_mem;
}
retval = fwu_scan_pdt();
if (retval < 0)
goto exit_free_mem;
if (!fwu->in_ub_mode) {
retval = fwu_read_f34_queries();
if (retval < 0)
goto exit_free_mem;
retval = fwu_get_device_config_id();
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to read device config ID\n",
__func__);
goto exit_free_mem;
}
}
fwu->force_update = FORCE_UPDATE;
fwu->do_lockdown = DO_LOCKDOWN;
fwu->initialized = true;
retval = synaptics_create_fwu_bin_file(rmi4_data);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to create sysfs bin file\n",
__func__);
goto exit_free_mem;
}
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj,
&attrs[attr_count].attr);
if (retval < 0) {
dev_err(rmi4_data->pdev->dev.parent,
"%s: Failed to create sysfs attributes\n",
__func__);
retval = -ENODEV;
goto exit_remove_attrs;
}
}
#ifdef DO_STARTUP_FW_UPDATE
fwu->fwu_workqueue = create_singlethread_workqueue("fwu_workqueue");
INIT_WORK(&fwu->fwu_work, fwu_startup_fw_update_work);
queue_work(fwu->fwu_workqueue,
&fwu->fwu_work);
#endif
return 0;
exit_remove_attrs:
for (attr_count--; attr_count >= 0; attr_count--) {
sysfs_remove_file(&rmi4_data->input_dev->dev.kobj,
&attrs[attr_count].attr);
}
synaptics_remove_fwu_bin_file(rmi4_data);
exit_free_mem:
kfree(fwu->image_name);
exit_free_fwu:
kfree(fwu);
fwu = NULL;
exit:
return retval;
}
static void synaptics_rmi4_fwu_remove(struct synaptics_rmi4_data *rmi4_data)
{
unsigned char attr_count;
if (!fwu)
goto exit;
#ifdef DO_STARTUP_FW_UPDATE
cancel_work_sync(&fwu->fwu_work);
flush_workqueue(fwu->fwu_workqueue);
destroy_workqueue(fwu->fwu_workqueue);
#endif
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
sysfs_remove_file(&rmi4_data->input_dev->dev.kobj,
&attrs[attr_count].attr);
}
synaptics_remove_fwu_bin_file(rmi4_data);
kfree(fwu->read_config_buf);
kfree(fwu->image_name);
kfree(fwu);
fwu = NULL;
exit:
complete(&fwu_remove_complete);
return;
}
static void synaptics_rmi4_fwu_reset(struct synaptics_rmi4_data *rmi4_data)
{
int retval;
if (!fwu) {
synaptics_rmi4_fwu_init(rmi4_data);
return;
}
retval = fwu_scan_pdt();
if (retval < 0)
return;
if (!fwu->in_ub_mode)
fwu_read_f34_queries();
return;
}
static struct synaptics_rmi4_exp_fn fwu_module = {
.fn_type = RMI_FW_UPDATER,
.init = synaptics_rmi4_fwu_init,
.remove = synaptics_rmi4_fwu_remove,
.reset = synaptics_rmi4_fwu_reset,
.reinit = NULL,
.early_suspend = NULL,
.suspend = NULL,
.resume = NULL,
.late_resume = NULL,
.attn = synaptics_rmi4_fwu_attn,
};
static int __init rmi4_fw_update_module_init(void)
{
synaptics_rmi4_new_function(&fwu_module, true);
return 0;
}
static void __exit rmi4_fw_update_module_exit(void)
{
synaptics_rmi4_new_function(&fwu_module, false);
wait_for_completion(&fwu_remove_complete);
return;
}
module_init(rmi4_fw_update_module_init);
module_exit(rmi4_fw_update_module_exit);
MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics DSX FW Update Module");
MODULE_LICENSE("GPL v2");