Firmware upgrade via update_fw sysfs node for Lego series. Lego series includes ILITEK 213X/23XX/25XX. Tested/passed with evaluation board with ILI2520 IC. Signed-off-by: Joe Hung <joe_hung@xxxxxxxxxx> --- drivers/input/touchscreen/ilitek_ts_i2c.c | 721 +++++++++++++++++++++- 1 file changed, 719 insertions(+), 2 deletions(-) diff --git a/drivers/input/touchscreen/ilitek_ts_i2c.c b/drivers/input/touchscreen/ilitek_ts_i2c.c index c5d259c76adc..b5ada9b44ab1 100644 --- a/drivers/input/touchscreen/ilitek_ts_i2c.c +++ b/drivers/input/touchscreen/ilitek_ts_i2c.c @@ -21,6 +21,7 @@ #include <linux/acpi.h> #include <linux/input/touchscreen.h> #include <asm/unaligned.h> +#include <linux/firmware.h> #define ILITEK_TS_NAME "ilitek_ts" @@ -37,14 +38,48 @@ #define ILITEK_TP_CMD_GET_MCU_VER 0x61 #define ILITEK_TP_CMD_GET_IC_MODE 0xC0 +#define ILITEK_TP_CMD_SET_MOD_CTRL 0xF0 +#define ILITEK_TP_CMD_GET_SYS_BUSY 0x80 +#define ILITEK_TP_CMD_SET_W_FLASH 0xCC +#define ILITEK_TP_CMD_SET_AP_MODE 0xC1 +#define ILITEK_TP_CMD_SET_BL_MODE 0xC2 +#define ILITEK_TP_CMD_GET_BLK_CRC 0xCD +#define ILITEK_TP_CMD_SET_W_DATA 0xC3 +#define ILITEK_TP_CMD_SET_DATA_LEN 0xC9 + #define REPORT_COUNT_ADDRESS 61 #define ILITEK_SUPPORT_MAX_POINT 40 +#define ILITEK_CRC_POLY 0x8408 +#define ILITEK_HEX_UPGRADE_SIZE (256 * 1024) +#define ILITEK_UPGRADE_LEN 2048 +#define MOD_BL 0x55 +#define MOD_AP 0x5A + +#define ENTER_NORMAL_MODE 0 +#define ENTER_SUSPEND_MODE 3 + struct ilitek_protocol_info { u16 ver; u8 ver_major; }; +struct ilitek_block_info { + u32 start; + u32 end; + u16 ic_crc; + u16 fw_crc; + bool chk_crc; +}; + +struct ilitek_upgrade_info { + u16 fw_mcu_ver; + u32 map_ver; + u32 blk_num; + u32 fw_size; + struct ilitek_block_info *blk; +}; + struct ilitek_ts_data { struct i2c_client *client; struct gpio_desc *reset_gpio; @@ -65,6 +100,9 @@ struct ilitek_ts_data { s32 screen_min_x; s32 screen_min_y; s32 max_tp; + + /* FW Upgrade */ + struct ilitek_upgrade_info upg; }; struct ilitek_protocol_map { @@ -84,6 +122,16 @@ enum ilitek_cmds { SET_IC_SLEEP, SET_IC_WAKE, + SET_MOD_CTRL, + GET_SYS_BUSY, + SET_FLASH_AP, + SET_BL_MODE, + SET_AP_MODE, + GET_BLK_CRC, + SET_DATA_LEN, + SET_FLASH_BL, + SET_W_DATA, + /* ALWAYS keep at the end */ MAX_CMD_CNT }; @@ -227,6 +275,133 @@ static int api_protocol_set_cmd(struct ilitek_ts_data *ts, return 0; } +static int ilitek_check_busy(struct ilitek_ts_data *ts, u32 timeout) +{ + int error; + u8 buf[2]; + u32 t = 0; + + do { + error = api_protocol_set_cmd(ts, GET_SYS_BUSY, NULL, buf); + if (error) + return error; + + if ((buf[0] & 0x51) == 0x50) + return 0; + + msleep(20); + t += 20; + } while (timeout > t); + + return -EBUSY; +} + +static int ilitek_set_flash_BL1_8(struct ilitek_ts_data *ts, u32 start, u32 end) +{ + u8 inbuf[64]; + + inbuf[3] = start & 0xFF; + inbuf[4] = (start >> 8) & 0xFF; + inbuf[5] = (start >> 16) & 0xFF; + inbuf[6] = end & 0xFF; + inbuf[7] = (end >> 8) & 0xFF; + inbuf[8] = (end >> 16) & 0xFF; + return api_protocol_set_cmd(ts, SET_FLASH_BL, inbuf, NULL); +} + +static int ilitek_set_data_len(struct ilitek_ts_data *ts, u32 data_len) +{ + u8 inbuf[3]; + + inbuf[1] = data_len & 0xFF; + inbuf[2] = (data_len >> 8) & 0xFF; + return api_protocol_set_cmd(ts, SET_DATA_LEN, inbuf, NULL); +} + +static int ilitek_get_blk_crc(struct ilitek_ts_data *ts, u32 start, u32 end, + u8 type, u16 *crc) +{ + u8 inbuf[8], outbuf[2]; + int error; + + inbuf[1] = type; + inbuf[2] = start & 0xFF; + inbuf[3] = (start >> 8) & 0xFF; + inbuf[4] = (start >> 16) & 0xFF; + inbuf[5] = end & 0xFF; + inbuf[6] = (end >> 8) & 0xFF; + inbuf[7] = (end >> 16) & 0xFF; + + error = api_protocol_set_cmd(ts, GET_BLK_CRC, inbuf, outbuf); + if (error < 0) + return error; + + *crc = get_unaligned_le16(outbuf); + return 0; +} + +static int ilitek_switch_BLmode(struct ilitek_ts_data *ts, bool to_BLmode) +{ + int error, i; + struct device *dev = &ts->client->dev; + u8 outbuf[64]; + + error = api_protocol_set_cmd(ts, GET_IC_MODE, NULL, outbuf); + if (error < 0) + return error; + + dev_dbg(dev, "change mode:%x to %x\n", ts->ic_mode, + (to_BLmode) ? MOD_BL : MOD_AP); + + if ((ts->ic_mode == MOD_AP && !to_BLmode) || + (ts->ic_mode == MOD_BL && to_BLmode)) + return 0; + + for (i = 0; i < 5; i++) { + error = api_protocol_set_cmd(ts, SET_FLASH_AP, NULL, NULL); + if (error < 0) + return error; + msleep(20); + + error = api_protocol_set_cmd(ts, (to_BLmode) ? SET_BL_MODE : + SET_AP_MODE, NULL, NULL); + if (error < 0) + return error; + + msleep(500 + i * 100); + + error = api_protocol_set_cmd(ts, GET_IC_MODE, NULL, outbuf); + if (error < 0) + return error; + + if ((ts->ic_mode == MOD_AP && !to_BLmode) || + (ts->ic_mode == MOD_BL && to_BLmode)) + return 0; + } + + dev_err(dev, "switch mode failed, current mode:%X\n", ts->ic_mode); + + return -EFAULT; +} + +static int ilitek_set_testmode(struct ilitek_ts_data *ts, bool testmode) +{ + u8 inbuf[3]; + int error; + + + if (testmode) + inbuf[1] = ENTER_SUSPEND_MODE; + else + inbuf[1] = ENTER_NORMAL_MODE; + + error = api_protocol_set_cmd(ts, SET_MOD_CTRL, inbuf, NULL); + if (error < 0) + return error; + + return 0; +} + static int api_protocol_get_ptl_ver(struct ilitek_ts_data *ts, u16 cmd, u8 *inbuf, u8 *outbuf) { @@ -351,6 +526,101 @@ static int api_protocol_set_ic_wake(struct ilitek_ts_data *ts, return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0); } +static int api_protocol_set_mode_ctrl(struct ilitek_ts_data *ts, u16 cmd, + u8 *inbuf, u8 *outbuf) +{ + inbuf[0] = cmd; + inbuf[2] = 0; + + return ilitek_i2c_write_and_read(ts, inbuf, 3, 100, NULL, 0); +} + +static int api_protocol_get_sys_busy(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + u8 buf[64]; + + buf[0] = cmd; + return ilitek_i2c_write_and_read(ts, buf, 1, 1, outbuf, 1); +} + +static int api_protocol_set_write_flash_ap(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + u8 buf[64]; + + buf[0] = cmd; + buf[1] = 0x5A; + buf[2] = 0xA5; + return ilitek_i2c_write_and_read(ts, buf, 3, 0, NULL, 0); +} + +static int api_protocol_set_write_flash_bl(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + inbuf[0] = cmd; + inbuf[1] = 0x5A; + inbuf[2] = 0xA5; + return ilitek_i2c_write_and_read(ts, inbuf, 9, 0, NULL, 0); +} + +static int api_protocol_set_bl_mode(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + u8 buf[64]; + + buf[0] = cmd; + return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0); +} + +static int api_protocol_set_ap_mode(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + u8 buf[64]; + + buf[0] = cmd; + return ilitek_i2c_write_and_read(ts, buf, 1, 0, NULL, 0); +} + +static int api_protocol_get_blk_crc(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + int ret = 0; + + inbuf[0] = cmd; + + /* Ask FW to calculate CRC first */ + if (inbuf[1] == 0) { + ret = ilitek_i2c_write_and_read(ts, inbuf, 8, 0, NULL, 0); + if (ret < 0) + return ret; + ret = ilitek_check_busy(ts, 2500); + if (ret < 0) + return ret; + } + + inbuf[1] = 1; + return ilitek_i2c_write_and_read(ts, inbuf, 2, 1, outbuf, 2); +} + +static int api_protocol_set_data_length(struct ilitek_ts_data *ts, + u16 cmd, u8 *inbuf, u8 *outbuf) +{ + + inbuf[0] = cmd; + return ilitek_i2c_write_and_read(ts, inbuf, 3, 0, NULL, 0); +} + +static int api_protocol_set_write_data(struct ilitek_ts_data *ts, u16 cmd, + u8 *inbuf, u8 *outbuf) +{ + + inbuf[0] = cmd; + + return ilitek_i2c_write_and_read(ts, inbuf, ILITEK_UPGRADE_LEN + 1, 0, + NULL, 0); +} + static const struct ilitek_protocol_map ptl_func_map[] = { /* common cmds */ [GET_PTL_VER] = { @@ -371,11 +641,11 @@ static const struct ilitek_protocol_map ptl_func_map[] = { }, [GET_IC_MODE] = { ILITEK_TP_CMD_GET_IC_MODE, "GET_IC_MODE", - api_protocol_get_ic_mode + api_protocol_get_ic_mode }, [GET_MCU_VER] = { ILITEK_TP_CMD_GET_MCU_VER, "GET_MOD_VER", - api_protocol_get_mcu_ver + api_protocol_get_mcu_ver }, [SET_IC_SLEEP] = { ILITEK_TP_CMD_SET_IC_SLEEP, "SET_IC_SLEEP", @@ -385,6 +655,42 @@ static const struct ilitek_protocol_map ptl_func_map[] = { ILITEK_TP_CMD_SET_IC_WAKE, "SET_IC_WAKE", api_protocol_set_ic_wake }, + [SET_MOD_CTRL] = { + ILITEK_TP_CMD_SET_MOD_CTRL, "SET_MOD_CTRL", + api_protocol_set_mode_ctrl + }, + [GET_SYS_BUSY] = { + ILITEK_TP_CMD_GET_SYS_BUSY, "GET_SYS_BUSY", + api_protocol_get_sys_busy + }, + [SET_FLASH_AP] = { + ILITEK_TP_CMD_SET_W_FLASH, "SET_FLASH_AP", + api_protocol_set_write_flash_ap + }, + [SET_BL_MODE] = { + ILITEK_TP_CMD_SET_BL_MODE, "SET_BL_MODE", + api_protocol_set_bl_mode + }, + [SET_AP_MODE] = { + ILITEK_TP_CMD_SET_AP_MODE, "SET_AP_MODE", + api_protocol_set_ap_mode + }, + [GET_BLK_CRC] = { + ILITEK_TP_CMD_GET_BLK_CRC, "GET_BLK_CRC", + api_protocol_get_blk_crc + }, + [SET_DATA_LEN] = { + ILITEK_TP_CMD_SET_DATA_LEN, "SET_DATA_LEN", + api_protocol_set_data_length + }, + [SET_FLASH_BL] = { + ILITEK_TP_CMD_SET_W_FLASH, "SET_FLASH_BL", + api_protocol_set_write_flash_bl + }, + [SET_W_DATA] = { + ILITEK_TP_CMD_SET_W_DATA, "SET_W_DATA", + api_protocol_set_write_data + }, }; /* Probe APIs */ @@ -532,9 +838,419 @@ static ssize_t product_id_show(struct device *dev, } static DEVICE_ATTR_RO(product_id); +static u32 ilitek_find_endaddr(u32 start, u32 end, u8 *buf, bool ap) +{ + u32 addr; + char ap_tag[16] = "ILITek AP CRC "; + char blk_tag[16] = "ILITek END TAG "; + char tag[32]; + + memset(tag, 0xff, 32); + if (ap) + memcpy(tag + 16, ap_tag, 16); + else + memcpy(tag + 16, blk_tag, 16); + + for (addr = start; addr <= end - 32 && + addr < ILITEK_HEX_UPGRADE_SIZE - 32; addr++) { + if (!strncmp(buf + addr, tag, 32)) + return addr + 33; + } + + return end; +} + +static int ilitek_info_mapping(struct ilitek_ts_data *ts, u32 addr, u8 *buf) +{ + u32 idx, i, start, end; + struct device *dev = &ts->client->dev; + + idx = addr + 0x06; + ts->upg.fw_mcu_ver = get_unaligned_le16(buf + idx); + + idx = addr + 0x00; + ts->upg.map_ver = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx]; + + if (ts->upg.map_ver < 0x10000) + return -EINVAL; + + ts->upg.blk_num = buf[addr + 0x50]; + + ts->upg.blk = kcalloc(ts->upg.blk_num, sizeof(struct ilitek_block_info), + GFP_KERNEL); + if (!ts->upg.blk) + return -ENOMEM; + + for (i = 0; i < ts->upg.blk_num; i++) { + idx = addr + 0x54 + 3 * i; + start = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx]; + + if (i == ts->upg.blk_num - 1) + idx = addr + 123; + else + idx += 3; + + end = buf[idx + 2] << 16 | buf[idx + 1] << 8 | buf[idx]; + + if (i == 0) + end = ilitek_find_endaddr(start, end, buf, true); + else + end = ilitek_find_endaddr(start, end, buf, false); + + ts->upg.blk[i].start = start; + ts->upg.blk[i].end = end; + + dev_dbg(dev, "block[%u] start: %#x, end: %#x\n", i, start, end); + } + + return 0; +} + +static u16 ilitek_update_crc(u16 crc, u8 newbyte) +{ + u8 i; + + crc = crc ^ newbyte; + + for (i = 0; i < 8; i++) { + if (crc & 0x01) { + crc = crc >> 1; + crc ^= ILITEK_CRC_POLY; + } else { + crc = crc >> 1; + } + } + return crc; +} + +static u16 ilitek_get_fw_crc(u32 start, u32 end, u8 *buf) +{ + u16 CRC = 0; + u32 i = 0; + + for (i = start; i <= end - 2; i++) + CRC = ilitek_update_crc(CRC, buf[i]); + + return CRC; +} + +static int ilitek_check_blk(struct ilitek_ts_data *ts, u8 *buf, bool *update) +{ + u32 i; + int error; + struct device *dev = &ts->client->dev; + struct ilitek_block_info *blk = ts->upg.blk; + + for (i = 0; i < ts->upg.blk_num; i++) { + error = ilitek_get_blk_crc(ts, blk[i].start, blk[i].end, 0, + &blk[i].ic_crc); + if (error < 0) { + dev_err(dev, "get blk crc failed, ret:%d\n", error); + return error; + } + + blk[i].fw_crc = ilitek_get_fw_crc(blk[i].start, + blk[i].end, buf); + + if (blk[i].ic_crc != blk[i].fw_crc) { + blk[i].chk_crc = false; + *update = true; + } else + blk[i].chk_crc = true; + + dev_dbg(dev, "block[%u]: start:%#x, end:%#x, ic crc:%#x, dri crc:%#x\n", + i, blk[i].start, blk[i].end, + blk[i].ic_crc, blk[i].fw_crc); + } + + return 0; +} + +static int ilitek_program_blk(struct ilitek_ts_data *ts, u8 *buf, + u32 cnt, const u32 len) +{ + int error; + u32 i; + u8 *inbuf; + struct device *dev = &ts->client->dev; + struct ilitek_block_info *blk = ts->upg.blk; + + inbuf = kmalloc(len + 1, GFP_KERNEL); + if (!inbuf) + return -ENOMEM; + memset(inbuf, 0xff, len + 1); + + error = ilitek_set_flash_BL1_8(ts, blk[cnt].start, blk[cnt].end); + if (error < 0) + goto err_free; + + for (i = blk[cnt].start; i < blk[cnt].end; i += len) { + memcpy(inbuf + 1, buf + i, len); + + error = api_protocol_set_cmd(ts, SET_W_DATA, inbuf, NULL); + if (error < 0) + goto err_free; + + error = ilitek_check_busy(ts, 2000); + if (error < 0) { + dev_err(dev, "check busy failed, ret:%d\n", error); + goto err_free; + } + } + + error = ilitek_get_blk_crc(ts, blk[cnt].start, blk[cnt].end, 1, + &blk[cnt].ic_crc); + if (error < 0) { + dev_err(dev, "get blk crc failed, ret:%d\n", error); + goto err_free; + } + + if (blk[cnt].ic_crc != blk[cnt].fw_crc) { + dev_err(dev, "ic_crc:%x dri_crc:%x not matched\n", + blk[cnt].ic_crc, blk[cnt].fw_crc); + error = -EFAULT; + } + +err_free: + kfree(inbuf); + return error; +} + +static int ilitek_upgrade_BL1_8(struct ilitek_ts_data *ts, u8 *buf) +{ + int error; + u32 cnt; + struct device *dev = &ts->client->dev; + struct ilitek_block_info *blk = ts->upg.blk; + + error = ilitek_set_data_len(ts, ILITEK_UPGRADE_LEN); + if (error < 0) + return error; + + for (cnt = 0; cnt < ts->upg.blk_num; cnt++) { + if (blk[cnt].chk_crc == true) + continue; + + error = ilitek_program_blk(ts, buf, cnt, ILITEK_UPGRADE_LEN); + if (error < 0) { + dev_err(dev, "upgrade failed, ret:%d\n", error); + return error; + } + } + + return 0; +} + +static int ilitek_upgrade_firmware(struct ilitek_ts_data *ts, u8 *buf) +{ + int error, retry = 2; + struct device *dev = &ts->client->dev; + u8 outbuf[64]; + bool need_update = false; + + /* Check MCU version between device and firmware file */ + if (ts->upg.fw_mcu_ver != ts->mcu_ver) { + dev_err(dev, "MCU version (MCU:%x and FW:%x) not match\n", + ts->mcu_ver, ts->upg.fw_mcu_ver); + return -EINVAL; + } + +Retry: + if (--retry < 0) { + dev_err(dev, "retry 2 times upgrade fail, ret:%d\n", error); + return error; + } + + ilitek_reset(ts, ts->reset_time); + + error = ilitek_set_testmode(ts, true); + if (error < 0) + goto Retry; + + error = ilitek_check_busy(ts, 1000); + if (error < 0) + goto Retry; + + error = ilitek_check_blk(ts, buf, &need_update); + if (error < 0) + goto Retry; + + if (need_update) { + error = ilitek_switch_BLmode(ts, true); + if (error < 0) + goto Retry; + + /* get bootloader version */ + error = api_protocol_set_cmd(ts, GET_PTL_VER, NULL, outbuf); + if (error < 0) + goto Retry; + + error = ilitek_upgrade_BL1_8(ts, buf); + if (error < 0) + goto Retry; + + error = ilitek_switch_BLmode(ts, false); + if (error < 0) + goto Retry; + } + + /* switch back to normal mode after reset */ + ilitek_reset(ts, ts->reset_time); + + error = ilitek_protocol_init(ts); + if (error < 0) + goto Retry; + + error = ilitek_read_tp_info(ts, true); + if (error < 0) + goto Retry; + + return 0; +} + +static int ilitek_parse_hex(struct ilitek_ts_data *ts, u32 *fw_size, u8 *fw_buf) +{ + int error; + char *fw_file; + const struct firmware *fw; + struct device *dev = &ts->client->dev; + u32 i, len, addr, type, exaddr = 0; + u8 info[4], data[16]; + + fw_file = kasprintf(GFP_KERNEL, "ilitek_%04x.hex", ts->mcu_ver); + if (!fw_file) + return -ENOMEM; + + error = request_firmware(&fw, fw_file, dev); + kfree(fw_file); + if (error) { + dev_err(dev, "request firmware:%s failed, ret:%d\n", + fw_file, error); + return error; + } + + for (i = 0; i < fw->size; i++) { + if (fw->data[i] == ':' || + fw->data[i] == 0x0D || + fw->data[i] == 0x0A) + continue; + + error = hex2bin(info, fw->data + i, sizeof(info)); + if (error) + goto release_fw; + + len = info[0]; + addr = get_unaligned_be16(info + 1); + type = info[3]; + + error = hex2bin(data, fw->data + i + 8, len); + if (error) + goto release_fw; + + switch (type) { + case 0x01: + goto release_fw; + case 0x02: + exaddr = get_unaligned_be16(data); + exaddr <<= 4; + break; + case 0x04: + exaddr = get_unaligned_be16(data); + exaddr <<= 16; + break; + case 0xAC: + case 0xAD: + break; + case 0x00: + addr += exaddr; + memcpy(fw_buf + addr, data, len); + *fw_size = addr + len; + break; + default: + dev_err(dev, "unexpected type:%x in hex\n", type); + goto err_invalid; + } + + i += 10 + (len * 2); + } + +err_invalid: + error = -EINVAL; + +release_fw: + release_firmware(fw); + + return error; +} + + +static int ilitek_update_fw_v6(struct ilitek_ts_data *ts) +{ + u8 *fw_buf; + int error; + struct device *dev = &ts->client->dev; + + fw_buf = vmalloc(ILITEK_HEX_UPGRADE_SIZE); + if (!fw_buf) + return -ENOMEM; + memset(fw_buf, 0xFF, ILITEK_HEX_UPGRADE_SIZE); + + error = ilitek_parse_hex(ts, &ts->upg.fw_size, fw_buf); + if (error < 0) { + dev_err(dev, "parse fw file failed, err:%d\n", error); + goto err_free_buf; + } + + error = ilitek_info_mapping(ts, 0x3020, fw_buf); + if (error < 0) { + dev_err(dev, "check hex mapping fail, ret:%d\n", error); + goto err_free_buf; + } + + error = ilitek_upgrade_firmware(ts, fw_buf); + if (error < 0) { + dev_err(dev, "upgrade fail, ret:%d\n", error); + goto err_free_buf; + } + + dev_dbg(dev, "update fw success\n"); + +err_free_buf: + vfree(fw_buf); + kfree(ts->upg.blk); + + return error; +} + +static ssize_t update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int error; + struct i2c_client *client = to_i2c_client(dev); + struct ilitek_ts_data *ts = i2c_get_clientdata(client); + + disable_irq(client->irq); + + error = ilitek_update_fw_v6(ts); + + enable_irq(client->irq); + + if (error < 0) { + dev_err(dev, "ILITEK FW upgrade failed, ret:%d\n", error); + return error; + } + + dev_dbg(dev, "firmware upgrade success !\n"); + + return count; +} +static DEVICE_ATTR_WO(update_fw); + static struct attribute *ilitek_sysfs_attrs[] = { &dev_attr_firmware_version.attr, &dev_attr_product_id.attr, + &dev_attr_update_fw.attr, NULL }; @@ -597,6 +1313,7 @@ static int ilitek_ts_i2c_probe(struct i2c_client *client, } error = devm_device_add_group(dev, &ilitek_attrs_group); + if (error) { dev_err(dev, "sysfs create group failed: %d\n", error); return error; -- 2.25.1