blob: 323bb793f44d358bce52938f866a1f072bd9b7c6 [file] [log] [blame]
/*
* Goodix Firmware Update Driver.
*
* Copyright (C) 2019 - 2020 Goodix, Inc.
*
* 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 a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* 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.
*/
#include "goodix_ts_core.h"
#include "goodix_cfg_bin.h"
#include "goodix_default_fw.h"
/* COMMON PART - START */
#define TS_DEFAULT_FIRMWARE "goodix_firmware.bin"
#define FW_HEADER_SIZE 256
#define FW_SUBSYS_INFO_SIZE 8
#define FW_SUBSYS_INFO_OFFSET 32
#define FW_SUBSYS_MAX_NUM 28
#define ISP_MAX_BUFFERSIZE (1024 * 4)
#define HW_REG_CPU_CTRL 0x2180
#define HW_REG_DSP_MCU_POWER 0x2010
#define HW_REG_RESET 0x2184
#define HW_REG_SCRAMBLE 0x2218
#define HW_REG_BANK_SELECT 0x2048
#define HW_REG_ACCESS_PATCH0 0x204D
#define HW_REG_EC_SRM_START 0x204F
#define HW_REG_CPU_RUN_FROM 0x4506 /* for nor_L is 0x4006 */
#define HW_REG_ISP_RUN_FLAG 0x6006
#define HW_REG_ISP_ADDR 0xC000
#define HW_REG_ISP_BUFFER 0x6100
#define HW_REG_SUBSYS_TYPE 0x6020
#define HW_REG_FLASH_FLAG 0x6022
#define HW_REG_CACHE 0x204B
#define HW_REG_ESD_KEY 0x2318
#define HW_REG_WTD_TIMER 0x20B0
#define FLASH_ADDR_CONFIG_DATA 0x1E000
#define FLASH_SUBSYS_TYPE_CONFIG 0x03
#define CPU_CTRL_PENDING 0x00
#define CPU_CTRL_RUNNING 0x01
#define ISP_STAT_IDLE 0xFF
#define ISP_STAT_READY 0xAA
#define ISP_STAT_WRITING 0xAA
#define ISP_FLASH_SUCCESS 0xBB
#define ISP_FLASH_ERROR 0xCC
#define ISP_FLASH_CHECK_ERROR 0xDD
#define ISP_CMD_PREPARE 0x55
#define ISP_CMD_FLASH 0xAA
#define TS_CHECK_ISP_STATE_RETRY_TIMES 200
#define TS_READ_FLASH_STATE_RETRY_TIMES 200
/**
* fw_subsys_info - subsytem firmware infomation
* @type: sybsystem type
* @size: firmware size
* @flash_addr: flash address
* @data: firmware data
*/
struct fw_subsys_info {
u8 type;
u32 size;
u16 flash_addr;
const u8 *data;
};
#pragma pack(1)
/**
* firmware_info
* @size: fw total length
* @checksum: checksum of fw
* @hw_pid: mask pid string
* @hw_pid: mask vid code
* @fw_pid: fw pid string
* @fw_vid: fw vid code
* @subsys_num: number of fw subsystem
* @chip_type: chip type
* @protocol_ver: firmware packing
* protocol version
* @subsys: sybsystem info
*/
struct firmware_info {
u32 size;
u16 checksum;
u8 hw_pid[6];
u8 hw_vid[3];
u8 fw_pid[8];
u8 fw_vid[4];
u8 subsys_num;
u8 chip_type;
u8 protocol_ver;
u8 reserved[2];
struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM];
};
#pragma pack()
/**
* firmware_data - firmware data structure
* @fw_info: firmware infomation
* @firmware: firmware data structure
*/
struct firmware_data {
struct firmware_info fw_info;
const struct firmware *firmware;
};
enum update_status {
UPSTA_NOTWORK = 0,
UPSTA_PREPARING,
UPSTA_UPDATING,
UPSTA_ABORT,
UPSTA_SUCCESS,
UPSTA_FAILED
};
/**
* fw_update_ctrl - sturcture used to control the
* firmware update process
* @initialized: struct init state
* @mode: indicate weather reflash config or not, fw data source,
* and run on block mode or not.
* @status: update status
* @progress: indicate the progress of update
* @allow_reset: control the reset callback
* @allow_irq: control the irq callback
* @allow_suspend: control the suspend callback
* @allow_resume: allow resume callback
* @fw_data: firmware data
* @ts_dev: touch device
* @fw_name: firmware name
* @attr_fwimage: sysfs bin attrs, for storing fw image
* @fw_data_src: firmware data source form sysfs, request or head file
*/
struct fw_update_ctrl {
struct mutex mutex;
int initialized;
int mode;
enum update_status status;
unsigned int progress;
bool allow_reset;
bool allow_irq;
bool allow_suspend;
bool allow_resume;
struct firmware_data fw_data;
struct goodix_ts_device *ts_dev;
struct goodix_ts_core *core_data;
char fw_name[32];
struct bin_attribute attr_fwimage;
};
static struct fw_update_ctrl goodix_fw_update_ctrl;
/**
* goodix_parse_firmware - parse firmware header infomation
* and subsystem infomation from firmware data buffer
*
* @fw_data: firmware struct, contains firmware header info
* and firmware data.
* return: 0 - OK, < 0 - error
*/
static int goodix_parse_firmware(struct firmware_data *fw_data)
{
const struct firmware *firmware;
struct firmware_info *fw_info;
unsigned int i, fw_offset, info_offset;
u16 checksum;
int r = 0;
if (!fw_data || !fw_data->firmware) {
ts_err("Invalid firmware data");
return -EINVAL;
}
fw_info = &fw_data->fw_info;
/* copy firmware head info */
firmware = fw_data->firmware;
if (firmware->size < FW_SUBSYS_INFO_OFFSET) {
ts_err("Invalid firmware size:%zu", firmware->size);
r = -EINVAL;
goto err_size;
}
memcpy(fw_info, firmware->data, FW_SUBSYS_INFO_OFFSET);
/* check firmware size */
fw_info->size = be32_to_cpu(fw_info->size);
if (firmware->size != fw_info->size + 6) {
ts_err("Bad firmware, size not match");
r = -EINVAL;
goto err_size;
}
/* calculate checksum, note: sum of bytes, but check
* by u16 checksum
*/
for (i = 6, checksum = 0; i < firmware->size; i++)
checksum += firmware->data[i];
/* byte order change, and check */
fw_info->checksum = be16_to_cpu(fw_info->checksum);
if (checksum != fw_info->checksum) {
ts_err("Bad firmware, cheksum error %x(file) != %x(cal)",
fw_info->checksum, checksum);
r = -EINVAL;
goto err_size;
}
if (fw_info->subsys_num > FW_SUBSYS_MAX_NUM) {
ts_err("Bad firmware, invalid subsys num: %d",
fw_info->subsys_num);
r = -EINVAL;
goto err_size;
}
/* parse subsystem info */
fw_offset = FW_HEADER_SIZE;
for (i = 0; i < fw_info->subsys_num; i++) {
info_offset = FW_SUBSYS_INFO_OFFSET +
i * FW_SUBSYS_INFO_SIZE;
fw_info->subsys[i].type = firmware->data[info_offset];
fw_info->subsys[i].size =
be32_to_cpup((__be32 *)&firmware->data[info_offset + 1]);
fw_info->subsys[i].flash_addr =
be16_to_cpup((__be16 *)&firmware->data[info_offset + 5]);
if (fw_offset > firmware->size) {
ts_err("Sybsys offset exceed Firmware size");
goto err_size;
}
fw_info->subsys[i].data = firmware->data + fw_offset;
fw_offset += fw_info->subsys[i].size;
}
ts_info("Firmware package protocol: V%u", fw_info->protocol_ver);
ts_info("Fimware PID:GT%s", fw_info->fw_pid);
ts_info("Fimware VID:%02X%02X%02X%02x", fw_info->fw_vid[0],
fw_info->fw_vid[1], fw_info->fw_vid[2], fw_info->fw_vid[3]);
ts_info("Firmware chip type:%02X", fw_info->chip_type);
ts_info("Firmware size:%u", fw_info->size);
ts_info("Firmware subsystem num:%u", fw_info->subsys_num);
#ifdef CONFIG_GOODIX_DEBUG
for (i = 0; i < fw_info->subsys_num; i++) {
ts_debug("------------------------------------------");
ts_debug("Index:%d", i);
ts_debug("Subsystem type:%02X", fw_info->subsys[i].type);
ts_debug("Subsystem size:%u", fw_info->subsys[i].size);
ts_debug("Subsystem flash_addr:%08X", fw_info->subsys[i].flash_addr);
ts_debug("Subsystem Ptr:%p", fw_info->subsys[i].data);
}
ts_debug("------------------------------------------");
#endif
err_size:
return r;
}
/**
* goodix_check_update - compare the version of firmware running in
* touch device with the version getting from the firmware file.
* @fw_info: firmware infomation to be compared
* return: 0 no need do update,
* otherwise need do update
*/
static int goodix_check_update(struct goodix_ts_device *dev,
const struct firmware_info *fw_info)
{
struct goodix_ts_version fw_ver;
int ret = -EINVAL;
/* read version from chip, if we got invalid
* firmware version, maybe fimware in flash is
* incorrect, so we need to update firmware
*/
ret = dev->hw_ops->read_version(dev, &fw_ver);
if (ret) {
ts_info("failed get active pid");
return -EINVAL;
}
if (fw_ver.valid) {
// should we compare PID before fw update?
// if fw patch demage the PID may unmatch but
// we should de update to recover it.
if (memcmp(fw_ver.pid, fw_info->fw_pid, dev->reg.pid_len)) {
ts_err("Product ID is not match");
return -EPERM;
}
ret = memcmp(fw_ver.vid, fw_info->fw_vid, dev->reg.vid_len);
if (ret == 0) {
ts_err("FW version is equal to the IC's");
return 0;
} else if (ret > 0) {
ts_info("Warning: fw version is lower the IC's");
}
} /* else invalid firmware, update firmware */
ts_info("Firmware needs to be updated");
return ret;
}
/**
* goodix_reg_write_confirm - write register and confirm the value
* in the register.
* @dev: pointer to touch device
* @addr: register address
* @data: pointer to data buffer
* @len: data length
* return: 0 write success and confirm ok
* < 0 failed
*/
static int goodix_reg_write_confirm(struct goodix_ts_device *dev,
unsigned int addr, unsigned char *data, unsigned int len)
{
u8 *cfm, cfm_buf[32];
int r, i;
if (len > sizeof(cfm_buf)) {
cfm = kzalloc(len, GFP_KERNEL);
if (!cfm) {
ts_err("Mem alloc failed");
return -ENOMEM;
}
} else {
cfm = &cfm_buf[0];
}
for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) {
r = dev->hw_ops->write_trans(dev, addr, data, len);
if (r < 0)
goto exit;
r = dev->hw_ops->read_trans(dev, addr, cfm, len);
if (r < 0)
goto exit;
if (memcmp(data, cfm, len)) {
ts_info("data[0]:0x%02x, data[1]:0x%02x,"
"read cfm[0]:0x%02x, cfm[1]:0x%02x",
data[0], data[1], cfm[0], cfm[1]);
dev->hw_ops->read_trans(dev, 0x6022, cfm, 2);
ts_info("read 0x6022 data[0]:0x%02x, data[1]:0x%02x",
cfm[0], cfm[1]);
r = -EMEMCMP;
continue;
} else {
r = 0;
break;
}
}
exit:
if (cfm != &cfm_buf[0])
kfree(cfm);
return r;
}
static inline int goodix_reg_write(struct goodix_ts_device *dev,
unsigned int addr, unsigned char *data, unsigned int len)
{
return dev->hw_ops->write_trans(dev, addr, data, len);
}
static inline int goodix_reg_read(struct goodix_ts_device *dev,
unsigned int addr, unsigned char *data, unsigned int len)
{
return dev->hw_ops->read_trans(dev, addr, data, len);
}
/**
* goodix_load_isp - load ISP program to deivce ram
* @dev: pointer to touch device
* @fw_data: firmware data
* return 0 ok, <0 error
*/
static int goodix_load_isp(struct goodix_ts_device *ts_dev,
struct firmware_data *fw_data)
{
struct fw_subsys_info *fw_isp;
u8 reg_val[8] = {0x00};
int r;
int i;
fw_isp = &fw_data->fw_info.subsys[0];
ts_info("Loading ISP start");
reg_val[0] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_BANK_SELECT,
reg_val, 1);
if (r < 0) {
ts_err("Failed to select bank0");
return r;
}
ts_debug("Success select bank0, Set 0x%x -->0x00", HW_REG_BANK_SELECT);
reg_val[0] = 0x01;
r = goodix_reg_write(ts_dev, HW_REG_ACCESS_PATCH0,
reg_val, 1);
if (r < 0) {
ts_err("Failed to enable patch0 access");
return r;
}
ts_debug("Success select bank0, Set 0x%x -->0x01", HW_REG_ACCESS_PATCH0);
r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_ADDR,
(u8 *)fw_isp->data, fw_isp->size);
if (r < 0) {
ts_err("Loading ISP error");
return r;
}
ts_debug("Success send ISP data to IC");
reg_val[0] = 0x00;
r = goodix_reg_write_confirm(ts_dev, HW_REG_ACCESS_PATCH0,
reg_val, 1);
if (r < 0) {
ts_err("Failed to disable patch0 access");
return r;
}
ts_debug("Success forbit bank0 accedd, set 0x%x -->0x00",
HW_REG_ACCESS_PATCH0);
reg_val[0] = 0x00;
reg_val[1] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_ISP_RUN_FLAG, reg_val, 2);
if (r < 0) {
ts_err("Failed to clear 0x%x", HW_REG_ISP_RUN_FLAG);
return r;
}
ts_debug("Success clear 0x%x", HW_REG_ISP_RUN_FLAG);
memset(reg_val, 0x55, 8);
r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM,
reg_val, 8);
if (r < 0) {
ts_err("Failed set backdoor flag");
return r;
}
ts_debug("Success write [8]0x55 to 0x%x", HW_REG_CPU_RUN_FROM);
reg_val[0] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL,
reg_val, 1);
if (r < 0) {
ts_err("Failed to run isp");
return r;
}
ts_debug("Success run isp, set 0x%x-->0x00", HW_REG_CPU_CTRL);
/* check isp work state */
for (i = 0; i < TS_CHECK_ISP_STATE_RETRY_TIMES; i++) {
msleep(10);
r = goodix_reg_read(ts_dev, HW_REG_ISP_RUN_FLAG,
reg_val, 2);
if (r < 0 || (reg_val[0] == 0xAA && reg_val[1] == 0xBB))
break;
}
if (reg_val[0] == 0xAA && reg_val[1] == 0xBB) {
ts_info("ISP working OK");
return 0;
}
ts_err("ISP not work,0x%x=0x%x, 0x%x=0x%x",
HW_REG_ISP_RUN_FLAG, reg_val[0],
HW_REG_ISP_RUN_FLAG + 1, reg_val[1]);
return -EFAULT;
}
/**
* goodix_update_prepare - update prepare, loading ISP program
* and make sure the ISP is running.
* @fwu_ctrl: pointer to fimrware control structure
* return: 0 ok, <0 error
*/
static int goodix_update_prepare(struct fw_update_ctrl *fwu_ctrl)
{
struct goodix_ts_device *ts_dev = fwu_ctrl->ts_dev;
u8 reg_val[4] = { 0x00 };
u8 temp_buf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
int retry = 20;
int r;
ts_dev->hw_ops->write(ts_dev, HW_REG_CPU_RUN_FROM, temp_buf, 8);
/*reset IC*/
fwu_ctrl->allow_reset = true;
ts_info("firmware update, reset");
gpio_direction_output(ts_dev->board_data.reset_gpio, 0);
udelay(2000);
gpio_direction_output(ts_dev->board_data.reset_gpio, 1);
usleep_range(10000, 11000);
fwu_ctrl->allow_reset = false;
retry = 20;
do {
reg_val[0] = 0x24;
r = goodix_reg_write_confirm(ts_dev, HW_REG_CPU_CTRL,
reg_val, 1);
if (r < 0) {
ts_info("Failed to hold ss51, retry");
msleep(20);
} else {
break;
}
} while (--retry);
if (!retry) {
ts_err("Failed hold ss51,return =%d", r);
return -EINVAL;
}
ts_debug("Success hold ss51");
/* enable DSP & MCU power */
reg_val[0] = 0x00;
r = goodix_reg_write_confirm(ts_dev, HW_REG_DSP_MCU_POWER, reg_val, 1);
if (r < 0) {
ts_err("Failed enable DSP&MCU power");
return r;
}
ts_debug("Success enabled DSP&MCU power,set 0x%x-->0x00",
HW_REG_DSP_MCU_POWER);
/* disable watchdog timer */
reg_val[0] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_CACHE, reg_val, 1);
if (r < 0) {
ts_err("Failed to clear cache");
return r;
}
ts_debug("Success clear cache");
reg_val[0] = 0x95;
r = goodix_reg_write(ts_dev, HW_REG_ESD_KEY, reg_val, 1);
reg_val[0] = 0x00;
r |= goodix_reg_write(ts_dev, HW_REG_WTD_TIMER, reg_val, 1);
reg_val[0] = 0x27;
r |= goodix_reg_write(ts_dev, HW_REG_ESD_KEY, reg_val, 1);
if (r < 0) {
ts_err("Failed to disable watchdog");
return r;
}
ts_debug("Success disable watchdog");
/* set scramble */
reg_val[0] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_SCRAMBLE, reg_val, 1);
if (r < 0) {
ts_err("Failed to set scramble");
return r;
}
ts_debug("Succcess set scramble");
/* load ISP code and run form isp */
r = goodix_load_isp(ts_dev, &fwu_ctrl->fw_data);
if (r < 0)
ts_err("Failed lode and run isp");
return r;
}
/**
* goodix_format_fw_packet - formate one flash packet
* @pkt: target firmware packet
* @flash_addr: flash address
* @size: packet size
* @data: packet data
*/
static int goodix_format_fw_packet(u8 *pkt, u32 flash_addr,
u16 len, const u8 *data)
{
u16 checksum;
if (!pkt || !data)
return -EINVAL;
/*
* checksum rule:sum of data in one format is equal to zero
* data format: byte/le16/be16/le32/be32/le64/be64
*/
pkt[0] = (len >> 8) & 0xff;
pkt[1] = len & 0xff;
/* u16 >> 16bit seems nosense but really important */
pkt[2] = (flash_addr >> 16) & 0xff;
pkt[3] = (flash_addr >> 8) & 0xff;
memcpy(&pkt[4], data, len);
checksum = checksum_be16(pkt, len + 4);
checksum = 0 - checksum;
pkt[len + 4] = (checksum >> 8) & 0xff;
pkt[len + 5] = checksum & 0xff;
return 0;
}
/**
* goodix_send_fw_packet - send one firmware packet to ISP
* @dev: target touch device
* @pkt: firmware packet
* return:0 ok, <0 error
*/
static int goodix_send_fw_packet(struct goodix_ts_device *dev, u8 type,
u8 *pkt, u32 len)
{
u8 reg_val[4];
int r, i;
if (!pkt)
return -EINVAL;
ts_info("target fw subsys type:0x%x, len %d", type, len);
r = goodix_reg_write_confirm(dev, HW_REG_ISP_BUFFER, pkt, len);
if (r < 0) {
ts_err("Failed to write firmware packet");
return r;
}
reg_val[0] = 0;
reg_val[1] = 0;
/* clear flash flag 0X6022 */
r = goodix_reg_write_confirm(dev, HW_REG_FLASH_FLAG, reg_val, 2);
if (r < 0) {
ts_err("Faile to clear flash flag");
return r;
}
/* write subsystem type 0X8020*/
reg_val[0] = type;
reg_val[1] = type;
r = goodix_reg_write_confirm(dev, HW_REG_SUBSYS_TYPE, reg_val, 2);
if (r < 0) {
ts_err("Failed write subsystem type to IC");
return r;
}
for (i = 0; i < TS_READ_FLASH_STATE_RETRY_TIMES; i++) {
r = goodix_reg_read(dev, HW_REG_FLASH_FLAG, reg_val, 2);
if (r < 0) {
ts_err("Failed read flash state");
return r;
}
/* flash haven't end */
if (reg_val[0] == ISP_STAT_WRITING &&
reg_val[1] == ISP_STAT_WRITING) {
ts_debug("Flash not ending...");
usleep_range(55000, 56000);
continue;
}
if (reg_val[0] == ISP_FLASH_SUCCESS &&
reg_val[1] == ISP_FLASH_SUCCESS) {
r = goodix_reg_read(dev, HW_REG_FLASH_FLAG, reg_val, 2);
if (!r && reg_val[0] == ISP_FLASH_SUCCESS &&
reg_val[1] == ISP_FLASH_SUCCESS) {
ts_info("Flash subsystem ok");
return 0;
}
}
if (reg_val[0] == ISP_FLASH_ERROR &&
reg_val[1] == ISP_FLASH_ERROR) {
ts_err(" Flash subsystem failed");
return -EAGAIN;
}
if (reg_val[0] == ISP_FLASH_CHECK_ERROR) {
ts_err("Subsystem checksum err");
return -EAGAIN;
}
usleep_range(250, 260);
}
ts_err("Wait for flash end timeout, 0x6022= %x %x",
reg_val[0], reg_val[1]);
return -EAGAIN;
}
/**
* goodix_flash_subsystem - flash subsystem firmware,
* Main flow of flashing firmware.
* Each firmware subsystem is divided into several
* packets, the max size of packet is limited to
* @{ISP_MAX_BUFFERSIZE}
* @dev: pointer to touch device
* @subsys: subsystem infomation
* return: 0 ok, < 0 error
*/
static int goodix_flash_subsystem(struct goodix_ts_device *dev,
struct fw_subsys_info *subsys)
{
u16 data_size, offset;
u32 total_size;
u32 subsys_base_addr = subsys->flash_addr << 8;
u8 *fw_packet;
int r = 0, i;
/*
* if bus(i2c/spi) error occued, then exit, we will do
* hardware reset and re-prepare ISP and then retry
* flashing
*/
total_size = subsys->size;
fw_packet = kzalloc(ISP_MAX_BUFFERSIZE + 6, GFP_KERNEL);
if (!fw_packet) {
ts_err("Failed alloc memory");
return -EINVAL;
}
offset = 0;
while (total_size > 0) {
data_size = total_size > ISP_MAX_BUFFERSIZE ?
ISP_MAX_BUFFERSIZE : total_size;
ts_info("Flash firmware to %08x,size:%u bytes",
subsys_base_addr + offset, data_size);
/* format one firmware packet */
r = goodix_format_fw_packet(fw_packet, subsys_base_addr + offset,
data_size, &subsys->data[offset]);
if (r < 0) {
ts_err("Invalid packet params");
goto exit;
}
/* send one firmware packet, retry 3 time if send failed */
for (i = 0; i < 3; i++) {
r = goodix_send_fw_packet(dev, subsys->type,
fw_packet, data_size + 6);
if (!r)
break;
}
if (r) {
ts_err("Failed flash subsystem");
goto exit;
}
offset += data_size;
total_size -= data_size;
} /* end while */
exit:
kfree(fw_packet);
return r;
}
/**
* goodix_flash_firmware - flash firmware
* @dev: pointer to touch device
* @fw_data: firmware data
* return: 0 ok, < 0 error
*/
static int goodix_flash_firmware(struct goodix_ts_device *dev,
struct firmware_data *fw_data)
{
struct fw_update_ctrl *fw_ctrl;
struct firmware_info *fw_info;
struct fw_subsys_info *fw_x;
int retry = GOODIX_BUS_RETRY_TIMES;
int i, r = 0, fw_num, prog_step;
u8 *fw_packet = NULL;
u8 *flash_cfg = NULL;
/* start from subsystem 1,
* subsystem 0 is the ISP program
*/
fw_ctrl = container_of(fw_data, struct fw_update_ctrl, fw_data);
fw_info = &fw_data->fw_info;
fw_num = fw_info->subsys_num;
/* we have 80% work here */
prog_step = 80 / (fw_num - 1);
for (i = 1; i < fw_num && retry;) {
ts_info("--- Start to flash subsystem[%d] ---", i);
fw_x = &fw_info->subsys[i];
r = goodix_flash_subsystem(dev, fw_x);
if (r == 0) {
ts_info("--- End flash subsystem[%d]: OK ---", i);
fw_ctrl->progress += prog_step;
i++;
} else if (r == -EAGAIN) {
retry--;
ts_err("--- End flash subsystem%d: Fail, errno:%d, retry:%d ---",
i, r, GOODIX_BUS_RETRY_TIMES - retry);
} else if (r < 0) { /* bus error */
ts_err("--- End flash subsystem%d: Fatal error:%d exit ---",
i, r);
break;
}
}
kfree(fw_packet);
fw_packet = NULL;
kfree(flash_cfg);
flash_cfg = NULL;
return r;
}
/**
* goodix_update_finish - update finished, free resource
* and reset flags---
* @fwu_ctrl: pointer to fw_update_ctrl structrue
* return: 0 ok, < 0 error
*/
static int goodix_update_finish(struct goodix_ts_device *ts_dev,
struct fw_update_ctrl *fwu_ctrl)
{
u8 reg_val[8] = {0};
int r = 0;
/* hold ss51 */
reg_val[0] = 0x24;
r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL,
reg_val, 1);
if (r < 0)
ts_err("Failed to hold ss51");
/* clear back door flag */
memset(reg_val, 0, sizeof(reg_val));
r = goodix_reg_write(ts_dev, HW_REG_CPU_RUN_FROM, reg_val, 8);
if (r) {
ts_err("Failed set CPU run from normal firmware");
return r;
}
/* release ss51 */
reg_val[0] = 0x00;
r = goodix_reg_write(ts_dev, HW_REG_CPU_CTRL, reg_val, 1);
if (r < 0)
ts_err("Failed to run ss51");
/*reset*/
r = ts_dev->hw_ops->reset(ts_dev);
return r;
}
static int goodix_flash_config(struct goodix_ts_device *ts_dev)
{
int ret;
u8 *fw_packet = NULL;
u8 *temp_data = NULL;
if (!ts_dev || !ts_dev->cfg_bin_state ||
!ts_dev->normal_cfg.initialized) {
ts_err("no valid config data for flash");
return -EINVAL;
}
temp_data = kzalloc(ISP_MAX_BUFFERSIZE, GFP_KERNEL);
fw_packet = kzalloc(ISP_MAX_BUFFERSIZE + 6, GFP_KERNEL);
if (!temp_data || !fw_packet) {
ts_err("Failed alloc memory");
ret = -EINVAL;
goto exit;
}
memset(temp_data, 0xFF, ISP_MAX_BUFFERSIZE);
ts_info("normal config length %d", ts_dev->normal_cfg.length);
memcpy(temp_data, ts_dev->normal_cfg.data, ts_dev->normal_cfg.length);
/* format one firmware packet */
ret = goodix_format_fw_packet(fw_packet, FLASH_ADDR_CONFIG_DATA,
ISP_MAX_BUFFERSIZE, temp_data);
if (ret < 0) {
ts_err("Invalid packet params");
goto exit;
}
ts_debug("fw_pack:%*ph", 10, fw_packet);
ts_info("try flash config");
ret = goodix_send_fw_packet(ts_dev, FLASH_SUBSYS_TYPE_CONFIG,
fw_packet, ISP_MAX_BUFFERSIZE + 6);
if (ret)
ts_err("failed flash config, ret %d", ret);
else
ts_info("success flash config with isp");
exit:
kfree(temp_data);
kfree(fw_packet);
return ret;
}
/**
* goodix_fw_update_proc - firmware update process, the entry of
* firmware update flow
* @fwu_ctrl: firmware control
* return: 0 ok, < 0 error
*/
static int goodix_fw_update_proc(struct fw_update_ctrl *fwu_ctrl)
{
#define FW_UPDATE_RETRY 2
int retry0 = FW_UPDATE_RETRY;
int retry1 = FW_UPDATE_RETRY;
int r = 0;
if (fwu_ctrl->status == UPSTA_PREPARING ||
fwu_ctrl->status == UPSTA_UPDATING) {
ts_err("Firmware update already in progress");
return -EINVAL;
}
fwu_ctrl->progress = 0;
fwu_ctrl->status = UPSTA_PREPARING;
r = goodix_parse_firmware(&fwu_ctrl->fw_data);
if (r < 0) {
fwu_ctrl->status = UPSTA_ABORT;
goto err_parse_fw;
}
fwu_ctrl->progress = 10;
if (!(fwu_ctrl->mode & UPDATE_MODE_FORCE)) {
r = goodix_check_update(fwu_ctrl->ts_dev,
&fwu_ctrl->fw_data.fw_info);
if (!r) {
fwu_ctrl->status = UPSTA_ABORT;
ts_info("fw update skiped");
goto err_check_update;
}
} else {
ts_info("force update mode");
}
start_update:
fwu_ctrl->progress = 20;
fwu_ctrl->status = UPSTA_UPDATING; /* show upgrading status */
r = goodix_update_prepare(fwu_ctrl);
if ((r == -EBUS || r == -EAGAIN) && --retry0 > 0) {
ts_err("Bus error, retry prepare ISP:%d",
FW_UPDATE_RETRY - retry0);
goto start_update;
} else if (r < 0) {
ts_err("Failed to prepare ISP, exit update:%d", r);
fwu_ctrl->status = UPSTA_FAILED;
goto err_fw_prepare;
}
if (GOODIX_FLASH_CONFIG_WITH_ISP &&
fwu_ctrl->mode & UPDATE_MODE_FLASH_CFG) {
ts_info("need flash config with isp");
goodix_flash_config(fwu_ctrl->ts_dev);
}
/* progress: 20%~100% */
r = goodix_flash_firmware(fwu_ctrl->ts_dev, &fwu_ctrl->fw_data);
if ((r == -EBUS || r == -ETIMEOUT) && --retry1 > 0) {
/* we will retry[twice] if returns bus error[i2c/spi]
* we will do hardware reset and re-prepare ISP and then retry
* flashing
*/
ts_err("Bus error, retry firmware update:%d",
FW_UPDATE_RETRY - retry1);
goto start_update;
} else if (r < 0) {
ts_err("Fatal error, exit update:%d", r);
fwu_ctrl->status = UPSTA_FAILED;
goto err_fw_flash;
}
fwu_ctrl->status = UPSTA_SUCCESS;
err_fw_flash:
err_fw_prepare:
goodix_update_finish(fwu_ctrl->ts_dev, fwu_ctrl);
err_check_update:
err_parse_fw:
if (fwu_ctrl->status == UPSTA_SUCCESS)
ts_info("Firmware update successfully");
else if (fwu_ctrl->status == UPSTA_FAILED)
ts_err("Firmware update failed");
fwu_ctrl->progress = 100; /* 100% */
ts_info("fw update ret %d", r);
return r;
}
static struct goodix_ext_module goodix_fwu_module;
/**
* goodix_request_firmware - request firmware data from user space
*
* @fw_data: firmware struct, contains firmware header info
* and firmware data pointer.
* return: 0 - OK, < 0 - error
*/
static int goodix_request_firmware(struct firmware_data *fw_data,
const char *name)
{
struct fw_update_ctrl *fw_ctrl =
container_of(fw_data, struct fw_update_ctrl, fw_data);
struct device *dev = fw_ctrl->ts_dev->dev;
int r;
ts_info("Request firmware image [%s]", name);
r = request_firmware(&fw_data->firmware, name, dev);
if (r < 0)
ts_err("Firmware image [%s] not available,errno:%d", name, r);
else
ts_info("Firmware image [%s] is ready", name);
return r;
}
/**
* relase firmware resources
*
*/
static inline void goodix_release_firmware(struct firmware_data *fw_data)
{
if (fw_data->firmware) {
release_firmware(fw_data->firmware);
fw_data->firmware = NULL;
}
}
static int goodix_fw_update_thread(void *data)
{
struct fw_update_ctrl *fwu_ctrl = data;
struct firmware *temp_firmware = NULL;
int r = -EINVAL;
mutex_lock(&fwu_ctrl->mutex);
if (fwu_ctrl->mode & UPDATE_MODE_SRC_HEAD) {
ts_info("Firmware header update starts");
temp_firmware = kzalloc(sizeof(struct firmware), GFP_KERNEL);
if (!temp_firmware) {
ts_err("Failed to allocate memory for firmware");
goto out;
}
temp_firmware->size = sizeof(goodix_default_fw);
temp_firmware->data = goodix_default_fw;
fwu_ctrl->fw_data.firmware = temp_firmware;
} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) {
ts_info("Firmware request update starts");
r = goodix_request_firmware(&fwu_ctrl->fw_data,
fwu_ctrl->fw_name);
if (r < 0) {
fwu_ctrl->status = UPSTA_ABORT;
fwu_ctrl->progress = 100;
goto out;
}
} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) {
if (!fwu_ctrl->fw_data.firmware) {
ts_err("Invalid firmware from sysfs");
fwu_ctrl->status = UPSTA_ABORT;
fwu_ctrl->progress = 100;
r = -EINVAL;
goto out;
}
} else {
ts_err("unknown update mode 0x%x", fwu_ctrl->mode);
r = -EINVAL;
goto out;
}
goodix_register_ext_module(&goodix_fwu_module);
/* DONT allow reset/irq/suspend/resume during update */
fwu_ctrl->allow_irq = false;
fwu_ctrl->allow_suspend = false;
fwu_ctrl->allow_resume = false;
fwu_ctrl->allow_reset = false;
ts_debug("notify update start");
goodix_ts_blocking_notify(NOTIFY_FWUPDATE_START, NULL);
/* ready to update */
ts_debug("start update proc");
r = goodix_fw_update_proc(fwu_ctrl);
fwu_ctrl->allow_reset = true;
fwu_ctrl->allow_irq = true;
fwu_ctrl->allow_suspend = true;
fwu_ctrl->allow_resume = true;
/* clean */
if (fwu_ctrl->mode & UPDATE_MODE_SRC_HEAD) {
kfree(fwu_ctrl->fw_data.firmware);
fwu_ctrl->fw_data.firmware = NULL;
temp_firmware = NULL;
} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_REQUEST) {
goodix_release_firmware(&fwu_ctrl->fw_data);
} else if (fwu_ctrl->mode & UPDATE_MODE_SRC_SYSFS) {
vfree(fwu_ctrl->fw_data.firmware);
fwu_ctrl->fw_data.firmware = NULL;
}
goodix_unregister_ext_module(&goodix_fwu_module);
out:
fwu_ctrl->mode = UPDATE_MODE_DEFAULT;
mutex_unlock(&fwu_ctrl->mutex);
if (r) {
ts_err("fw update failed, %d", r);
goodix_ts_blocking_notify(NOTIFY_FWUPDATE_FAILED, NULL);
} else {
ts_info("fw update success");
goodix_ts_blocking_notify(NOTIFY_FWUPDATE_SUCCESS, NULL);
}
return r;
}
/*
* goodix_sysfs_update_en_store: start fw update manually
* @buf: '1'[001] update in blocking mode with fwdata from sysfs
* '2'[010] update in blocking mode with fwdata from request
* '5'[101] update in unblocking mode with fwdata from sysfs
* '6'[110] update in unblocking mode with fwdata from request
*/
static ssize_t goodix_sysfs_update_en_store(
struct goodix_ext_module *module,
const char *buf, size_t count)
{
int ret = 0;
int mode = 0;
struct fw_update_ctrl *fw_ctrl = module->priv_data;
if (!buf || count <= 0) {
ts_err("invalid params");
return -EINVAL;
}
if (!fw_ctrl || !fw_ctrl->initialized) {
ts_err("fw module uninit");
return -EINVAL;
}
ts_info("set update mode:0x%x", buf[0]);
if (buf[0] == '1') {
mode = UPDATE_MODE_FORCE|UPDATE_MODE_BLOCK|UPDATE_MODE_SRC_SYSFS;
} else if (buf[0] == '2') {
mode = UPDATE_MODE_FORCE|UPDATE_MODE_BLOCK|UPDATE_MODE_SRC_REQUEST;
} else if (buf[0] == '5') {
mode = UPDATE_MODE_FORCE|UPDATE_MODE_SRC_SYSFS;
} else if (buf[0] == '6') {
mode = UPDATE_MODE_FORCE|UPDATE_MODE_SRC_REQUEST;
} else {
ts_err("invalid update mode:0x%x", buf[0]);
return -EINVAL;
}
ret = goodix_do_fw_update(mode);
if (!ret) {
ts_info("success start update work");
return count;
}
ts_err("failed start fw update work");
return -EINVAL;
}
static ssize_t goodix_sysfs_update_progress_show(
struct goodix_ext_module *module,
char *buf)
{
struct fw_update_ctrl *fw_ctrl = module->priv_data;
return scnprintf(buf, PAGE_SIZE, "%d\n", fw_ctrl->progress);
}
static ssize_t goodix_sysfs_update_result_show(
struct goodix_ext_module *module,
char *buf)
{
char *result = NULL;
struct fw_update_ctrl *fw_ctrl = module->priv_data;
ts_info("result show");
switch (fw_ctrl->status) {
case UPSTA_NOTWORK:
result = "notwork";
break;
case UPSTA_PREPARING:
result = "preparing";
break;
case UPSTA_UPDATING:
result = "upgrading";
break;
case UPSTA_ABORT:
result = "abort";
break;
case UPSTA_SUCCESS:
result = "success";
break;
case UPSTA_FAILED:
result = "failed";
break;
default:
break;
}
return scnprintf(buf, PAGE_SIZE, "%s\n", result);
}
static ssize_t goodix_sysfs_update_fwversion_show(
struct goodix_ext_module *module,
char *buf)
{
struct goodix_ts_version fw_ver;
struct fw_update_ctrl *fw_ctrl = module->priv_data;
int r = 0;
char str[5];
/* read version from chip */
r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev,
&fw_ver);
if (!r) {
memcpy(str, fw_ver.pid, 4);
str[4] = '\0';
return scnprintf(buf, PAGE_SIZE, "PID:%s VID:%02x %02x %02x %02x SENSOR_ID:%d\n",
str, fw_ver.vid[0], fw_ver.vid[1],
fw_ver.vid[2], fw_ver.vid[3], fw_ver.sensor_id);
}
return 0;
}
static ssize_t goodix_sysfs_fwsize_show(struct goodix_ext_module *module,
char *buf)
{
struct fw_update_ctrl *fw_ctrl = module->priv_data;
int r = -EINVAL;
if (fw_ctrl && fw_ctrl->fw_data.firmware)
r = snprintf(buf, PAGE_SIZE, "%zu\n",
fw_ctrl->fw_data.firmware->size);
return r;
}
static ssize_t goodix_sysfs_fwsize_store(struct goodix_ext_module *module,
const char *buf, size_t count)
{
struct fw_update_ctrl *fw_ctrl = module->priv_data;
struct firmware *fw;
u8 **data;
size_t size = 0;
if (!fw_ctrl)
return -EINVAL;
if (sscanf(buf, "%zu", &size) < 0 || !size) {
ts_err("Failed to get fwsize");
return -EFAULT;
}
/* use vmalloc to alloc huge memory */
fw = vmalloc(sizeof(*fw) + size);
if (fw == NULL) {
ts_err("Failed to alloc memory,size:%zu",
size + sizeof(*fw));
return -ENOMEM;
}
mutex_lock(&fw_ctrl->mutex);
memset(fw, 0x00, sizeof(*fw) + size);
data = (u8 **)&fw->data;
*data = (u8 *)fw + sizeof(struct firmware);
fw->size = size;
fw_ctrl->fw_data.firmware = fw;
fw_ctrl->mode = UPDATE_MODE_SRC_SYSFS;
mutex_unlock(&fw_ctrl->mutex);
return count;
}
static ssize_t goodix_sysfs_fwimage_store(struct file *file,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t pos, size_t count)
{
struct fw_update_ctrl *fw_ctrl;
struct firmware_data *fw_data;
fw_ctrl = container_of(attr, struct fw_update_ctrl,
attr_fwimage);
fw_data = &fw_ctrl->fw_data;
if (!fw_data->firmware) {
ts_err("Need set fw image size first");
return -ENOMEM;
}
if (fw_data->firmware->size == 0) {
ts_err("Invalid firmware size");
return -EINVAL;
}
if (pos + count > fw_data->firmware->size)
return -EFAULT;
mutex_lock(&fw_ctrl->mutex);
memcpy((u8 *)&fw_data->firmware->data[pos], buf, count);
mutex_unlock(&fw_ctrl->mutex);
return count;
}
/* this interface has ben deprecated */
static ssize_t goodix_sysfs_force_update_store(
struct goodix_ext_module *module,
const char *buf, size_t count)
{
return count;
}
static struct goodix_ext_attribute goodix_fwu_attrs[] = {
__EXTMOD_ATTR(update_en, S_IWUGO, NULL, goodix_sysfs_update_en_store),
__EXTMOD_ATTR(progress, S_IRUGO, goodix_sysfs_update_progress_show, NULL),
__EXTMOD_ATTR(result, S_IRUGO, goodix_sysfs_update_result_show, NULL),
__EXTMOD_ATTR(fwversion, S_IRUGO,
goodix_sysfs_update_fwversion_show, NULL),
__EXTMOD_ATTR(fwsize, S_IRUGO | S_IWUGO, goodix_sysfs_fwsize_show,
goodix_sysfs_fwsize_store),
__EXTMOD_ATTR(force_update, S_IWUGO, NULL,
goodix_sysfs_force_update_store),
};
static int goodix_fw_sysfs_init(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct fw_update_ctrl *fw_ctrl = module->priv_data;
struct kobj_type *ktype;
int ret = 0, i;
ktype = goodix_get_default_ktype();
ret = kobject_init_and_add(&module->kobj,
ktype,
&core_data->pdev->dev.kobj,
"fwupdate");
if (ret) {
ts_err("Create fwupdate sysfs node error!");
goto exit_sysfs_init;
}
ret = 0;
for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs) && !ret; i++)
ret = sysfs_create_file(&module->kobj, &goodix_fwu_attrs[i].attr);
if (ret) {
ts_err("failed create fwu sysfs files");
while (--i >= 0)
sysfs_remove_file(&module->kobj, &goodix_fwu_attrs[i].attr);
kobject_put(&module->kobj);
ret = -EINVAL;
goto exit_sysfs_init;
}
fw_ctrl->attr_fwimage.attr.name = "fwimage";
fw_ctrl->attr_fwimage.attr.mode = S_IRUGO | S_IWUGO;
fw_ctrl->attr_fwimage.size = 0;
fw_ctrl->attr_fwimage.write = goodix_sysfs_fwimage_store;
ret = sysfs_create_bin_file(&module->kobj,
&fw_ctrl->attr_fwimage);
exit_sysfs_init:
return ret;
}
static void goodix_fw_sysfs_remove(struct goodix_ext_module *module)
{
struct fw_update_ctrl *fw_ctrl = module->priv_data;
int i;
sysfs_remove_bin_file(&module->kobj, &fw_ctrl->attr_fwimage);
for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++)
sysfs_remove_file(&module->kobj,
&goodix_fwu_attrs[i].attr);
kobject_put(&module->kobj);
}
int goodix_do_fw_update(int mode)
{
struct task_struct *fwu_thrd;
struct fw_update_ctrl *fwu_ctrl = &goodix_fw_update_ctrl;
int ret;
if (!fwu_ctrl->initialized) {
ts_err("fw mode uninit");
return -EINVAL;
}
fwu_ctrl->mode = mode;
ts_debug("fw update mode 0x%x", mode);
if (fwu_ctrl->mode & UPDATE_MODE_BLOCK) {
ret = goodix_fw_update_thread(fwu_ctrl);
ts_info("fw update return %d", ret);
return ret;
} else {
/* create and run update thread */
fwu_thrd = kthread_run(goodix_fw_update_thread,
fwu_ctrl, "goodix-fwu");
if (IS_ERR_OR_NULL(fwu_thrd)) {
ts_err("Failed to create update thread:%ld",
PTR_ERR(fwu_thrd));
return -EFAULT;
}
ts_info("success create fw update thread");
return 0;
}
}
static int goodix_fw_update_init(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
int ret = 0;
struct goodix_ts_board_data *ts_bdata = board_data(core_data);
if (goodix_fw_update_ctrl.initialized) {
ts_info("no need reinit");
return ret;
}
if (!core_data || !ts_bdata || !core_data->ts_dev) {
ts_err("core_data && ts_dev cann't be null");
return -ENODEV;
}
mutex_lock(&goodix_fw_update_ctrl.mutex);
module->priv_data = &goodix_fw_update_ctrl;
goodix_fw_update_ctrl.ts_dev = core_data->ts_dev;
goodix_fw_update_ctrl.allow_reset = true;
goodix_fw_update_ctrl.allow_irq = true;
goodix_fw_update_ctrl.allow_suspend = true;
goodix_fw_update_ctrl.allow_resume = true;
goodix_fw_update_ctrl.core_data = core_data;
goodix_fw_update_ctrl.mode = 0;
/* find a valid firmware image name */
if (ts_bdata && ts_bdata->fw_name)
strlcpy(goodix_fw_update_ctrl.fw_name, ts_bdata->fw_name,
sizeof(goodix_fw_update_ctrl.fw_name));
else
strlcpy(goodix_fw_update_ctrl.fw_name, TS_DEFAULT_FIRMWARE,
sizeof(goodix_fw_update_ctrl.fw_name));
ret = goodix_fw_sysfs_init(core_data, module);
if (ret) {
ts_err("failed create fwupate sysfs node");
goto err_out;
}
goodix_fw_update_ctrl.initialized = 1;
err_out:
mutex_unlock(&goodix_fw_update_ctrl.mutex);
return ret;
}
static int goodix_fw_update_exit(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
return 0;
}
static struct goodix_ext_module goodix_fwu_module;
void goodix_fw_module_preinit(struct goodix_ts_core *core_data)
{
if (goodix_fw_update_init(core_data, &goodix_fwu_module))
ts_err("fw module preinit failed");
else
ts_info("fw module preinit success");
}
EXPORT_SYMBOL_GPL(goodix_fw_module_preinit);
static int goodix_fw_before_suspend(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct fw_update_ctrl *fwu_ctrl = module->priv_data;
return fwu_ctrl->allow_suspend ?
EVT_HANDLED : EVT_CANCEL_SUSPEND;
}
static int goodix_fw_before_resume(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct fw_update_ctrl *fwu_ctrl = module->priv_data;
return fwu_ctrl->allow_resume ?
EVT_HANDLED : EVT_CANCEL_RESUME;
}
static int goodix_fw_after_resume(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
return 0;
}
static int goodix_fw_irq_event(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct fw_update_ctrl *fwu_ctrl = module->priv_data;
return fwu_ctrl->allow_irq ?
EVT_HANDLED : EVT_CANCEL_IRQEVT;
}
static int goodix_fw_before_reset(struct goodix_ts_core *core_data,
struct goodix_ext_module *module)
{
struct fw_update_ctrl *fwu_ctrl = module->priv_data;
return fwu_ctrl->allow_reset ?
EVT_HANDLED : EVT_CANCEL_RESET;
}
static const struct goodix_ext_module_funcs goodix_ext_funcs = {
.init = goodix_fw_update_init,
.exit = goodix_fw_update_exit,
.before_reset = goodix_fw_before_reset,
.after_reset = NULL,
.before_suspend = goodix_fw_before_suspend,
.after_suspend = NULL,
.before_resume = goodix_fw_before_resume,
.after_resume = goodix_fw_after_resume,
.irq_event = goodix_fw_irq_event,
};
static struct goodix_ext_module goodix_fwu_module = {
.name = "goodix-fwu",
.funcs = &goodix_ext_funcs,
.priority = EXTMOD_PRIO_FWUPDATE,
};
static int __init goodix_fwu_module_init(void)
{
ts_info("goodix_fwupdate_module_ini IN");
mutex_init(&goodix_fw_update_ctrl.mutex);
return goodix_register_ext_module(&goodix_fwu_module);
}
static void __exit goodix_fwu_module_exit(void)
{
mutex_lock(&goodix_fw_update_ctrl.mutex);
goodix_unregister_ext_module(&goodix_fwu_module);
if (goodix_fw_update_ctrl.initialized) {
goodix_fw_sysfs_remove(&goodix_fwu_module);
goodix_fw_update_ctrl.initialized = 0;
}
mutex_lock(&goodix_fw_update_ctrl.mutex);
}
module_init(goodix_fwu_module_init);
module_exit(goodix_fwu_module_exit);
MODULE_DESCRIPTION("Goodix FWU Module");
MODULE_AUTHOR("Goodix, Inc.");
MODULE_LICENSE("GPL v2");