| /* drivers/input/touchscreen/goodix_tool.c |
| * |
| * 2010 - 2014 Goodix Technology. |
| * |
| * 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. |
| * |
| * Version: 1.4 |
| * Release Date: 2015/07/10 |
| */ |
| |
| #include <linux/delay.h> |
| #include <asm/uaccess.h> |
| #include <linux/proc_fs.h> |
| #include <generated/utsrelease.h> |
| #include "gt1x_generic.h" |
| |
| static ssize_t gt1x_tool_read(struct file *filp, char __user * buffer, size_t count, loff_t * ppos); |
| static ssize_t gt1x_tool_write(struct file *filp, const char *buffer, size_t count, loff_t * ppos); |
| |
| |
| static int gt1x_tool_release(struct inode *inode, struct file *filp); |
| static int gt1x_tool_open(struct inode *inode,struct file *file); |
| |
| #pragma pack(1) |
| typedef struct { |
| u8 wr; //write read flag£¬0:R 1:W 2:PID 3: |
| u8 flag; //0:no need flag/int 1: need flag 2:need int |
| u8 flag_addr[2]; //flag address |
| u8 flag_val; //flag val |
| u8 flag_relation; //flag_val:flag 0:not equal 1:equal 2:> 3:< |
| u16 circle; //polling cycle |
| u8 times; //plling times |
| u8 retry; //I2C retry times |
| u16 delay; //delay befor read or after write |
| u16 data_len; //data length |
| u8 addr_len; //address length |
| u8 addr[2]; //address |
| u8 res[3]; //reserved |
| u8 *data; //data pointer |
| } st_cmd_head; |
| #pragma pack() |
| static st_cmd_head cmd_head; |
| |
| static s32 DATA_LENGTH; |
| static s8 IC_TYPE[16] = "GT1X"; |
| |
| #define UPDATE_FUNCTIONS |
| #define DATA_LENGTH_UINT 512 |
| #define CMD_HEAD_LENGTH (sizeof(st_cmd_head) - sizeof(u8*)) |
| |
| static char procname[20] = { 0 }; |
| |
| static struct proc_dir_entry *gt1x_tool_proc_entry; |
| static struct file_operations gt1x_tool_fops = { |
| .read = gt1x_tool_read, |
| .write = gt1x_tool_write, |
| .open = gt1x_tool_open, |
| .release = gt1x_tool_release, |
| .owner = THIS_MODULE, |
| }; |
| |
| static void set_tool_node_name(char *procname) |
| { |
| |
| int v0 = 0, v1 = 0, v2 = 0; |
| |
| sscanf(UTS_RELEASE, "%d.%d.%d", &v0, &v1, &v2); |
| sprintf(procname, "gmnode%02d%02d%02d", v0, v1, v2); |
| } |
| |
| int gt1x_init_tool_node(void) |
| { |
| DATA_LENGTH = 0; |
| memset(&cmd_head, 0, sizeof(cmd_head)); |
| cmd_head.wr = 1; //if the first operation is read, will return fail. |
| cmd_head.data = kzalloc(DATA_LENGTH_UINT, GFP_KERNEL); |
| if (NULL == cmd_head.data) { |
| GTP_ERROR("Apply for memory failed."); |
| return -1; |
| } |
| GTP_INFO("Alloc memory size:%d.", DATA_LENGTH_UINT); |
| DATA_LENGTH = DATA_LENGTH_UINT - GTP_ADDR_LENGTH; |
| |
| set_tool_node_name(procname); |
| |
| gt1x_tool_proc_entry = proc_create(procname, 0666, NULL, >1x_tool_fops); |
| if (gt1x_tool_proc_entry == NULL) { |
| GTP_ERROR("CAN't create proc entry /proc/%s.", procname); |
| return -1; |
| } else { |
| GTP_INFO("Created proc entry /proc/%s.", procname); |
| } |
| return 0; |
| } |
| |
| void gt1x_deinit_tool_node(void) |
| { |
| remove_proc_entry(procname, NULL); |
| kfree(cmd_head.data); |
| cmd_head.data = NULL; |
| } |
| |
| static s32 tool_i2c_read(u8 * buf, u16 len) |
| { |
| u16 addr = (buf[0] << 8) + buf[1]; |
| if (!gt1x_i2c_read(addr, &buf[2], len)) { |
| return 1; |
| } |
| return -1; |
| } |
| |
| static s32 tool_i2c_write(u8 * buf, u16 len) |
| { |
| u16 addr = (buf[0] << 8) + buf[1]; |
| if (!gt1x_i2c_write(addr, &buf[2], len - 2)) { |
| return 1; |
| } |
| return -1; |
| } |
| |
| static u8 relation(u8 src, u8 dst, u8 rlt) |
| { |
| u8 ret = 0; |
| |
| switch (rlt) { |
| case 0: |
| ret = (src != dst) ? true : false; |
| break; |
| |
| case 1: |
| ret = (src == dst) ? true : false; |
| GTP_DEBUG("equal:src:0x%02x dst:0x%02x ret:%d.", src, dst, (s32) ret); |
| break; |
| |
| case 2: |
| ret = (src > dst) ? true : false; |
| break; |
| |
| case 3: |
| ret = (src < dst) ? true : false; |
| break; |
| |
| case 4: |
| ret = (src & dst) ? true : false; |
| break; |
| |
| case 5: |
| ret = (!(src | dst)) ? true : false; |
| break; |
| |
| default: |
| ret = false; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /******************************************************* |
| Function: |
| Comfirm function. |
| Input: |
| None. |
| Output: |
| Return write length. |
| ********************************************************/ |
| static u8 comfirm(void) |
| { |
| s32 i = 0; |
| u8 buf[32]; |
| |
| memcpy(buf, cmd_head.flag_addr, cmd_head.addr_len); |
| |
| for (i = 0; i < cmd_head.times; i++) { |
| if (tool_i2c_read(buf, 1) <= 0) { |
| GTP_ERROR("Read flag data failed!"); |
| return -1; |
| } |
| |
| if (true == relation(buf[GTP_ADDR_LENGTH], cmd_head.flag_val, cmd_head.flag_relation)) { |
| GTP_DEBUG("value at flag addr:0x%02x.", buf[GTP_ADDR_LENGTH]); |
| GTP_DEBUG("flag value:0x%02x.", cmd_head.flag_val); |
| break; |
| } |
| |
| msleep(cmd_head.circle); |
| } |
| |
| if (i >= cmd_head.times) { |
| GTP_ERROR("Didn't get the flag to continue!"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /******************************************************* |
| Function: |
| Goodix tool write function. |
| Input: |
| standard proc write function param. |
| Output: |
| Return write length. |
| ********************************************************/ |
| static ssize_t gt1x_tool_write(struct file *filp, const char __user * buff, size_t len, loff_t * data) |
| { |
| u64 ret = 0; |
| GTP_DEBUG_FUNC(); |
| GTP_DEBUG_ARRAY((u8 *) buff, len); |
| |
| ret = copy_from_user(&cmd_head, buff, CMD_HEAD_LENGTH); |
| if (ret) { |
| GTP_ERROR("copy_from_user failed."); |
| } |
| |
| GTP_DEBUG("wr :0x%02x.", cmd_head.wr); |
| /* |
| GTP_DEBUG("flag:0x%02x.", cmd_head.flag); |
| GTP_DEBUG("flag addr:0x%02x%02x.", cmd_head.flag_addr[0], cmd_head.flag_addr[1]); |
| GTP_DEBUG("flag val:0x%02x.", cmd_head.flag_val); |
| GTP_DEBUG("flag rel:0x%02x.", cmd_head.flag_relation); |
| GTP_DEBUG("circle :%d.", (s32)cmd_head.circle); |
| GTP_DEBUG("times :%d.", (s32)cmd_head.times); |
| GTP_DEBUG("retry :%d.", (s32)cmd_head.retry); |
| GTP_DEBUG("delay :%d.", (s32)cmd_head.delay); |
| GTP_DEBUG("data len:%d.", (s32)cmd_head.data_len); |
| GTP_DEBUG("addr len:%d.", (s32)cmd_head.addr_len); |
| GTP_DEBUG("addr:0x%02x%02x.", cmd_head.addr[0], cmd_head.addr[1]); |
| GTP_DEBUG("len:%d.", (s32)len); |
| GTP_DEBUG("buf[20]:0x%02x.", buff[CMD_HEAD_LENGTH]); |
| */ |
| |
| if (1 == cmd_head.wr) { |
| u16 addr, data_len, pos; |
| |
| if (1 == cmd_head.flag) { |
| if (comfirm()) { |
| GTP_ERROR("[WRITE]Comfirm fail!"); |
| return -1; |
| } |
| } else if (2 == cmd_head.flag) { |
| //Need interrupt! |
| } |
| |
| addr = (cmd_head.addr[0] << 8) + cmd_head.addr[1]; |
| data_len = cmd_head.data_len; |
| pos = 0; |
| while (data_len > 0) { |
| len = data_len > DATA_LENGTH ? DATA_LENGTH : data_len; |
| ret = copy_from_user(&cmd_head.data[GTP_ADDR_LENGTH], &buff[CMD_HEAD_LENGTH + pos], len); |
| if (ret) { |
| GTP_ERROR("[WRITE]copy_from_user failed."); |
| return -1; |
| } |
| cmd_head.data[0] = ((addr >> 8) & 0xFF); |
| cmd_head.data[1] = (addr & 0xFF); |
| |
| GTP_DEBUG_ARRAY(cmd_head.data, len + GTP_ADDR_LENGTH); |
| |
| if (tool_i2c_write(cmd_head.data, len + GTP_ADDR_LENGTH) <= 0) { |
| GTP_ERROR("[WRITE]Write data failed!"); |
| return -1; |
| } |
| addr += len; |
| pos += len; |
| data_len -= len; |
| } |
| |
| if (cmd_head.delay) { |
| msleep(cmd_head.delay); |
| } |
| |
| return cmd_head.data_len + CMD_HEAD_LENGTH; |
| } else if (3 == cmd_head.wr) { //gt1x unused |
| |
| memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); |
| return cmd_head.data_len + CMD_HEAD_LENGTH; |
| } else if (5 == cmd_head.wr) { //? |
| |
| //memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); |
| return cmd_head.data_len + CMD_HEAD_LENGTH; |
| } else if (7 == cmd_head.wr) { //disable irq! |
| gt1x_irq_disable(); |
| #if GTP_ESD_PROTECT |
| gt1x_esd_switch(SWITCH_OFF); |
| #endif |
| return CMD_HEAD_LENGTH; |
| } else if (9 == cmd_head.wr) { //enable irq! |
| gt1x_irq_enable(); |
| #if GTP_ESD_PROTECT |
| gt1x_esd_switch(SWITCH_ON); |
| #endif |
| return CMD_HEAD_LENGTH; |
| } else if (17 == cmd_head.wr) { |
| ret = copy_from_user(&cmd_head.data[GTP_ADDR_LENGTH], &buff[CMD_HEAD_LENGTH], cmd_head.data_len); |
| if (ret) { |
| GTP_ERROR("copy_from_user failed."); |
| return -1; |
| } |
| |
| if (cmd_head.data[GTP_ADDR_LENGTH]) { |
| GTP_DEBUG("gtp enter rawdiff."); |
| gt1x_rawdiff_mode = true; |
| } else { |
| gt1x_rawdiff_mode = false; |
| GTP_DEBUG("gtp leave rawdiff."); |
| } |
| |
| return CMD_HEAD_LENGTH; |
| } else if (11 == cmd_head.wr) { |
| gt1x_enter_update_mode(); |
| } else if (13 == cmd_head.wr) { |
| gt1x_leave_update_mode(); |
| } else if (15 == cmd_head.wr) { |
| struct task_struct *thrd = NULL; |
| memset(cmd_head.data, 0, cmd_head.data_len + 1); |
| memcpy(cmd_head.data, &buff[CMD_HEAD_LENGTH], cmd_head.data_len); |
| GTP_DEBUG("update firmware, filename: %s", cmd_head.data); |
| thrd = kthread_run(gt1x_update_firmware, (void *)cmd_head.data, "GT1x FW Update"); |
| if (IS_ERR(thrd)) { |
| return PTR_ERR(thrd); |
| } |
| } |
| return CMD_HEAD_LENGTH; |
| } |
| |
| static u8 devicecount = 0; |
| static int gt1x_tool_open(struct inode *inode,struct file *file) |
| { |
| if (devicecount > 0) { |
| return -ERESTARTSYS; |
| GTP_ERROR("tools open failed!"); |
| } |
| |
| devicecount++; |
| return 0; |
| } |
| |
| static int gt1x_tool_release(struct inode *inode, struct file *filp) |
| { |
| devicecount--; |
| return 0; |
| } |
| /******************************************************* |
| Function: |
| Goodix tool read function. |
| Input: |
| standard proc read function param. |
| Output: |
| Return read length. |
| ********************************************************/ |
| static ssize_t gt1x_tool_read(struct file *filp, char __user * buffer, size_t count, loff_t * ppos) |
| { |
| GTP_DEBUG_FUNC(); |
| if(*ppos) { |
| GTP_DEBUG("[PARAM]size: %zd, *ppos: %d", count, (int)*ppos); |
| *ppos = 0; |
| return 0; |
| } |
| |
| if (cmd_head.wr % 2) { |
| GTP_ERROR("[READ] invaild operator fail!"); |
| return -1; |
| } else if (!cmd_head.wr) { |
| /* general i2c read */ |
| u16 addr, data_len, len, loc; |
| |
| if (1 == cmd_head.flag) { |
| if (comfirm()) { |
| GTP_ERROR("[READ]Comfirm fail!"); |
| return -1; |
| } |
| } else if (2 == cmd_head.flag) { |
| //Need interrupt! |
| } |
| |
| addr = (cmd_head.addr[0] << 8) + cmd_head.addr[1]; |
| data_len = cmd_head.data_len; |
| loc = 0; |
| |
| GTP_DEBUG("[READ] ADDR:0x%04X.", addr); |
| GTP_DEBUG("[READ] Length: %d", data_len); |
| |
| if (cmd_head.delay) { |
| msleep(cmd_head.delay); |
| } |
| |
| while (data_len > 0) { |
| len = data_len > DATA_LENGTH ? DATA_LENGTH : data_len; |
| cmd_head.data[0] = (addr >> 8) & 0xFF; |
| cmd_head.data[1] = (addr & 0xFF); |
| if (tool_i2c_read(cmd_head.data, len) <= 0) { |
| GTP_ERROR("[READ]Read data failed!"); |
| return -1; |
| } |
| memcpy(&buffer[loc], &cmd_head.data[GTP_ADDR_LENGTH], len); |
| data_len -= len; |
| addr += len; |
| loc += len; |
| GTP_DEBUG_ARRAY(&cmd_head.data[GTP_ADDR_LENGTH], len); |
| } |
| *ppos += cmd_head.data_len; |
| return cmd_head.data_len; |
| } else if (2 == cmd_head.wr) { |
| GTP_DEBUG("Return ic type:%s len:%d.", buffer, (s32) cmd_head.data_len); |
| return -1; |
| } else if (4 == cmd_head.wr) { |
| /* read fw update progress */ |
| buffer[0] = update_info.progress >> 8; |
| buffer[1] = update_info.progress & 0xff; |
| buffer[2] = update_info.max_progress >> 8; |
| buffer[3] = update_info.max_progress & 0xff; |
| *ppos += 4; |
| return 4; |
| } else if (6 == cmd_head.wr) { |
| //Read error code! |
| return -1; |
| } else if (8 == cmd_head.wr) { |
| /* Read driver version */ |
| s32 tmp_len; |
| tmp_len = strlen(GTP_DRIVER_VERSION); |
| memcpy(buffer, GTP_DRIVER_VERSION, tmp_len); |
| buffer[tmp_len] = 0; |
| *ppos += tmp_len + 1; |
| return (tmp_len + 1); |
| } |
| *ppos += cmd_head.data_len; |
| return cmd_head.data_len; |
| } |