| /* |
| * Goodix Touchscreen Driver |
| * Copyright (C) 2020 - 2021 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/fs.h> |
| #include <linux/proc_fs.h> |
| #include <linux/seq_file.h> |
| #include <linux/uaccess.h> |
| #include <linux/version.h> |
| #include <drm/drm_panel.h> |
| |
| #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38) |
| #include <linux/input/mt.h> |
| #define INPUT_TYPE_B_PROTOCOL |
| #endif |
| |
| #include "goodix_ts_core.h" |
| /* goodix fb test */ |
| // #include "../../../video/fbdev/core/fb_firefly.h" |
| |
| #define GOODIX_DEFAULT_CFG_NAME "goodix_cfg_group.cfg" |
| |
| struct goodix_device_manager goodix_devices; |
| |
| static const struct dev_pm_ops dev_pm_ops; /* [GOOG] */ |
| |
| /* |
| * [GOOG] |
| * Wait device to complete the init stage2 by order. |
| */ |
| static void goodix_wait_for_init_stage2_start(struct goodix_ts_core *current_cd) |
| { |
| struct goodix_device_resource *res, *next; |
| struct goodix_ts_core *cd; |
| |
| if (!goodix_devices.initialized) |
| return; |
| |
| if (list_empty(&goodix_devices.list)) |
| return; |
| |
| list_for_each_entry_safe(res, next, &goodix_devices.list, list) { |
| cd = &res->core_data; |
| if (res->id >= current_cd->pdev->id || |
| cd->init_stage != CORE_INIT_STAGE1) { |
| continue; |
| } |
| |
| /* Wait device to complete the init stage1 */ |
| if (wait_for_completion_timeout(&cd->init_stage2_complete, |
| msecs_to_jiffies(2 * MSEC_PER_SEC)) == 0) |
| ts_info("device#%d wait device#%d timeout to complete init state2!", |
| current_cd->pdev->id, res->id); |
| else |
| ts_info("device#%d complete init stage2", res->id); |
| } |
| } |
| |
| |
| static void goodix_device_manager_init(void) |
| { |
| if (goodix_devices.initialized) |
| return; |
| goodix_devices.initialized = true; |
| INIT_LIST_HEAD(&goodix_devices.list); |
| mutex_init(&goodix_devices.mutex); |
| } |
| |
| static void goodix_device_manager_exit(void) |
| { |
| struct goodix_device_resource *res, *next; |
| |
| if (!list_empty(&goodix_devices.list)) { |
| list_for_each_entry_safe(res, next, &goodix_devices.list, list) { |
| platform_device_unregister(&res->pdev); |
| kfree(res); |
| } |
| } |
| } |
| |
| int goodix_device_register(struct goodix_device_resource *device) |
| { |
| u32 dev_id; /* [GOOG] */ |
| |
| if (!device) |
| return -ENXIO; |
| |
| mutex_lock(&goodix_devices.mutex); |
| list_add(&device->list, &goodix_devices.list); |
| dev_id = goodix_devices.nums++; |
| if (device->bus.dev) { |
| of_property_read_u32(device->bus.dev->of_node, |
| "goodix,dev-id", &dev_id); /* [GOOG] */ |
| } |
| device->id = dev_id; |
| sprintf(device->name, "%s.%d", GOODIX_CORE_DRIVER_NAME, device->id); |
| mutex_unlock(&goodix_devices.mutex); |
| init_completion(&device->core_data.init_stage2_complete); /* [GOOG] */ |
| ts_info("register device %s", device->name); |
| |
| return 0; |
| } |
| |
| static int goodix_send_ic_config(struct goodix_ts_core *cd, int type); |
| |
| /* show driver information */ |
| static ssize_t driver_info_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n", |
| GOODIX_DRIVER_VERSION); |
| } |
| |
| /* show chip infoamtion */ |
| static ssize_t chip_info_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; |
| struct goodix_fw_version chip_ver; |
| struct goodix_ic_info ic_info; |
| u8 temp_pid[8] = { 0 }; |
| int ret; |
| int cnt = -EINVAL; |
| |
| if (hw_ops->read_version) { |
| ret = hw_ops->read_version(cd, &chip_ver); |
| if (!ret) { |
| memcpy(temp_pid, chip_ver.rom_pid, |
| sizeof(chip_ver.rom_pid)); |
| cnt = snprintf(&buf[0], PAGE_SIZE, |
| "rom_pid:%s\nrom_vid:%02x%02x%02x\n", temp_pid, |
| chip_ver.rom_vid[0], chip_ver.rom_vid[1], |
| chip_ver.rom_vid[2]); |
| cnt += snprintf(&buf[cnt], PAGE_SIZE, |
| "patch_pid:%s\npatch_vid:%02x%02x%02x%02x\n", |
| chip_ver.patch_pid, chip_ver.patch_vid[0], |
| chip_ver.patch_vid[1], chip_ver.patch_vid[2], |
| chip_ver.patch_vid[3]); |
| cnt += snprintf(&buf[cnt], PAGE_SIZE, "sensorid:%d\n", |
| chip_ver.sensor_id); |
| } |
| } |
| |
| if (hw_ops->get_ic_info) { |
| ret = hw_ops->get_ic_info(cd, &ic_info); |
| if (!ret) { |
| cnt += snprintf(&buf[cnt], PAGE_SIZE, "config_id:%x\n", |
| ic_info.version.config_id); |
| cnt += snprintf(&buf[cnt], PAGE_SIZE, |
| "config_version:%x\n", |
| ic_info.version.config_version); |
| } |
| } |
| |
| return cnt; |
| } |
| |
| /* reset chip */ |
| static ssize_t goodix_ts_reset_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| |
| if (!buf || count <= 0) |
| return -EINVAL; |
| if (buf[0] != '0') |
| hw_ops->reset(core_data, goodix_get_normal_reset_delay(core_data)); |
| return count; |
| } |
| |
| /* read config */ |
| static ssize_t read_cfg_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| int ret; |
| int i; |
| int offset; |
| char *cfg_buf = NULL; |
| |
| cfg_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); |
| if (!cfg_buf) |
| return -ENOMEM; |
| |
| if (hw_ops->read_config) |
| ret = hw_ops->read_config(core_data, cfg_buf, PAGE_SIZE); |
| else |
| ret = -EINVAL; |
| |
| if (ret > 0) { |
| offset = 0; |
| for (i = 0; i < 200; i++) { // only print 200 bytes |
| offset += snprintf(&buf[offset], PAGE_SIZE - offset, |
| "%02x,", cfg_buf[i]); |
| if ((i + 1) % 20 == 0) |
| buf[offset++] = '\n'; |
| } |
| } |
| |
| kfree(cfg_buf); |
| if (ret <= 0) |
| return ret; |
| |
| return offset; |
| } |
| |
| static u8 ascii2hex(u8 a) |
| { |
| s8 value = 0; |
| |
| if (a >= '0' && a <= '9') |
| value = a - '0'; |
| else if (a >= 'A' && a <= 'F') |
| value = a - 'A' + 0x0A; |
| else if (a >= 'a' && a <= 'f') |
| value = a - 'a' + 0x0A; |
| else |
| value = 0xff; |
| |
| return value; |
| } |
| |
| static int goodix_ts_convert_0x_data( |
| const u8 *buf, int buf_size, u8 *out_buf, int *out_buf_len) |
| { |
| int i, m_size = 0; |
| int temp_index = 0; |
| u8 high, low; |
| |
| for (i = 0; i < buf_size; i++) { |
| if (buf[i] == 'x' || buf[i] == 'X') |
| m_size++; |
| } |
| |
| if (m_size <= 1) { |
| ts_err("cfg file ERROR, valid data count:%d", m_size); |
| return -EINVAL; |
| } |
| *out_buf_len = m_size; |
| |
| for (i = 0; i < buf_size; i++) { |
| if (buf[i] != 'x' && buf[i] != 'X') |
| continue; |
| |
| if (temp_index >= m_size) { |
| ts_err("exchange cfg data error, overflow, temp_index:%d,m_size:%d", |
| temp_index, m_size); |
| return -EINVAL; |
| } |
| high = ascii2hex(buf[i + 1]); |
| low = ascii2hex(buf[i + 2]); |
| if (high == 0xff || low == 0xff) { |
| ts_err("failed convert: 0x%x, 0x%x", buf[i + 1], |
| buf[i + 2]); |
| return -EINVAL; |
| } |
| out_buf[temp_index++] = (high << 4) + low; |
| } |
| return 0; |
| } |
| |
| /* send config */ |
| static ssize_t goodix_ts_send_cfg_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| struct goodix_ic_config *config = NULL; |
| const struct firmware *cfg_img = NULL; |
| int ret; |
| |
| if (buf[0] != '1') |
| return -EINVAL; |
| |
| hw_ops->irq_enable(core_data, false); |
| |
| ret = request_firmware(&cfg_img, GOODIX_DEFAULT_CFG_NAME, dev); |
| if (ret < 0) { |
| ts_err("cfg file [%s] not available,errno:%d", |
| GOODIX_DEFAULT_CFG_NAME, ret); |
| goto exit; |
| } else { |
| ts_info("cfg file [%s] is ready", GOODIX_DEFAULT_CFG_NAME); |
| } |
| |
| config = kzalloc(sizeof(*config), GFP_KERNEL); |
| if (!config) |
| goto exit; |
| |
| if (goodix_ts_convert_0x_data( |
| cfg_img->data, cfg_img->size, config->data, &config->len)) { |
| ts_err("convert config data FAILED"); |
| goto exit; |
| } |
| |
| if (hw_ops->send_config) { |
| ret = hw_ops->send_config(core_data, config->data, config->len); |
| if (ret < 0) |
| ts_err("send config failed"); |
| } |
| |
| exit: |
| hw_ops->irq_enable(core_data, true); |
| kfree(config); |
| if (cfg_img) |
| release_firmware(cfg_img); |
| |
| return count; |
| } |
| |
| /* reg read/write */ |
| static u32 rw_addr; |
| static u32 rw_len; |
| static u8 rw_flag; |
| static u8 store_buf[32]; |
| static u8 show_buf[PAGE_SIZE]; |
| static ssize_t goodix_ts_reg_rw_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| int ret; |
| |
| if (!rw_addr || !rw_len) { |
| ts_err("address(0x%x) and length(%d) can't be null", rw_addr, |
| rw_len); |
| return -EINVAL; |
| } |
| |
| if (rw_flag != 1) { |
| ts_err("invalid rw flag %d, only support [1/2]", rw_flag); |
| return -EINVAL; |
| } |
| |
| ret = hw_ops->read(core_data, rw_addr, show_buf, rw_len); |
| if (ret < 0) { |
| ts_err("failed read addr(%x) length(%d)", rw_addr, rw_len); |
| return snprintf(buf, PAGE_SIZE, |
| "failed read addr(%x), len(%d)\n", rw_addr, rw_len); |
| } |
| |
| return snprintf(buf, PAGE_SIZE, "0x%x,%d {%*ph}\n", rw_addr, rw_len, |
| rw_len, show_buf); |
| } |
| |
| static ssize_t goodix_ts_reg_rw_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| char *pos = NULL; |
| char *token = NULL; |
| long result = 0; |
| int ret; |
| int i; |
| |
| if (!buf || !count) { |
| ts_err("invalid parame"); |
| goto err_out; |
| } |
| |
| if (buf[0] == 'r') { |
| rw_flag = 1; |
| } else if (buf[0] == 'w') { |
| rw_flag = 2; |
| } else { |
| ts_err("string must start with 'r/w'"); |
| goto err_out; |
| } |
| |
| /* get addr */ |
| pos = (char *)buf; |
| pos += 2; |
| token = strsep(&pos, ":"); |
| if (!token) { |
| ts_err("invalid address info"); |
| goto err_out; |
| } else { |
| if (kstrtol(token, 16, &result)) { |
| ts_err("failed get addr info"); |
| goto err_out; |
| } |
| rw_addr = (u32)result; |
| ts_info("rw addr is 0x%x", rw_addr); |
| } |
| |
| /* get length */ |
| token = strsep(&pos, ":"); |
| if (!token) { |
| ts_err("invalid length info"); |
| goto err_out; |
| } else { |
| if (kstrtol(token, 0, &result)) { |
| ts_err("failed get length info"); |
| goto err_out; |
| } |
| rw_len = (u32)result; |
| ts_info("rw length info is %d", rw_len); |
| if (rw_len > sizeof(store_buf)) { |
| ts_err("data len > %lu", sizeof(store_buf)); |
| goto err_out; |
| } |
| } |
| |
| if (rw_flag == 1) |
| return count; |
| |
| for (i = 0; i < rw_len; i++) { |
| token = strsep(&pos, ":"); |
| if (!token) { |
| ts_err("invalid data info"); |
| goto err_out; |
| } else { |
| if (kstrtol(token, 16, &result)) { |
| ts_err("failed get data[%d] info", i); |
| goto err_out; |
| } |
| store_buf[i] = (u8)result; |
| ts_info("get data[%d]=0x%x", i, store_buf[i]); |
| } |
| } |
| ret = hw_ops->write(core_data, rw_addr, store_buf, rw_len); |
| if (ret < 0) { |
| ts_err("failed write addr(%x) data %*ph", rw_addr, rw_len, |
| store_buf); |
| goto err_out; |
| } |
| |
| ts_info("%s write to addr (%x) with data %*ph", "success", rw_addr, |
| rw_len, store_buf); |
| |
| return count; |
| err_out: |
| snprintf(show_buf, PAGE_SIZE, "%s\n", |
| "invalid params, format{r/w:4100:length:[41:21:31]}"); |
| return -EINVAL; |
| } |
| |
| /* show irq information */ |
| static ssize_t goodix_ts_irq_info_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct irq_desc *desc; |
| size_t offset = 0; |
| int r; |
| |
| r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n", core_data->irq); |
| if (r < 0) |
| return -EINVAL; |
| |
| offset += r; |
| r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n", |
| atomic_read(&core_data->irq_enabled) ? "enabled" : "disabled"); |
| if (r < 0) |
| return -EINVAL; |
| |
| desc = irq_to_desc(core_data->irq); |
| offset += r; |
| r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n", |
| desc->depth); |
| if (r < 0) |
| return -EINVAL; |
| |
| offset += r; |
| r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n", |
| core_data->irq_trig_cnt); |
| if (r < 0) |
| return -EINVAL; |
| |
| offset += r; |
| r = snprintf(&buf[offset], PAGE_SIZE - offset, |
| "echo 0/1 > irq_info to disable/enable irq\n"); |
| if (r < 0) |
| return -EINVAL; |
| |
| offset += r; |
| return offset; |
| } |
| |
| /* enable/disable irq */ |
| static ssize_t goodix_ts_irq_info_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| |
| if (!buf || count <= 0) |
| return -EINVAL; |
| |
| if (buf[0] != '0') |
| hw_ops->irq_enable(core_data, true); |
| else |
| hw_ops->irq_enable(core_data, false); |
| return count; |
| } |
| |
| /* show esd status */ |
| static ssize_t goodix_ts_esd_info_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ts_esd *ts_esd = &core_data->ts_esd; |
| int r = 0; |
| |
| r = snprintf(buf, PAGE_SIZE, "state:%s\n", |
| atomic_read(&ts_esd->esd_on) ? "enabled" : "disabled"); |
| |
| return r; |
| } |
| |
| /* enable/disable esd */ |
| static ssize_t goodix_ts_esd_info_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| |
| if (!buf || count <= 0) |
| return -EINVAL; |
| |
| if (buf[0] != '0') |
| goodix_ts_esd_on(cd); |
| else |
| goodix_ts_esd_off(cd); |
| return count; |
| } |
| |
| /* debug level show */ |
| static ssize_t goodix_ts_debug_log_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int r = 0; |
| |
| r = snprintf(buf, PAGE_SIZE, "state:%s\n", |
| debug_log_flag ? "enabled" : "disabled"); |
| |
| return r; |
| } |
| |
| /* debug level store */ |
| static ssize_t goodix_ts_debug_log_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| if (!buf || count <= 0) |
| return -EINVAL; |
| |
| if (buf[0] != '0') |
| debug_log_flag = true; |
| else |
| debug_log_flag = false; |
| return count; |
| } |
| |
| static int goodix_refresh_pen_pair(struct goodix_ts_core *cd) |
| { |
| struct goodix_ble_data *ble_data = &cd->ble_data; |
| u8 checksum = 0; |
| int i; |
| |
| mutex_lock(&ble_data->lock); |
| ble_data->cmd.cmd = 0xC5; |
| ble_data->cmd.len = 5; |
| ble_data->cmd.data[0] = 1; |
| ble_data->cmd.data[1] = ble_data->tx1_freq_index; |
| ble_data->cmd.data[2] = ble_data->tx2_freq_index; |
| ble_data->cmd.data[3] = 0; |
| ble_data->cmd.data[4] = 0; |
| for (i = 0; i < 7; i++) |
| checksum += ble_data->cmd.buf[i]; |
| ble_data->cmd.data[5] = checksum; |
| mutex_unlock(&ble_data->lock); |
| sysfs_notify(&cd->pdev->dev.kobj, NULL, "pen_get"); |
| ts_info("pen pair event"); |
| return 0; |
| } |
| |
| /* debug level show */ |
| static ssize_t goodix_ts_pen_get_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ble_data *ble_data = &core_data->ble_data; |
| |
| mutex_lock(&ble_data->lock); |
| memcpy(buf, ble_data->cmd.buf, sizeof(ble_data->cmd)); |
| mutex_unlock(&ble_data->lock); |
| return sizeof(ble_data->cmd); |
| } |
| |
| /* debug level store */ |
| static ssize_t goodix_ts_pen_debug_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| |
| sysfs_notify(&core_data->pdev->dev.kobj, NULL, "pen_get"); |
| return count; |
| } |
| |
| static ssize_t goodix_ts_pen_set_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| struct goodix_ble_data *ble_data = &core_data->ble_data; |
| struct goodix_ble_cmd temp_cmd; |
| struct goodix_ts_cmd cmd; |
| |
| if (count > sizeof(temp_cmd)) { |
| ts_err("data count to long"); |
| return -EINVAL; |
| } |
| ts_debug("get ble cmd:%*ph", (int)count, buf); |
| |
| mutex_lock(&ble_data->lock); |
| memcpy(temp_cmd.buf, buf, count); |
| switch (temp_cmd.cmd) { |
| case 0x4B: |
| ble_data->pressure = (temp_cmd.data[1] << 8) | temp_cmd.data[0]; |
| ble_data->hogp_ready = 1; |
| break; |
| case 0xC4: |
| cmd.cmd = 0xAA; |
| cmd.len = 6; |
| cmd.data[0] = temp_cmd.data[1]; |
| cmd.data[1] = temp_cmd.data[2]; |
| core_data->hw_ops->send_cmd(core_data, &cmd); |
| cmd.cmd = 0xBB; |
| cmd.len = 5; |
| cmd.data[0] = temp_cmd.data[4]; |
| core_data->hw_ops->send_cmd(core_data, &cmd); |
| break; |
| } |
| mutex_unlock(&ble_data->lock); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(driver_info, 0440, driver_info_show, NULL); |
| static DEVICE_ATTR(chip_info, 0440, chip_info_show, NULL); |
| static DEVICE_ATTR(hw_reset, 0220, NULL, goodix_ts_reset_store); /* [GOOG] */ |
| static DEVICE_ATTR(send_cfg, 0220, NULL, goodix_ts_send_cfg_store); |
| static DEVICE_ATTR(read_cfg, 0440, read_cfg_show, NULL); |
| static DEVICE_ATTR(reg_rw, 0664, goodix_ts_reg_rw_show, goodix_ts_reg_rw_store); |
| static DEVICE_ATTR( |
| irq_info, 0664, goodix_ts_irq_info_show, goodix_ts_irq_info_store); |
| static DEVICE_ATTR( |
| esd_info, 0664, goodix_ts_esd_info_show, goodix_ts_esd_info_store); |
| static DEVICE_ATTR( |
| debug_log, 0664, goodix_ts_debug_log_show, goodix_ts_debug_log_store); |
| static DEVICE_ATTR(pen_get, 0440, goodix_ts_pen_get_show, NULL); |
| static DEVICE_ATTR(pen_debug, 0220, NULL, goodix_ts_pen_debug_store); |
| static DEVICE_ATTR(pen_set, 0220, NULL, goodix_ts_pen_set_store); |
| |
| static struct attribute *sysfs_attrs[] = { |
| &dev_attr_driver_info.attr, |
| &dev_attr_chip_info.attr, |
| &dev_attr_hw_reset.attr,/* [GOOG] use touch_apis.c to create `reset` sysfs instead */ |
| &dev_attr_send_cfg.attr, |
| &dev_attr_read_cfg.attr, |
| &dev_attr_reg_rw.attr, |
| &dev_attr_irq_info.attr, |
| &dev_attr_esd_info.attr, |
| &dev_attr_debug_log.attr, |
| &dev_attr_pen_get.attr, |
| &dev_attr_pen_debug.attr, |
| &dev_attr_pen_set.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group sysfs_group = { |
| .attrs = sysfs_attrs, |
| }; |
| |
| static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data) |
| { |
| int ret; |
| |
| ret = sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group); |
| if (ret) { |
| ts_err("failed create core sysfs group"); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data) |
| { |
| sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group); |
| } |
| |
| /* [GOOG] */ |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_MOTION_FILTER) |
| int set_continuously_report_enabled(struct device *dev, bool enabled) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return cd->hw_ops->set_continuously_report_enabled(cd, enabled); |
| } |
| #endif |
| |
| int get_fw_version(struct device *dev, char *buf, size_t buf_size) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| int ret = 0; |
| |
| ret = cd->hw_ops->read_version(cd, &cd->fw_version); |
| if (ret) { |
| return ret; |
| } |
| |
| snprintf(buf, buf_size, "%02x.%02x.%02x.%02x", |
| cd->fw_version.patch_vid[0], cd->fw_version.patch_vid[1], |
| cd->fw_version.patch_vid[2], cd->fw_version.patch_vid[3]); |
| return ret; |
| } |
| |
| int get_irq_enabled(struct device *dev) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return atomic_read(&cd->irq_enabled); |
| } |
| |
| int set_irq_enabled(struct device *dev, bool enabled) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return cd->hw_ops->irq_enable(cd, enabled); |
| } |
| |
| bool is_scan_mode_supported(struct device *dev, enum scan_mode mode) |
| { |
| return mode == SCAN_MODE_AUTO || mode == SCAN_MODE_NORMAL_ACTIVE || |
| mode == SCAN_MODE_NORMAL_IDLE; |
| } |
| |
| int ping(struct device *dev) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return cd->hw_ops->ping(cd); |
| } |
| |
| int hardware_reset(struct device *dev) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return cd->hw_ops->reset(cd, goodix_get_normal_reset_delay(cd)); |
| } |
| |
| int set_scan_mode(struct device *dev, enum scan_mode mode) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return cd->hw_ops->set_scan_mode(cd, (enum raw_scan_mode)mode); |
| } |
| |
| int set_sensing_enabled(struct device *dev, bool enabled) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| if (enabled) { |
| cd->hw_ops->resume(cd); |
| cd->hw_ops->irq_enable(cd, true); |
| goodix_ts_esd_on(cd); |
| ts_info("set sense ON"); |
| } else { |
| goodix_ts_esd_off(cd); |
| cd->hw_ops->irq_enable(cd, false); |
| cd->hw_ops->suspend(cd); |
| ts_info("set sense OFF"); |
| } |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| #if IS_ENABLED(CONFIG_GTI_PM) |
| bool get_wake_lock_state(struct device *dev, enum gti_pm_wakelock_type type) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| return goog_pm_wake_check_locked(cd->gti, type); |
| } |
| |
| int set_wake_lock_state( |
| struct device *dev, enum gti_pm_wakelock_type type, bool locked) |
| { |
| struct goodix_ts_core *cd = dev_get_drvdata(dev); |
| int ret = 0; |
| |
| if (locked) |
| ret = goog_pm_wake_lock(cd->gti, type, false); |
| else |
| ret = goog_pm_wake_unlock(cd->gti, type); |
| return ret; |
| } |
| #endif |
| |
| static int gti_default_handler(void *private_data, enum gti_cmd_type cmd_type, |
| struct gti_union_cmd_data *cmd) |
| { |
| int err = 0; |
| |
| switch (cmd_type) { |
| case GTI_CMD_NOTIFY_DISPLAY_STATE: |
| case GTI_CMD_NOTIFY_DISPLAY_VREFRESH: |
| err = -EOPNOTSUPP; |
| break; |
| default: |
| err = -ESRCH; |
| break; |
| } |
| return err; |
| } |
| |
| static int get_mutual_sensor_data( |
| void *private_data, struct gti_sensor_data_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| int tx = cd->ic_info.parm.drv_num; |
| int rx = cd->ic_info.parm.sen_num; |
| int ret = 0; |
| |
| if (cmd->type == GTI_SENSOR_DATA_TYPE_MS) { |
| cmd->buffer = (u8 *)cd->mutual_data; |
| cmd->size = tx * rx * sizeof(uint16_t); |
| } else { |
| /* disable esd */ |
| goodix_ts_esd_off(cd); |
| |
| ret = -EINVAL; |
| if (cmd->type == GTI_SENSOR_DATA_TYPE_MS_DIFF) { |
| ret = cd->hw_ops->get_mutual_data(cd, FRAME_DATA_TYPE_DIFF); |
| } else if (cmd->type == GTI_SENSOR_DATA_TYPE_MS_RAW) { |
| ret = cd->hw_ops->get_mutual_data(cd, FRAME_DATA_TYPE_RAW); |
| } else if (cmd->type == GTI_SENSOR_DATA_TYPE_MS_BASELINE) { |
| ret = cd->hw_ops->get_mutual_data(cd, FRAME_DATA_TYPE_BASE); |
| } |
| |
| if (ret == 0) { |
| cmd->buffer = (u8 *)cd->mutual_data_manual; |
| cmd->size = tx * rx * sizeof(uint16_t); |
| } |
| /* enable esd */ |
| goodix_ts_esd_on(cd); |
| } |
| return ret; |
| } |
| |
| static int get_self_sensor_data( |
| void *private_data, struct gti_sensor_data_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| int tx = cd->ic_info.parm.drv_num; |
| int rx = cd->ic_info.parm.sen_num; |
| int ret = 0; |
| |
| if (cmd->type == GTI_SENSOR_DATA_TYPE_SS) { |
| cmd->buffer = (u8 *)cd->self_sensing_data; |
| cmd->size = (tx + rx) * sizeof(uint16_t); |
| } else { |
| /* disable esd */ |
| goodix_ts_esd_off(cd); |
| |
| ret = -EINVAL; |
| if (cmd->type == GTI_SENSOR_DATA_TYPE_SS_DIFF) { |
| ret = cd->hw_ops->get_self_sensing_data(cd, FRAME_DATA_TYPE_DIFF); |
| } else if (cmd->type == GTI_SENSOR_DATA_TYPE_SS_RAW) { |
| ret = cd->hw_ops->get_self_sensing_data(cd, FRAME_DATA_TYPE_RAW); |
| } else if (cmd->type == GTI_SENSOR_DATA_TYPE_SS_BASELINE) { |
| ret = cd->hw_ops->get_self_sensing_data(cd, FRAME_DATA_TYPE_BASE); |
| } |
| |
| if (ret == 0) { |
| cmd->buffer = (u8 *)cd->self_sensing_data_manual; |
| cmd->size = (tx + rx) * sizeof(uint16_t); |
| } |
| |
| /* enable esd */ |
| goodix_ts_esd_on(cd); |
| } |
| return ret; |
| } |
| |
| static int set_continuous_report( |
| void *private_data, struct gti_continuous_report_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->set_continuously_report_enabled(cd, |
| cmd->setting == GTI_CONTINUOUS_REPORT_ENABLE); |
| } |
| |
| static int set_grip_enabled(struct goodix_ts_core *cd, bool enabled) |
| { |
| return cd->hw_ops->set_grip_enabled(cd, enabled); |
| } |
| |
| static int set_grip_mode(void *private_data, struct gti_grip_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return set_grip_enabled(cd, cmd->setting == GTI_GRIP_ENABLE); |
| } |
| |
| static int get_grip_mode(void *private_data, struct gti_grip_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| bool enabled = false; |
| |
| cd->hw_ops->get_grip_enabled(cd, &enabled); |
| cmd->setting = enabled ? GTI_GRIP_ENABLE : GTI_GRIP_DISABLE; |
| return 0; |
| } |
| |
| static int set_palm_enabled(struct goodix_ts_core *cd, bool enabled) |
| { |
| return cd->hw_ops->set_palm_enabled(cd, enabled); |
| } |
| |
| static int set_palm_mode(void *private_data, struct gti_palm_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return set_palm_enabled(cd, cmd->setting == GTI_PALM_ENABLE); |
| } |
| |
| static int get_palm_mode(void *private_data, struct gti_palm_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| bool enabled = false; |
| |
| cd->hw_ops->get_palm_enabled(cd, &enabled); |
| cmd->setting = enabled ? GTI_PALM_ENABLE : GTI_PALM_DISABLE; |
| return 0; |
| } |
| |
| static int goodix_set_screen_protector_mode_enabled( |
| struct goodix_ts_core *cd, bool enabled) |
| { |
| int ret = 0; |
| ret = cd->hw_ops->set_screen_protector_mode_enabled(cd, enabled); |
| if (ret == 0) |
| cd->screen_protector_mode_enabled = enabled; |
| return ret; |
| } |
| |
| static int set_screen_protector_mode( |
| void *private_data, struct gti_screen_protector_mode_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return goodix_set_screen_protector_mode_enabled( |
| cd, cmd->setting == GTI_SCREEN_PROTECTOR_MODE_ENABLE); |
| } |
| |
| static int get_screen_protector_mode( |
| void *private_data, struct gti_screen_protector_mode_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| bool enabled = false; |
| |
| cd->hw_ops->get_screen_protector_mode_enabled(cd, &enabled); |
| cmd->setting = enabled ? GTI_SCREEN_PROTECTOR_MODE_ENABLE : |
| GTI_SCREEN_PROTECTOR_MODE_DISABLE; |
| return 0; |
| } |
| |
| static int set_coord_filter_enabled(void *private_data, |
| struct gti_coord_filter_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->set_coord_filter_enabled(cd, |
| cmd->setting == GTI_COORD_FILTER_ENABLE); |
| } |
| |
| static int get_coord_filter_enabled(void *private_data, |
| struct gti_coord_filter_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| bool enabled = false; |
| |
| cd->hw_ops->get_coord_filter_enabled(cd, &enabled); |
| cmd->setting = enabled ? GTI_COORD_FILTER_ENABLE : GTI_COORD_FILTER_DISABLE; |
| return 0; |
| } |
| |
| static int set_heatmap_enabled( |
| void *private_data, struct gti_heatmap_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->set_heatmap_enabled(cd, cmd->setting == GTI_HEATMAP_ENABLE); |
| } |
| |
| static int gti_get_fw_version(void *private_data, |
| struct gti_fw_version_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| int ret = 0; |
| |
| ret = cd->hw_ops->read_version(cd, &cd->fw_version); |
| if (ret) { |
| return ret; |
| } |
| |
| snprintf(cmd->buffer, sizeof(cmd->buffer), "%02x.%02x.%02x.%02x", |
| cd->fw_version.patch_vid[0], cd->fw_version.patch_vid[1], |
| cd->fw_version.patch_vid[2], cd->fw_version.patch_vid[3]); |
| return ret; |
| } |
| |
| static int gti_set_irq_mode(void *private_data, struct gti_irq_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->irq_enable(cd, cmd->setting == GTI_IRQ_MODE_ENABLE); |
| } |
| |
| static int gti_get_irq_mode(void *private_data, struct gti_irq_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| |
| if (atomic_read(&cd->irq_enabled) == 1) |
| cmd->setting = GTI_IRQ_MODE_ENABLE; |
| else |
| cmd->setting = GTI_IRQ_MODE_DISABLE; |
| |
| return 0; |
| } |
| |
| static int gti_reset(void *private_data, struct gti_reset_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| |
| if (cmd->setting == GTI_RESET_MODE_HW || cmd->setting == GTI_RESET_MODE_AUTO) |
| return cd->hw_ops->reset(cd, goodix_get_normal_reset_delay(cd)); |
| else |
| return -EOPNOTSUPP; |
| } |
| |
| static int gti_ping(void *private_data, struct gti_ping_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->ping(cd); |
| } |
| |
| static int gti_calibrate(void *private_data, struct gti_calibrate_cmd *cmd) |
| { |
| (void)private_data; |
| |
| /* Return successful calibration since there is nothing to do. */ |
| cmd->result = GTI_CALIBRATE_RESULT_DONE; |
| return 0; |
| } |
| |
| static int gti_selftest(void *private_data, struct gti_selftest_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| |
| cmd->result = GTI_SELFTEST_RESULT_DONE; |
| return driver_test_selftest(cd, cmd->buffer); |
| } |
| |
| static int gti_get_context_driver(void *private_data, |
| struct gti_context_driver_cmd *cmd) |
| { |
| /* There is no context from this driver. */ |
| return 0; |
| } |
| |
| static int gti_set_report_rate(void *private_data, |
| struct gti_report_rate_cmd *cmd) |
| { |
| struct goodix_ts_core *cd = private_data; |
| return cd->hw_ops->set_report_rate(cd, cmd->setting); |
| } |
| |
| #endif |
| /*~[GOOG] */ |
| |
| /* prosfs create */ |
| static int rawdata_proc_show(struct seq_file *m, void *v) |
| { |
| struct ts_rawdata_info *info; |
| struct goodix_ts_core *cd = m->private; |
| int tx; |
| int rx; |
| int ret; |
| int i; |
| int index; |
| |
| if (!m || !v || !cd) |
| return -EIO; |
| |
| info = kzalloc(sizeof(*info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| ret = cd->hw_ops->get_capacitance_data(cd, info); |
| if (ret < 0) { |
| ts_err("failed to get_capacitance_data, exit!"); |
| goto exit; |
| } |
| |
| rx = info->buff[0]; |
| tx = info->buff[1]; |
| seq_printf(m, "TX:%d RX:%d\n", tx, rx); |
| seq_puts(m, "mutual_rawdata:\n"); |
| index = 2; |
| for (i = 0; i < tx * rx; i++) { |
| seq_printf(m, "%5d,", info->buff[index + i]); |
| if ((i + 1) % tx == 0) |
| seq_puts(m, "\n"); |
| } |
| seq_puts(m, "mutual_diffdata:\n"); |
| index += tx * rx; |
| for (i = 0; i < tx * rx; i++) { |
| seq_printf(m, "%3d,", info->buff[index + i]); |
| if ((i + 1) % tx == 0) |
| seq_puts(m, "\n"); |
| } |
| |
| exit: |
| kfree(info); |
| return ret; |
| } |
| |
| static int rawdata_proc_open(struct inode *inode, struct file *file) |
| { |
| return single_open_size(file, rawdata_proc_show, |
| PDE_DATA(inode), PAGE_SIZE * 10); |
| } |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) |
| static const struct proc_ops rawdata_proc_fops = { |
| .proc_open = rawdata_proc_open, |
| .proc_read = seq_read, |
| .proc_lseek = seq_lseek, |
| .proc_release = single_release, |
| }; |
| #else |
| static const struct file_operations rawdata_proc_fops = { |
| .open = rawdata_proc_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| #endif |
| |
| static int goodix_ts_procfs_init(struct goodix_ts_core *core_data) |
| { |
| int dev_id = core_data->pdev->id; |
| struct proc_dir_entry *proc_entry; |
| char proc_node[32] = {0}; |
| int ret = 0; /* [GOOG] */ |
| |
| sprintf(proc_node, "goodix_ts.%d", dev_id); |
| |
| core_data->proc_dir_entry = proc_mkdir(proc_node, NULL); |
| if (!core_data->proc_dir_entry) |
| return -ENOMEM; |
| proc_entry = proc_create_data("tp_capacitance_data", |
| 0664, core_data->proc_dir_entry, &rawdata_proc_fops, core_data); |
| if (!proc_entry) { |
| ts_err("failed to create proc entry: goodix_ts.%d/tp_capacitance_data", |
| dev_id); |
| ret = -ENOMEM; |
| goto err_create_data; |
| } |
| |
| ret = driver_test_proc_init(core_data); |
| if (ret != 0) { |
| ts_err("failed to create proc entry: goodix_ts.%d/driver_test", dev_id); |
| ret = -ENOMEM; |
| goto err_create_driver; |
| } |
| |
| /* |
| * [GOOG] |
| * Create symlink `goodix_ts` to `goodix_ts.0` for backward compatibility. |
| */ |
| if (dev_id == 0) |
| proc_symlink("goodix_ts", NULL, proc_node); |
| |
| return 0; |
| |
| err_create_driver: |
| remove_proc_entry("tp_capacitance_data", core_data->proc_dir_entry); |
| err_create_data: |
| remove_proc_entry(proc_node, NULL); |
| return ret; |
| } |
| |
| static void goodix_ts_procfs_exit(struct goodix_ts_core *core_data) |
| { |
| int dev_id = core_data->pdev->id; |
| char proc_node[32] = {0}; |
| |
| sprintf(proc_node, "goodix_ts.%d", dev_id); |
| |
| driver_test_proc_remove(core_data); |
| remove_proc_entry("tp_capacitance_data", core_data->proc_dir_entry); |
| remove_proc_entry(proc_node, NULL); |
| } |
| |
| #if IS_ENABLED(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 ret; |
| |
| ret = of_property_read_u32( |
| node, "goodix,panel-max-x", &board_data->panel_max_x); |
| if (ret) { |
| ts_err("failed get panel-max-x"); |
| return ret; |
| } |
| |
| ret = of_property_read_u32( |
| node, "goodix,panel-max-y", &board_data->panel_max_y); |
| if (ret) { |
| ts_err("failed get panel-max-y"); |
| return ret; |
| } |
| |
| ret = of_property_read_u32( |
| node, "goodix,panel-max-w", &board_data->panel_max_w); |
| if (ret) { |
| ts_err("failed get panel-max-w"); |
| return ret; |
| } |
| |
| ret = of_property_read_u32( |
| node, "goodix,panel-max-p", &board_data->panel_max_p); |
| if (ret) { |
| ts_err("failed get panel-max-p, use default"); |
| board_data->panel_max_p = GOODIX_PEN_MAX_PRESSURE; |
| } |
| |
| ret = of_property_read_u32( |
| node, "goodix,panel-height-mm", &board_data->panel_height_mm); |
| if (ret) { |
| ts_err("failed get panel-height-mm"); |
| return ret; |
| } |
| |
| 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) |
| { |
| const char *name_tmp; |
| int r; |
| int index; |
| struct of_phandle_args panelmap; |
| struct drm_panel *panel = NULL; |
| const char *name; |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| int panel_id = -1; |
| #endif |
| |
| if (!board_data) { |
| ts_err("invalid board data"); |
| return -EINVAL; |
| } |
| |
| r = of_get_named_gpio(node, "goodix,avdd-gpio", 0); |
| if (r < 0) { |
| ts_info("can't find avdd-gpio, use other power supply"); |
| board_data->avdd_gpio = 0; |
| } else { |
| ts_info("get avdd-gpio[%d] from dt", r); |
| board_data->avdd_gpio = r; |
| } |
| |
| r = of_get_named_gpio(node, "goodix,iovdd-gpio", 0); |
| if (r < 0) { |
| ts_info("can't find iovdd-gpio, use other power supply"); |
| board_data->iovdd_gpio = 0; |
| } else { |
| ts_info("get iovdd-gpio[%d] from dt", r); |
| board_data->iovdd_gpio = r; |
| } |
| |
| 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 from 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)); |
| } |
| |
| memset(board_data->iovdd_name, 0, sizeof(board_data->iovdd_name)); |
| r = of_property_read_string(node, "goodix,iovdd-name", &name_tmp); |
| if (!r) { |
| ts_info("iovdd name from dt: %s", name_tmp); |
| if (strlen(name_tmp) < sizeof(board_data->iovdd_name)) |
| strncpy(board_data->iovdd_name, name_tmp, |
| sizeof(board_data->iovdd_name)); |
| else |
| ts_info("invalied iovdd name length: %ld > %ld", |
| strlen(name_tmp), |
| sizeof(board_data->iovdd_name)); |
| } |
| |
| /* get use-one-binary flag */ |
| board_data->use_one_binary = |
| of_property_read_bool(node, "goodix,use-one-binary"); |
| if (board_data->use_one_binary) |
| ts_info("use one binary"); |
| |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| if (of_property_read_bool(node, "goog,panel_map")) { |
| panel_id = goog_get_panel_id(node); |
| if (panel_id < 0) |
| return -EPROBE_DEFER; |
| goog_get_firmware_name(node, panel_id, board_data->fw_name, sizeof(board_data->fw_name)); |
| if (!board_data->use_one_binary) |
| goog_get_config_name(node, panel_id, board_data->cfg_bin_name, sizeof(board_data->cfg_bin_name)); |
| goog_get_test_limits_name(node, panel_id, board_data->test_limits_name, sizeof(board_data->test_limits_name)); |
| } else if (of_property_read_bool(node, "goodix,panel_map")) { |
| #else |
| if (of_property_read_bool(node, "goodix,panel_map")) { |
| #endif |
| for (index = 0;; index++) { |
| r = of_parse_phandle_with_fixed_args( |
| node, "goodix,panel_map", 1, index, &panelmap); |
| if (r) |
| return -EPROBE_DEFER; |
| panel = of_drm_find_panel(panelmap.np); |
| of_node_put(panelmap.np); |
| if (!IS_ERR_OR_NULL(panel)) { |
| r = of_property_read_string_index(node, |
| "goodix,firmware_names", panelmap.args[0], &name); |
| if (r < 0) |
| name = TS_DEFAULT_FIRMWARE; |
| |
| strncpy(board_data->fw_name, name, |
| sizeof(board_data->fw_name)); |
| ts_info("Firmware name %s", |
| board_data->fw_name); |
| |
| if (!board_data->use_one_binary) { |
| r = of_property_read_string_index(node, |
| "goodix,config_names", |
| panelmap.args[0], &name); |
| if (r < 0) |
| name = TS_DEFAULT_CFG_BIN; |
| |
| strncpy(board_data->cfg_bin_name, name, |
| sizeof(board_data->cfg_bin_name)); |
| ts_info("Config name %s", |
| board_data->cfg_bin_name); |
| } |
| |
| r = of_property_read_string_index(node, |
| "goodix,test_limits_names", panelmap.args[0], &name); |
| if (r < 0) |
| name = TS_DEFAULT_TEST_LIMITS; |
| |
| strncpy(board_data->test_limits_name, name, |
| sizeof(board_data->test_limits_name)); |
| ts_info("test limits name %s", |
| board_data->test_limits_name); |
| |
| break; |
| } |
| } |
| } else { |
| /* get firmware file name */ |
| r = of_property_read_string( |
| node, "goodix,firmware-name", &name_tmp); |
| if (!r) { |
| ts_info("firmware name from dt: %s", name_tmp); |
| strncpy(board_data->fw_name, name_tmp, |
| sizeof(board_data->fw_name)); |
| } else { |
| ts_info("can't find firmware name, use default: %s", |
| TS_DEFAULT_FIRMWARE); |
| strncpy(board_data->fw_name, TS_DEFAULT_FIRMWARE, |
| sizeof(board_data->fw_name)); |
| } |
| |
| /* get config file name */ |
| if (!board_data->use_one_binary) { |
| r = of_property_read_string( |
| node, "goodix,config-name", &name_tmp); |
| if (!r) { |
| ts_info("config name from dt: %s", name_tmp); |
| strncpy(board_data->cfg_bin_name, name_tmp, |
| sizeof(board_data->cfg_bin_name)); |
| } else { |
| ts_info("can't find config name, use default: %s", |
| TS_DEFAULT_CFG_BIN); |
| strncpy(board_data->cfg_bin_name, TS_DEFAULT_CFG_BIN, |
| sizeof(board_data->cfg_bin_name)); |
| } |
| } |
| |
| /* get test limits file name */ |
| r = of_property_read_string( |
| node, "goodix,test-limits-name", &name_tmp); |
| if (!r) { |
| ts_info("test limits name from dt: %s", name_tmp); |
| strncpy(board_data->test_limits_name, name_tmp, |
| sizeof(board_data->test_limits_name)); |
| } else { |
| /* use default test limits name */ |
| ts_info("can't find test limits name, use default: %s\n", |
| TS_DEFAULT_TEST_LIMITS); |
| strncpy(board_data->test_limits_name, TS_DEFAULT_TEST_LIMITS, |
| sizeof(board_data->test_limits_name)); |
| } |
| } |
| |
| /* get xyz resolutions */ |
| r = goodix_parse_dt_resolution(node, board_data); |
| if (r) { |
| ts_err("Failed to parse resolutions:%d", r); |
| return r; |
| } |
| |
| r = of_property_read_u32(node, "goodix,udfps-x", &board_data->udfps_x); |
| if (r) |
| ts_info("undefined udfps-x(optional)!"); |
| |
| r = of_property_read_u32(node, "goodix,udfps-y", &board_data->udfps_y); |
| if (r) |
| ts_info("undefined udfps-y(optional)!"); |
| |
| /* get sleep mode flag */ |
| board_data->sleep_enable = |
| of_property_read_bool(node, "goodix,sleep-enable"); |
| |
| /*get pen-enable switch and pen keys, must after "key map"*/ |
| board_data->pen_enable = |
| of_property_read_bool(node, "goodix,pen-enable"); |
| |
| ts_info("[DT]x:%d, y:%d, w:%d, p:%d sleep_enable:%d pen_enable:%d", |
| board_data->panel_max_x, board_data->panel_max_y, |
| board_data->panel_max_w, board_data->panel_max_p, |
| board_data->sleep_enable, board_data->pen_enable); |
| return 0; |
| } |
| #endif |
| |
| static void goodix_ts_report_pen( |
| struct goodix_ts_core *cd, struct goodix_pen_data *pen_data) |
| { |
| struct input_dev *dev = cd->pen_dev; |
| int i; |
| struct goodix_ble_data *ble_data = &cd->ble_data; |
| char trace_tag[128]; |
| ktime_t pen_ktime; |
| |
| mutex_lock(&dev->mutex); |
| input_set_timestamp(dev, cd->coords_timestamp); |
| pen_ktime = ktime_get(); |
| |
| if (pen_data->coords.status == TS_TOUCH) { |
| scnprintf(trace_tag, sizeof(trace_tag), |
| "stylus-active: IN_TS=%lld TS=%lld DELTA=%lld ns.\n", |
| ktime_to_ns(cd->coords_timestamp), ktime_to_ns(pen_ktime), |
| ktime_to_ns(ktime_sub(pen_ktime, cd->coords_timestamp))); |
| ATRACE_BEGIN(trace_tag); |
| if (pen_data->is_hover) |
| input_report_key(dev, BTN_TOUCH, 0); |
| else |
| input_report_key(dev, BTN_TOUCH, 1); |
| input_report_key(dev, BTN_TOOL_PEN, 1); |
| input_report_abs(dev, ABS_X, pen_data->coords.x); |
| input_report_abs(dev, ABS_Y, pen_data->coords.y); |
| mutex_lock(&ble_data->lock); |
| if (ble_data->hogp_ready) { |
| cd->pen_pressure = ble_data->pressure; |
| ts_debug("update pen pressure from ble %d", |
| cd->pen_pressure); |
| } |
| ble_data->hogp_ready = 0; |
| mutex_unlock(&ble_data->lock); |
| |
| if (pen_data->coords.p && cd->pen_pressure) |
| pen_data->coords.p = cd->pen_pressure; |
| input_report_abs(dev, ABS_PRESSURE, pen_data->coords.p); |
| if (pen_data->coords.p == 0) |
| input_report_abs(dev, ABS_DISTANCE, 1); |
| else |
| input_report_abs(dev, ABS_DISTANCE, 0); |
| input_report_abs(dev, ABS_TILT_X, pen_data->coords.tilt_x); |
| input_report_abs(dev, ABS_TILT_Y, pen_data->coords.tilt_y); |
| ts_debug( |
| "pen_data:x %d, y %d, p %d, tilt_x %d tilt_y %d key[%d %d]", |
| pen_data->coords.x, pen_data->coords.y, |
| pen_data->coords.p, pen_data->coords.tilt_x, |
| pen_data->coords.tilt_y, |
| pen_data->keys[0].status == TS_TOUCH ? 1 : 0, |
| pen_data->keys[1].status == TS_TOUCH ? 1 : 0); |
| |
| if (pen_data->custom_flag) { |
| if (ble_data->tx1_freq_index != pen_data->tx1_freq_index || |
| ble_data->tx2_freq_index != pen_data->tx2_freq_index) { |
| ble_data->tx1_freq_index = pen_data->tx1_freq_index; |
| ble_data->tx2_freq_index = pen_data->tx2_freq_index; |
| goodix_refresh_pen_pair(cd); |
| } |
| } |
| } else { |
| scnprintf(trace_tag, sizeof(trace_tag), |
| "stylus-inactive: IN_TS=%lld TS=%lld DELTA=%lld ns.\n", |
| ktime_to_ns(cd->coords_timestamp), ktime_to_ns(pen_ktime), |
| ktime_to_ns(ktime_sub(pen_ktime, cd->coords_timestamp))); |
| ATRACE_BEGIN(trace_tag); |
| cd->pen_pressure = 0; |
| input_report_key(dev, BTN_TOUCH, 0); |
| input_report_key(dev, BTN_TOOL_PEN, 0); |
| } |
| /* report pen button */ |
| for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) { |
| if (pen_data->keys[i].status == TS_TOUCH) |
| input_report_key(dev, pen_data->keys[i].code, 1); |
| else |
| input_report_key(dev, pen_data->keys[i].code, 0); |
| } |
| input_sync(dev); |
| ATRACE_END(); |
| mutex_unlock(&dev->mutex); |
| } |
| |
| #if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| static void goodix_ts_report_finger( |
| struct goodix_ts_core *cd, struct goodix_touch_data *touch_data) |
| { |
| struct input_dev *dev = cd->input_dev; |
| unsigned int touch_num = touch_data->touch_num; |
| int i; |
| int panel_height_mm = cd->board_data.panel_height_mm; |
| int panel_height_pixel = cd->board_data.panel_max_y + 1; |
| |
| mutex_lock(&dev->mutex); |
| |
| for (i = 0; i < GOODIX_MAX_TOUCH; i++) { |
| if (touch_data->coords[i].status == TS_TOUCH) { |
| ts_debug( |
| "report: id[%d], x %d, y %d, w %d, p %d, major %d, minor %d, angle %d", |
| i, touch_data->coords[i].x, |
| touch_data->coords[i].y, |
| touch_data->coords[i].w, |
| touch_data->coords[i].p, |
| touch_data->coords[i].major, |
| touch_data->coords[i].minor, |
| touch_data->coords[i].angle); |
| input_mt_slot(dev, i); |
| input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); |
| input_report_abs(dev, ABS_MT_POSITION_X, |
| touch_data->coords[i].x); |
| input_report_abs(dev, ABS_MT_POSITION_Y, |
| touch_data->coords[i].y); |
| input_report_abs( |
| dev, ABS_MT_PRESSURE, touch_data->coords[i].p); |
| input_report_abs(dev, ABS_MT_TOUCH_MAJOR, |
| (touch_data->coords[i].major * panel_height_pixel) / |
| (10 * panel_height_mm)); |
| input_report_abs(dev, ABS_MT_TOUCH_MINOR, |
| (touch_data->coords[i].minor * panel_height_pixel) / |
| (10 * panel_height_mm)); |
| input_report_abs(dev, ABS_MT_ORIENTATION, |
| (touch_data->coords[i].angle * 2048) / 45); |
| } else { |
| input_mt_slot(dev, i); |
| input_mt_report_slot_state(dev, MT_TOOL_FINGER, false); |
| } |
| } |
| |
| input_report_key(dev, BTN_TOUCH, touch_num > 0 ? 1 : 0); |
| input_set_timestamp(dev, cd->coords_timestamp); |
| input_sync(dev); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_MOTION_FILTER) |
| touch_mf_update_state(&cd->tmf, touch_num); |
| #endif |
| |
| mutex_unlock(&dev->mutex); |
| } |
| #endif |
| |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| static void goodix_ts_report_finger_goog( |
| struct goodix_ts_core *cd, struct goodix_touch_data *touch_data) |
| { |
| struct input_dev *dev = cd->input_dev; |
| struct goog_touch_interface *gti = cd->gti; |
| unsigned int touch_num = touch_data->touch_num; |
| int i; |
| int panel_height_mm = cd->board_data.panel_height_mm; |
| int panel_height_pixel = cd->board_data.panel_max_y + 1; |
| |
| goog_input_lock(gti); |
| |
| goog_input_set_timestamp(gti, dev, cd->coords_timestamp); |
| for (i = 0; i < GOODIX_MAX_TOUCH; i++) { |
| struct goodix_ts_coords *coord = &touch_data->coords[i]; |
| if (coord->status == TS_TOUCH) { |
| goog_input_mt_slot(gti, dev, i); |
| goog_input_mt_report_slot_state( |
| gti, dev, MT_TOOL_FINGER, true); |
| goog_input_report_abs( |
| gti, dev, ABS_MT_POSITION_X, coord->x); |
| goog_input_report_abs( |
| gti, dev, ABS_MT_POSITION_Y, coord->y); |
| goog_input_report_abs( |
| gti, dev, ABS_MT_PRESSURE, coord->p); |
| goog_input_report_abs(gti, dev, ABS_MT_TOUCH_MAJOR, |
| (touch_data->coords[i].major * panel_height_pixel) / |
| (10 * panel_height_mm)); |
| goog_input_report_abs(gti, dev, ABS_MT_TOUCH_MINOR, |
| (touch_data->coords[i].minor * panel_height_pixel) / |
| (10 * panel_height_mm)); |
| goog_input_report_abs( |
| gti, dev, ABS_MT_ORIENTATION, (coord->angle * 2048) / 45); |
| } else { |
| goog_input_mt_slot(gti, dev, i); |
| goog_input_mt_report_slot_state( |
| gti, dev, MT_TOOL_FINGER, false); |
| } |
| } |
| |
| goog_input_report_key(gti, dev, BTN_TOUCH, touch_num > 0 ? 1 : 0); |
| goog_input_sync(gti, dev); |
| |
| goog_input_unlock(gti); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_MOTION_FILTER) |
| touch_mf_update_state(&cd->tmf, touch_num); |
| #endif |
| } |
| #endif |
| |
| static void goodix_ts_report_gesture_up(struct goodix_ts_core *cd) |
| { |
| struct input_dev *dev = cd->input_dev; |
| |
| ts_info("goodix_ts_report_gesture_up"); |
| |
| mutex_lock(&dev->mutex); |
| |
| input_set_timestamp(dev, cd->coords_timestamp); |
| |
| /* Finger down on UDFPS area. */ |
| input_mt_slot(dev, 0); |
| input_report_key(dev, BTN_TOUCH, 1); |
| input_mt_report_slot_state(dev, MT_TOOL_FINGER, 1); |
| input_report_abs(dev, ABS_MT_POSITION_X, cd->board_data.udfps_x); |
| input_report_abs(dev, ABS_MT_POSITION_Y, cd->board_data.udfps_y); |
| input_report_abs(dev, ABS_MT_TOUCH_MAJOR, 200); |
| input_report_abs(dev, ABS_MT_TOUCH_MINOR, 200); |
| #ifndef SKIP_PRESSURE |
| input_report_abs(dev, ABS_MT_PRESSURE, 1); |
| #endif |
| /*input_report_abs(dev, ABS_MT_ORIENTATION, |
| ts_data->fts_gesture_data.orientation[0]);*/ |
| input_sync(dev); |
| |
| /* Report MT_TOOL_PALM for canceling the touch event. */ |
| input_mt_slot(dev, 0); |
| input_report_key(dev, BTN_TOUCH, 1); |
| input_mt_report_slot_state(dev, MT_TOOL_PALM, 1); |
| input_sync(dev); |
| |
| /* Release touches. */ |
| input_mt_slot(dev, 0); |
| #ifndef SKIP_PRESSURE |
| input_report_abs(dev, ABS_MT_PRESSURE, 0); |
| #endif |
| input_mt_report_slot_state(dev, MT_TOOL_FINGER, 0); |
| input_report_abs(dev, ABS_MT_TRACKING_ID, -1); |
| input_report_key(dev, BTN_TOUCH, 0); |
| input_sync(dev); |
| |
| mutex_unlock(&dev->mutex); |
| } |
| |
| static int goodix_ts_request_handle( |
| struct goodix_ts_core *cd, struct goodix_ts_event *ts_event) |
| { |
| struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; |
| int ret = -1; |
| |
| if (ts_event->request_code == REQUEST_TYPE_CONFIG) |
| ret = goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL); |
| else if (ts_event->request_code == REQUEST_TYPE_RESET) |
| ret = hw_ops->reset(cd, goodix_get_normal_reset_delay(cd)); |
| else if (ts_event->request_code == REQUEST_TYPE_UPDATE) |
| ret = goodix_do_fw_update(cd, UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK | |
| UPDATE_MODE_SRC_REQUEST); |
| else |
| ts_info("can not handle request type 0x%x", |
| ts_event->request_code); |
| if (ret) |
| ts_err("failed handle request 0x%x", ts_event->request_code); |
| else |
| ts_info("success handle ic request 0x%x", |
| ts_event->request_code); |
| return ret; |
| } |
| |
| static irqreturn_t goodix_ts_isr(int irq, void *data) |
| { |
| struct goodix_ts_core *core_data = data; |
| |
| core_data->isr_timestamp = ktime_get(); |
| |
| return IRQ_WAKE_THREAD; |
| } |
| |
| void goodix_ts_report_status(struct goodix_ts_core *core_data, |
| struct goodix_ts_event *ts_event) |
| { |
| struct goodix_status_data *st = &ts_event->status_data; |
| int i; |
| u8 checksum = 0; |
| int len = sizeof(ts_event->status_data); |
| u8 *data = (u8 *)st; |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| struct gti_fw_status_data status_data = { 0 }; |
| #endif |
| |
| for (i = 0; i < len - 1; i++) |
| checksum += data[i]; |
| if (checksum != st->checksum) { |
| ts_err("status data checksum error"); |
| return; |
| } |
| |
| ts_info("grip_change[%d] noise_lv_change[%d] palm_change[%d] soft_reset[%d] base_update[%d] hop_change[%d] water_change[%d]", |
| st->grip_change, st->noise_lv_change, st->palm_change, |
| st->soft_reset, st->base_update, st->hop_change, |
| st->water_change); |
| ts_info("water_status[%d] before_factorA[%d] after_factorA[%d]" \ |
| " base_update_type[0x%x] soft_reset_type[0x%x] palm_status[%d]" \ |
| " noise_lv[%d] grip_type[%d] event_id[%d] clear_count1[%d]" \ |
| " clear_count2[%d]", st->water_sta, st->before_factorA, |
| st->after_factorA, st->base_update_type, st->soft_reset_type, |
| st->palm_sta, st->noise_lv, st->grip_type, st->event_id, |
| ts_event->clear_count1, ts_event->clear_count2); |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| if (st->soft_reset) |
| goog_notify_fw_status_changed(core_data->gti, GTI_FW_STATUS_RESET, |
| &status_data); |
| |
| if (st->palm_change) { |
| goog_notify_fw_status_changed(core_data->gti, |
| st->palm_sta ? GTI_FW_STATUS_PALM_ENTER : GTI_FW_STATUS_PALM_EXIT, |
| &status_data); |
| } |
| |
| if (st->grip_change) { |
| goog_notify_fw_status_changed(core_data->gti, |
| st->grip_type ? GTI_FW_STATUS_GRIP_ENTER : GTI_FW_STATUS_GRIP_EXIT, |
| &status_data); |
| } |
| |
| if (st->water_change) { |
| goog_notify_fw_status_changed(core_data->gti, |
| st->water_sta ? GTI_FW_STATUS_WATER_ENTER : |
| GTI_FW_STATUS_WATER_EXIT, &status_data); |
| } |
| |
| if (st->noise_lv_change) { |
| status_data.noise_level = st->noise_lv; |
| goog_notify_fw_status_changed(core_data->gti, GTI_FW_STATUS_NOISE_MODE, |
| &status_data); |
| } |
| #endif |
| } |
| |
| /** |
| * goodix_ts_threadirq_func - Bottom half of interrupt |
| * This functions is excuted in thread context, |
| * sleep in this function is permit. |
| * |
| * @data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| static irqreturn_t goodix_ts_threadirq_func(int irq, void *data) |
| { |
| struct goodix_ts_core *core_data = data; |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| struct goodix_ts_event *ts_event = &core_data->ts_event; |
| struct goodix_ts_esd *ts_esd = &core_data->ts_esd; |
| int ret; |
| |
| /* [GOOG] |
| * Remove the control to enable/disable the interrupt for bottom-half. |
| disable_irq_nosync(core_data->irq); |
| */ |
| |
| /* [GOOG] |
| * Since we received an interrupt from touch firmware, it means touch |
| * firmware is still alive. So skip esd check once. |
| */ |
| ts_esd->skip_once = true; |
| |
| core_data->irq_trig_cnt++; |
| |
| /* read touch data from touch device */ |
| ret = hw_ops->event_handler(core_data, ts_event); |
| if (likely(!ret)) { |
| if (ts_event->event_type & EVENT_TOUCH) { |
| /* report touch */ |
| core_data->coords_timestamp = core_data->isr_timestamp; |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| goodix_ts_report_finger_goog( |
| core_data, &ts_event->touch_data); |
| #else |
| goodix_ts_report_finger( |
| core_data, &ts_event->touch_data); |
| #endif |
| } |
| if (ts_event->event_type & EVENT_GESTURE) { |
| core_data->coords_timestamp = core_data->isr_timestamp; |
| mutex_lock(&core_data->gesture_data_lock); |
| memcpy(&core_data->gesture_data, &core_data->ts_event.temp_gesture_data, |
| sizeof(core_data->gesture_data)); |
| mutex_unlock(&core_data->gesture_data_lock); |
| } |
| if (core_data->board_data.pen_enable && |
| ts_event->event_type & EVENT_PEN) { |
| core_data->coords_timestamp = core_data->isr_timestamp; |
| goodix_ts_report_pen(core_data, &ts_event->pen_data); |
| } |
| /* [GOOG] |
| * Move to goodix_ts_post_threadirq_func. |
| if (ts_event->event_type & EVENT_REQUEST) |
| goodix_ts_request_handle(core_data, ts_event); |
| if (ts_event->event_type & EVENT_STATUS) |
| goodix_ts_report_status(core_data, ts_event); |
| */ |
| /* [GOOG] |
| * Don't need to report gesture events in our use cases. |
| if (ts_event->event_type & EVENT_GESTURE) |
| goodix_ts_report_gesture(core_data, ts_event); |
| */ |
| } |
| |
| /* [GOOG] |
| * Remove the control to enable/disable the interrupt for bottom-half. |
| enable_irq(core_data->irq); |
| */ |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t goodix_ts_post_threadirq_func(int irq, void *data) |
| { |
| struct goodix_ts_core *core_data = data; |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| struct goodix_ts_event *ts_event = &core_data->ts_event; |
| |
| if (ts_event->event_type != EVENT_INVALID) { |
| if (ts_event->event_type & EVENT_REQUEST) |
| goodix_ts_request_handle(core_data, ts_event); |
| |
| if (ts_event->event_type & EVENT_STATUS) { |
| hw_ops->read(core_data, 0x1021C, (u8 *)&ts_event->status_data, |
| sizeof(ts_event->status_data)); |
| goodix_ts_report_status(core_data, ts_event); |
| } |
| |
| /* read done */ |
| hw_ops->after_event_handler(core_data); /* [GOOG] */ |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /** |
| * goodix_ts_init_irq - Request interrput line from system |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| static int goodix_ts_irq_setup(struct goodix_ts_core *core_data) |
| { |
| const struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| int ret; |
| |
| /* if ts_bdata-> irq is invalid */ |
| core_data->irq = gpio_to_irq(ts_bdata->irq_gpio); |
| if (core_data->irq < 0) { |
| ts_err("failed get irq num %d", core_data->irq); |
| return -EINVAL; |
| } |
| |
| ts_info("IRQ:%u,flags:%d", core_data->irq, (int)ts_bdata->irq_flags); |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| ret = goog_devm_request_threaded_irq(core_data->gti, |
| #else |
| ret = devm_request_threaded_irq( |
| #endif |
| &core_data->pdev->dev, core_data->irq, |
| goodix_ts_isr, goodix_ts_threadirq_func, |
| ts_bdata->irq_flags | IRQF_ONESHOT, GOODIX_CORE_DRIVER_NAME, |
| core_data); |
| if (ret < 0) |
| ts_err("Failed to requeset threaded irq:%d", ret); |
| else |
| atomic_set(&core_data->irq_enabled, 1); |
| |
| return ret; |
| } |
| |
| /** |
| * goodix_ts_power_init - Get regulator for touch device |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| static int goodix_ts_power_init(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| struct device *dev = core_data->bus->dev; |
| int ret = 0; |
| |
| ts_info("Power init"); |
| if (strlen(ts_bdata->avdd_name)) { |
| core_data->avdd = devm_regulator_get(dev, ts_bdata->avdd_name); |
| if (IS_ERR_OR_NULL(core_data->avdd)) { |
| ret = PTR_ERR(core_data->avdd); |
| ts_err("Failed to get regulator avdd:%d", ret); |
| core_data->avdd = NULL; |
| return ret; |
| } |
| } else { |
| ts_info("Avdd name is NULL"); |
| } |
| |
| if (strlen(ts_bdata->iovdd_name)) { |
| core_data->iovdd = |
| devm_regulator_get(dev, ts_bdata->iovdd_name); |
| if (IS_ERR_OR_NULL(core_data->iovdd)) { |
| ret = PTR_ERR(core_data->iovdd); |
| ts_err("Failed to get regulator iovdd:%d", ret); |
| core_data->iovdd = NULL; |
| } |
| } else { |
| ts_info("iovdd name is NULL"); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * goodix_ts_power_on - Turn on power to the touch device |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| int goodix_ts_power_on(struct goodix_ts_core *cd) |
| { |
| int ret = 0; |
| |
| ts_info("Device power on"); |
| if (cd->power_on) |
| return 0; |
| |
| ret = cd->hw_ops->power_on(cd, true); |
| if (!ret) |
| cd->power_on = 1; |
| else |
| ts_err("failed power on, %d", ret); |
| return ret; |
| } |
| |
| /** |
| * goodix_ts_power_off - Turn off power to the touch device |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| int goodix_ts_power_off(struct goodix_ts_core *cd) |
| { |
| int ret; |
| |
| ts_info("Device power off"); |
| if (!cd->power_on) |
| return 0; |
| |
| ret = cd->hw_ops->power_on(cd, false); |
| if (!ret) |
| cd->power_on = 0; |
| else |
| ts_err("failed power off, %d", ret); |
| |
| return ret; |
| } |
| |
| /** |
| * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| static int goodix_ts_gpio_setup(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| int r = 0; |
| |
| ts_info("GPIO setup,reset-gpio:%d, irq-gpio:%d", ts_bdata->reset_gpio, |
| ts_bdata->irq_gpio); |
| /* |
| * after kenerl3.13, gpio_ api is deprecated, new |
| * driver should use gpiod_ api. |
| */ |
| r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->reset_gpio, |
| GPIOF_OUT_INIT_LOW, "ts_reset_gpio"); |
| if (r < 0) { |
| ts_err("Failed to request reset gpio, r:%d", r); |
| return r; |
| } |
| |
| r = devm_gpio_request_one(&core_data->pdev->dev, ts_bdata->irq_gpio, |
| GPIOF_IN, "ts_irq_gpio"); |
| if (r < 0) { |
| ts_err("Failed to request irq gpio, r:%d", r); |
| return r; |
| } |
| |
| if (ts_bdata->avdd_gpio > 0) { |
| r = devm_gpio_request_one(&core_data->pdev->dev, |
| ts_bdata->avdd_gpio, GPIOF_OUT_INIT_LOW, |
| "ts_avdd_gpio"); |
| if (r < 0) { |
| ts_err("Failed to request avdd-gpio, r:%d", r); |
| return r; |
| } |
| } |
| |
| if (ts_bdata->iovdd_gpio > 0) { |
| r = devm_gpio_request_one(&core_data->pdev->dev, |
| ts_bdata->iovdd_gpio, GPIOF_OUT_INIT_LOW, |
| "ts_iovdd_gpio"); |
| if (r < 0) { |
| ts_err("Failed to request iovdd-gpio, r:%d", r); |
| return r; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int goodix_pinctrl_init(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| |
| ts_bdata->pinctrl = devm_pinctrl_get(core_data->bus->dev); |
| ts_bdata->state_active = |
| pinctrl_lookup_state(ts_bdata->pinctrl, "ts_active"); |
| if (IS_ERR_OR_NULL(ts_bdata->state_active)) { |
| ts_err("Could not get active pinstate\n"); |
| return -ENODEV; |
| } |
| |
| ts_bdata->state_suspend = |
| pinctrl_lookup_state(ts_bdata->pinctrl, "ts_suspend"); |
| if (IS_ERR_OR_NULL(ts_bdata->state_suspend)) { |
| ts_err("Could not get suspend pinstate\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int goodix_set_pinctrl_state( |
| struct goodix_ts_core *core_data, enum PINCTRL_MODE mode) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| struct pinctrl_state *state; |
| |
| ts_debug("goodix_set_pinctrl_state: %s\n", |
| mode == PINCTRL_MODE_ACTIVE ? "ACTIVE" : "SUSPEND"); |
| |
| state = mode == PINCTRL_MODE_ACTIVE ? ts_bdata->state_active |
| : ts_bdata->state_suspend; |
| return pinctrl_select_state(ts_bdata->pinctrl, state); |
| } |
| |
| /** |
| * goodix_ts_input_dev_config - Request and config a input device |
| * then register it to input sybsystem. |
| * @core_data: pointer to touch core data |
| * return: 0 ok, <0 failed |
| */ |
| static int goodix_ts_input_dev_config(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| struct input_dev *input_dev = NULL; |
| int dev_id = core_data->pdev->id; |
| int r; |
| |
| input_dev = input_allocate_device(); |
| if (!input_dev) { |
| ts_err("Failed to allocated input device"); |
| return -ENOMEM; |
| } |
| |
| sprintf(core_data->input_name, "%s%d", GOODIX_CORE_DRIVER_NAME, dev_id); |
| input_dev->dev.parent = &core_data->pdev->dev; /* [GOOG] */ |
| input_dev->name = core_data->input_name; |
| input_dev->uniq = input_dev->name; |
| input_dev->phys = input_dev->name; |
| input_dev->id.bustype = core_data->bus->bus_type; |
| input_dev->id.product = 0x0100 + dev_id; |
| input_dev->id.vendor = 0x27C6; |
| input_dev->id.version = 0x0100; |
| |
| set_bit(EV_SYN, input_dev->evbit); |
| set_bit(EV_KEY, input_dev->evbit); |
| set_bit(EV_ABS, input_dev->evbit); |
| set_bit(BTN_TOUCH, input_dev->keybit); |
| set_bit(BTN_TOOL_FINGER, input_dev->keybit); |
| set_bit(INPUT_PROP_DIRECT, input_dev->propbit); |
| |
| /* set input parameters */ |
| input_set_abs_params( |
| input_dev, ABS_MT_POSITION_X, 0, ts_bdata->panel_max_x, 0, 0); |
| input_set_abs_params( |
| input_dev, ABS_MT_POSITION_Y, 0, ts_bdata->panel_max_y, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, ts_bdata->panel_max_y, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, ts_bdata->panel_max_x, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_ORIENTATION, -4096, 4096, 0, 0); |
| input_set_abs_params( |
| input_dev, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER, MT_TOOL_PALM, 0, 0); |
| #ifdef INPUT_TYPE_B_PROTOCOL |
| #if LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0) |
| input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH, INPUT_MT_DIRECT); |
| #else |
| input_mt_init_slots(input_dev, GOODIX_MAX_TOUCH); |
| #endif |
| #endif |
| |
| input_set_capability(input_dev, EV_KEY, KEY_POWER); |
| input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); |
| input_set_capability(input_dev, EV_KEY, KEY_GOTO); |
| |
| core_data->ble_data.tx1_freq_index = 0xFF; |
| core_data->ble_data.tx2_freq_index = 0xFF; |
| |
| r = input_register_device(input_dev); |
| if (r < 0) { |
| ts_err("Unable to register input device"); |
| input_free_device(input_dev); |
| return r; |
| } |
| |
| core_data->input_dev = input_dev; |
| input_set_drvdata(input_dev, core_data); |
| |
| return 0; |
| } |
| |
| static int goodix_ts_pen_dev_config(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_board_data *ts_bdata = board_data(core_data); |
| struct input_dev *pen_dev = NULL; |
| int dev_id = core_data->pdev->id; |
| int r; |
| |
| pen_dev = input_allocate_device(); |
| if (!pen_dev) { |
| ts_err("Failed to allocated pen device"); |
| return -ENOMEM; |
| } |
| |
| sprintf(core_data->input_pen_name, "%s%d%s", GOODIX_CORE_DRIVER_NAME, dev_id, ",pen"); |
| pen_dev->dev.parent = &core_data->pdev->dev; /* [GOOG] */ |
| pen_dev->name = core_data->input_pen_name; |
| pen_dev->uniq = pen_dev->name; |
| pen_dev->phys = pen_dev->name; |
| pen_dev->id.bustype = core_data->bus->bus_type; |
| pen_dev->id.product = 0x0200 + dev_id; |
| pen_dev->id.vendor = 0x27C6; |
| pen_dev->id.version = 0x0100; |
| |
| pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
| set_bit(ABS_X, pen_dev->absbit); |
| set_bit(ABS_Y, pen_dev->absbit); |
| set_bit(ABS_TILT_X, pen_dev->absbit); |
| set_bit(ABS_TILT_Y, pen_dev->absbit); |
| set_bit(BTN_STYLUS, pen_dev->keybit); |
| set_bit(BTN_STYLUS2, pen_dev->keybit); |
| set_bit(BTN_TOUCH, pen_dev->keybit); |
| set_bit(BTN_TOOL_PEN, pen_dev->keybit); |
| set_bit(INPUT_PROP_DIRECT, pen_dev->propbit); |
| input_set_abs_params(pen_dev, ABS_X, 0, ts_bdata->panel_max_x, 0, 0); |
| input_set_abs_params(pen_dev, ABS_Y, 0, ts_bdata->panel_max_y, 0, 0); |
| input_set_abs_params( |
| pen_dev, ABS_PRESSURE, 0, ts_bdata->panel_max_p, 0, 0); |
| input_set_abs_params(pen_dev, ABS_DISTANCE, 0, 255, 0, 0); |
| input_set_abs_params(pen_dev, ABS_TILT_X, -GOODIX_PEN_MAX_TILT, |
| GOODIX_PEN_MAX_TILT, 0, 0); |
| input_set_abs_params(pen_dev, ABS_TILT_Y, -GOODIX_PEN_MAX_TILT, |
| GOODIX_PEN_MAX_TILT, 0, 0); |
| |
| r = input_register_device(pen_dev); |
| if (r < 0) { |
| ts_err("Unable to register pen device"); |
| input_free_device(pen_dev); |
| return r; |
| } |
| |
| core_data->pen_dev = pen_dev; |
| input_set_drvdata(pen_dev, core_data); |
| |
| return 0; |
| } |
| |
| void goodix_ts_input_dev_remove(struct goodix_ts_core *core_data) |
| { |
| if (!core_data->input_dev) |
| return; |
| input_unregister_device(core_data->input_dev); |
| core_data->input_dev = NULL; |
| } |
| |
| void goodix_ts_pen_dev_remove(struct goodix_ts_core *core_data) |
| { |
| if (!core_data->pen_dev) |
| return; |
| mutex_destroy(&core_data->ble_data.lock); |
| input_unregister_device(core_data->pen_dev); |
| core_data->pen_dev = NULL; |
| } |
| |
| /** |
| * goodix_ts_esd_work - check hardware status and recovery |
| * the hardware if needed. |
| */ |
| static void goodix_ts_esd_work(struct work_struct *work) |
| { |
| struct delayed_work *dwork = to_delayed_work(work); |
| struct goodix_ts_esd *ts_esd = |
| container_of(dwork, struct goodix_ts_esd, esd_work); |
| struct goodix_ts_core *cd = |
| container_of(ts_esd, struct goodix_ts_core, ts_esd); |
| const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; |
| int ret = 0; |
| |
| if (ts_esd->skip_once) |
| goto exit; |
| |
| if (!atomic_read(&ts_esd->esd_on) || atomic_read(&cd->suspended)) |
| return; |
| |
| if (!hw_ops->esd_check) |
| return; |
| |
| ret = hw_ops->esd_check(cd); |
| if (ret) { |
| ts_err("esd check failed"); |
| gpio_direction_output(cd->board_data.reset_gpio, 0); |
| if (cd->iovdd) |
| ret = regulator_disable(cd->iovdd); |
| if (cd->avdd) |
| ret = regulator_disable(cd->avdd); |
| |
| usleep_range(5000, 5100); |
| |
| if (cd->iovdd) { |
| ret = regulator_enable(cd->iovdd); |
| usleep_range(3000, 3100); |
| } |
| if (cd->avdd) |
| ret = regulator_enable(cd->avdd); |
| usleep_range(15000, 15100); |
| gpio_direction_output(cd->board_data.reset_gpio, 1); |
| } |
| |
| exit: |
| ts_esd->skip_once = false; |
| if (atomic_read(&ts_esd->esd_on)) |
| schedule_delayed_work(&ts_esd->esd_work, 2 * HZ); |
| } |
| |
| /** |
| * goodix_ts_esd_on - turn on esd protection |
| */ |
| void goodix_ts_esd_on(struct goodix_ts_core *cd) |
| { |
| struct goodix_ic_info_misc *misc = &cd->ic_info.misc; |
| struct goodix_ts_esd *ts_esd = &cd->ts_esd; |
| |
| if (!misc->esd_addr) |
| return; |
| |
| if (atomic_read(&ts_esd->esd_on)) |
| return; |
| |
| atomic_set(&ts_esd->esd_on, 1); |
| if (!schedule_delayed_work(&ts_esd->esd_work, 2 * HZ)) |
| ts_info("esd work already in workqueue"); |
| |
| ts_info("esd on"); |
| } |
| |
| /** |
| * goodix_ts_esd_off - turn off esd protection |
| */ |
| void goodix_ts_esd_off(struct goodix_ts_core *cd) |
| { |
| struct goodix_ts_esd *ts_esd = &cd->ts_esd; |
| int ret; |
| |
| if (!atomic_read(&ts_esd->esd_on)) |
| return; |
| |
| atomic_set(&ts_esd->esd_on, 0); |
| ret = cancel_delayed_work_sync(&ts_esd->esd_work); |
| ts_info("Esd off, esd work state %d", ret); |
| } |
| |
| /** |
| * goodix_ts_esd_init - initialize esd protection |
| */ |
| int goodix_ts_esd_init(struct goodix_ts_core *cd) |
| { |
| struct goodix_ic_info_misc *misc = &cd->ic_info.misc; |
| struct goodix_ts_esd *ts_esd = &cd->ts_esd; |
| |
| if (!cd->hw_ops->esd_check || !misc->esd_addr) { |
| ts_info("missing key info for esd check"); |
| return 0; |
| } |
| |
| INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work); |
| ts_esd->ts_core = cd; |
| atomic_set(&ts_esd->esd_on, 0); |
| goodix_ts_esd_on(cd); |
| |
| return 0; |
| } |
| |
| void goodix_ts_esd_uninit(struct goodix_ts_core *cd) |
| { |
| struct goodix_ts_esd *ts_esd = &cd->ts_esd; |
| if (atomic_read(&ts_esd->esd_on)) |
| goodix_ts_esd_off(cd); |
| } |
| |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| static void goodix_ts_release_connects(struct goodix_ts_core *core_data) |
| { |
| } |
| #else |
| static void goodix_ts_release_connects(struct goodix_ts_core *core_data) |
| { |
| struct input_dev *input_dev = core_data->input_dev; |
| int i; |
| |
| mutex_lock(&input_dev->mutex); |
| |
| for (i = 0; i < GOODIX_MAX_TOUCH; i++) { |
| input_mt_slot(input_dev, i); |
| input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); |
| } |
| input_report_key(input_dev, BTN_TOUCH, 0); |
| input_mt_sync_frame(input_dev); |
| input_sync(input_dev); |
| |
| mutex_unlock(&input_dev->mutex); |
| } |
| #endif |
| |
| /** |
| * goodix_ts_suspend - Touchscreen suspend function |
| * Called by PM/FB/EARLYSUSPEN module to put the device to sleep |
| */ |
| static int goodix_ts_suspend(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| |
| if (core_data->init_stage < CORE_INIT_STAGE2 || |
| atomic_read(&core_data->suspended)) |
| return 0; |
| |
| ts_info("Suspend start"); |
| atomic_set(&core_data->suspended, 1); |
| /* disable irq */ |
| hw_ops->disable_irq_nosync(core_data); |
| goodix_ts_esd_off(core_data); |
| |
| if (core_data->gesture_type) { |
| /* enter gesture mode */ |
| hw_ops->gesture(core_data, 0); |
| hw_ops->irq_enable(core_data, true); |
| enable_irq_wake(core_data->irq); |
| } else { |
| /* enter sleep mode or power off */ |
| if (core_data->board_data.sleep_enable) |
| hw_ops->suspend(core_data); |
| else |
| goodix_ts_power_off(core_data); |
| } |
| goodix_ts_release_connects(core_data); |
| |
| goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND); /* [GOOG] */ |
| ts_info("Suspend end"); |
| return 0; |
| } |
| |
| static bool check_gesture_mode(struct goodix_ts_core *core_data) |
| { |
| enum raw_scan_mode scan_mode = RAW_SCAN_MODE_AUTO; |
| int err = 0; |
| |
| err = core_data->hw_ops->get_scan_mode(core_data, &scan_mode); |
| if (err != 0) { |
| return false; |
| } |
| return (scan_mode == RAW_SCAN_MODE_LOW_POWER_ACTIVE) || |
| (scan_mode == RAW_SCAN_MODE_LOW_POWER_IDLE); |
| } |
| |
| static void monitor_gesture_event(struct work_struct *work) |
| { |
| struct delayed_work *delayed_work = container_of( |
| work, struct delayed_work, work); |
| struct goodix_ts_core *cd = container_of(delayed_work, struct goodix_ts_core, |
| monitor_gesture_work); |
| struct goodix_gesture_data* gesture_data = &cd->gesture_data; |
| unsigned char event_type = GOODIX_GESTURE_UNKNOWN; |
| ktime_t now = ktime_get(); |
| bool timeout = false; |
| |
| mutex_lock(&cd->gesture_data_lock); |
| event_type = gesture_data->event_type; |
| mutex_unlock(&cd->gesture_data_lock); |
| |
| timeout = event_type == GOODIX_GESTURE_FOD_DOWN ? |
| now >= cd->gesture_up_timeout : now >= cd->gesture_down_timeout; |
| |
| if (event_type != GOODIX_GESTURE_FOD_UP && !timeout) { |
| queue_delayed_work(cd->event_wq, &cd->monitor_gesture_work, |
| msecs_to_jiffies(5)); |
| return; |
| } |
| |
| if (event_type == GOODIX_GESTURE_FOD_UP || |
| event_type == GOODIX_GESTURE_UNKNOWN) { |
| if (event_type == GOODIX_GESTURE_UNKNOWN) |
| cd->coords_timestamp = now; |
| goodix_ts_report_gesture_up(cd); |
| } |
| |
| /* reset device or power on*/ |
| if (cd->board_data.sleep_enable) |
| cd->hw_ops->reset(cd, goodix_get_normal_reset_delay(cd)); |
| else |
| goodix_ts_power_on(cd); |
| } |
| |
| /** |
| * goodix_ts_resume - Touchscreen resume function |
| * Called by PM/FB/EARLYSUSPEN module to wakeup device |
| */ |
| static int goodix_ts_resume(struct goodix_ts_core *core_data) |
| { |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| if (core_data->init_stage < CORE_INIT_STAGE2 || |
| !atomic_read(&core_data->suspended)) |
| return 0; |
| |
| ts_info("Resume start"); |
| goodix_set_pinctrl_state(core_data, PINCTRL_MODE_ACTIVE); /* [GOOG] */ |
| |
| atomic_set(&core_data->suspended, 0); |
| /* [GOOG] |
| * This will cause a deadlock with wakelock. Since we already disable irq |
| * when touch is suspended, we don't need to disable irq here again. |
| */ |
| //hw_ops->irq_enable(core_data, false); |
| |
| /* [GOOG] */ |
| if (check_gesture_mode(core_data)) { |
| struct goodix_gesture_data *gesture_data = &core_data->gesture_data; |
| |
| gesture_data->event_type = GOODIX_GESTURE_UNKNOWN; |
| core_data->gesture_down_timeout = ktime_add_ms(ktime_get(), 100); |
| core_data->gesture_up_timeout = ktime_add_ms(ktime_get(), 200); |
| queue_delayed_work(core_data->event_wq, &core_data->monitor_gesture_work, |
| msecs_to_jiffies(5)); |
| } else { |
| if (core_data->gesture_type) { |
| disable_irq_wake(core_data->irq); |
| hw_ops->reset(core_data, goodix_get_normal_reset_delay(core_data)); |
| } else { |
| /* [GOOG] |
| * Force to reset T-IC as touch resume process instead using brl_resume(). |
| */ |
| /* reset device or power on*/ |
| if (core_data->board_data.sleep_enable) { |
| hw_ops->reset(core_data, goodix_get_normal_reset_delay(core_data)); |
| //hw_ops->resume(core_data); /* [GOOG] */ |
| } else { |
| goodix_ts_power_on(core_data); |
| } |
| } |
| } |
| |
| /* enable irq */ |
| hw_ops->irq_enable(core_data, true); |
| /* open esd */ |
| goodix_ts_esd_on(core_data); |
| ts_info("Resume end"); |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_FB) |
| /** |
| * goodix_ts_fb_notifier_callback - Framebuffer notifier callback |
| * Called by kernel during framebuffer blanck/unblank phrase |
| */ |
| int goodix_ts_fb_notifier_callback( |
| struct notifier_block *self, unsigned long event, void *data) |
| { |
| struct goodix_ts_core *core_data = |
| container_of(self, struct goodix_ts_core, fb_notifier); |
| struct fb_event *fb_event = data; |
| |
| if (fb_event && fb_event->data && core_data) { |
| if (event == FB_EVENT_BLANK) { |
| int *blank = fb_event->data; |
| |
| if (*blank == FB_BLANK_UNBLANK) |
| goodix_ts_resume(core_data); |
| else if (*blank == FB_BLANK_POWERDOWN) |
| goodix_ts_suspend(core_data); |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| #if IS_ENABLED(CONFIG_PM) |
| #if !IS_ENABLED(CONFIG_FB) && !IS_ENABLED(CONFIG_HAS_EARLYSUSPEND) |
| /** |
| * goodix_ts_pm_suspend - PM suspend function |
| * Called by kernel during system suspend phrase |
| */ |
| static int goodix_ts_pm_suspend(struct device *dev) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| |
| return goodix_ts_suspend(core_data); |
| } |
| /** |
| * goodix_ts_pm_resume - PM resume function |
| * Called by kernel during system wakeup |
| */ |
| static int goodix_ts_pm_resume(struct device *dev) |
| { |
| struct goodix_ts_core *core_data = dev_get_drvdata(dev); |
| |
| return goodix_ts_resume(core_data); |
| } |
| #endif |
| #endif |
| |
| int goodix_ts_stage2_init(struct goodix_ts_core *cd) |
| { |
| int ret; |
| int tx = cd->ic_info.parm.drv_num; |
| int rx = cd->ic_info.parm.sen_num; |
| size_t mutual_size = tx * rx * sizeof(s16); |
| size_t self_sensing_size = (tx + rx) * sizeof(s16); |
| struct goodix_ic_info_misc *misc = &cd->ic_info.misc; |
| size_t touch_frame_size = |
| misc->frame_data_addr - misc->touch_data_addr + |
| misc->frame_data_head_len + misc->fw_attr_len + |
| misc->fw_log_len + sizeof(struct goodix_mutual_data) + |
| mutual_size + sizeof(struct goodix_self_sensing_data) + |
| self_sensing_size; |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| struct gti_optional_configuration *options; |
| #endif |
| |
| /* alloc/config/register input device */ |
| ret = goodix_ts_input_dev_config(cd); |
| if (ret < 0) { |
| ts_err("failed set input device"); |
| return ret; |
| } |
| |
| if (cd->board_data.pen_enable) { |
| ret = goodix_ts_pen_dev_config(cd); |
| if (ret < 0) { |
| ts_err("failed set pen device"); |
| goto err_finger; |
| } |
| mutex_init(&cd->ble_data.lock); |
| } |
| |
| #if IS_ENABLED(CONFIG_FB) |
| cd->fb_notifier.notifier_call = goodix_ts_fb_notifier_callback; |
| if (fb_register_client(&cd->fb_notifier)) |
| ts_err("Failed to register fb notifier client:%d", ret); |
| #endif |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_MOTION_FILTER) |
| cd->tmf.pdev = cd->pdev; |
| cd->tmf.set_continuously_report_enabled = |
| set_continuously_report_enabled; |
| touch_mf_init(&cd->tmf); |
| #endif |
| |
| /* create sysfs files */ |
| ret = goodix_ts_sysfs_init(cd); |
| if (ret < 0) { |
| ts_err("failed set init sysfs"); |
| goto err_init_sysfs; |
| } |
| |
| /* create sysfs files for our own APIs */ |
| cd->apis_data.get_fw_version = get_fw_version; |
| cd->apis_data.get_irq_enabled = get_irq_enabled; |
| cd->apis_data.set_irq_enabled = set_irq_enabled; |
| cd->apis_data.is_scan_mode_supported = is_scan_mode_supported; |
| cd->apis_data.ping = ping; |
| cd->apis_data.hardware_reset = hardware_reset; |
| cd->apis_data.set_scan_mode = set_scan_mode; |
| cd->apis_data.set_sensing_enabled = set_sensing_enabled; |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) && IS_ENABLED(CONFIG_GTI_PM) |
| cd->apis_data.get_wake_lock_state = get_wake_lock_state; |
| cd->apis_data.set_wake_lock_state = set_wake_lock_state; |
| #endif |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_MOTION_FILTER) |
| cd->apis_data.tmf = &cd->tmf; |
| #endif |
| |
| ret = touch_apis_init(&cd->pdev->dev, &cd->apis_data); |
| if (ret < 0) { |
| ts_err("failed set init apis"); |
| goto err_init_apis; |
| } |
| |
| cd->event_wq = alloc_workqueue("goodix_wq", WQ_UNBOUND | |
| WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); |
| if (!cd->event_wq) { |
| ts_err("Cannot create work thread\n"); |
| ret = -ENOMEM; |
| goto err_alloc_workqueue; |
| } |
| INIT_DELAYED_WORK(&cd->monitor_gesture_work, monitor_gesture_event); |
| |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| options = devm_kzalloc(&cd->pdev->dev, |
| sizeof(struct gti_optional_configuration), GFP_KERNEL); |
| if (options == NULL) { |
| ts_err("Failed to alloc gti options\n"); |
| ret = -ENOMEM; |
| goto err_alloc_gti_options; |
| } |
| options->get_mutual_sensor_data = get_mutual_sensor_data; |
| options->get_self_sensor_data = get_self_sensor_data; |
| options->set_continuous_report = set_continuous_report; |
| options->set_grip_mode = set_grip_mode; |
| options->get_grip_mode = get_grip_mode; |
| options->set_palm_mode = set_palm_mode; |
| options->get_palm_mode = get_palm_mode; |
| options->set_screen_protector_mode = set_screen_protector_mode; |
| options->get_screen_protector_mode = get_screen_protector_mode; |
| options->set_coord_filter_enabled = set_coord_filter_enabled; |
| options->get_coord_filter_enabled = get_coord_filter_enabled; |
| options->set_heatmap_enabled = set_heatmap_enabled; |
| options->get_fw_version = gti_get_fw_version; |
| options->set_irq_mode = gti_set_irq_mode; |
| options->get_irq_mode = gti_get_irq_mode; |
| options->reset = gti_reset; |
| options->ping = gti_ping; |
| options->calibrate = gti_calibrate; |
| options->selftest = gti_selftest; |
| options->get_context_driver = gti_get_context_driver; |
| options->set_report_rate = gti_set_report_rate; |
| options->post_irq_thread_fn = goodix_ts_post_threadirq_func; |
| |
| cd->gti = goog_touch_interface_probe( |
| cd, cd->bus->dev, cd->input_dev, gti_default_handler, options); |
| |
| #if IS_ENABLED(CONFIG_GTI_PM) |
| ret = goog_pm_register_notification(cd->gti, &dev_pm_ops); |
| if (ret < 0) { |
| ts_info("Failed to register gti pm"); |
| goto err_init_tpm; |
| } |
| #endif |
| #endif |
| |
| /* create procfs files */ |
| ret = goodix_ts_procfs_init(cd); |
| if (ret < 0) { |
| ts_err("failed set init procfs"); |
| goto err_init_procfs; |
| } |
| |
| /* esd protector */ |
| ret = goodix_ts_esd_init(cd); |
| if (ret < 0) { |
| ts_err("failed set init procfs"); |
| goto err_init_esd; |
| } |
| |
| #if IS_ENABLED(CONFIG_GOODIX_GESTURE) |
| /* gesture init */ |
| ret = gesture_module_init(cd); |
| if (ret < 0) { |
| ts_err("failed set init gesture"); |
| goto err_init_gesture; |
| } |
| #endif |
| |
| /* inspect init */ |
| ret = inspect_module_init(cd); |
| if (ret < 0) { |
| ts_err("failed set init inspect"); |
| goto err_init_inspect; |
| } |
| |
| /* |
| * [GOOG] |
| * Touch frame package will read into `struct goodix_rx_package`. |
| * The total read size for SPI is `touch_frame_size` + 8 bytes(SPI prefix header). |
| * Therefore, `touch_frame_package` will need to allocate 8 extra bytes for SPI I/O. |
| */ |
| if (cd->bus->sub_ic_type == IC_TYPE_SUB_GT7986) { |
| touch_frame_size = misc->touch_data_head_len + |
| misc->point_struct_len * GOODIX_MAX_TOUCH + 2; |
| } |
| |
| cd->touch_frame_size = touch_frame_size; |
| cd->touch_frame_package = |
| devm_kzalloc(&cd->pdev->dev, touch_frame_size + 8, GFP_KERNEL); |
| if (cd->touch_frame_package == NULL) { |
| ts_err("failed to alloc touch_frame_package"); |
| ret = -ENOMEM; |
| goto err_setup_irq; |
| } |
| cd->mutual_data = devm_kzalloc(&cd->pdev->dev, mutual_size, GFP_KERNEL); |
| if (cd->mutual_data == NULL) { |
| ts_err("failed to alloc mutual_data"); |
| ret = -ENOMEM; |
| goto err_setup_irq; |
| } |
| cd->mutual_data_manual = devm_kzalloc(&cd->pdev->dev, mutual_size, |
| GFP_KERNEL); |
| if (cd->mutual_data_manual == NULL) { |
| ts_err("failed to alloc mutual_data_manual"); |
| ret = -ENOMEM; |
| goto err_setup_irq; |
| } |
| cd->self_sensing_data = |
| devm_kzalloc(&cd->pdev->dev, self_sensing_size, GFP_KERNEL); |
| if (cd->self_sensing_data == NULL) { |
| ts_err("failed to alloc self_sensing_data"); |
| ret = -ENOMEM; |
| goto err_setup_irq; |
| } |
| cd->self_sensing_data_manual = |
| devm_kzalloc(&cd->pdev->dev, self_sensing_size, GFP_KERNEL); |
| if (cd->self_sensing_data_manual == NULL) { |
| ts_err("failed to alloc self_sensing_data_manual"); |
| ret = -ENOMEM; |
| goto err_setup_irq; |
| } |
| /*~[GOOG]*/ |
| |
| /* request irq line */ |
| ret = goodix_ts_irq_setup(cd); |
| if (ret < 0) { |
| ts_info("failed set irq"); |
| goto err_setup_irq; |
| } |
| ts_info("success register irq"); |
| |
| return 0; |
| |
| err_setup_irq: |
| inspect_module_exit(cd); |
| err_init_inspect: |
| #if IS_ENABLED(CONFIG_GOODIX_GESTURE) |
| gesture_module_exit(cd); |
| err_init_gesture: |
| #endif |
| goodix_ts_esd_uninit(cd); |
| err_init_esd: |
| goodix_ts_procfs_exit(cd); |
| err_init_procfs: |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| #if IS_ENABLED(CONFIG_GTI_PM) |
| goog_pm_unregister_notification(cd->gti); |
| err_init_tpm: |
| #endif |
| err_alloc_gti_options: |
| #endif |
| destroy_workqueue(cd->event_wq); |
| err_alloc_workqueue: |
| touch_apis_deinit(&cd->pdev->dev); |
| err_init_apis: |
| goodix_ts_sysfs_exit(cd); |
| err_init_sysfs: |
| #if IS_ENABLED(CONFIG_FB) |
| fb_unregister_client(&cd->fb_notifier); |
| #endif |
| goodix_ts_pen_dev_remove(cd); |
| err_finger: |
| goodix_ts_input_dev_remove(cd); |
| return ret; |
| } |
| |
| /* try send the config specified with type */ |
| static int goodix_send_ic_config(struct goodix_ts_core *cd, int type) |
| { |
| u32 config_id; |
| struct goodix_ic_config *cfg; |
| |
| if (cd->board_data.use_one_binary) |
| return 0; |
| |
| if (type >= GOODIX_MAX_CONFIG_GROUP) { |
| ts_err("unsupported config type %d", type); |
| return -EINVAL; |
| } |
| |
| cfg = cd->ic_configs[type]; |
| if (!cfg || cfg->len <= 0) { |
| ts_info("no valid normal config found"); |
| return -EINVAL; |
| } |
| |
| config_id = goodix_get_file_config_id(cfg->data); |
| if (cd->ic_info.version.config_id == config_id) { |
| ts_info("config id is equal 0x%x, skiped", config_id); |
| return 0; |
| } |
| |
| ts_info("try send config, id=0x%x", config_id); |
| return cd->hw_ops->send_config(cd, cfg->data, cfg->len); |
| } |
| |
| /** |
| * goodix_later_init_thread - init IC fw and config |
| * @data: point to goodix_ts_core |
| * |
| * This function respond for get fw version and try upgrade fw and config. |
| * Note: when init encounter error, need release all resource allocated here. |
| */ |
| static int goodix_later_init_thread(void *data) |
| { |
| int ret, i; |
| int update_flag = UPDATE_MODE_BLOCK | UPDATE_MODE_SRC_REQUEST; |
| struct goodix_ts_core *cd = data; |
| struct goodix_ts_hw_ops *hw_ops = cd->hw_ops; |
| |
| goodix_wait_for_init_stage2_start(cd); /* [GOOG] */ |
| |
| /* step 1: read version */ |
| ret = cd->hw_ops->read_version(cd, &cd->fw_version); |
| if (ret < 0) { |
| ts_err("failed to get version info, try to upgrade"); |
| update_flag |= UPDATE_MODE_FORCE; |
| } |
| |
| /* step 2: read ic info */ |
| ret = hw_ops->get_ic_info(cd, &cd->ic_info); |
| if (ret < 0) { |
| ts_err("failed to get ic info, try to upgrade"); |
| update_flag |= UPDATE_MODE_FORCE; |
| } |
| |
| /* step 3: get config data from config bin */ |
| ret = goodix_get_config_proc(cd); |
| if (ret < 0) |
| ts_info("no valid ic config found"); |
| else if (ret == 0) |
| ts_info("success get valid ic config"); |
| else |
| ts_info("one binary, no need find config"); |
| |
| /* step 4: init fw struct add try do fw upgrade */ |
| ret = goodix_fw_update_init(cd); |
| if (ret) { |
| ts_err("failed init fw update module"); |
| goto err_out; |
| } |
| |
| /* step 5: do upgrade */ |
| ts_info("update flag: 0x%X", update_flag); |
| ret = goodix_do_fw_update(cd, update_flag); |
| if (ret) |
| ts_err("failed do fw update"); |
| |
| print_ic_info(&cd->ic_info); |
| |
| /* the recommend way to update ic config is throuth ISP, |
| * if not we will send config with interactive mode |
| */ |
| goodix_send_ic_config(cd, CONFIG_TYPE_NORMAL); |
| |
| /* init other resources */ |
| ret = goodix_ts_stage2_init(cd); |
| if (ret) { |
| ts_err("stage2 init failed"); |
| goto uninit_fw; |
| } |
| cd->init_stage = CORE_INIT_STAGE2; |
| |
| complete_all(&cd->init_stage2_complete); /* [GOOG] */ |
| |
| return 0; |
| |
| uninit_fw: |
| goodix_fw_update_uninit(cd); |
| err_out: |
| ts_err("stage2 init failed"); |
| cd->init_stage = CORE_INIT_FAIL; |
| for (i = 0; i < GOODIX_MAX_CONFIG_GROUP; i++) { |
| kfree(cd->ic_configs[i]); |
| cd->ic_configs[i] = NULL; |
| } |
| return ret; |
| } |
| |
| static int goodix_start_later_init(struct goodix_ts_core *ts_core) |
| { |
| struct task_struct *init_thrd; |
| /* create and run update thread */ |
| init_thrd = kthread_run( |
| goodix_later_init_thread, ts_core, "goodix_init_thread"); |
| if (IS_ERR_OR_NULL(init_thrd)) { |
| ts_err("Failed to create update thread:%ld", |
| PTR_ERR(init_thrd)); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| /* goodix fb test */ |
| // static void test_suspend(void) |
| // { |
| // goodix_ts_suspend(goodix_modules.core_data); |
| // } |
| |
| // static void test_resume(void) |
| // { |
| // goodix_ts_resume(goodix_modules.core_data); |
| // } |
| |
| /** |
| * goodix_ts_probe - called by kernel when Goodix touch |
| * platform driver is added. |
| */ |
| static int goodix_ts_probe(struct platform_device *pdev) |
| { |
| struct goodix_device_resource *dev_res = |
| container_of(pdev, struct goodix_device_resource, pdev); |
| struct goodix_ts_core *core_data; |
| struct goodix_bus_interface *bus_interface; |
| int ret; |
| |
| ts_info("IN"); |
| |
| core_data = &dev_res->core_data; |
| bus_interface = &dev_res->bus; |
| |
| if (IS_ENABLED(CONFIG_OF) && bus_interface->dev->of_node) { |
| /* parse devicetree property */ |
| ret = goodix_parse_dt( |
| bus_interface->dev->of_node, &core_data->board_data); |
| if (ret) { |
| ts_err("failed parse device info form dts, %d", ret); |
| return -EINVAL; |
| } |
| } else { |
| ts_err("no valid device tree node found"); |
| return -ENODEV; |
| } |
| |
| core_data->hw_ops = goodix_get_hw_ops(); |
| if (!core_data->hw_ops) { |
| ts_err("hw ops is NULL"); |
| return -EINVAL; |
| } |
| mutex_init(&core_data->cmd_lock); |
| mutex_init(&core_data->gesture_data_lock); |
| |
| /* touch core layer is a platform driver */ |
| core_data->pdev = pdev; |
| core_data->bus = bus_interface; |
| platform_set_drvdata(pdev, core_data); |
| dev_set_drvdata(bus_interface->dev, core_data); |
| |
| ret = goodix_pinctrl_init(core_data); |
| if (ret) { |
| ts_err("failed init pinctrl"); |
| goto err_out; |
| } |
| |
| ret = goodix_set_pinctrl_state(core_data, PINCTRL_MODE_ACTIVE); |
| if (ret) { |
| ts_err("failed set pinctrl state"); |
| goto err_out; |
| } |
| |
| /* get GPIO resource */ |
| ret = goodix_ts_gpio_setup(core_data); |
| if (ret) { |
| ts_err("failed init gpio"); |
| goto err_setup_gpio; |
| } |
| |
| ret = goodix_ts_power_init(core_data); |
| if (ret) { |
| ts_err("failed init power"); |
| goto err_setup_gpio; |
| } |
| |
| ret = goodix_ts_power_on(core_data); |
| if (ret) { |
| ts_err("failed power on"); |
| goto err_setup_gpio; |
| } |
| |
| /* debug node init */ |
| ret = goodix_tools_init(core_data); |
| if (ret) { |
| ts_err("failed init tools"); |
| goto err_init_tools; |
| } |
| |
| /* goodix fb test */ |
| // fb_firefly_register(test_suspend, test_resume); |
| |
| core_data->init_stage = CORE_INIT_STAGE1; |
| |
| /* Try start a thread to get config-bin info */ |
| ret = goodix_start_later_init(core_data); |
| if (ret) { |
| ts_err("failed start late init"); |
| goto err_start_late_init; |
| } |
| |
| ts_info("%s: goodix_ts_core probe success", __func__); |
| return 0; |
| |
| err_start_late_init: |
| goodix_tools_exit(core_data); |
| err_init_tools: |
| goodix_ts_power_off(core_data); |
| err_setup_gpio: |
| goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND); |
| err_out: |
| mutex_destroy(&core_data->gesture_data_lock); |
| mutex_destroy(&core_data->cmd_lock); |
| core_data->init_stage = CORE_INIT_FAIL; |
| ts_err("goodix_ts_core failed, ret:%d", ret); |
| return ret; |
| } |
| |
| static int goodix_ts_remove(struct platform_device *pdev) |
| { |
| struct goodix_ts_core *core_data = platform_get_drvdata(pdev); |
| struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops; |
| struct goodix_ts_esd *ts_esd = &core_data->ts_esd; |
| |
| |
| if (core_data->init_stage >= CORE_INIT_STAGE2) { |
| /* [GOOG] |
| * Follow the reversed order of probe() to release resources. |
| */ |
| hw_ops->irq_enable(core_data, false); |
| |
| /* goodix_ts_stage2_init() */ |
| inspect_module_exit(core_data); |
| #if IS_ENABLED(CONFIG_GOODIX_GESTURE) |
| gesture_module_exit(core_data); |
| #endif |
| if (atomic_read(&ts_esd->esd_on)) |
| goodix_ts_esd_off(core_data); |
| goodix_ts_procfs_exit(core_data); |
| #if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE) |
| #if IS_ENABLED(CONFIG_GTI_PM) |
| goog_pm_unregister_notification(core_data->gti); |
| #endif |
| goog_touch_interface_remove(core_data->gti); |
| destroy_workqueue(core_data->event_wq); |
| touch_apis_deinit(&core_data->pdev->dev); |
| #endif |
| goodix_ts_sysfs_exit(core_data); |
| #if IS_ENABLED(CONFIG_FB) |
| fb_unregister_client(&core_data->fb_notifier); |
| #endif |
| goodix_ts_pen_dev_remove(core_data); |
| goodix_ts_input_dev_remove(core_data); |
| /* goodix_later_init_thread() */ |
| goodix_fw_update_uninit(core_data); |
| } |
| |
| /* goodix_ts_probe() */ |
| goodix_tools_exit(core_data); |
| goodix_ts_power_off(core_data); |
| /*~[GOOG] */ |
| goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND); |
| mutex_destroy(&core_data->gesture_data_lock); |
| mutex_destroy(&core_data->cmd_lock); |
| |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_PM) |
| static const struct dev_pm_ops dev_pm_ops = { |
| .suspend = goodix_ts_pm_suspend, |
| .resume = goodix_ts_pm_resume, |
| }; |
| #endif |
| |
| static const struct platform_device_id ts_core_ids[] = { |
| { .name = GOODIX_CORE_DRIVER_NAME }, {} |
| }; |
| MODULE_DEVICE_TABLE(platform, ts_core_ids); |
| |
| static struct platform_driver goodix_ts_driver = { |
| .driver = { |
| .name = GOODIX_CORE_DRIVER_NAME, |
| .owner = THIS_MODULE, |
| #if IS_ENABLED(CONFIG_PM) |
| #if !IS_ENABLED(CONFIG_FB) && !IS_ENABLED(CONFIG_HAS_EARLYSUSPEND) && \ |
| !IS_ENABLED(CONFIG_GTI_PM) |
| .pm = &dev_pm_ops, |
| #endif |
| #endif |
| }, |
| .probe = goodix_ts_probe, |
| .remove = goodix_ts_remove, |
| .id_table = ts_core_ids, |
| }; |
| |
| static int __init goodix_ts_core_init(void) |
| { |
| int ret; |
| |
| ts_info("Core layer init:%s", GOODIX_DRIVER_VERSION); |
| goodix_device_manager_init(); |
| |
| #ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI |
| ret = goodix_spi_bus_init(); |
| if (ret) { |
| ts_err("failed add spi bus driver"); |
| return ret; |
| } |
| #endif |
| #ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_I2C |
| ret = goodix_i2c_bus_init(); |
| if (ret) { |
| ts_err("failed add i2c bus driver"); |
| return ret; |
| } |
| #endif |
| |
| return platform_driver_register(&goodix_ts_driver); |
| } |
| |
| static void __exit goodix_ts_core_exit(void) |
| { |
| ts_info("Core layer exit"); |
| platform_driver_unregister(&goodix_ts_driver); |
| #ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI |
| goodix_spi_bus_exit(); |
| #endif |
| #ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_I2C |
| goodix_i2c_bus_exit(); |
| #endif |
| goodix_device_manager_exit(); |
| } |
| |
| late_initcall(goodix_ts_core_init); |
| module_exit(goodix_ts_core_exit); |
| |
| MODULE_DESCRIPTION("Goodix Touchscreen Core Module"); |
| MODULE_AUTHOR("Goodix, Inc."); |
| MODULE_LICENSE("GPL v2"); |