/* * Copyright (C) 2010,Imagis Technology Co. Ltd. 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include "ist30xxc.h" #include "ist30xxc_update.h" #include "ist30xxc_tracking.h" #if IST30XX_INTERNAL_BIN #if defined(CONFIG_MACH_SS_SHARKLT8) || defined(CONFIG_MACH_SS_SHARKLT8LIGHTSLEEP) #include "./firmware/ss_sharklt8_fw.h" #else #include "./firmware/coreprimeve3g_fw.h" #endif #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) #if IST30XX_MULTIPLE_TSP //#include "./firmware/ist30xxc_fw_tsp_Ver0000_porting.h" #endif #endif #endif // IST30XX_INTERNAL_BIN #define TAGS_PARSE_OK (0) // ISP burst r/w int ist30xx_isp_read_burst(struct i2c_client *client, u32 addr, u32 *buf32, u16 len) { int ret = 0; int i; u16 max_len = I2C_MAX_READ_SIZE / IST30XX_DATA_LEN; u16 remain_len = len; for (i = 0; i < len; i += max_len) { if (remain_len < max_len) max_len = remain_len; ret = ist30xx_read_buf(client, addr, buf32, max_len); if (unlikely(ret)) { tsp_err("Burst fail, addr: %x\n", __func__, addr); return ret; } buf32 += max_len; remain_len -= max_len; } return 0; } int ist30xx_isp_write_burst(struct i2c_client *client, u32 addr, u32 *buf32, u16 len) { int ret = 0; int i; u16 max_len = I2C_MAX_WRITE_SIZE / IST30XX_DATA_LEN; u16 remain_len = len; for (i = 0; i < len; i += max_len) { if (remain_len < max_len) max_len = remain_len; ret = ist30xx_write_buf(client, addr, buf32, max_len); if (unlikely(ret)) { tsp_err("Burst fail, addr: %x\n", addr); return ret; } buf32 += max_len; remain_len -= max_len; } return 0; } #define IST30XX_ISP_READ_TOTAL_S (0x01) #define IST30XX_ISP_READ_TOTAL_B (0x11) #define IST30XX_ISP_READ_MAIN_S (0x02) #define IST30XX_ISP_READ_MAIN_B (0x12) #define IST30XX_ISP_READ_INFO_S (0x03) #define IST30XX_ISP_READ_INFO_B (0x13) #define IST30XX_ISP_PROG_TOTAL_S (0x04) #define IST30XX_ISP_PROG_TOTAL_B (0x14) #define IST30XX_ISP_PROG_MAIN_S (0x05) #define IST30XX_ISP_PROG_MAIN_B (0x15) #define IST30XX_ISP_PROG_INFO_S (0x06) #define IST30XX_ISP_PROG_INFO_B (0x16) #define IST30XX_ISP_ERASE_BLOCK (0x07) #define IST30XX_ISP_ERASE_SECTOR (0x08) #define IST30XX_ISP_ERASE_PAGE (0x09) #define IST30XX_ISP_ERASE_INFO (0x0A) #define IST30XX_ISP_READ_TOTAL_CRC (0x1B) #define IST30XX_ISP_READ_MAIN_CRC (0x1C) #define IST30XX_ISP_READ_INFO_CRC (0x1D) int ist30xxc_isp_enable(struct i2c_client *client, bool enable) { int ret = 0; if (enable) { ret = ist30xx_write_cmd(client, IST30XX_FLASH_ISPEN, 0xDE01); if (unlikely(ret)) return ret; #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) ret = ist30xx_write_cmd(client, IST30XX_FLASH_MODE, 0x4100); if (unlikely(ret)) return ret; ret = ist30xx_write_cmd(client, IST30XX_FLASH_TEST_MODE1, 0x38); if (unlikely(ret)) return ret; #endif } else { ret = ist30xx_write_cmd(client, IST30XX_FLASH_ISPEN, 0x2); if (unlikely(ret)) return ret; } msleep(1); return ret; } int ist30xxc_isp_mode(struct i2c_client *client, int mode) { int ret = 0; u32 val = 0; switch (mode) { case IST30XX_ISP_READ_TOTAL_S: val = 0x8090; break; case IST30XX_ISP_READ_TOTAL_B: case IST30XX_ISP_READ_TOTAL_CRC: val = 0x8190; break; case IST30XX_ISP_READ_MAIN_S: val = 0x0090; break; case IST30XX_ISP_READ_MAIN_B: case IST30XX_ISP_READ_MAIN_CRC: val = 0x0190; break; case IST30XX_ISP_READ_INFO_S: val = 0x0098; break; case IST30XX_ISP_READ_INFO_B: case IST30XX_ISP_READ_INFO_CRC: val = 0x0198; break; case IST30XX_ISP_PROG_TOTAL_S: val = 0x8050; break; case IST30XX_ISP_PROG_TOTAL_B: val = 0x8150; break; case IST30XX_ISP_PROG_MAIN_S: val = 0x0050; break; case IST30XX_ISP_PROG_MAIN_B: val = 0x0150; break; case IST30XX_ISP_PROG_INFO_S: val = 0x0058; break; case IST30XX_ISP_PROG_INFO_B: val = 0x0158; break; case IST30XX_ISP_ERASE_BLOCK: val = 0x0031; break; case IST30XX_ISP_ERASE_SECTOR: val = 0x0032; break; case IST30XX_ISP_ERASE_PAGE: val = 0x0030; break; case IST30XX_ISP_ERASE_INFO: val = 0x0038; break; default: tsp_err("ISP fail, unknown mode\n"); return -EINVAL; } #if (IMAGIS_TSP_IC > IMAGIS_IST3032C) val &= ~(0x8000); #endif ret = ist30xx_write_cmd(client, IST30XX_FLASH_MODE, val); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_MODE\n"); return ret; } return 0; } int ist30xxc_isp_erase(struct i2c_client *client, int mode, u32 index) { int ret = 0; ret = ist30xxc_isp_mode(client, mode); if (unlikely(ret)) return ret; ret = ist30xx_write_cmd(client, IST30XX_FLASH_DIN, index); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_DIN\n"); return ret; } msleep(50); // Flash erase time : Mininum 40msec return ret; } int ist30xxc_isp_program(struct i2c_client *client, u32 addr, int mode, const u32 *buf32, int len) { int ret = 0; ret = ist30xxc_isp_mode(client, mode); if (unlikely(ret)) return ret; ret = ist30xx_write_cmd(client, IST30XX_FLASH_ADDR, addr); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_ADDR\n"); return ret; } if (mode & 0x10) ret = ist30xx_isp_write_burst(client, IST30XX_FLASH_DIN, (u32 *)buf32, len); else ret = ist30xx_write_buf(client, IST30XX_FLASH_DIN, (u32 *)buf32, len); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_DIN\n"); return ret; } return ret; } int ist30xxc_isp_read(struct i2c_client *client, u32 addr, int mode, u32 *buf32, int len) { int ret = 0; /* IST30xxB ISP read mode */ ret = ist30xxc_isp_mode(client, mode); if (unlikely(ret)) return ret; ret = ist30xx_write_cmd(client, IST30XX_FLASH_ADDR, addr); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_ADDR\n"); return ret; } if (mode & 0x10) ret = ist30xx_isp_read_burst(client, IST30XX_FLASH_DOUT, buf32, len); else ret = ist30xx_read_buf(client, IST30XX_FLASH_DOUT, buf32, len); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_DOUT\n"); return ret; } return 0; } int ist30xxc_cmd_read_chksum(struct i2c_client *client, int mode, u32 start_addr, u32 end_addr, u32 *chksum) { int ret = 0; u32 val = (1 << 28) | (1 << 25) | (1 << 24) | (1 << 20) | (1 << 16); val |= (end_addr / IST30XX_ADDR_LEN) - 1; ret = ist30xxc_isp_mode(client, mode); if (unlikely(ret)) return ret; ret = ist30xx_write_cmd(client, IST30XX_FLASH_ADDR, start_addr); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_ADDR (%x)\n", val); return ret; } ret = ist30xx_write_cmd(client, IST30XX_FLASH_AUTO_READ, val); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_AUTO_READ (%x)\n", val); return ret; } msleep(100); ret = ist30xx_read_reg(client, IST30XX_FLASH_CRC, chksum); if (unlikely(ret)) { tsp_err("ISP fail, IST30XX_FLASH_CRC (%x)\n", chksum); return ret; } return 0; } int ist30xxc_read_chksum(struct ist30xx_data *data, u32 *chksum) { int ret = 0; u32 start_addr, end_addr; start_addr = data->tags.fw_addr; end_addr = data->tags.sensor_addr + data->tags.sensor_size; ret = ist30xxc_cmd_read_chksum(data->client, IST30XX_ISP_READ_TOTAL_CRC, start_addr, end_addr, chksum); if (unlikely(ret)) return ret; tsp_info("chksum: %x(%x~%x)\n", *chksum, start_addr, end_addr); return 0; } int ist30xxc_read_chksum_all(struct i2c_client *client, u32 *chksum) { int ret = 0; u32 start_addr, end_addr; start_addr = IST30XX_FLASH_BASE_ADDR; end_addr = IST30XX_FLASH_BASE_ADDR + IST30XX_FLASH_TOTAL_SIZE; ret = ist30xxc_cmd_read_chksum(client, IST30XX_ISP_READ_TOTAL_CRC, start_addr, end_addr, chksum); if (unlikely(ret)) return ret; tsp_info("chksum: %x(%x~%x)\n", *chksum, start_addr, end_addr); return 0; } int ist30xxc_isp_fw_read(struct ist30xx_data *data, u32 *buf32) { int ret = 0; int i; int len; u32 addr = IST30XX_FLASH_BASE_ADDR; ist30xx_reset(data, true); /* IST30xxB ISP enable */ ret = ist30xxc_isp_enable(data->client, true); if (unlikely(ret)) return ret; #if I2C_BURST_MODE for (i = 0; i < IST30XX_FLASH_TOTAL_SIZE; i += I2C_MAX_READ_SIZE) { len = I2C_MAX_READ_SIZE / IST30XX_DATA_LEN; if ((IST30XX_FLASH_TOTAL_SIZE - i) < I2C_MAX_READ_SIZE) len = (IST30XX_FLASH_TOTAL_SIZE - i) / IST30XX_DATA_LEN; ret = ist30xxc_isp_read(data->client, addr, IST30XX_ISP_READ_TOTAL_B, buf32, len); if (unlikely(ret)) goto isp_fw_read_end; addr += len; buf32 += len; } #else for (i = 0; i < IST30XX_FLASH_MAIN_SIZE; i += IST30XX_DATA_LEN) { ret = ist30xxc_isp_read(data->client, addr, IST30XX_ISP_READ_TOTAL_S, buf32, 1); if (unlikely(ret)) goto isp_fw_read_end; addr++; buf32++; } #endif isp_fw_read_end: /* IST30xxC ISP disable */ ist30xxc_isp_enable(data->client, false); ist30xx_reset(data, false); return ret; } int ist30xxc_isp_fw_update(struct ist30xx_data *data, const u8 *buf) { #if !(I2C_BURST_MODE) int i; #endif int ret = 0; u32 addr = IST30XX_FLASH_BASE_ADDR; ist30xx_reset(data, true); /* IST30xxC ISP enable */ ret = ist30xxc_isp_enable(data->client, true); if (unlikely(ret)) goto isp_fw_update_end; /* IST30xxC ISP erase */ ret = ist30xxc_isp_erase(data->client, IST30XX_ISP_ERASE_BLOCK, 0); if (unlikely(ret)) goto isp_fw_update_end; #if (IST30XX_FLASH_INFO_SIZE > 0) ret = ist30xxc_isp_erase(data->client, IST30XX_ISP_ERASE_INFO, 0); if (unlikely(ret)) goto isp_fw_update_end; #endif ist30xx_reset(data, true); /* IST30xxC ISP enable */ ret = ist30xxc_isp_enable(data->client, true); if (unlikely(ret)) goto isp_fw_update_end; #if I2C_BURST_MODE /* IST30xxC ISP write burst */ #if 0 for (i = 0; i < IST30XX_FLASH_TOTAL_SIZE / IST30XX_FLASH_PAGE_SIZE; i++) { ret = ist30xxc_isp_program(data->client, addr, IST30XX_ISP_PROG_TOTAL_B, (u32 *)buf, IST30XX_FLASH_PAGE_SIZE / IST30XX_DATA_LEN); if (unlikely(ret)) goto isp_fw_update_end; addr += IST30XX_FLASH_PAGE_SIZE / IST30XX_DATA_LEN; buf += IST30XX_FLASH_PAGE_SIZE; } #else ret = ist30xxc_isp_program(data->client, addr, IST30XX_ISP_PROG_TOTAL_B, (u32 *)buf, IST30XX_FLASH_TOTAL_SIZE / IST30XX_DATA_LEN); if (unlikely(ret)) goto isp_fw_update_end; #endif #else /* IST30xxC ISP write single */ for (i = 0; i < IST30XX_FLASH_TOTAL_SIZE; i += IST30XX_DATA_LEN) { ret = ist30xxc_isp_program(data->client, addr, IST30XX_ISP_PROG_TOTAL_S, (u32 *)buf, 1); if (unlikely(ret)) goto isp_fw_update_end; addr++; buf += IST30XX_DATA_LEN; } #endif isp_fw_update_end: /* IST30xxC ISP disable */ ist30xxc_isp_enable(data->client, false); ist30xx_reset(data, false); return ret; } u32 ist30xx_parse_ver(struct ist30xx_data *data, int flag, const u8 *buf) { u32 ver = 0; u32 *buf32 = (u32 *)buf; if (flag == FLAG_MAIN) ver = (u32)buf32[(data->tags.flag_addr + 0x1FC) >> 2]; else if (flag == FLAG_TEST) ver = (u32)buf32[(data->tags.flag_addr + 0x1F4) >> 2]; else if (flag == FLAG_FW) ver = (u32)(buf32[(data->tags.cfg_addr + 0x4) >> 2] & 0xFFFF); else if (flag == FLAG_CORE) ver = (u32)buf32[(data->tags.flag_addr + 0x1F0) >> 2]; else tsp_warn("Parsing ver's flag is not corrent!\n"); return ver; } int calib_ms_delay = CALIB_WAIT_TIME; int ist30xx_calib_wait(struct ist30xx_data *data) { int cnt = calib_ms_delay; data->status.calib_msg = 0; while (cnt-- > 0) { msleep(100); if (data->status.calib_msg) { tsp_info("Calibration status : %d, Max raw gap : %d - (%08x)\n", CALIB_TO_STATUS(data->status.calib_msg), CALIB_TO_GAP(data->status.calib_msg), data->status.calib_msg); if (CALIB_TO_STATUS(data->status.calib_msg) == 0) return 0; // Calibrate success else return -EAGAIN; } } tsp_warn("Calibration time out\n"); return -EPERM; } int ist30xx_calibrate(struct ist30xx_data *data, int wait_cnt) { int ret = -ENOEXEC; tsp_info("*** Calibrate %ds ***\n", calib_ms_delay / 10); data->status.update = 1; ist30xx_disable_irq(data); while (1) { ret = ist30xx_cmd_calibrate(data->client); if (unlikely(ret)) continue; ist30xx_enable_irq(data); ret = ist30xx_calib_wait(data); if (likely(!ret)) break; ist30xx_disable_irq(data); if (--wait_cnt == 0) break; ist30xx_reset(data, false); } ist30xx_disable_irq(data); ist30xx_reset(data, false); data->status.update = 2; ist30xx_enable_irq(data); return ret; } int ist30xx_parse_tags(struct ist30xx_data *data, const u8 *buf, const u32 size) { int ret = -EPERM; struct ist30xx_tags *tags; tags = (struct ist30xx_tags *)(&buf[size - sizeof(struct ist30xx_tags)]); if (!strncmp(tags->magic1, IST30XX_TAG_MAGIC, sizeof(tags->magic1)) && !strncmp(tags->magic2, IST30XX_TAG_MAGIC, sizeof(tags->magic2)) ) { data->tags = *tags; data->tags.fw_addr -= data->tags.rom_base; data->tags.cfg_addr -= data->tags.rom_base; data->tags.sensor_addr -= data->tags.rom_base; data->tags.cp_addr -= data->tags.rom_base; data->tags.flag_addr -= data->tags.rom_base; data->fw.index = data->tags.fw_addr; data->fw.size = tags->flag_addr - tags->fw_addr + tags->flag_size; data->fw.chksum = tags->chksum; tsp_verb("Tagts magic1: %s, magic2: %s\n", data->tags.magic1, data->tags.magic2); tsp_verb(" rom: %x\n", data->tags.rom_base); tsp_verb(" ram: %x\n", data->tags.ram_base); tsp_verb(" fw: %x(%x)\n", data->tags.fw_addr, data->tags.fw_size); tsp_verb(" cfg: %x(%x)\n", data->tags.cfg_addr, data->tags.cfg_size); tsp_verb(" sensor: %x(%x)\n", data->tags.sensor_addr, data->tags.sensor_size); tsp_verb(" cp: %x(%x)\n", data->tags.cp_addr, data->tags.cp_size); tsp_verb(" flag: %x(%x)\n", data->tags.flag_addr, data->tags.flag_size); tsp_verb(" zvalue: %x\n", data->tags.zvalue_base); tsp_verb(" algo: %x\n", data->tags.algo_base); tsp_verb(" raw: %x\n", data->tags.raw_base); tsp_verb(" filter: %x\n", data->tags.filter_base); tsp_verb(" chksum: %x\n", data->tags.chksum); tsp_verb(" chksum_all: %x\n", data->tags.chksum_all); tsp_verb(" build time: %04d/%02d/%02d (%02d:%02d:%02d)\n", data->tags.year, data->tags.month, data->tags.day, data->tags.hour, data->tags.min, data->tags.sec); ret = 0; } return ret; } int ist30xx_get_update_info(struct ist30xx_data *data, const u8 *buf, const u32 size) { int ret; ret = ist30xx_parse_tags(data, buf, size); if (unlikely(ret != TAGS_PARSE_OK)) tsp_warn("Cannot find tags of F/W, make a tags by 'tagts.exe'\n"); return ret; } #define TSP_INFO_SWAP_XY (1 << 0) #define TSP_INFO_FLIP_X (1 << 1) #define TSP_INFO_FLIP_Y (1 << 2) u32 ist30xx_info_cal_crc(u32 *buf) { int i; u32 chksum32 = 0; for (i = 0; i < IST30XX_MAX_CMD_SIZE - 1; i++) { chksum32 += *buf++; } return chksum32; } int ist30xx_tsp_update_info(struct ist30xx_data *data) { int ret = 0; u32 chksum; u32 info[IST30XX_MAX_CMD_SIZE]; u32 tsp_lcd, tsp_swap, tsp_scr, tsp_gtx, tsp_ch; u32 tkey_info0, tkey_info1, tkey_info2; u32 finger_info, baseline, threshold; TSP_INFO *tsp = &data->tsp_info; #if IST30XX_USE_KEY TKEY_INFO *tkey = &data->tkey_info; #endif ret = ist30xx_cmd_hold(data->client, 1); if (unlikely(ret)) return ret; ret = ist30xx_burst_read(data->client, IST30XX_DA_ADDR(eHCOM_GET_CHIP_ID), &info[0], IST30XX_MAX_CMD_SIZE, true); if (unlikely(ret)) return ret; ist30xx_cmd_hold(data->client, 0); if (unlikely(ret)) { ist30xx_reset(data, false); return ret; } ret = ist30xx_read_cmd(data, IST30XX_REG_CHIPID, &data->chip_id); if (unlikely(ret)) return ret; if ((info[IST30XX_CMD_VALUE(eHCOM_GET_CHIP_ID)] != data->chip_id) || (info[IST30XX_CMD_VALUE(eHCOM_GET_CHIP_ID)] == 0) || (info[IST30XX_CMD_VALUE(eHCOM_GET_CHIP_ID)] == 0xFFFF)) return -EINVAL; chksum = ist30xx_info_cal_crc((u32*)info); if (chksum != info[IST30XX_MAX_CMD_SIZE - 1]) { tsp_err("info checksum : %08X, %08X\n", chksum, info[IST30XX_MAX_CMD_SIZE - 1]); return -EINVAL; } tsp_info("info read success\n"); tsp_lcd = info[IST30XX_CMD_VALUE(eHCOM_GET_LCD_INFO)]; tsp_ch = info[IST30XX_CMD_VALUE(eHCOM_GET_TSP_INFO)]; tkey_info0 = info[IST30XX_CMD_VALUE(eHCOM_GET_KEY_INFO_0)]; tkey_info1 = info[IST30XX_CMD_VALUE(eHCOM_GET_KEY_INFO_1)]; tkey_info2 = info[IST30XX_CMD_VALUE(eHCOM_GET_KEY_INFO_2)]; tsp_scr = info[IST30XX_CMD_VALUE(eHCOM_GET_SCR_INFO)]; tsp_gtx = info[IST30XX_CMD_VALUE(eHCOM_GET_GTX_INFO)]; tsp_swap = info[IST30XX_CMD_VALUE(eHCOM_GET_SWAP_INFO)]; finger_info = info[IST30XX_CMD_VALUE(eHCOM_GET_FINGER_INFO)]; baseline = info[IST30XX_CMD_VALUE(eHCOM_GET_BASELINE)]; threshold = info[IST30XX_CMD_VALUE(eHCOM_GET_TOUCH_TH)]; tsp->ch_num.rx = (tsp_ch >> 16) & 0xFFFF; tsp->ch_num.tx = tsp_ch & 0xFFFF; tsp->node.len = tsp->ch_num.tx * tsp->ch_num.rx; tsp->gtx.num = (tsp_gtx >> 24) & 0xFF; tsp->gtx.ch_num[0] = (tsp_gtx >> 16) & 0xFF; tsp->gtx.ch_num[1] = (tsp_gtx >> 8) & 0xFF; tsp->gtx.ch_num[2] = 0xFF; tsp->gtx.ch_num[3] = 0xFF; tsp->finger_num = finger_info; tsp->dir.swap_xy = (tsp_swap & TSP_INFO_SWAP_XY ? true : false); tsp->dir.flip_x = (tsp_swap & TSP_INFO_FLIP_X ? true : false); tsp->dir.flip_y = (tsp_swap & TSP_INFO_FLIP_Y ? true : false); tsp->baseline = baseline & 0xFFFF; tsp->screen.rx = (tsp_scr >> 16) & 0xFFFF; tsp->screen.tx = tsp_scr & 0xFFFF; if (tsp->dir.swap_xy) { tsp->width = tsp_lcd & 0xFFFF; tsp->height = (tsp_lcd >> 16) & 0xFFFF; } else { tsp->width = (tsp_lcd >> 16) & 0xFFFF; tsp->height = tsp_lcd & 0xFFFF; } #if IST30XX_USE_KEY tkey->enable = (((tkey_info0 >> 24) & 0xFF) ? true : false); tkey->key_num = (tkey_info0 >> 16) & 0xFF; tkey->ch_num[0].tx = tkey_info0 & 0xFF; tkey->ch_num[0].rx = (tkey_info0 >> 8) & 0xFF; tkey->ch_num[1].tx = (tkey_info1 >> 16) & 0xFF; tkey->ch_num[1].rx = (tkey_info1 >> 24) & 0xFF; tkey->ch_num[2].tx = tkey_info1 & 0xFF; tkey->ch_num[2].rx = (tkey_info1 >> 8) & 0xFF; tkey->ch_num[3].tx = (tkey_info2 >> 16) & 0xFF; tkey->ch_num[3].rx = (tkey_info2 >> 24) & 0xFF; tkey->ch_num[4].tx = tkey_info2 & 0xFF; tkey->ch_num[4].rx = (tkey_info2 >> 8) & 0xFF; tkey->baseline = (baseline >> 16) & 0xFFFF; #endif return ret; } int ist30xx_get_tsp_info(struct ist30xx_data *data) { int ret = 0; int retry = 3; #if IST30XX_INTERNAL_BIN TSP_INFO *tsp = &data->tsp_info; #if IST30XX_USE_KEY TKEY_INFO *tkey = &data->tkey_info; #endif u8 *cfg_buf; #endif while (retry--) { ret = ist30xx_tsp_update_info(data); if (ret == 0) { tsp_info("tsp update info success!\n"); return ret; } } #if IST30XX_INTERNAL_BIN cfg_buf = (u8 *)&data->fw.buf[data->tags.cfg_addr]; tsp->ch_num.rx = (u8)cfg_buf[0x0C]; tsp->ch_num.tx = (u8)cfg_buf[0x0D]; tsp->node.len = tsp->ch_num.tx * tsp->ch_num.rx; tsp->gtx.num = (u8)cfg_buf[0x10]; tsp->gtx.ch_num[0] = (u8)cfg_buf[0x11]; tsp->gtx.ch_num[1] = (u8)cfg_buf[0x12]; tsp->gtx.ch_num[2] = (u8)cfg_buf[0x13]; tsp->gtx.ch_num[3] = (u8)cfg_buf[0x14]; tsp->finger_num = (u8)cfg_buf[0x1A]; tsp->dir.swap_xy = (bool)(cfg_buf[0x1B] & TSP_INFO_SWAP_XY ? true : false); tsp->dir.flip_x = (bool)(cfg_buf[0x1B] & TSP_INFO_FLIP_X ? true : false); tsp->dir.flip_y = (bool)(cfg_buf[0x1B] & TSP_INFO_FLIP_Y ? true : false); tsp->baseline = (u16)((cfg_buf[0x31] << 8) | cfg_buf[0x30]); tsp->screen.rx = (u8)cfg_buf[0x0E]; tsp->screen.tx = (u8)cfg_buf[0x0F]; if (tsp->dir.swap_xy) { tsp->width = (u16)((cfg_buf[0x19] << 8) | cfg_buf[0x18]); tsp->height = (u16)((cfg_buf[0x17] << 8) | cfg_buf[0x16]); } else { tsp->width = (u16)((cfg_buf[0x17] << 8) | cfg_buf[0x16]); tsp->height = (u16)((cfg_buf[0x19] << 8) | cfg_buf[0x18]); } #if IST30XX_USE_KEY tkey->enable = (bool)(cfg_buf[0x3A] & 1); tkey->key_num = (u8)cfg_buf[0x3B]; tkey->ch_num[0].tx = (u8)cfg_buf[0x3E]; tkey->ch_num[0].rx = (u8)cfg_buf[0x43]; tkey->ch_num[1].tx = (u8)cfg_buf[0x3F]; tkey->ch_num[1].rx = (u8)cfg_buf[0x44]; tkey->ch_num[2].tx = (u8)cfg_buf[0x40]; tkey->ch_num[2].rx = (u8)cfg_buf[0x45]; tkey->ch_num[3].tx = (u8)cfg_buf[0x41]; tkey->ch_num[3].rx = (u8)cfg_buf[0x46]; tkey->ch_num[4].tx = (u8)cfg_buf[0x42]; tkey->ch_num[4].rx = (u8)cfg_buf[0x47]; tkey->baseline = (u16)((cfg_buf[0x3D] << 8) | cfg_buf[0x3C]); #endif #endif return ret; } void ist30xx_print_info(struct ist30xx_data *data) { TSP_INFO *tsp = &data->tsp_info; TKEY_INFO *tkey = &data->tkey_info; tsp_info("*** TSP/TKEY info ***\n"); tsp_info("TSP info: \n"); tsp_info(" finger num: %d\n", tsp->finger_num); tsp_info(" dir swap: %d, flip x: %d, y: %d\n", tsp->dir.swap_xy, tsp->dir.flip_x, tsp->dir.flip_y); tsp_info(" baseline: %d\n", tsp->baseline); tsp_info(" ch_num tx: %d, rx: %d\n", tsp->ch_num.tx, tsp->ch_num.rx); tsp_info(" screen tx: %d, rx: %d\n", tsp->screen.tx, tsp->screen.rx); tsp_info(" width: %d, height: %d\n", tsp->width, tsp->height); tsp_info(" gtx num: %d, ch [1]: %d, [2]: %d, [3]: %d, [4]: %d\n", tsp->gtx.num, tsp->gtx.ch_num[0], tsp->gtx.ch_num[1], tsp->gtx.ch_num[2], tsp->gtx.ch_num[3]); tsp_info(" node len: %d\n", tsp->node.len); tsp_info("TKEY info: \n"); tsp_info(" enable: %d, key num: %d\n", tkey->enable, tkey->key_num); tsp_info(" ch [0]: %d,%d [1]: %d,%d [2]: %d,%d [3]: %d,%d [4]: %d,%d\n", tkey->ch_num[0].tx, tkey->ch_num[0].rx, tkey->ch_num[1].tx, tkey->ch_num[1].rx, tkey->ch_num[2].tx, tkey->ch_num[2].rx, tkey->ch_num[3].tx, tkey->ch_num[3].rx, tkey->ch_num[4].tx, tkey->ch_num[4].rx); tsp_info(" baseline : %d\n", tkey->baseline); } #define update_next_step(ret) { if (unlikely(ret)) goto end; } int ist30xx_fw_update(struct ist30xx_data *data, const u8 *buf, int size) { int ret = 0; u32 chksum = 0; struct ist30xx_fw *fw = &data->fw; tsp_info("*** Firmware update ***\n"); tsp_info(" main: %x, fw: %x, test: %x, core: %x(addr: 0x%x ~ 0x%x)\n", fw->bin.main_ver, fw->bin.fw_ver, fw->bin.test_ver, fw->bin.core_ver, fw->index, (fw->index + fw->size)); data->status.update = 1; data->status.update_result = 0; ist30xx_disable_irq(data); ret = ist30xxc_isp_fw_update(data, buf); update_next_step(ret); ret = ist30xx_read_cmd(data, eHCOM_GET_CRC32, &chksum); if (unlikely((ret) || (chksum != fw->chksum))) { if (unlikely(ret)) ist30xx_reset(data, false); goto end; } ret = ist30xx_get_ver_info(data); update_next_step(ret); end: if (unlikely(ret)) { data->status.update_result = 1; tsp_warn("Firmware update Fail!, ret=%d\n", ret); } else if (unlikely(chksum != fw->chksum)) { data->status.update_result = 1; tsp_warn("Error CheckSum: %x(%x)\n", chksum, fw->chksum); ret = -ENOEXEC; } ist30xx_enable_irq(data); data->status.update = 2; return ret; } int ist30xx_fw_recovery(struct ist30xx_data *data) { int ret = -EPERM; u8 *fw = data->fw.buf; int fw_size = data->fw.buf_size; ret = ist30xx_get_update_info(data, fw, fw_size); if (ret) return ret; data->fw.bin.main_ver = ist30xx_parse_ver(data, FLAG_MAIN, fw); data->fw.bin.fw_ver = ist30xx_parse_ver(data, FLAG_FW, fw); data->fw.bin.test_ver = ist30xx_parse_ver(data, FLAG_TEST, fw); data->fw.bin.core_ver = ist30xx_parse_ver(data, FLAG_CORE, fw); mutex_lock(&ist30xx_mutex); ret = ist30xx_fw_update(data, fw, fw_size); if (ret == 0) ist30xx_calibrate(data, 1); mutex_unlock(&ist30xx_mutex); ist30xx_start(data); return ret; } #if IST30XX_INTERNAL_BIN int ist30xx_check_fw(struct ist30xx_data *data, const u8 *buf) { int ret; u32 chksum; ret = ist30xx_read_cmd(data, eHCOM_GET_CRC32, &chksum); if (unlikely(ret)) { ist30xx_reset(data, false); return ret; } if (unlikely(chksum != data->fw.chksum)) { tsp_warn("Checksum compare error, (IC: %08x, Bin: %08x)\n", chksum, data->fw.chksum); return -EPERM; } return 0; } bool ist30xx_check_valid_vendor(u32 tsp_vendor) { #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) switch (tsp_vendor) { case TSP_TYPE_ALPS: case TSP_TYPE_EELY: case TSP_TYPE_TOP: case TSP_TYPE_MELFAS: case TSP_TYPE_ILJIN: case TSP_TYPE_SYNOPEX: case TSP_TYPE_SMAC: case TSP_TYPE_TAEYANG: case TSP_TYPE_TOVIS: case TSP_TYPE_ELK: case TSP_TYPE_OTHERS: return true; default: return false; } #else if (tsp_vendor < TSP_TYPE_NO) return true; #endif return false; } #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) #if IST30XX_MULTIPLE_TSP void ist30xx_set_tsp_fw(struct ist30xx_data *data) { char *str; struct ist30xx_fw *fw = &data->fw; switch (data->tsp_type) { case TSP_TYPE_ILJIN: str = "ALPS/ILJIN"; fw->buf = (u8 *)ist30xxc_fw; fw->buf_size = sizeof(ist30xxc_fw); break; case TSP_TYPE_EELY: str = "EELY"; fw->buf = (u8 *)ist30xxc_fw2; fw->buf_size = sizeof(ist30xxc_fw2); break; case TSP_TYPE_UNKNOWN: default: str = "Unknown"; tsp_warn("Unknown TSP vendor(0x%x)\n", data->tsp_type); break; } tsp_info("TSP vendor : %s(%x)\n", str, data->tsp_type); } #endif // IST30XX_MULTIPLE_TSP #endif // (IMAGIS_TSP_IC < IMAGIS_IST3038C) int ist30xx_check_auto_update(struct ist30xx_data *data) { int ret = 0; int retry = IST30XX_MAX_RETRY_CNT; u32 tsp_type = TSP_TYPE_UNKNOWN; bool tsp_check = false; u32 chksum; struct ist30xx_fw *fw = &data->fw; while (retry--) { ret = ist30xx_read_cmd(data, eHCOM_GET_TSP_VENDOR, &tsp_type); if (likely(ret == 0)) { if (likely(ist30xx_check_valid_vendor(tsp_type) == true)) tsp_check = true; break; } ist30xx_reset(data, false); } #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) tsp_info("TSP vendor: %x\n", tsp_type); #endif if (unlikely(!tsp_check)) goto fw_check_end; ist30xx_get_ver_info(data); if (likely((fw->cur.fw_ver > 0) && (fw->cur.fw_ver < 0xFFFFFFFF))) { tsp_info("Version compare IC: %x(%x), BIN: %x(%x)\n", fw->cur.fw_ver, fw->cur.main_ver, fw->bin.fw_ver, fw->bin.main_ver); /* If FW version is same, check FW checksum */ if (likely((fw->cur.main_ver == fw->bin.main_ver) && (fw->cur.fw_ver == fw->bin.fw_ver) && (fw->cur.test_ver == 0))) { ret = ist30xx_read_cmd(data, eHCOM_GET_CRC32, &chksum); if (unlikely((ret) || (chksum != fw->chksum))) { tsp_warn("Checksum error, IC: %x, Bin: %x (ret: %d)\n", chksum, fw->chksum, ret); goto fw_check_end; } } /* * fw->cur.main_ver : Main version in TSP IC * fw->cur.fw_ver : FW version if TSP IC * fw->bin.main_ver : Main version in FW Binary * fw->bin.fw_ver : FW version in FW Binary */ /* If the ver of binary is higher than ver of IC, FW update operate. */ if (likely((fw->cur.main_ver >= fw->bin.main_ver) && (fw->cur.fw_ver >= fw->bin.fw_ver))) return 0; } fw_check_end: return -EAGAIN; } int ist30xx_auto_bin_update(struct ist30xx_data *data) { int ret = 0; int retry = IST30XX_MAX_RETRY_CNT; struct ist30xx_fw *fw = &data->fw; fw->buf = (u8 *)ist30xxc_fw; fw->buf_size = sizeof(ist30xxc_fw); #if (IMAGIS_TSP_IC < IMAGIS_IST3038C) #if IST30XX_MULTIPLE_TSP ist30xx_set_tsp_fw(data); #endif #endif ret = ist30xx_get_update_info(data, fw->buf, fw->buf_size); if (unlikely(ret)) return 1; fw->bin.main_ver = ist30xx_parse_ver(data, FLAG_MAIN, fw->buf); fw->bin.fw_ver = ist30xx_parse_ver(data, FLAG_FW, fw->buf); fw->bin.test_ver = ist30xx_parse_ver(data, FLAG_TEST, fw->buf); fw->bin.core_ver = ist30xx_parse_ver(data, FLAG_CORE, fw->buf); tsp_info("IC: %x, Binary ver main: %x, fw: %x, test: %x, core: %x\n", data->chip_id, fw->bin.main_ver, fw->bin.fw_ver, fw->bin.test_ver, fw->bin.core_ver); mutex_lock(&ist30xx_mutex); ret = ist30xx_check_auto_update(data); mutex_unlock(&ist30xx_mutex); if (likely(ret >= 0)) return ret; update_bin: // TSP is not ready / FW update tsp_info("Update version. fw(main, test, core): %x(%x, %x, %x) -> %x(%x, %x, %x)\n", fw->cur.fw_ver, fw->cur.main_ver, fw->cur.test_ver, fw->cur.core_ver, fw->bin.fw_ver, fw->bin.main_ver, fw->bin.test_ver, fw->bin.core_ver); mutex_lock(&ist30xx_mutex); while (retry--) { ret = ist30xx_fw_update(data, fw->buf, fw->buf_size); if (unlikely(!ret)) break; } mutex_unlock(&ist30xx_mutex); if (unlikely(ret)) return ret; if (unlikely(retry > 0 && ist30xx_check_fw(data, fw->buf))) goto update_bin; mutex_lock(&ist30xx_mutex); ist30xx_calibrate(data, IST30XX_MAX_RETRY_CNT); mutex_unlock(&ist30xx_mutex); return ret; } #endif // IST30XX_INTERNAL_BIN #define MAX_FILE_PATH 255 /* sysfs: /sys/class/touch/firmware/firmware */ ssize_t ist30xx_fw_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int ret; int fw_size = 0; u8 *fw = NULL; const u8 *buff = 0; mm_segment_t old_fs = { 0 }; struct file *fp = NULL; long fsize = 0, nread = 0; char fw_path[MAX_FILE_PATH]; const struct firmware *request_fw = NULL; int mode = 0; int calib = 1; struct ist30xx_data *data = dev_get_drvdata(dev); sscanf(buf, "%d %d", &mode, &calib); switch (mode) { case MASK_UPDATE_INTERNAL: #if IST30XX_INTERNAL_BIN fw = data->fw.buf; fw_size = data->fw.buf_size; #else tsp_warn("Not support internal bin!!\n"); return size; #endif break; case MASK_UPDATE_FW: ret = request_firmware(&request_fw, IST30XX_FW_NAME, &data->client->dev); if (ret) { tsp_warn("File not found, %s\n", IST30XX_FW_NAME); return size; } fw = (u8 *)request_fw->data; fw_size = (u32)request_fw->size; tsp_info("firmware is loaded!!\n"); break; case MASK_UPDATE_SDCARD: old_fs = get_fs(); set_fs(get_ds()); snprintf(fw_path, MAX_FILE_PATH, "/sdcard/%s", IST30XX_FW_NAME); fp = filp_open(fw_path, O_RDONLY, 0); if (IS_ERR(fp)) { tsp_info("file %s open error:%d\n", fw_path, (s32)fp); goto err_file_open; } fsize = fp->f_path.dentry->d_inode->i_size; buff = kzalloc((size_t)fsize, GFP_KERNEL); if (!buff) { tsp_info("fail to alloc buffer\n"); goto err_alloc; } nread = vfs_read(fp, (char __user *)buff, fsize, &fp->f_pos); if (nread != fsize) { tsp_info("mismatch fw size\n"); goto err_fw_size; } fw = (u8 *)buff; fw_size = (u32)fsize; filp_close(fp, current->files); tsp_info("firmware is loaded!!\n"); break; case MASK_UPDATE_ERASE: tsp_info("EEPROM all erase!!\n"); mutex_lock(&ist30xx_mutex); ist30xx_disable_irq(data); ist30xx_reset(data, true); ist30xxc_isp_enable(data->client, true); ist30xxc_isp_erase(data->client, IST30XX_ISP_ERASE_BLOCK, 0); #if (IST30XX_FLASH_INFO_SIZE > 0) ist30xxc_isp_erase(data->client, IST30XX_ISP_ERASE_INFO, 0); #endif ist30xxc_isp_enable(data->client, false); ist30xx_reset(data, false); ist30xx_start(data); ist30xx_enable_irq(data); mutex_unlock(&ist30xx_mutex); default: return size; } ret = ist30xx_get_update_info(data, fw, fw_size); if (ret) goto err_get_info; data->fw.bin.main_ver = ist30xx_parse_ver(data, FLAG_MAIN, fw); data->fw.bin.fw_ver = ist30xx_parse_ver(data, FLAG_FW, fw); data->fw.bin.test_ver = ist30xx_parse_ver(data, FLAG_TEST, fw); data->fw.bin.core_ver = ist30xx_parse_ver(data, FLAG_CORE, fw); mutex_lock(&ist30xx_mutex); ist30xx_fw_update(data, fw, fw_size); if (calib) ist30xx_calibrate(data, 1); mutex_unlock(&ist30xx_mutex); ist30xx_start(data); err_get_info: if (request_fw != NULL) release_firmware(request_fw); if (fp != NULL) { err_fw_size: kfree(buff); err_alloc: filp_close(fp, NULL); err_file_open: set_fs(old_fs); } return size; } /* sysfs: /sys/class/touch/firmware/fw_sdcard */ ssize_t ist30xx_fw_sdcard_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0; int fw_size = 0; u8 *fw = NULL; const u8 *buff = 0; mm_segment_t old_fs = { 0 }; struct file *fp = NULL; long fsize = 0, nread = 0; char fw_path[MAX_FILE_PATH]; struct ist30xx_data *data = dev_get_drvdata(dev); old_fs = get_fs(); set_fs(get_ds()); snprintf(fw_path, MAX_FILE_PATH, "/sdcard/%s", IST30XX_FW_NAME); fp = filp_open(fw_path, O_RDONLY, 0); if (IS_ERR(fp)) { tsp_info("file %s open error:%d\n", fw_path, (s32)fp); goto err_file_open; } fsize = fp->f_path.dentry->d_inode->i_size; buff = kzalloc((size_t)fsize, GFP_KERNEL); if (!buff) { tsp_info("fail to alloc buffer\n"); goto err_alloc; } nread = vfs_read(fp, (char __user *)buff, fsize, &fp->f_pos); if (nread != fsize) { tsp_info("mismatch fw size\n"); goto err_fw_size; } fw = (u8 *)buff; fw_size = (u32)fsize; filp_close(fp, current->files); tsp_info("firmware is loaded!!\n"); ret = ist30xx_get_update_info(data, fw, fw_size); if (ret) goto err_get_info; data->fw.bin.main_ver = ist30xx_parse_ver(data, FLAG_MAIN, fw); data->fw.bin.fw_ver = ist30xx_parse_ver(data, FLAG_FW, fw); data->fw.bin.test_ver = ist30xx_parse_ver(data, FLAG_TEST, fw); data->fw.bin.core_ver = ist30xx_parse_ver(data, FLAG_CORE, fw); mutex_lock(&ist30xx_mutex); ist30xx_fw_update(data, fw, fw_size); mutex_unlock(&ist30xx_mutex); ist30xx_start(data); err_get_info: err_fw_size: if (buff) kfree(buff); err_alloc: if (fp) filp_close(fp, NULL); err_file_open: set_fs(old_fs); return 0; } /* sysfs: /sys/class/touch/firmware/firmware */ ssize_t ist30xx_fw_status_show(struct device *dev, struct device_attribute *attr, char *buf) { int count; struct ist30xx_data *data = dev_get_drvdata(dev); switch (data->status.update) { case 1: count = sprintf(buf, "Downloading\n"); break; case 2: if (data->status.update_result) { count = sprintf(buf, "Update fail\n"); } else { count = sprintf(buf, "Update success, ver %x(%x, %x, %x)-> %x(%x, %x, %x), " "status : %d, gap : %d\n", data->fw.prev.fw_ver, data->fw.prev.main_ver, data->fw.prev.test_ver, data->fw.prev.core_ver, data->fw.cur.fw_ver, data->fw.cur.main_ver, data->fw.cur.test_ver, data->fw.cur.core_ver, CALIB_TO_STATUS(data->status.calib_msg), CALIB_TO_GAP(data->status.calib_msg)); } break; default: count = sprintf(buf, "Pass\n"); } return count; } /* sysfs: /sys/class/touch/firmware/fw_read */ u32 buf32_flash[IST30XX_FLASH_TOTAL_SIZE / IST30XX_DATA_LEN]; ssize_t ist30xx_fw_read_show(struct device *dev, struct device_attribute *attr, char *buf) { int i; mm_segment_t old_fs = { 0 }; struct file *fp = NULL; char fw_path[MAX_FILE_PATH]; u8 *buf8 = (u8 *)buf32_flash; struct ist30xx_data *data = dev_get_drvdata(dev); mutex_lock(&ist30xx_mutex); ist30xx_disable_irq(data); ist30xxc_isp_fw_read(data, buf32_flash); for (i = 0; i < IST30XX_FLASH_TOTAL_SIZE; i += 16) { tsp_debug("%07x: %02x%02x %02x%02x %02x%02x %02x%02x " "%02x%02x %02x%02x %02x%02x %02x%02x\n", i, buf8[i], buf8[i + 1], buf8[i + 2], buf8[i + 3], buf8[i + 4], buf8[i + 5], buf8[i + 6], buf8[i + 7], buf8[i + 8], buf8[i + 9], buf8[i + 10], buf8[i + 11], buf8[i + 12], buf8[i + 13], buf8[i + 14], buf8[i + 15]); } old_fs = get_fs(); set_fs(get_ds()); snprintf(fw_path, MAX_FILE_PATH, "/sdcard/%s", IST30XX_BIN_NAME); fp = filp_open(fw_path, O_CREAT|O_WRONLY|O_TRUNC, 0); if (IS_ERR(fp)) { tsp_info("file %s open error:%d\n", fw_path, (s32)fp); goto err_file_open; } fp->f_op->write(fp, buf8, IST30XX_FLASH_TOTAL_SIZE, &fp->f_pos); fput(fp); filp_close(fp, NULL); set_fs(old_fs); err_file_open: ist30xx_enable_irq(data); mutex_unlock(&ist30xx_mutex); ist30xx_start(data); return 0; } /* sysfs: /sys/class/touch/firmware/version */ ssize_t ist30xx_fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) { int count; struct ist30xx_data *data = dev_get_drvdata(dev); count = sprintf(buf, "ID: %x, main: %x, fw: %x, test: %x, core: %x\n", data->chip_id, data->fw.cur.main_ver, data->fw.cur.fw_ver, data->fw.cur.test_ver, data->fw.cur.core_ver); #if IST30XX_INTERNAL_BIN { char msg[128]; ist30xx_get_update_info(data, data->fw.buf, data->fw.buf_size); count += snprintf(msg, sizeof(msg), " Header - main: %x, fw: %x, test: %x, core: %x\n", ist30xx_parse_ver(data, FLAG_MAIN, data->fw.buf), ist30xx_parse_ver(data, FLAG_FW, data->fw.buf), ist30xx_parse_ver(data, FLAG_TEST, data->fw.buf), ist30xx_parse_ver(data, FLAG_CORE, data->fw.buf)); strncat(buf, msg, sizeof(msg)); } #endif return count; } /* sysfs */ static DEVICE_ATTR(fw_read, S_IRWXUGO, ist30xx_fw_read_show, NULL); static DEVICE_ATTR(firmware, S_IRWXUGO, ist30xx_fw_status_show, ist30xx_fw_store); static DEVICE_ATTR(fw_sdcard, S_IRWXUGO, ist30xx_fw_sdcard_show, NULL); static DEVICE_ATTR(version, S_IRWXUGO, ist30xx_fw_version_show, NULL); struct class *ist30xx_class; struct device *ist30xx_fw_dev; static struct attribute *fw_attributes[] = { &dev_attr_fw_read.attr, &dev_attr_firmware.attr, &dev_attr_fw_sdcard.attr, &dev_attr_version.attr, NULL, }; static struct attribute_group fw_attr_group = { .attrs = fw_attributes, }; int ist30xx_init_update_sysfs(struct ist30xx_data *data) { /* /sys/class/touch */ ist30xx_class = class_create(THIS_MODULE, "touch"); /* /sys/class/touch/firmware */ ist30xx_fw_dev = device_create(ist30xx_class, NULL, 0, data, "firmware"); /* /sys/class/touch/firmware/... */ if (unlikely(sysfs_create_group(&ist30xx_fw_dev->kobj, &fw_attr_group))) tsp_err("Failed to create sysfs group(%s)!\n", "firmware"); data->status.update = 0; data->status.calib = 0; data->status.update_result = 0; return 0; }