| /* |
| * Goodix I2C Module |
| * Hardware interface layer of touchdriver architecture. |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/ctype.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/platform_device.h> |
| #include <linux/interrupt.h> |
| #include "goodix_ts_core.h" |
| #include "goodix_cfg_bin.h" |
| |
| #define TS_DT_COMPATIBLE "goodix,gt9889" |
| #define TS_DRIVER_NAME "gtx8" |
| #define I2C_MAX_TRANSFER_SIZE 256 |
| #define TS_ADDR_LENGTH 2 |
| #define TS_DOZE_ENABLE_RETRY_TIMES 3 |
| #define TS_DOZE_DISABLE_RETRY_TIMES 9 |
| #define TS_WAIT_CFG_READY_RETRY_TIMES 30 |
| #define TS_WAIT_CMD_FREE_RETRY_TIMES 10 |
| |
| #define TS_REG_COORDS_BASE 0x824E |
| #define TS_REG_CMD 0x8040 |
| #define TS_REG_REQUEST 0x8044 |
| #define TS_REG_VERSION 0x8240 |
| #define TS_REG_CFG_BASE 0x8050 |
| #define TS_REG_DOZE_CTRL 0x30F0 |
| #define TS_REG_DOZE_STAT 0x3100 |
| #define TS_REG_ESD_TICK_R 0x3103 |
| |
| #define CFG_XMAX_OFFSET (0x8052 - 0x8050) |
| #define CFG_YMAX_OFFSET (0x8054 - 0x8050) |
| |
| #define REQUEST_HANDLED 0x00 |
| #define REQUEST_CONFIG 0x01 |
| #define REQUEST_BAKREF 0x02 |
| #define REQUEST_RESET 0x03 |
| #define REQUEST_RELOADFW 0x05 |
| #define REQUEST_IDLE 0xff |
| |
| #define COMMAND_SLEEP 0x05 |
| #define COMMAND_CLOSE_HID 0xaa |
| #define COMMAND_START_SEND_CFG 0x80 |
| #define COMMAND_END_SEND_CFG 0x83 |
| #define COMMAND_SEND_SMALL_CFG 0x81 |
| #define COMMAND_SEND_CFG_PREPARE_OK 0x82 |
| #define COMMAND_START_READ_CFG 0x86 |
| #define COMMAND_READ_CFG_PREPARE_OK 0x85 |
| |
| #define BYTES_PER_COORD 8 |
| #define TS_MAX_SENSORID 5 |
| #define TS_CFG_HEAD_LEN 4 |
| #define TS_CFG_BAG_NUM_INDEX 2 |
| #define TS_CFG_BAG_START_INDEX 4 |
| |
| #define TS_DOZE_DISABLE_DATA 0xAA |
| #define TS_DOZE_CLOSE_OK_DATA 0xBB |
| #define TS_DOZE_ENABLE_DATA 0xCC |
| #define TS_CMD_REG_READY 0xFF |
| |
| int goodix_ts_core_init(void); |
| #ifdef CONFIG_OF |
| /** |
| * goodix_parse_dt_resolution - parse resolution from dt |
| * @node: devicetree node |
| * @board_data: pointer to board data structure |
| * return: 0 - no error, <0 error |
| */ |
| static int goodix_parse_dt_resolution(struct device_node *node, |
| struct goodix_ts_board_data *board_data) |
| { |
| int r, err; |
| |
| r = of_property_read_u32(node, "goodix,panel-max-x", |
| &board_data->panel_max_x); |
| if (r) |
| err = -ENOENT; |
| |
| r = of_property_read_u32(node, "goodix,panel-max-y", |
| &board_data->panel_max_y); |
| if (r) |
| err = -ENOENT; |
| |
| r = of_property_read_u32(node, "goodix,panel-max-w", |
| &board_data->panel_max_w); |
| if (r) |
| err = -ENOENT; |
| |
| board_data->swap_axis = of_property_read_bool(node, |
| "goodix,swap-axis"); |
| board_data->x2x = of_property_read_bool(node, "goodix,x2x"); |
| board_data->y2y = of_property_read_bool(node, "goodix,y2y"); |
| |
| return 0; |
| } |
| |
| /** |
| * goodix_parse_dt - parse board data from dt |
| * @dev: pointer to device |
| * @board_data: pointer to board data structure |
| * return: 0 - no error, <0 error |
| */ |
| static int goodix_parse_dt(struct device_node *node, |
| struct goodix_ts_board_data *board_data) |
| { |
| struct property *prop; |
| const char *name_tmp; |
| int r; |
| |
| if (!board_data) { |
| ts_err("invalid board data"); |
| return -EINVAL; |
| } |
| |
| r = of_get_named_gpio(node, "goodix,reset-gpio", 0); |
| if (r < 0) { |
| ts_err("invalid reset-gpio in dt: %d", r); |
| return -EINVAL; |
| } |
| ts_info("get reset-gpio[%d] from dt", r); |
| board_data->reset_gpio = r; |
| |
| r = of_get_named_gpio(node, "goodix,irq-gpio", 0); |
| if (r < 0) { |
| ts_err("invalid irq-gpio in dt: %d", r); |
| return -EINVAL; |
| } |
| ts_info("get irq-gpio[%d] from dt", r); |
| board_data->irq_gpio = r; |
| |
| r = of_property_read_u32(node, "goodix,irq-flags", |
| &board_data->irq_flags); |
| if (r) { |
| ts_err("invalid irq-flags"); |
| return -EINVAL; |
| } |
| |
| memset(board_data->avdd_name, 0, sizeof(board_data->avdd_name)); |
| r = of_property_read_string(node, "goodix,avdd-name", &name_tmp); |
| if (!r) { |
| ts_info("avdd name form dt: %s", name_tmp); |
| if (strlen(name_tmp) < sizeof(board_data->avdd_name)) |
| strncpy(board_data->avdd_name, |
| name_tmp, sizeof(board_data->avdd_name)); |
| else |
| ts_info("invalied avdd name length: %ld > %ld", |
| strlen(name_tmp), |
| sizeof(board_data->avdd_name)); |
| } |
| r = of_property_read_u32(node, "goodix,power-on-delay-us", |
| &board_data->power_on_delay_us); |
| if (!r) { |
| /* 1000ms is too large, maybe you have pass a wrong value */ |
| if (board_data->power_on_delay_us > 1000 * 1000) { |
| ts_err("Power on delay time exceed 1s, please check"); |
| board_data->power_on_delay_us = 0; |
| } |
| } |
| |
| r = of_property_read_u32(node, "goodix,power-off-delay-us", |
| &board_data->power_off_delay_us); |
| if (!r) { |
| /* 1000ms is too large, maybe you have pass */ |
| if (board_data->power_off_delay_us > 1000 * 1000) { |
| ts_err("Power off delay time exceed 1s, please check"); |
| board_data->power_off_delay_us = 0; |
| } |
| } |
| |
| /* get xyz resolutions */ |
| r = goodix_parse_dt_resolution(node, board_data); |
| if (r < 0) { |
| ts_err("Failed to parse resolutions:%d", r); |
| return r; |
| } |
| |
| /* key map */ |
| prop = of_find_property(node, "goodix,panel-key-map", NULL); |
| if (prop && prop->length) { |
| if (prop->length / sizeof(u32) > GOODIX_MAX_TP_KEY) { |
| ts_err("Size of panel-key-map is invalid"); |
| return r; |
| } |
| |
| board_data->panel_max_key = prop->length / sizeof(u32); |
| board_data->tp_key_num = prop->length / sizeof(u32); |
| r = of_property_read_u32_array(node, |
| "goodix,panel-key-map", |
| &board_data->panel_key_map[0], |
| board_data->panel_max_key); |
| if (r) { |
| ts_err("failed get key map, %d", r); |
| return r; |
| } |
| } |
| |
| /*get pen-enable switch and pen keys, must after "key map"*/ |
| board_data->pen_enable = of_property_read_bool(node, |
| "goodix,pen-enable"); |
| if (board_data->pen_enable) |
| ts_info("goodix pen enabled"); |
| |
| ts_info("***key:%d, %d, %d, %d", |
| board_data->panel_key_map[0], board_data->panel_key_map[1], |
| board_data->panel_key_map[2], board_data->panel_key_map[3]); |
| |
| ts_debug("[DT]x:%d, y:%d, w:%d, p:%d", board_data->panel_max_x, |
| board_data->panel_max_y, board_data->panel_max_w, |
| board_data->panel_max_p); |
| return 0; |
| } |
| #endif |
| |
| int goodix_i2c_test(struct goodix_ts_device *dev) |
| { |
| #define TEST_ADDR 0x4100 |
| #define TEST_LEN 1 |
| struct i2c_client *client = to_i2c_client(dev->dev); |
| unsigned char test_buf[TEST_LEN + 1], addr_buf[2]; |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = !I2C_M_RD, |
| .buf = &addr_buf[0], |
| .len = TS_ADDR_LENGTH, |
| }, { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| .buf = &test_buf[0], |
| .len = TEST_LEN, |
| } |
| }; |
| |
| msgs[0].buf[0] = (TEST_ADDR >> 8) & 0xFF; |
| msgs[0].buf[1] = TEST_ADDR & 0xFF; |
| |
| if (likely(i2c_transfer(client->adapter, msgs, 2) == 2)) |
| return 0; |
| |
| /* test failed */ |
| return -EINVAL; |
| } |
| |
| /* confirm current device is goodix or not. |
| * If confirmed 0 will return. |
| */ |
| int goodix_ts_dev_confirm(struct goodix_ts_device *ts_dev) |
| { |
| #define DEV_CONFIRM_RETRY 3 |
| int retry; |
| |
| for (retry = 0; retry < DEV_CONFIRM_RETRY; retry++) { |
| gpio_direction_output(ts_dev->board_data.reset_gpio, 0); |
| udelay(2000); |
| gpio_direction_output(ts_dev->board_data.reset_gpio, 1); |
| mdelay(5); |
| if (!goodix_i2c_test(ts_dev)) { |
| msleep(95); |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| /** |
| * goodix_i2c_read_trans - read device register through i2c bus |
| * @dev: pointer to device data |
| * @addr: register address |
| * @data: read buffer |
| * @len: bytes to read |
| * return: 0 - read ok, < 0 - i2c transter error |
| */ |
| int goodix_i2c_read_trans(struct goodix_ts_device *dev, unsigned int reg, |
| unsigned char *data, unsigned int len) |
| { |
| struct i2c_client *client = to_i2c_client(dev->dev); |
| unsigned int transfer_length = 0; |
| unsigned int pos = 0, address = reg; |
| unsigned char get_buf[64], addr_buf[2]; |
| int retry, r = 0; |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = !I2C_M_RD, |
| .buf = &addr_buf[0], |
| .len = TS_ADDR_LENGTH, |
| }, { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| } |
| }; |
| |
| if (likely(len < sizeof(get_buf))) { |
| /* code optimize, use stack memory */ |
| msgs[1].buf = &get_buf[0]; |
| } else { |
| msgs[1].buf = kzalloc(I2C_MAX_TRANSFER_SIZE < len |
| ? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL); |
| if (msgs[1].buf == NULL) |
| return -ENOMEM; |
| } |
| |
| while (pos != len) { |
| if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE)) |
| transfer_length = I2C_MAX_TRANSFER_SIZE; |
| else |
| transfer_length = len - pos; |
| |
| msgs[0].buf[0] = (address >> 8) & 0xFF; |
| msgs[0].buf[1] = address & 0xFF; |
| msgs[1].len = transfer_length; |
| |
| for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { |
| if (likely(i2c_transfer(client->adapter, |
| msgs, 2) == 2)) { |
| memcpy(&data[pos], msgs[1].buf, |
| transfer_length); |
| pos += transfer_length; |
| address += transfer_length; |
| break; |
| } |
| ts_info("I2c read retry[%d]:0x%x", retry + 1, reg); |
| msleep(20); |
| } |
| if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { |
| ts_err("I2c read failed,dev:%02x,reg:%04x,size:%u", |
| client->addr, reg, len); |
| r = -EBUS; |
| goto read_exit; |
| } |
| } |
| |
| read_exit: |
| if (unlikely(len >= sizeof(get_buf))) |
| kfree(msgs[1].buf); |
| return r; |
| } |
| |
| /** |
| * goodix_i2c_write_trans - write device register through i2c bus |
| * @dev: pointer to device data |
| * @addr: register address |
| * @data: write buffer |
| * @len: bytes to write |
| * return: 0 - write ok; < 0 - i2c transter error. |
| */ |
| int goodix_i2c_write_trans(struct goodix_ts_device *dev, unsigned int reg, |
| unsigned char *data, unsigned int len) |
| { |
| struct i2c_client *client = to_i2c_client(dev->dev); |
| unsigned int pos = 0, transfer_length = 0; |
| unsigned int address = reg; |
| unsigned char put_buf[64]; |
| int retry, r = 0; |
| struct i2c_msg msg = { |
| .addr = client->addr, |
| .flags = !I2C_M_RD, |
| }; |
| |
| if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) { |
| /* code optimize,use stack memory*/ |
| msg.buf = &put_buf[0]; |
| } else { |
| msg.buf = kmalloc(I2C_MAX_TRANSFER_SIZE < len + TS_ADDR_LENGTH |
| ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, |
| GFP_KERNEL); |
| if (msg.buf == NULL) |
| return -ENOMEM; |
| } |
| |
| while (pos != len) { |
| if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH)) |
| transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH; |
| else |
| transfer_length = len - pos; |
| |
| msg.buf[0] = (unsigned char)((address >> 8) & 0xFF); |
| msg.buf[1] = (unsigned char)(address & 0xFF); |
| msg.len = transfer_length + 2; |
| memcpy(&msg.buf[2], &data[pos], transfer_length); |
| |
| for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { |
| if (likely(i2c_transfer(client->adapter, |
| &msg, 1) == 1)) { |
| pos += transfer_length; |
| address += transfer_length; |
| break; |
| } |
| ts_debug("I2c write retry[%d]", retry + 1); |
| msleep(20); |
| } |
| if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { |
| ts_err("I2c write failed,dev:%02x,reg:%04x,size:%u", |
| client->addr, reg, len); |
| r = -EBUS; |
| goto write_exit; |
| } |
| } |
| |
| write_exit: |
| if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf))) |
| kfree(msg.buf); |
| return r; |
| } |
| |
| |
| /** |
| * goodix_set_i2c_doze_mode - disable or enable doze mode |
| * @dev: pointer to device data |
| * @enable: true/flase |
| * return: 0 - ok; < 0 - error. |
| * This func must be used in pairs, when you disable doze |
| * mode, then you must enable it again. |
| * Between set_doze_false and set_doze_true, do not reset |
| * IC! |
| */ |
| static int goodix_set_i2c_doze_mode(struct goodix_ts_device *dev, int enable) |
| { |
| static DEFINE_MUTEX(doze_mode_lock); |
| static int doze_mode_set_count; |
| int result = -EINVAL; |
| int i; |
| u8 w_data, r_data; |
| |
| if (dev->ic_type != IC_TYPE_NORMANDY) |
| return 0; |
| |
| mutex_lock(&doze_mode_lock); |
| |
| if (enable) { |
| if (doze_mode_set_count != 0) |
| doze_mode_set_count--; |
| |
| /*when count equal 0, allow ic enter doze mode*/ |
| if (doze_mode_set_count == 0) { |
| w_data = TS_DOZE_ENABLE_DATA; |
| for (i = 0; i < TS_DOZE_ENABLE_RETRY_TIMES; i++) { |
| result = goodix_i2c_write_trans(dev, |
| TS_REG_DOZE_CTRL, &w_data, 1); |
| if (!result) { |
| result = 0; |
| goto exit; |
| } |
| usleep_range(1000, 1100); |
| } |
| if (i >= TS_DOZE_ENABLE_RETRY_TIMES) |
| ts_err("i2c doze mode enable failed"); |
| } else { |
| /*ts_info("doze count not euqal 0, |
| * so skip doze mode enable"); |
| */ |
| result = 0; |
| goto exit; |
| } |
| } else { |
| doze_mode_set_count++; |
| |
| if (doze_mode_set_count == 1) { |
| w_data = TS_DOZE_DISABLE_DATA; |
| goodix_i2c_write_trans(dev, TS_REG_DOZE_CTRL, |
| &w_data, 1); |
| usleep_range(1000, 1100); |
| for (i = 0; i < TS_DOZE_DISABLE_RETRY_TIMES; i++) { |
| goodix_i2c_read_trans(dev, |
| TS_REG_DOZE_STAT, &r_data, 1); |
| if (TS_DOZE_CLOSE_OK_DATA == r_data) { |
| result = 0; |
| goto exit; |
| } else if (0xAA != r_data) { |
| w_data = TS_DOZE_DISABLE_DATA; |
| goodix_i2c_write_trans(dev, |
| TS_REG_DOZE_CTRL, &w_data, 1); |
| } |
| usleep_range(10000, 10100); |
| } |
| ts_err("doze mode disable FAILED"); |
| } else { |
| result = 0; |
| goto exit; |
| } |
| } |
| |
| exit: |
| mutex_unlock(&doze_mode_lock); |
| return result; |
| } |
| |
| /** |
| * goodix_i2c_write - write device register through i2c bus |
| * @dev: pointer to device data |
| * @addr: register address |
| * @data: write buffer |
| * @len: bytes to write |
| * return: 0 - write ok; < 0 - i2c transter error. |
| */ |
| int goodix_i2c_write(struct goodix_ts_device *dev, unsigned int reg, |
| unsigned char *data, unsigned int len) |
| { |
| int r = -EINVAL; |
| |
| if (goodix_set_i2c_doze_mode(dev, false)) { |
| ts_err(" faild disable doze, i2c write:0x%04x", reg); |
| goto exit; |
| } |
| r = goodix_i2c_write_trans(dev, reg, data, len); |
| |
| exit: |
| if (goodix_set_i2c_doze_mode(dev, true)) |
| ts_err("failed enable doze write:0x%04x", reg); |
| |
| return r; |
| } |
| |
| /** |
| * goodix_i2c_read - read device register through i2c bus |
| * @dev: pointer to device data |
| * @addr: register address |
| * @data: read buffer |
| * @len: bytes to read |
| * return: 0 - read ok, < 0 - i2c transter error |
| */ |
| int goodix_i2c_read(struct goodix_ts_device *dev, unsigned int reg, |
| unsigned char *data, unsigned int len) |
| { |
| int r = -EINVAL; |
| |
| if (goodix_set_i2c_doze_mode(dev, false)) { |
| ts_err("failed disable doze:0x%04x", reg); |
| goto exit; |
| } |
| r = goodix_i2c_read_trans(dev, reg, data, len); |
| |
| exit: |
| if (goodix_set_i2c_doze_mode(dev, true)) |
| ts_err("failed enable doze :0x%04x", reg); |
| |
| return r; |
| } |
| |
| /** |
| * goodix_i2c_write_trans_once |
| * write device register through i2c bus, no retry |
| * @dev: pointer to device data |
| * @addr: register address |
| * @data: write buffer |
| * @len: bytes to write |
| * return: 0 - write ok; < 0 - i2c transter error. |
| */ |
| int goodix_i2c_write_trans_once(struct goodix_ts_device *dev, unsigned int reg, |
| unsigned char *data, unsigned int len) |
| { |
| struct i2c_client *client = to_i2c_client(dev->dev); |
| unsigned int pos = 0, transfer_length = 0; |
| unsigned int address = reg; |
| unsigned char put_buf[64]; |
| struct i2c_msg msg = { |
| .addr = client->addr, |
| .flags = !I2C_M_RD, |
| }; |
| |
| if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) { |
| /* code optimize,use stack memory*/ |
| msg.buf = &put_buf[0]; |
| } else { |
| msg.buf = kmalloc(I2C_MAX_TRANSFER_SIZE < len + TS_ADDR_LENGTH |
| ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, |
| GFP_KERNEL); |
| if (msg.buf == NULL) { |
| ts_err("Malloc failed"); |
| return -ENOMEM; |
| } |
| } |
| |
| while (pos != len) { |
| if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH)) |
| transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH; |
| else |
| transfer_length = len - pos; |
| |
| msg.buf[0] = (unsigned char)((address >> 8) & 0xFF); |
| msg.buf[1] = (unsigned char)(address & 0xFF); |
| msg.len = transfer_length + 2; |
| memcpy(&msg.buf[2], &data[pos], transfer_length); |
| |
| i2c_transfer(client->adapter, &msg, 1); |
| pos += transfer_length; |
| address += transfer_length; |
| } |
| |
| if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf))) |
| kfree(msg.buf); |
| return 0; |
| } |
| |
| static void goodix_cmds_init(struct goodix_ts_cmd *ts_cmd, |
| u8 cmds, u8 cmd_data, u32 reg_addr) |
| { |
| if (reg_addr) { |
| ts_cmd->cmd_reg = reg_addr; |
| ts_cmd->length = 3; |
| ts_cmd->cmds[0] = cmds; |
| ts_cmd->cmds[1] = cmd_data; |
| ts_cmd->cmds[2] = 0 - cmds - cmd_data; |
| ts_cmd->initialized = true; |
| } else { |
| ts_cmd->initialized = false; |
| } |
| } |
| |
| |
| /** |
| * goodix_send_command - seng cmd to firmware |
| * |
| * @dev: pointer to device |
| * @cmd: pointer to command struct which cotain command data |
| * Returns 0 - succeed,<0 - failed |
| */ |
| int goodix_send_command(struct goodix_ts_device *dev, |
| struct goodix_ts_cmd *cmd) |
| { |
| int ret; |
| |
| if (!cmd || !cmd->initialized) |
| return -EINVAL; |
| |
| ret = goodix_i2c_write(dev, cmd->cmd_reg, cmd->cmds, cmd->length); |
| |
| return ret; |
| } |
| |
| |
| static int goodix_read_version(struct goodix_ts_device *dev, |
| struct goodix_ts_version *version) |
| { |
| u8 buffer[GOODIX_PID_MAX_LEN + 1]; |
| u8 temp_buf[256], checksum; |
| u8 pid_read_len = dev->reg.pid_len; |
| u8 vid_read_len = dev->reg.vid_len; |
| u8 sensor_id_mask = dev->reg.sensor_id_mask; |
| int r; |
| |
| if (!version) { |
| ts_err("pointer of version is NULL"); |
| return -EINVAL; |
| } |
| version->valid = false; |
| |
| /*check reg info valid*/ |
| if (!dev->reg.pid || !dev->reg.sensor_id || !dev->reg.vid) { |
| ts_err("reg is NULL, pid:0x%04x, vid:0x%04x, sensor_id:0x%04x", |
| dev->reg.pid, dev->reg.vid, dev->reg.sensor_id); |
| return -EINVAL; |
| } |
| if (!pid_read_len || pid_read_len > GOODIX_PID_MAX_LEN || |
| !vid_read_len || vid_read_len > GOODIX_VID_MAX_LEN) { |
| ts_err("invalied pid vid length, pid_len:%d, vid_len:%d", |
| pid_read_len, vid_read_len); |
| return -EINVAL; |
| } |
| |
| /*disable doze mode, just valid for normandy |
| * this func must be used in pairs |
| */ |
| if (goodix_set_i2c_doze_mode(dev, false)) { |
| ts_err("failed disable doze"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*check checksum*/ |
| if (dev->reg.version_base && dev->reg.version_len < 256) { |
| r = goodix_i2c_read(dev, dev->reg.version_base, |
| temp_buf, dev->reg.version_len); |
| if (r < 0) { |
| ts_err("Read version base failed, reg:0x%02x, len:%d", |
| dev->reg.version_base, dev->reg.version_len); |
| if (version) |
| version->valid = false; |
| goto exit; |
| } |
| |
| checksum = checksum_u8(temp_buf, dev->reg.version_len); |
| if (checksum) { |
| ts_err("checksum error:0x%02x, base:0x%02x, len:%d", |
| checksum, dev->reg.version_base, |
| dev->reg.version_len); |
| ts_err("%*ph", (int)(dev->reg.version_len / 2), |
| temp_buf); |
| ts_err("%*ph", (int)(dev->reg.version_len - |
| dev->reg.version_len / 2), |
| &temp_buf[dev->reg.version_len / 2]); |
| |
| if (version) |
| version->valid = false; |
| r = -EINVAL; |
| goto exit; |
| } |
| } |
| |
| /*read pid*/ |
| memset(buffer, 0, sizeof(buffer)); |
| memset(version->pid, 0, sizeof(version->pid)); |
| r = goodix_i2c_read(dev, dev->reg.pid, buffer, pid_read_len); |
| if (r < 0) { |
| ts_err("Read pid failed"); |
| if (version) |
| version->valid = false; |
| goto exit; |
| } |
| |
| /* check pid is digit or not, current we only support digital pid */ |
| if (!isdigit(buffer[0]) || !isdigit(buffer[1])) { |
| ts_err("pid not digit: 0x%x,0x%x", buffer[0], buffer[1]); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| memcpy(version->pid, buffer, pid_read_len); |
| |
| /*read vid*/ |
| memset(buffer, 0, sizeof(buffer)); |
| memset(version->vid, 0, sizeof(version->vid)); |
| r = goodix_i2c_read(dev, dev->reg.vid, buffer, vid_read_len); |
| if (r < 0) { |
| ts_err("Read vid failed"); |
| if (version) |
| version->valid = false; |
| goto exit; |
| } |
| memcpy(version->vid, buffer, vid_read_len); |
| |
| /*read sensor_id*/ |
| memset(buffer, 0, sizeof(buffer)); |
| r = goodix_i2c_read(dev, dev->reg.sensor_id, buffer, 1); |
| if (r < 0) { |
| ts_err("Read sensor_id failed"); |
| if (version) |
| version->valid = false; |
| goto exit; |
| } |
| if (sensor_id_mask != 0) { |
| version->sensor_id = buffer[0] & sensor_id_mask; |
| ts_info("sensor_id_mask:0x%02x, sensor_id:0x%02x", |
| sensor_id_mask, version->sensor_id); |
| } else { |
| version->sensor_id = buffer[0]; |
| } |
| |
| version->valid = true; |
| |
| ts_info("PID:%s,SensorID:%d, VID:%*ph", version->pid, |
| version->sensor_id, (int)sizeof(version->vid), version->vid); |
| exit: |
| /*enable doze mode, just valid for normandy |
| * this func must be used in pairs |
| */ |
| goodix_set_i2c_doze_mode(dev, true); |
| |
| return r; |
| } |
| |
| static int goodix_wait_cfg_cmd_ready(struct goodix_ts_device *dev, |
| u8 right_cmd, u8 send_cmd) |
| { |
| int try_times = 0; |
| u8 cmd_flag = 0; |
| u8 cmd_buf[3] = {0}; |
| u16 command_reg = dev->reg.command; |
| struct goodix_ts_cmd ts_cmd; |
| |
| goodix_cmds_init(&ts_cmd, send_cmd, 0, command_reg); |
| |
| for (try_times = 0; try_times < TS_WAIT_CFG_READY_RETRY_TIMES; |
| try_times++) { |
| if (goodix_i2c_read(dev, command_reg, cmd_buf, 3)) { |
| ts_err("Read cmd_reg error"); |
| return -EINVAL; |
| } |
| cmd_flag = cmd_buf[0]; |
| if (cmd_flag == right_cmd) { |
| return 0; |
| } else if (cmd_flag != send_cmd) { |
| ts_err("failed cmd_reg:0x%X, 0x%X, 0x%X", |
| cmd_buf[0], cmd_buf[1], cmd_buf[2]); |
| if (goodix_send_command(dev, &ts_cmd)) { |
| ts_err("Resend cmd 0x%02X FAILED", send_cmd); |
| return -EINVAL; |
| } |
| } |
| usleep_range(10000, 11000); |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int goodix_send_small_config(struct goodix_ts_device *dev, |
| struct goodix_ts_config *config) |
| { |
| int r = 0; |
| int try_times = 0; |
| u8 buf = 0; |
| u16 command_reg = dev->reg.command; |
| u16 cfg_reg = dev->reg.cfg_addr; |
| struct goodix_ts_cmd ts_cmd; |
| |
| /*1. Inquire command_reg until it's free*/ |
| for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; |
| try_times++) { |
| if (!goodix_i2c_read(dev, command_reg, &buf, 1) && |
| buf == TS_CMD_REG_READY) |
| break; |
| usleep_range(10000, 11000); |
| } |
| if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { |
| ts_err("failed send small cfg, reg:0x%04x is not 0xff", |
| command_reg); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*2. write cfg data*/ |
| if (goodix_i2c_write(dev, cfg_reg, config->data, config->length)) { |
| ts_err("send small cfg FAILED, write cfg to fw ERROR"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*3. send 0x81 command*/ |
| goodix_cmds_init(&ts_cmd, COMMAND_SEND_SMALL_CFG, 0, dev->reg.command); |
| if (goodix_send_command(dev, &ts_cmd)) { |
| ts_err("failed send large cfg, COMMAND_SEND_SMALL_CFG ERROR"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| r = 0; |
| ts_info("send small cfg SUCCESS"); |
| |
| exit: |
| return r; |
| } |
| |
| static int goodix_send_large_config(struct goodix_ts_device *dev, |
| struct goodix_ts_config *config) |
| { |
| int r = 0; |
| int try_times = 0; |
| u8 buf = 0; |
| u16 command_reg = dev->reg.command; |
| u16 cfg_reg = dev->reg.cfg_addr; |
| struct goodix_ts_cmd ts_cmd; |
| |
| /*1. Inquire command_reg until it's free*/ |
| for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; |
| try_times++) { |
| if (!goodix_i2c_read(dev, command_reg, &buf, 1) && |
| buf == TS_CMD_REG_READY) |
| break; |
| usleep_range(10000, 11000); |
| } |
| if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { |
| ts_err("failed send large cfg, reg:0x%04x is not 0xff", |
| command_reg); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*2. send "start write cfg" command*/ |
| goodix_cmds_init(&ts_cmd, COMMAND_START_SEND_CFG, 0, dev->reg.command); |
| if (goodix_send_command(dev, &ts_cmd)) { |
| ts_err("failed send large cfg, COMMAND_START_SEND_CFG ERROR"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*3. wait ic set command_reg to 0x82*/ |
| if (goodix_wait_cfg_cmd_ready(dev, COMMAND_SEND_CFG_PREPARE_OK, |
| COMMAND_START_SEND_CFG)) { |
| ts_err("failed send large cfg, reg:0x%04x is not 0x82", |
| command_reg); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*4. write cfg*/ |
| if (goodix_i2c_write(dev, cfg_reg, config->data, config->length)) { |
| ts_err("Send large cfg FAILED, write cfg to fw ERROR"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*5. send "end send cfg" command*/ |
| goodix_cmds_init(&ts_cmd, COMMAND_END_SEND_CFG, 0, dev->reg.command); |
| if (goodix_send_command(dev, &ts_cmd)) { |
| ts_err("failed send large cfg, COMMAND_END_SEND_CFG ERROR"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*6. wait ic set command_reg to 0xff*/ |
| for (try_times = 0; try_times < TS_WAIT_CMD_FREE_RETRY_TIMES; |
| try_times++) { |
| if (!goodix_i2c_read(dev, command_reg, &buf, 1) && |
| buf == TS_CMD_REG_READY) |
| break; |
| usleep_range(10000, 11000); |
| } |
| if (try_times >= TS_WAIT_CMD_FREE_RETRY_TIMES) { |
| ts_err("failed send large cfg, reg:0x%04x is not 0xff", |
| command_reg); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| ts_info("Send large cfg SUCCESS"); |
| r = 0; |
| |
| exit: |
| return r; |
| } |
| |
| static int goodix_check_cfg_valid(struct goodix_ts_device *dev, u8 *cfg, u32 length) |
| { |
| int ret; |
| u8 bag_num; |
| u8 checksum; |
| int i, j; |
| int bag_start = 0; |
| int bag_end = 0; |
| |
| if (!cfg || length < TS_CFG_HEAD_LEN) { |
| ts_err("cfg is INVALID, len:%d", length); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| if (dev->ic_type == IC_TYPE_NANJING) { |
| /*check configuration head checksum*/ |
| checksum = 0; |
| for (i = 0; i < 3; i++) |
| checksum += cfg[i]; |
| |
| if (checksum != 0) { |
| ts_err("cfg head checksum ERROR, checksum:0x%02x", |
| checksum); |
| ret = -EINVAL; |
| goto exit; |
| } |
| bag_num = cfg[1]; |
| bag_start = 3; |
| } else if (dev->ic_type == IC_TYPE_NORMANDY) { |
| checksum = 0; |
| for (i = 0; i < TS_CFG_HEAD_LEN; i++) |
| checksum += cfg[i]; |
| if (checksum != 0) { |
| ts_err("cfg head checksum ERROR, checksum:0x%02x", |
| checksum); |
| ret = -EINVAL; |
| goto exit; |
| } |
| bag_num = cfg[TS_CFG_BAG_NUM_INDEX]; |
| bag_start = TS_CFG_BAG_START_INDEX; |
| } else { |
| ts_err("cfg check FAILED, unkonw ic_type"); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| ts_info("cfg bag_num:%d, cfg length:%d", bag_num, length); |
| |
| /*check each bag's checksum*/ |
| for (j = 0; j < bag_num; j++) { |
| if (bag_start >= length - 1) { |
| ts_err("ERROR, overflow!!bag_start:%d, cfg_len:%d", |
| bag_start, length); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| bag_end = bag_start + cfg[bag_start + 1] + 3; |
| if ((j == 0) && (dev->ic_type == IC_TYPE_NANJING)) |
| /*the first bag of nanjing cfg is different!*/ |
| bag_end = 336; |
| |
| checksum = 0; |
| if (bag_end > length) { |
| ts_err("ERROR, overflow!!bag:%d, bag_start:%d," |
| "bag_end:%d, cfg length:%d", |
| j, bag_start, bag_end, length); |
| ret = -EINVAL; |
| goto exit; |
| } |
| for (i = bag_start; i < bag_end; i++) |
| checksum += cfg[i]; |
| if (checksum != 0) { |
| ts_err("cfg INVALID, bag:%d checksum ERROR:0x%02x", |
| j, checksum); |
| ret = -EINVAL; |
| goto exit; |
| } |
| bag_start = bag_end; |
| } |
| |
| ret = 0; |
| ts_info("configuration check SUCCESS"); |
| |
| exit: |
| return ret; |
| } |
| |
| static int goodix_send_config(struct goodix_ts_device *dev, |
| struct goodix_ts_config *config) |
| { |
| int r = 0; |
| |
| if (!config || !config->initialized) { |
| ts_err("invalid config data"); |
| return -EINVAL; |
| } |
| |
| /*check configuration valid*/ |
| r = goodix_check_cfg_valid(dev, config->data, config->length); |
| if (r != 0) { |
| ts_err("cfg check FAILED"); |
| return -EINVAL; |
| } |
| |
| ts_info("ver:%02xh,size:%d", config->data[0], config->length); |
| mutex_lock(&config->lock); |
| if (dev->ic_type == IC_TYPE_NANJING) |
| r = goodix_send_large_config(dev, config); |
| else if (dev->ic_type == IC_TYPE_NORMANDY) { |
| /*disable doze mode*/ |
| if(!goodix_set_i2c_doze_mode(dev, false)) { |
| if (config->length > 32) |
| r = goodix_send_large_config(dev, config); |
| else |
| r = goodix_send_small_config(dev, config); |
| } else { |
| ts_err("failed disable doze[abort]"); |
| r = -EINVAL; |
| } |
| /*enable doze mode*/ |
| goodix_set_i2c_doze_mode(dev, true); |
| } |
| if (r != 0) |
| ts_err("send_cfg FAILED, ic_type:%d, cfg_len:%d", |
| dev->ic_type, config->length); |
| mutex_unlock(&config->lock); |
| return r; |
| } |
| |
| /** |
| * goodix_close_hidi2c_mode |
| * Called by touch core module when bootup |
| * @ts_dev: pointer to touch device |
| * return: 0 - no error, <0 error |
| */ |
| static int goodix_close_hidi2c_mode(struct goodix_ts_device *ts_dev) |
| { |
| int r = 0; |
| int try_times; |
| int j; |
| unsigned char buffer[1]; |
| unsigned char reg_sta; |
| struct goodix_ts_cmd ts_cmd; |
| |
| for (try_times = 0; try_times < 10; try_times++) { |
| if (goodix_i2c_read(ts_dev, 0x8040, ®_sta, 1) != 0) |
| continue; |
| else if (reg_sta == 0xff) |
| break; |
| usleep_range(10000, 11000); |
| } |
| if (try_times >= 10) { |
| ts_info("failed close hidi2c mode"); |
| return -EINVAL; |
| } |
| |
| goodix_cmds_init(&ts_cmd, COMMAND_CLOSE_HID, 0, 0x8040); |
| for (try_times = 0; try_times < 3; try_times++) { |
| if (ts_cmd.initialized) { |
| r = goodix_send_command(ts_dev, &ts_cmd); |
| if (r) |
| continue; |
| |
| usleep_range(100000, 110000); |
| |
| /*read 0x8040, if it's not 0xFF,continue*/ |
| for (j = 0; j < 3; j++) { |
| if (goodix_i2c_read(ts_dev, 0x8040, buffer, 1) != 0) |
| continue; |
| else { |
| if (buffer[0] != 0xFF) { |
| ts_info("try_times:%d:%d, read 0x8040:0x%02x", |
| try_times, j, buffer[0]); |
| usleep_range(10000, 11000); |
| continue; |
| } else |
| goto exit; |
| } |
| } |
| } |
| } |
| |
| exit: |
| if (try_times >= 3) { |
| ts_info("close hid_i2c mode FAILED"); |
| r = -EINVAL; |
| } else { |
| ts_info("close hid_i2c mode SUCCESS"); |
| r = 0; |
| } |
| return r; |
| } |
| |
| /* success return config length else return -1 */ |
| static int _goodix_do_read_config(struct goodix_ts_device *dev, |
| u32 base_addr, u8 *buf) |
| { |
| int sub_bags = 0; |
| int offset = 0; |
| int subbag_len; |
| u8 checksum; |
| int i; |
| int ret; |
| |
| /*disable doze mode*/ |
| if (goodix_set_i2c_doze_mode(dev, false)) { |
| ts_err("failed disable doze mode[abort]"); |
| ret = -EINVAL; |
| goto err_out; |
| } |
| |
| ret = goodix_i2c_read(dev, base_addr, buf, TS_CFG_HEAD_LEN); |
| if (ret) |
| goto err_out; |
| |
| if (dev->ic_type == IC_TYPE_NANJING) { |
| offset = 3; |
| sub_bags = buf[1]; |
| checksum = checksum_u8(buf, 3); |
| } else { |
| offset = TS_CFG_BAG_START_INDEX; |
| sub_bags = buf[TS_CFG_BAG_NUM_INDEX]; |
| checksum = checksum_u8(buf, TS_CFG_HEAD_LEN); |
| } |
| if (checksum) { |
| ts_err("Config head checksum err:0x%x,data:%*ph", |
| checksum, TS_CFG_HEAD_LEN, buf); |
| ret = -EINVAL; |
| goto err_out; |
| } |
| |
| ts_info("config_version:%u, vub_bags:%u", buf[0], sub_bags); |
| for (i = 0; i < sub_bags; i++) { |
| /* read sub head [0]: sub bag num, [1]: sub bag length */ |
| ret = goodix_i2c_read(dev, base_addr + offset, buf + offset, 2); |
| if (ret) |
| goto err_out; |
| |
| /* read sub bag data */ |
| if (dev->ic_type == IC_TYPE_NANJING && i == 0) |
| subbag_len = buf[offset + 1] + 256; |
| else |
| subbag_len = buf[offset + 1]; |
| |
| ts_debug("sub bag num:%u,sub bag length:%u", |
| buf[offset], subbag_len); |
| ret = goodix_i2c_read(dev, base_addr + offset + 2, |
| buf + offset + 2, subbag_len + 1); |
| if (ret) |
| goto err_out; |
| checksum = checksum_u8(buf + offset, subbag_len + 3); |
| if (checksum) { |
| ts_err("sub bag checksum err:0x%x", checksum); |
| ret = -EINVAL; |
| goto err_out; |
| } |
| offset += subbag_len + 3; |
| ts_debug("sub bag %d, data:%*ph", |
| buf[offset], buf[offset + 1] + 3, buf + offset); |
| } |
| ret = offset; |
| |
| err_out: |
| /*enable doze mode*/ |
| goodix_set_i2c_doze_mode(dev, true); |
| |
| return ret; |
| } |
| |
| /* success return config_len, <= 0 failed */ |
| static int goodix_read_config(struct goodix_ts_device *dev, |
| u8 *config_data, u32 config_len) |
| { |
| struct goodix_ts_cmd ts_cmd; |
| u8 cmd_flag; |
| u32 cmd_reg = dev->reg.command; |
| int r = 0; |
| int i; |
| |
| if (!config_data || config_len > GOODIX_CFG_MAX_SIZE) { |
| ts_err("Illegal params"); |
| return -EINVAL; |
| } |
| if (!dev->reg.command) { |
| ts_err("command register ERROR:0x%04x", dev->reg.command); |
| return -EINVAL; |
| } |
| |
| /*disable doze mode*/ |
| if (goodix_set_i2c_doze_mode(dev, false)) { |
| ts_err("failed disabled doze[abort]"); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /* wait for IC in IDLE state */ |
| for (i = 0; i < TS_WAIT_CMD_FREE_RETRY_TIMES; i++) { |
| cmd_flag = 0; |
| r = goodix_i2c_read(dev, cmd_reg, &cmd_flag, 1); |
| if (r < 0 || cmd_flag == TS_CMD_REG_READY) |
| break; |
| usleep_range(10000, 11000); |
| } |
| if (cmd_flag != TS_CMD_REG_READY) { |
| ts_err("Wait for IC ready IDEL state timeout:addr 0x%x\n", |
| cmd_reg); |
| r = -EAGAIN; |
| goto exit; |
| } |
| /* 0x86 read config command */ |
| goodix_cmds_init(&ts_cmd, COMMAND_START_READ_CFG, 0, cmd_reg); |
| r = goodix_send_command(dev, &ts_cmd); |
| if (r) { |
| ts_err("Failed send read config command"); |
| goto exit; |
| } |
| /* wait for config data ready */ |
| if (goodix_wait_cfg_cmd_ready(dev, COMMAND_READ_CFG_PREPARE_OK, |
| COMMAND_START_READ_CFG)) { |
| ts_err("Wait for config data ready timeout"); |
| r = -EAGAIN; |
| goto exit; |
| } |
| if (config_len) { |
| r = goodix_i2c_read(dev, cmd_reg + 16, config_data, config_len); |
| if (r) |
| ts_err("Failed read config data"); |
| else |
| r = config_len; |
| } else { |
| r = _goodix_do_read_config(dev, cmd_reg + 16, config_data); |
| if (r < 0) |
| ts_err("Failed read config data"); |
| } |
| if (r > 0) |
| ts_info("success read config, len:%d", r); |
| /* clear command */ |
| goodix_cmds_init(&ts_cmd, TS_CMD_REG_READY, 0, cmd_reg); |
| goodix_send_command(dev, &ts_cmd); |
| |
| exit: |
| /*enable doze mode*/ |
| goodix_set_i2c_doze_mode(dev, true); |
| |
| return r; |
| } |
| |
| /** |
| * goodix_hw_init - hardware initialize |
| * Called by touch core module when bootup |
| * @ts_dev: pointer to touch device |
| * return: 0 - no error, <0 error |
| */ |
| static int goodix_hw_init(struct goodix_ts_device *ts_dev) |
| { |
| int r = 0; |
| |
| BUG_ON(!ts_dev); |
| |
| /*for Nanjing IC, close HID_I2C mode when driver is probed*/ |
| if (ts_dev->ic_type == IC_TYPE_NANJING) { |
| r = goodix_close_hidi2c_mode(ts_dev); |
| if (r < 0) |
| ts_info("close hid i2c mode FAILED"); |
| } |
| |
| return r; |
| } |
| |
| /** |
| * goodix_hw_reset - reset device |
| * |
| * @dev: pointer to touch device |
| * Returns 0 - succeed,<0 - failed |
| */ |
| int goodix_hw_reset(struct goodix_ts_device *dev) |
| { |
| u8 data[2] = {0x00}; |
| int r = 0; |
| |
| ts_info("HW reset"); |
| |
| if (dev->ic_type == IC_TYPE_NANJING) { |
| ts_info("nanjing reset"); |
| |
| /*close watch dog*/ |
| data[0] = 0; |
| goodix_i2c_write(dev, 0x40b0, data, 1); |
| usleep_range(10000, 20000); |
| |
| /*soft reset*/ |
| data[0] = 1; |
| goodix_i2c_write_trans_once(dev, 0x4180, data, 1); |
| msleep(250); |
| goodix_close_hidi2c_mode(dev); |
| |
| /*clear coor_reg*/ |
| data[0] = 0; |
| data[1] = 0; |
| goodix_i2c_write(dev, 0x824d, data, 2); |
| } else { |
| ts_info("normandy reset"); |
| gpio_direction_output(dev->board_data.reset_gpio, 0); |
| udelay(2000); |
| gpio_direction_output(dev->board_data.reset_gpio, 1); |
| msleep(100); |
| } |
| |
| /*init static esd*/ |
| data[0] = GOODIX_ESD_TICK_WRITE_DATA; |
| if (dev->ic_type == IC_TYPE_NANJING) { |
| r = goodix_i2c_write(dev, 0x8043, data, 1); |
| if (r < 0) |
| ts_err("fialed init static esd"); |
| } |
| |
| /*init dynamic esd*/ |
| if (dev->reg.esd) { |
| r = goodix_i2c_write_trans(dev, dev->reg.esd, data, 1); |
| if (r < 0) |
| ts_err("IC reset, init dynamic esd FAILED"); |
| } else { |
| ts_info("reg.esd is NULL, skip dynamic esd init"); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * goodix_request_handler - handle firmware request |
| * |
| * @dev: pointer to touch device |
| * @request_data: requset information |
| * Returns 0 - succeed,<0 - failed |
| */ |
| static int goodix_request_handler(struct goodix_ts_device *dev) |
| { |
| unsigned char buffer[1]; |
| int r; |
| |
| r = goodix_i2c_read_trans(dev, dev->reg.fw_request, buffer, 1); |
| if (r < 0) |
| return r; |
| |
| switch (buffer[0]) { |
| case REQUEST_CONFIG: |
| ts_info("HW request config"); |
| r = goodix_send_config(dev, &(dev->normal_cfg)); |
| if (r != 0) |
| ts_info("request config, send config faild"); |
| break; |
| case REQUEST_BAKREF: |
| ts_info("HW request bakref"); |
| break; |
| case REQUEST_RESET: |
| ts_info("HW requset reset"); |
| r = goodix_hw_reset(dev); |
| if (r != 0) |
| ts_info("request reset, reset faild"); |
| break; |
| case REQUEST_RELOADFW: |
| ts_info("HW request reload fw"); |
| goodix_do_fw_update(UPDATE_MODE_FORCE|UPDATE_MODE_SRC_REQUEST); |
| break; |
| case REQUEST_IDLE: |
| ts_info("HW request idle"); |
| break; |
| default: |
| ts_info("Unknown hw request:%d", buffer[0]); |
| break; |
| } |
| |
| buffer[0] = 0x00; |
| r = goodix_i2c_write_trans(dev, dev->reg.fw_request, buffer, 1); |
| return r; |
| } |
| |
| static void goodix_swap_coords(struct goodix_ts_device *dev, |
| unsigned int *coor_x, unsigned int *coor_y) |
| { |
| unsigned int temp; |
| struct goodix_ts_board_data *bdata = &dev->board_data; |
| |
| if (bdata->swap_axis) { |
| temp = *coor_x; |
| *coor_x = *coor_y; |
| *coor_y = temp; |
| } |
| if (bdata->x2x) |
| *coor_x = bdata->panel_max_x - *coor_x; |
| if (bdata->y2y) |
| *coor_y = bdata->panel_max_y - *coor_y; |
| } |
| |
| #define GOODIX_KEY_STATE 0x10 |
| static void goodix_parse_finger(struct goodix_ts_device *dev, |
| struct goodix_touch_data *touch_data, unsigned char *buf, int touch_num) |
| { |
| unsigned int id = 0, x = 0, y = 0, w = 0; |
| static u8 pre_key_map; |
| u8 cur_key_map = 0; |
| static u32 pre_finger_map; |
| u32 cur_finger_map = 0; |
| u8 *coor_data; |
| int i; |
| |
| coor_data = &buf[2]; |
| for (i = 0; i < touch_num; i++) { |
| id = coor_data[i * BYTES_PER_COORD]; |
| if(id >= GOODIX_MAX_TOUCH){ |
| ts_info("invaild finger id =%d", id); |
| break; |
| } |
| x = coor_data[i * BYTES_PER_COORD + 1] | |
| coor_data[i * BYTES_PER_COORD + 2] << 8; |
| y = coor_data[i * BYTES_PER_COORD + 3] | |
| coor_data[i * BYTES_PER_COORD + 4] << 8; |
| w = coor_data[i * BYTES_PER_COORD + 5]; |
| goodix_swap_coords(dev, &x, &y); |
| touch_data->coords[id].status = TS_TOUCH; |
| touch_data->coords[id].x = x; |
| touch_data->coords[id].y = y; |
| touch_data->coords[id].w = w; |
| cur_finger_map |= (1 << id); |
| } |
| |
| /* process finger release */ |
| for (i = 0; i < GOODIX_MAX_TOUCH; i++) { |
| if (cur_finger_map & (1 << i)) |
| continue; |
| if (pre_finger_map & (1 << i)) |
| touch_data->coords[i].status = TS_RELEASE; |
| } |
| pre_finger_map = cur_finger_map; |
| touch_data->touch_num = touch_num; |
| |
| if (buf[1] & GOODIX_KEY_STATE) { |
| /* have key */ |
| cur_key_map = buf[touch_num * BYTES_PER_COORD + 2] & 0x0F; |
| for (i = 0; i < GOODIX_MAX_TP_KEY; i++) { |
| if (cur_key_map & (1 << i)) { |
| touch_data->keys[i].status = TS_TOUCH; |
| touch_data->keys[i].code = |
| dev->board_data.panel_key_map[i]; |
| } |
| } |
| } |
| /* process key release */ |
| for (i = 0; i < GOODIX_MAX_TP_KEY; i++) { |
| if (cur_key_map & (1 << i) || !(pre_key_map & (1 << i))) |
| continue; |
| touch_data->keys[i].status = TS_RELEASE; |
| touch_data->keys[i].code = dev->board_data.panel_key_map[i]; |
| } |
| pre_key_map = cur_key_map; |
| } |
| |
| static unsigned int goodix_pen_btn_code[] = {BTN_STYLUS, BTN_STYLUS2}; |
| static void goodix_parse_pen(struct goodix_ts_device *dev, |
| struct goodix_pen_data *pen_data, unsigned char *buf, int touch_num) |
| { |
| unsigned int id = 0; |
| static u8 pre_key_map; |
| u8 cur_key_map = 0; |
| static u32 pre_pen_status; |
| u32 cur_pen_status = 0; |
| u8 *coor_data; |
| int i; |
| |
| coor_data = &buf[2]; |
| for (i = 0; i < touch_num; i++) { |
| /* search for pen coordinate */ |
| id = coor_data[i * BYTES_PER_COORD]; |
| if (id < 0x80) |
| continue; |
| pen_data->coords.x = coor_data[i * BYTES_PER_COORD + 1] | |
| coor_data[i * BYTES_PER_COORD + 2] << 8; |
| pen_data->coords.y = coor_data[i * BYTES_PER_COORD + 3] | |
| coor_data[i * BYTES_PER_COORD + 4] << 8; |
| pen_data->coords.p = coor_data[i * BYTES_PER_COORD + 5] | |
| coor_data[i * BYTES_PER_COORD + 6] << 8; |
| goodix_swap_coords(dev, &pen_data->coords.x, |
| &pen_data->coords.y); |
| pen_data->coords.status = TS_TOUCH; |
| pen_data->coords.tool_type = BTN_TOOL_PEN; |
| cur_pen_status = 1; |
| /* currently only support one stylus */ |
| break; |
| } |
| if (!cur_pen_status && pre_pen_status) { |
| pen_data->coords.status = TS_RELEASE; |
| } |
| pre_pen_status = cur_pen_status; |
| |
| /* process pen button */ |
| if (buf[1] & GOODIX_KEY_STATE) { |
| cur_key_map = (buf[touch_num * BYTES_PER_COORD + 2] >> 4) & 0x0F; |
| for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { |
| if (!(cur_key_map & (1 << i))) |
| continue; |
| pen_data->keys[i].status = TS_TOUCH; |
| pen_data->keys[i].code = goodix_pen_btn_code[i]; |
| } |
| } |
| for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { |
| if (cur_key_map & (1 << i) || !(pre_key_map & (1 << i))) |
| continue; |
| pen_data->keys[i].status = TS_RELEASE; |
| pen_data->keys[i].code = goodix_pen_btn_code[i]; |
| } |
| pre_key_map = cur_key_map; |
| } |
| |
| static int goodix_touch_handler(struct goodix_ts_device *dev, |
| struct goodix_ts_event *ts_event, |
| u8 *pre_buf, u32 pre_buf_len) |
| { |
| struct goodix_touch_data *touch_data = &ts_event->touch_data; |
| struct goodix_pen_data *pen_data = &ts_event->pen_data; |
| unsigned char buffer[4 + BYTES_PER_COORD * GOODIX_MAX_TOUCH]; |
| int touch_num = 0, r; |
| unsigned char chksum = 0; |
| |
| static u8 pre_finger_num = 0; |
| static u8 pre_pen_num = 0; |
| |
| /* clean event buffer */ |
| memset(ts_event, 0, sizeof(*ts_event)); |
| /* copy pre-data to buffer */ |
| memcpy(buffer, pre_buf, pre_buf_len); |
| |
| touch_num = buffer[1] & 0x0F; |
| |
| if (unlikely(touch_num > GOODIX_MAX_TOUCH)) { |
| touch_num = -EINVAL; |
| goto exit_clean_sta; |
| } |
| if (unlikely(touch_num > 1)) { |
| r = goodix_i2c_read_trans(dev, |
| dev->reg.coor + 4 + BYTES_PER_COORD, |
| &buffer[4 + BYTES_PER_COORD], |
| (touch_num - 1) * BYTES_PER_COORD); |
| if (unlikely(r < 0)) |
| goto exit_clean_sta; |
| } |
| |
| chksum = checksum_u8(&buffer[0], touch_num * BYTES_PER_COORD + 4); |
| if (unlikely(chksum != 0)) { |
| ts_err("Checksum error:%X, ic_type:%d", chksum, dev->ic_type); |
| r = -EINVAL; |
| goto exit_clean_sta; |
| } |
| |
| if (touch_num >= 1 && |
| buffer[(touch_num - 1) * BYTES_PER_COORD + 2] >= 0x80) { |
| if (pre_finger_num) { |
| ts_event->event_type = EVENT_TOUCH; |
| goodix_parse_finger(dev, touch_data, buffer, 0); |
| pre_finger_num = 0; |
| } else { |
| pre_pen_num = 1; |
| ts_event->event_type = EVENT_PEN; |
| goodix_parse_pen(dev, pen_data, buffer, touch_num); |
| } |
| } else { |
| if (pre_pen_num) { |
| ts_event->event_type = EVENT_PEN; |
| goodix_parse_pen(dev, pen_data, buffer, 0); |
| pre_pen_num = 0; |
| } else { |
| ts_event->event_type = EVENT_TOUCH; |
| goodix_parse_finger(dev, touch_data, |
| buffer, touch_num); |
| pre_finger_num = touch_num; |
| } |
| } |
| exit_clean_sta: |
| return r; |
| } |
| |
| static int goodix_event_handler(struct goodix_ts_device *dev, |
| struct goodix_ts_event *ts_event) |
| { |
| unsigned char pre_buf[4 + BYTES_PER_COORD]; |
| unsigned char event_sta; |
| int r; |
| |
| r = goodix_i2c_read_trans(dev, dev->reg.coor, |
| pre_buf, 4 + BYTES_PER_COORD); |
| if (unlikely(r < 0)) |
| return r; |
| |
| /* buffer[0]: event state */ |
| event_sta = pre_buf[0]; |
| if (likely((event_sta & GOODIX_TOUCH_EVENT) == GOODIX_TOUCH_EVENT)) { |
| /* handle touch event */ |
| goodix_touch_handler(dev, ts_event, pre_buf, |
| 4 + BYTES_PER_COORD); |
| } else if (unlikely((event_sta & GOODIX_REQUEST_EVENT) == |
| GOODIX_REQUEST_EVENT)) { |
| /* handle request event */ |
| ts_event->event_type = EVENT_REQUEST; |
| goodix_request_handler(dev); |
| } else if ((event_sta & GOODIX_GESTURE_EVENT) == |
| GOODIX_GESTURE_EVENT) { |
| /* handle gesture event */ |
| ts_debug("Gesture event"); |
| } else if ((event_sta & GOODIX_HOTKNOT_EVENT) == |
| GOODIX_HOTKNOT_EVENT) { |
| /* handle hotknot event */ |
| ts_debug("Hotknot event"); |
| } else { |
| ts_debug("unknow event type:0x%x", event_sta); |
| r = -EINVAL; |
| } |
| |
| return r; |
| } |
| |
| |
| /** |
| * goodix_hw_suspend - Let touch deivce stay in lowpower mode. |
| * @dev: pointer to goodix touch device |
| * @return: 0 - succeed, < 0 - failed |
| */ |
| static int goodix_hw_suspend(struct goodix_ts_device *dev) |
| { |
| struct goodix_ts_cmd sleep_cmd; |
| int r = 0; |
| |
| goodix_cmds_init(&sleep_cmd, COMMAND_SLEEP, 0, dev->reg.command); |
| if (sleep_cmd.initialized) { |
| r = goodix_send_command(dev, &sleep_cmd); |
| if (!r) |
| ts_info("Chip in sleep mode"); |
| } else { |
| ts_err("Uninitialized sleep command"); |
| } |
| return r; |
| } |
| |
| /** |
| * goodix_hw_resume - Let touch deivce stay in active mode. |
| * @dev: pointer to goodix touch device |
| * @return: 0 - succeed, < 0 - failed |
| */ |
| static int goodix_hw_resume(struct goodix_ts_device *dev) |
| { |
| int retry = GOODIX_BUS_RETRY_TIMES; |
| u8 temp_buf[256], checksum; |
| u8 data[2] = {0x00}; |
| int r = 0, i; |
| |
| for (; retry > 0; retry--) { |
| if (dev->ic_type == IC_TYPE_NORMANDY) { |
| goodix_hw_reset(dev); |
| } else if (dev->ic_type == IC_TYPE_NANJING) { |
| /* 1. read 0x8000 to resume nanjing */ |
| goodix_i2c_read(dev, 0x8000, data, 1); |
| msleep(150); |
| /* 2. check resume success or not */ |
| for (i = 0; i < 10; i++) { |
| r = goodix_i2c_read(dev, dev->reg.command, |
| data, 1); |
| if (!r && data[0] == 0xff) |
| break; |
| msleep(20); |
| } |
| |
| if (i >= 10) { |
| ts_err("failed resume from sleep mode"); |
| continue; |
| } |
| /* 3. close hid i2c */ |
| goodix_close_hidi2c_mode(dev); |
| |
| /* 4. clear coor */ |
| data[0] = 0; |
| data[1] = 0; |
| goodix_i2c_write(dev, 0x824d, data, 2); |
| } |
| |
| /* Why need do this?? read version and check checksum */ |
| if (dev->reg.version_base && dev->reg.version_len < 256) { |
| r = goodix_i2c_read(dev, dev->reg.version_base, |
| temp_buf, dev->reg.version_len); |
| if (r < 0) |
| continue; |
| |
| checksum = checksum_u8(temp_buf, dev->reg.version_len); |
| if (!checksum) { |
| ts_info("read version SUCCESS"); |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| return r; |
| } |
| |
| static int goodix_esd_check(struct goodix_ts_device *dev) |
| { |
| int r; |
| u8 data = 0; |
| |
| if (dev->reg.esd == 0) { |
| ts_err("esd reg is NULL"); |
| return 0; |
| } |
| |
| /*check dynamic esd*/ |
| if (dev->ic_type == IC_TYPE_NORMANDY) |
| r = dev->hw_ops->read_trans(dev, TS_REG_ESD_TICK_R, &data, 1); |
| else |
| r = dev->hw_ops->read_trans(dev, dev->reg.esd, &data, 1); |
| |
| if (r < 0 || (data == GOODIX_ESD_TICK_WRITE_DATA)) { |
| ts_info("dynamic esd occur, r:%d, data:0x%02x", r, data); |
| r = -EINVAL; |
| goto exit; |
| } |
| |
| /*check static esd*/ |
| if (dev->ic_type == IC_TYPE_NANJING) { |
| r = dev->hw_ops->read_trans(dev, 0x8043, &data, 1); |
| if (r < 0 || (data != 0xaa)) { |
| ts_info("static esd occur, r:%d, data:0x%02x", r, data); |
| r = -EINVAL; |
| goto exit; |
| } |
| } |
| |
| exit: |
| return r; |
| } |
| |
| /* hardware opeation funstions */ |
| static const struct goodix_ts_hw_ops hw_i2c_ops = { |
| .init = goodix_hw_init, |
| .dev_confirm = goodix_ts_dev_confirm, |
| .read = goodix_i2c_read, |
| .write = goodix_i2c_write, |
| .read_trans = goodix_i2c_read_trans, |
| .write_trans = goodix_i2c_write_trans, |
| .reset = goodix_hw_reset, |
| .event_handler = goodix_event_handler, |
| .send_config = goodix_send_config, |
| .read_config = goodix_read_config, |
| .send_cmd = goodix_send_command, |
| .read_version = goodix_read_version, |
| .suspend = goodix_hw_suspend, |
| .resume = goodix_hw_resume, |
| .check_hw = goodix_esd_check, |
| }; |
| |
| static struct platform_device *goodix_pdev; |
| |
| static void goodix_pdev_release(struct device *dev) |
| { |
| ts_info("goodix pdev released"); |
| } |
| |
| static int goodix_i2c_probe(struct i2c_client *client, |
| const struct i2c_device_id *dev_id) |
| { |
| struct goodix_ts_device *ts_device = NULL; |
| int r = 0; |
| |
| ts_info("goodix_i2c_probe IN"); |
| |
| r = i2c_check_functionality(client->adapter, |
| I2C_FUNC_I2C); |
| if (!r) |
| return -EIO; |
| |
| /* ts device data */ |
| ts_device = devm_kzalloc(&client->dev, |
| sizeof(struct goodix_ts_device), GFP_KERNEL); |
| if (!ts_device) |
| return -ENOMEM; |
| |
| if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { |
| /* parse devicetree property */ |
| r = goodix_parse_dt(client->dev.of_node, |
| &ts_device->board_data); |
| if (r < 0) { |
| ts_err("failed parse device info form dts, %d", r); |
| return -EINVAL; |
| } |
| } else { |
| ts_err("no valid device tree node found"); |
| return -ENODEV; |
| } |
| |
| ts_device->name = "Goodix TouchDevcie"; |
| ts_device->dev = &client->dev; |
| ts_device->hw_ops = &hw_i2c_ops; |
| |
| /* ts core device */ |
| goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); |
| if (!goodix_pdev) |
| return -ENOMEM; |
| |
| goodix_pdev->name = GOODIX_CORE_DRIVER_NAME; |
| goodix_pdev->id = 0; |
| goodix_pdev->num_resources = 0; |
| /* |
| * you can find this platform dev in |
| * /sys/devices/platfrom/goodix_ts.0 |
| * goodix_pdev->dev.parent = &client->dev; |
| */ |
| goodix_pdev->dev.platform_data = ts_device; |
| goodix_pdev->dev.release = goodix_pdev_release; |
| |
| /* register platform device, then the goodix_ts_core |
| * module will probe the touch deivce. |
| */ |
| r = platform_device_register(goodix_pdev); |
| if (r) { |
| ts_err("failed register goodix platform device, %d", r); |
| goto err_pdev; |
| } |
| r = goodix_ts_core_init(); |
| if (r) { |
| ts_err("failed register platform driver, %d", r); |
| goto err_pdriver; |
| } |
| ts_info("i2c probe out"); |
| return r; |
| |
| err_pdriver: |
| platform_device_unregister(goodix_pdev); |
| err_pdev: |
| kfree(goodix_pdev); |
| goodix_pdev = NULL; |
| ts_info("i2c probe out, %d", r); |
| return r; |
| } |
| |
| static int goodix_i2c_remove(struct i2c_client *client) |
| { |
| if (goodix_pdev) { |
| platform_device_unregister(goodix_pdev); |
| kfree(goodix_pdev); |
| goodix_pdev = NULL; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id i2c_matchs[] = { |
| {.compatible = "goodix,gt9886",}, |
| {.compatible = "goodix,gt9889",}, |
| {.compatible = "goodix,gt8589",}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, i2c_matchs); |
| #endif |
| |
| static const struct i2c_device_id i2c_id_table[] = { |
| {TS_DRIVER_NAME, 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, i2c_id_table); |
| |
| static struct i2c_driver goodix_i2c_driver = { |
| .driver = { |
| .name = TS_DRIVER_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(i2c_matchs), |
| }, |
| .probe = goodix_i2c_probe, |
| .remove = goodix_i2c_remove, |
| .id_table = i2c_id_table, |
| }; |
| |
| /* release manully when prob failed */ |
| void goodix_ts_dev_release(void) |
| { |
| if (goodix_pdev) { |
| platform_device_unregister(goodix_pdev); |
| kfree(goodix_pdev); |
| goodix_pdev = NULL; |
| } |
| i2c_del_driver(&goodix_i2c_driver); |
| } |
| |
| static int __init goodix_i2c_init(void) |
| { |
| ts_info("Goodix driver init"); |
| return i2c_add_driver(&goodix_i2c_driver); |
| } |
| |
| static void __exit goodix_i2c_exit(void) |
| { |
| i2c_del_driver(&goodix_i2c_driver); |
| ts_info("Goodix driver exit"); |
| } |
| |
| module_init(goodix_i2c_init); |
| module_exit(goodix_i2c_exit); |
| |
| MODULE_DESCRIPTION("Goodix Touchscreen Hardware Module"); |
| MODULE_AUTHOR("Goodix, Inc."); |
| MODULE_LICENSE("GPL v2"); |